File DB & entries
The file DB maps a build-relative path → entry (or a removal). It has three interchangeable storage backends — every command works the same regardless of which is used, because they all load to the same shape.
Backends
| Format | Extensions | Model |
|---|---|---|
jsonl (default) |
.jsonl, .ndjson |
append-only JSON Lines, last record per path wins |
yaml |
.yaml, .yml |
append-only YAML documents, last mapping key wins |
sqlite |
.db, .sqlite, .sqlite3 |
a real SQLite store, upserted in place |
The backend is chosen from the --db file's extension; --db-format
(or PKGFORGE_DB_FORMAT) overrides it. When reading an existing file, its
actual content is sniffed, so a legacy or mislabeled file still loads.
# jsonl
{"group": "root", "meta": {}, "mode": "755", "owner": "root", "path": "/usr/bin/tool", "type": "file"}
{"group": "adm", "meta": {"rpmprefix": "%config(noreplace)"}, "mode": "640", "owner": "root", "path": "/etc/tool/config", "type": "file"}
The JSON Lines default is fast to write and parse and stays greppable —
grep '"/usr/bin/tool"' files.jsonl finds every record for a path. SQLite suits
very large DBs or when you want to query with SQL; YAML is the most
human-editable.
Compaction
The jsonl and yaml backends are append logs: a path installed twice leaves
two records (the later wins) and a removal leaves a tombstone. The
compact command rewrites the log with one record per live path.
The sqlite backend upserts in place, so it never accumulates history (compact
just reclaims space). initdb starts a fresh, empty DB in any backend.
Adding a backend
Backends are pluggable. A third-party package can register its own by
subclassing DbProvider and calling register_provider at import time — core
pkgforge never needs to know about it:
import pkgforge
class TomlDb(pkgforge.DbProvider):
format = "toml"
def load(self): ...
def add(self, path, entry): ...
def remove(self, path): ...
def compact(self): ...
def init(self): ...
pkgforge.register_provider(
"toml",
TomlDb,
suffixes=(".toml",), # infer from a --db suffix
sniff=lambda head: head.startswith(b"#toml"), # or from file content
)
Once registered, the format is selectable with --db-format toml, by a .toml
--db suffix, or by content sniffing on read. A DbProvider is constructed as
provider_cls(path) and must implement load/add/remove/compact/init;
load() returns {path: entry-or-None} like the built-ins.
Entry fields
| Field | Meaning |
|---|---|
mode |
octal permission string, e.g. "644" |
owner / group |
user / group name |
type |
file, directory, or symlink |
meta |
free-form string map (e.g. rpmprefix, a symlink target) |
Sentinels
Two sentinel values let a command defer a field to the staged file:
-— leave at the OS default: do not set this field (e.g. don't chown).--— resolve from the file on disk: read the actual value from the staged file when the entry is recorded.
For example, install -m -- ... records whatever mode the source already has,
while install -m 644 ... records and applies 644.
Build root mapping
--buildroot is the staging directory that maps to / in the DB. Installing
app.conf to /etc under --buildroot /tmp/stage stages the file at
/tmp/stage/etc/app.conf and records it under the key /etc/app.conf. This
keeps the DB independent of where the staging happened, so a dump produces
absolute target paths a packager expects.