Repository navigation
Extending Plugin Manifest Dependencies
Prev: Writing Lua Plugins | Up: Extending ReqPack | Next: Lua Plugin Cookbook
ReqPack now supports plugin-owned reqpack.lua manifests in three ways:
-
Declarative bundle dependencies in the plugin root
reqpack.lua -
Runtime lookup from plugin Lua through
reqpack.manifest -
Runtime provisioning from plugin Lua through
context.packages,context.manifest.ensure, andcontext.rqp(ReqPack v1.3.1+)
This page explains how plugin authors declare dependencies and how ReqPack resolves nested manifest files inside a plugin bundle.
This is separate from:
-
Project manifests used by
rqp install .andrqp audit . -
Native
.rqppackage hook manifests used inside.rqparchives
Those formats live in the user guide. This page is for plugin authors.
ReqPack uses the filename reqpack.lua in several places, but not always with the same schema.
| Location | Schema | Used for |
|---|---|---|
| Project directory | packages = { ... } |
rqp install ., rqp audit ., snapshots |
| Plugin bundle root |
apiVersion = 1, optional depends, packages, manifests
|
Plugin dependency provisioning |
| Nested path inside plugin bundle | packages = { ... } |
Driver-specific or feature-specific plugin requirements |
Native .rqp package |
hooks = { ... } |
Install/remove/update hooks for .rqp packages |
Do not mix these schemas in one file unless you know exactly which fields belong together.
Every valid plugin bundle still needs a root reqpack.lua with:
return {
apiVersion = 1,
}On top of that, ReqPack supports three dependency surfaces in the same plugin-root manifest.
Compact dependency specs for the planner.
return {
apiVersion = 1,
depends = {
"curl",
"dnf:python3@3.12",
"npm:typescript",
},
}Accepted forms:
packagepackage@versionsystem:packagesystem:package@version
ReqPack expands these into ENSURE dependency packages before your plugin runs.
Project-style manifest entries declared directly in the plugin root manifest.
return {
apiVersion = 1,
packages = {
{ system = "dnf", name = "gcc" },
{ system = "npm", name = "typescript", version = "5.6.0" },
},
}Each entry must contain at least:
systemname
Optional fields:
versionflags
ReqPack treats these the same way as entries loaded from nested project manifests.
Relative paths to additional project-style manifest files inside the plugin bundle.
return {
apiVersion = 1,
manifests = {
"drivers/ffmpeg/reqpack.lua",
"drivers/vllm/reqpack.lua",
},
}Each referenced file should use the normal project manifest format:
return {
packages = {
{ system = "dnf", name = "ffmpeg" },
},
}ReqPack resolves these paths relative to the plugin bundle directory, not relative to the current working directory.
Example layout:
plugins/
wuji/
metadata.json
reqpack.lua
run.lua
scripts/
install.lua
remove.lua
drivers/
ffmpeg/
reqpack.lua
vllm/
reqpack.lua
Example root manifest:
return {
apiVersion = 1,
depends = {
"dnf:lua",
},
manifests = {
"drivers/ffmpeg/reqpack.lua",
"drivers/vllm/reqpack.lua",
},
packages = {
{ system = "dnf", name = "curl" },
},
}When a user runs:
rqp install wuji demo-packageReqPack plans:
- the requested
wujipackage -
dnf:luafromdepends -
dnf:curlfrom rootpackages - all packages from nested driver manifests
All of these are provisioned as plugin dependency ENSURE steps before the main install work runs.
Inside run.lua, plugins can resolve and load manifest files from their own bundle through the global reqpack.manifest namespace.
Available functions:
| Function | Returns | Purpose |
|---|---|---|
reqpack.manifest.resolve(path) |
absolute path string or nil
|
Resolve a manifest path relative to REQPACK_PLUGIN_DIR
|
reqpack.manifest.load(path) |
table or nil
|
Resolve and parse a project-style manifest |
Path rules:
- explicit paths starting with
.or/are resolved normally - bare paths such as
drivers/ffmpeg/reqpack.luaare resolved relative to the plugin directory - directory arguments such as
drivers/ffmpegresolve todrivers/ffmpeg/reqpack.lua
Example inside plugin.install():
function plugin.install(context, packages)
local manifest = reqpack.manifest.load("drivers/ffmpeg")
if manifest == nil then
context.tx.failed("ffmpeg driver manifest missing")
return false
end
for _, entry in ipairs(manifest.packages) do
context.log.info("driver requires " .. entry.system .. ":" .. entry.name)
end
-- continue with install logic
return true
endreqpack.manifest.load(path) returns a table like:
{
path = "/home/user/.local/share/reqpack/plugins/wuji/drivers/ffmpeg/reqpack.lua",
packages = {
{
system = "dnf",
name = "ffmpeg",
},
},
}If the file is missing, invalid, or has no packages table, the function returns nil.
Use this API when plugin logic needs manifest contents at runtime, for example:
- choosing a driver-specific dependency set,
- validating that a bundled manifest exists before install,
- reading feature flags from nested manifest entries.
If you only want ReqPack to provision dependencies automatically before your plugin runs, prefer declarative depends, packages, or manifests in the plugin root manifest instead of loading them manually in Lua.
Since ReqPack v1.3.1, plugins can ask the core executor to install dependencies during plugin.install() or plugin.installLocal() — without shelling out to rqp install ....
These APIs are available on context inside action methods. They run in-process through the same executor that invoked your plugin.
| API | Returns | Purpose |
|---|---|---|
context.packages.ensure(packages) |
bool |
Install missing packages from a project-style package table |
context.manifest.ensure(path) |
bool |
Resolve a manifest path, load its packages, then ensure them |
context.rqp.installLocal(path) |
bool |
Hand a local .rqp file or directory to the native rqp plugin |
Package table shape matches project manifests:
{
{ system = "dnf", name = "ffmpeg-devel" },
{ system = "dnf", name = "gcc", version = "14.0" },
}Path rules for context.manifest.ensure(path):
- explicit paths starting with
.or/resolve from the filesystem (for downloaded driver trees) - bare paths such as
drivers/ffmpegresolve relative tocontext.plugin.dir - missing manifest or empty
packagestable is treated as success (nothing to ensure)
Example: driver downloaded outside the plugin bundle (Wuji-style flow):
function plugin.install(context, packages)
local driver_dir = download_driver_to_local_store(packages[1].name)
if driver_dir == nil then
return false
end
-- read reqpack.lua from the downloaded driver tree, then ensure OS deps
if not context.manifest.ensure(driver_dir) then
context.tx.failed("driver dependency ensure failed")
return false
end
return build_driver(driver_dir)
endExample: install a downloaded .rqp archive in-process:
function plugin.install(context, packages)
local archive_path = download_catalog_rqp(packages[1])
if archive_path == nil then
return false
end
if not context.rqp.installLocal(archive_path) then
context.tx.failed("rqp installLocal failed")
return false
end
return true
end| Goal | Mechanism |
|---|---|
| Always provision the same deps before any install | declarative depends / packages / manifests in plugin root |
| Provision deps for one selected driver/profile at runtime |
context.manifest.ensure(path) after you know the path |
| Read manifest contents for branching/logging only | reqpack.manifest.load(path) |
Install a local .rqp from plugin logic |
context.rqp.installLocal(path) |
| Legacy fallback / bootstrap | context.exec.run("rqp install ...") |
Important differences from declarative manifests:
- declarative entries must live inside the installed plugin bundle and are expanded for all listed manifests at plan time
-
context.manifest.ensure(path)can target any path on disk, including trees your plugin just downloaded - declarative provisioning happens before
plugin.install(); runtime ensure happens inside your install logic
Do not shell out to rqp install ... for normal dependency or .rqp work when these context APIs are available.
ReqPack resolves plugin manifest paths in the core layer.
Resolution order for reqpack.manifest.resolve(path) and planner dependency expansion:
- If the argument is an explicit path (
.or/), resolve it from the current filesystem context. - Otherwise resolve it relative to the plugin bundle directory.
- If the resolved path is a directory, append
/reqpack.lua. - If the resolved path is already a
reqpack.luafile, use it directly.
Core entry points:
-
resolve_manifest_path(...)for general manifest path resolution -
resolve_plugin_manifest_path(...)for plugin-relative resolution -
ManifestLoader::load(...)for strict project-manifest parsing -
plugin_bundle_project_packages(...)for planner dependency expansion
The CLI reuses the same core resolver for project manifest arguments such as:
rqp install .
rqp audit ./reqpack.luaThat CLI path is still project-oriented. Plugin-relative resolution is primarily for plugin bundles and reqpack.manifest.
Use depends:
return {
apiVersion = 1,
depends = { "curl" },
}Use nested manifests:
return {
apiVersion = 1,
manifests = {
"drivers/ffmpeg/reqpack.lua",
"drivers/vllm/reqpack.lua",
},
}Combine all three surfaces:
return {
apiVersion = 1,
depends = { "dnf:lua" },
packages = {
{ system = "dnf", name = "make" },
},
manifests = {
"profiles/server/reqpack.lua",
},
}Use reqpack.manifest.load(...) inside action methods when install logic must branch on manifest contents.
Do not duplicate the same dependency list in both declarative manifest fields and manual Lua provisioning unless you have a deliberate reason.
plugin.getRequirements() is still part of the runtime plugin contract.
Best practice:
- treat plugin root
reqpack.luaas source of truth for shipped dependencies - keep
plugin.getRequirements()consistent with that manifest - use nested manifests when different plugin features need different dependency sets
If bundle manifest and getRequirements() diverge, planning and recovery behavior becomes harder to reason about.
Hermetic plugin tests can assert manifest lookup directly:
function plugin.install(context, packages)
local resolved = reqpack.manifest.resolve("drivers/tool/reqpack.lua")
local manifest = reqpack.manifest.load("drivers/tool")
return resolved ~= nil
and manifest ~= nil
and manifest.packages[1].name == "curl"
endFor dependency provisioning behavior, run a real smoke test:
rqp install <your-plugin> <package>Then verify that ENSURE steps for declared dependencies appear before the main plugin action.
For runtime context.manifest.ensure(...) or context.rqp.installLocal(...), verify the missing OS packages or .rqp install happens inside the plugin action itself rather than as separate top-level ENSURE steps planned from the plugin root manifest.
Prev: Writing Lua Plugins | Up: Extending ReqPack | Next: Lua Plugin Cookbook
- User Guide
- Getting Started
- Command Reference
- Configuration
- Configuration Reference
- Security, Audit, and SBOM
- Output and Report Formats
- Remote Mode
- Remote Protocol Reference
- Using Native
rqpPackages - Troubleshooting