Skip to main content

pkgforge

PyPI Python CI Docs License: MIT

Stage files into a build root and record their intended install metadata (mode, owner, group, type, and free-form key/value meta) in a file DB (JSON Lines, YAML, or SQLite), then dump that DB into packaging manifests — an RPM %files list or Debian install + permissions + dirs + fixperms files.

pkgforge is a small, dependency-light helper for unattended build pipelines on Linux: install a source into place, remember how it should be owned and permissioned, and emit that record for the packager.

Install

pip install pkgforge

Requires Python 3.9+. Runtime operations use POSIX facilities (chmod, chown, symlinks), so runtime targets Linux; the CLI and --help import cleanly on any platform. A tar-family archive given as a real path extracts via stdlib tarfile — no external archiver needed; a - (stdin) source and other formats (e.g. .iso) fall back to bsdtar. Neither path restores an archive's ownership or special bits as root, and a device node, FIFO or socket in the archive is refused.

Quick start

work="$(mktemp -d)"
export PKGFORGE_ROOT="$work/stage" PKGFORGE_DB="$work/files.jsonl"
mkdir -p "$PKGFORGE_ROOT"

pkgforge initdb
pkgforge install -p -m 755 -o root -g root ./build/tool /usr/bin
pkgforge install -p -m 640 -o root -g adm -O rpmprefix=%config ./tool.conf /etc
pkgforge install -D -d -m 755 -o root -g root --record-tree ./share /usr/share/tool
pkgforge dbdump -f rpmspecfiles rpm-files.txt
pkgforge dbdump -f debian debian/

See examples/stage_and_package.sh for a runnable end-to-end walkthrough.

Commands

Command Purpose
initdb create or reset (truncate) the file DB
install [opts] SRC… DEST stage a source and record its entry
scan [opts] PATH walk PATH, recording every file and directory below it (never PATH itself); -m applies to files only (directories take --dir-mode, symlinks never get a mode); fields default to - unless --mode=--/--dir-mode=--/--owner=--/--group=-- reads them from disk; replaces existing entries unless --missing; --drop-stale removes entries whose files are gone
compact collapse an append-log DB to one record per live path
dbdump -f FORMAT [OUT] render the DB into a packaging manifest

install --method {copy,link,move} (env PKGFORGE_INSTALL_METHOD) stages a file or directory source by hardlinking or moving it instead of the default copy; install --record-tree (env PKGFORGE_INSTALL_RECORD_TREE) also records a directory or archive install's own contents, not just DESTINATION itself -- see the commands guide.

Global options (also read from the environment):

Option Env Meaning
--db PATH PKGFORGE_DB file DB to read/write (- = write records to stdout; reads see an empty DB; dbdump --stdin reads records from stdin instead)
--db-format FMT PKGFORGE_DB_FORMAT backend: jsonl / yaml / sqlite (else from the --db suffix)
--buildroot DIR PKGFORGE_ROOT staging root that maps to / in the DB; DESTINATION/PATH must resolve inside it

Global flags work before or after the subcommand. With no --buildroot or PKGFORGE_ROOT, the default is the current directory -- except that a cwd of / is refused (exit 2); pass --buildroot / to target the live filesystem on purpose.

Storage backends

The file DB has three interchangeable backends — every command behaves the same regardless of which is used:

Format Extensions Model
jsonl (default) .jsonl, .ndjson append-only JSON Lines
yaml .yaml, .yml append-only YAML
sqlite .db, .sqlite, .sqlite3 SQLite store, upserted in place

The backend is picked from the --db extension (override with --db-format); reading an existing file auto-detects its actual format.

Dump formats

Format Aliases Output
rpmspecfiles rpm, rpmspec RPM %files lines (%attr(...), %dir, meta.rpmprefix) to a file or -; rpm 4.19+
rpmspecfiles-pre419 rpm-pre419 Same, for rpm older than 4.19 (measured on 4.14/4.16/4.18): refuses a space, a glob character, or % instead of packaging the wrong file
debian deb install + permissions + dirs + fixperms files into an output directory (or -, sectioned)

Output of the Quick start above:

$ pkgforge dbdump -f rpmspecfiles -
%config %attr(640,root,adm) "/etc/tool.conf"
%attr(755,root,root) "/usr/bin/tool"
%dir %attr(755,root,root) "/usr/share/tool"
%attr(644,root,root) "/usr/share/tool/data.txt"
$ pkgforge dbdump -f debian -
# === install ===
etc/tool.conf etc
usr/bin/tool usr/bin
usr/share/tool/data.txt usr/share/tool
# === permissions ===
/etc/tool.conf 640 root adm
/usr/bin/tool 755 root root
/usr/share/tool 755 root root
/usr/share/tool/data.txt 644 root root
# === dirs ===
usr/share/tool
# === fixperms ===
#!/bin/sh
# Generated by pkgforge dbdump -f debian. Usage: sh fixperms PACKAGE-DIR
set -e
d=${1:?usage: sh fixperms PACKAGE-DIR}
chown -- root:adm "$d"/etc/tool.conf
chmod -- 640 "$d"/etc/tool.conf
chown -- root:root "$d"/usr/bin/tool
chmod -- 755 "$d"/usr/bin/tool
chown -- root:root "$d"/usr/share/tool
chmod -- 755 "$d"/usr/share/tool
chown -- root:root "$d"/usr/share/tool/data.txt
chmod -- 644 "$d"/usr/share/tool/data.txt

File entries

Each entry records mode (octal string, e.g. 644), owner, group, type (file/directory/symlink), and a meta map. Two sentinels defer a field to the staged file: - ("leave at OS default") and -- ("resolve from disk", also spelled auto for -m). -m accepts only 1-4 octal digits or a sentinel and is validated before anything is staged.

Exclude / filter syntax

An --exclude statement is an optional leading ! (negate), zero or more inline tests, and a trailing glob:

(?type:file)**/*.pyc            # every .pyc file, at any depth
!(?meta:keep=1)**/tmp/**        # keep entries tagged keep=1 under tmp/ ...
**/tmp/**                       # ... paired with a broader exclude

Tests are (?type:file|directory|symlink) and (?meta:key=value); prefix a test name with ! ((?!type:file)) to invert just that test. ** recurses (a trailing ** means "the contents of this directory"); an absolute pattern anchors at the install path — the /-rooted path an entry has (dbdump) or will have (install/scan) in the file DB — the same coordinate on every command. install/scan prune an excluded directory's subtree, while dbdump decides entry by entry on its flat key list. See the exclude grammar guide for the full grammar and worked examples.

Documentation

Full docs at https://jose-pr.github.io/pkgforge/ — command reference, file-DB model, exclude grammar, dump formats, and the API reference.

API overview

pkgforge is primarily a CLI; the modules below are its importable surface. See the full contract in pkgforge/AGENTS.md, shipped inside the installed package, or the API reference.

Module Purpose
pkgforge the main entry point, __version__, and the errors (PkgForgeError, UsageError)
pkgforge.entry FileEntry/FileType records and their mode/owner/group resolution
pkgforge.command PkgForgeCmd/PkgForge, the build-root and DB helpers every command shares
pkgforge.db the file-DB backend registry (jsonl/yaml/sqlite, plus your own)
pkgforge.dbdump the packaging-manifest format registry (rpmspecfiles/debian, plus your own)
pkgforge.exclude the --exclude match grammar shared by install/scan/dbdump

Development

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,docs]"

black src tests benchmarks      # format (Python 3.10+)
pytest -q                       # tests
python benchmarks/run.py        # benchmarks (add --save to record; see `benchmarks/README.md`)
mkdocs serve                    # docs preview at http://127.0.0.1:8000

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.

Releasing

This project follows Semantic Versioning and keeps a CHANGELOG.md. Pushing a tag matching v* triggers the release workflow: test gate → build (checking the tag names the version built) → a strict docs build as a gate → GitHub release → publish. The release workflow never deploys the docs site itself: for a final release its last job dispatches the docs workflow at the tag, which owns every Pages deploy. Before 1.0, a MINOR version bump means the documented API broke; everything else (fixes, additions) is a PATCH.

tests/smoke_installed.py is not collected by pytest. Run it with a non-editable install's interpreter (e.g. from a built wheel in a fresh venv) to check the installed CLI surface (--version, --help, completion, the example) and the shipped files (AGENTS.md, README.md, py.typed):

python -m build --wheel --outdir dist
python -m venv /tmp/smoke && /tmp/smoke/bin/pip install dist/*.whl
/tmp/smoke/bin/python tests/smoke_installed.py

License

MIT — see LICENSE.

Metadata

Release files for pkgforge 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pkgforge 0.2.0
File Size Uploaded
pkgforge-0.2.0.tar.gz 226.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pkgforge 0.2.0
File Interpreter ABI Platform
pkgforge-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 346.5 kB

Release files / pkgforge-0.2.0.tar.gz

Download URL pkgforge-0.2.0.tar.gz
Size 226.9 kB
Tags Source
SHA-256 checksum
How to use checksums
f8f9d7dd7b98100d43363f4b63e23bd16d0617d8c5aff85225e8fd5240a38360
BLAKE2b-256 checksum
How to use checksums
4630ba4d644d4932e3cdbea2b88221e723e661c5d7706a4bbd3c7a8daed5766e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.

Transparency log

Release files / pkgforge-0.2.0-py3-none-any.whl

Download URL pkgforge-0.2.0-py3-none-any.whl
Size 119.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
02a3b03ab929a5026bcf9e3f1985324dc6e74315e27a6f85e6b2c1f919131a08
BLAKE2b-256 checksum
How to use checksums
7e19c1ab24a6367e65a3b64f3881ed6577033365cc8407192f40ab022cea8df0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page