Package Management
Zynx includes a single, focused package system designed to be:
- Predictable – one package kind, one resolver, one lockfile.
- Portable – works with both source-based and binary artifacts.
- Simple to operate – all flows are driven by the
zynxCLI.
This document explains how dependencies are declared, how registries are organized, how resolution and locking work, and how to publish and consume packages.
Concepts at a Glance
- Package – A Zynx library that can be consumed by other projects.
- Registry – A static mirror containing package metadata and artifacts.
- Artifact – A concrete deliverable for a package version:
source– a source archive that will be built locally.binary– a prebuilt archive for a specifictargetandprofile.
- Lockfile –
zynx-lock.jsonrecords exact versions and artifact choices. - Modules directory –
modules/<name>/<target>/holds installed packages.
Declaring Dependencies
Dependencies are declared in the zynx.modules section of zynx.json.
{
"name": "app",
"version": "0.1.0",
"zynx": {
"registries": [{ "name": "default", "url": "https://mirror.example.com" }],
"modules": {
"io": {
"kind": "package",
"version": "^1.2.0"
},
"fmt": {
"kind": "package",
"git": "https://github.com/org/fmt",
"version": "^0.3.0"
}
}
}
}
Rules
kind: "package"is the only supported package dependency kind.- Registry-backed packages:
- Identified by name (e.g.,
"io"). - Constrained by a SemVer
versionrange (e.g.,"^1.2.0").
- Identified by name (e.g.,
- Git-backed packages:
- Use
gitto specify the repository URL. - Still use a SemVer
versionrange, resolved against tags.
- Use
- FFI:
- Local C source and headers are configured separately as
ffimodules. ffiis not a package kind; it lives outside the package resolver.
- Local C source and headers are configured separately as
- Artifact selection:
- Chosen at install time via CLI flags (see below); not configured in the manifest.
Configuring Registries
Registries are listed in zynx.registries. They are simple static mirrors, not dynamic services.
"registries": [
{ "name": "default", "url": "https://mirror.example.com" }
]
Registry URL Forms
A registry URL can be:
https://…– HTTP(S) mirror.- Absolute local filesystem path.
file://…– local file-based mirror.
Registries are tried in the order specified in the manifest.
On-Disk Layout
Each registry is expected to provide:
packages/<name>/index.jsonpackages/<name>/<version>/release.json
Example index.json:
{
"versions": ["1.0.0", "1.1.0"]
}
Example release.json:
{
"version": "1.1.0",
"artifacts": [
{
"kind": "source",
"url": "packages/io/1.1.0/io-src.tar.gz",
"size": 12345,
"digest": "sha256:..."
},
{
"kind": "binary",
"target": "darwin-aarch64-apple",
"profile": "release",
"url": "packages/io/1.1.0/io-darwin-aarch64-apple-release.tar.gz",
"size": 45678,
"digest": "sha256:..."
}
]
}
Binary artifact matching is exact on target and profile.
Target Triple Forms
Zynx currently accepts both triple orders in different commands:
arch-platform-abi(for example,aarch64-linux-gnu) is the common CLI form.platform-arch-abi(for example,darwin-aarch64-apple) is used in package artifact metadata and install paths.
The package tooling normalizes between them when resolving/installing artifacts.
Artifact Selection
Zynx separates what packages you depend on from how they are materialized. Artifact selection is controlled purely by CLI flags:
--artifact auto- Prefer exact-match binary artifacts when available.
- Fall back to source if no matching binary exists.
--artifact source- Ignore binary artifacts; always build from source.
--artifact binary- Require exact-match binaries; fail if none exist.
- Git-backed packages are always source-only and therefore incompatible.
These flags apply to:
zynx lockzynx fetchzynx installzynx sync
Resolution and Locking
Resolution Rules
When you run a package operation (lock, fetch, install, sync):
- Manifest read –
zynx.jsonandzynx.modulesare loaded. - Registry resolution:
- For each registry package, the resolver selects the highest version that:
- Satisfies the SemVer range in the manifest.
- Is available in the configured registries.
- For git packages, the resolver:
- Reads tags from the repository.
- Picks the highest tag satisfying the SemVer range.
- For each registry package, the resolver selects the highest version that:
- Artifact selection:
- Governed by
--artifactmode as described above. - Git packages always resolve to source artifacts.
- Governed by
Lockfile
Exact resolutions are written to zynx-lock.json. The lockfile captures:
- Chosen package versions and sources.
- Selected artifact kind per dependency.
- Global resolution mode under
resolution.artifact.
Typical lock entries include:
-
Git package:
- Repository URL.
- Resolved tag and commit hash.
- Exact version.
-
Registry source artifact:
- Registry URL.
- Release metadata URL.
- Source tarball URL and checksum.
- Exact version.
-
Registry binary artifact:
- Registry URL.
- Release metadata URL.
- Binary tarball URL and checksum.
- Exact version,
target, andprofile.
The lockfile is meant to be checked into version control to ensure reproducible builds.
Install and Sync Flow
Two primary commands materialize dependencies:
zynx installzynx sync
Both follow a three-step process:
-
Resolve and lock
- Compute dependency graph.
- Select artifacts according to
--artifactpolicy. - Write or update
zynx-lock.json.
-
Fetch artifacts
- Download source or binary tarballs into a local cache, typically:
modules/.cache/srcfor source/binary bundles.
- Use checksums from
release.jsonto verify integrity.
- Download source or binary tarballs into a local cache, typically:
-
Materialize modules
- For source artifacts:
- Extract source.
- Build the package for the current target.
- Produce
modules/<name>/<target>/lib<name>.a.
- For binary artifacts:
- Extract the prebuilt bundle directly into
modules/<name>/<target>/.
- Extract the prebuilt bundle directly into
- For source artifacts:
From the consumer’s perspective, all installed packages look the same:
modules/<name>/<target>/lib<name>.a- Associated headers and metadata.
Whether the library came from a binary bundle or a local build is an implementation detail.
Binary Artifact Contents
A binary artifact is a .tar.gz bundle with a well-known layout:
lib<name>.a– the compiled library archive.<name>.zxh– public header exports for Zynx.zynx.json– package metadata.
No vendored dependencies are embedded. Dependencies are modeled and resolved separately through the package system.
Command Reference
Below is a typical sequence of package management commands, illustrating registry and git usage:
# Add a registry package
zynx add io --version ^1.2.0
# Add a direct git package
zynx add fmt --git https://github.com/org/fmt --version ^0.3.0
# Resolve and lock dependencies, preferring binaries when possible
zynx lock --artifact auto
# Fetch locked artifacts into the local cache
zynx fetch --artifact auto
# Install packages, forcing local source builds
zynx install --artifact source
# Install packages using only exact-match binaries
zynx install --artifact binary \
--target aarch64-darwin-apple \
--profile release
# Refresh ffi modules and package archives according to the lockfile
zynx sync --artifact auto
zynx add
zynx add is a convenience command that edits zynx.json:
-
For registry packages:
zynx add io --version ^1.2.0 -
For git packages:
zynx add fmt --git https://github.com/org/fmt --version ^0.3.0
Packages vs FFI
Zynx distinguishes between:
packagedependencies – Zynx libraries pulled from registries or git.ffimodules – Local C sources and headers that are compiled into static archives with generated Zynx bindings.
package and ffi serve different purposes:
- Use
packagewhen:- Consuming a Zynx library that adheres to the package registry/lockfile model.
- Use
ffiwhen:- Integrating locally maintained C code that should be compiled as part of the project.
- Generating bindings via
bindgenfor local headers.
ffi modules are not replaced by registry binary packages; they remain the right tool for local C interop.
Publishing Packages
Zynx can produce both source releases and binary releases. The publishing workflow is driven by:
zynx publish pack [--target <triple>] [--profile <debug|release>]
Source Releases
By default, zynx publish pack always produces a source release:
dist/<name>-<version>-src.tar.gzrelease.json
This source archive is what registries expose as a kind: "source" artifact.
Binary Releases
To publish a binary artifact in addition to the source release, provide a target triple and profile:
zynx publish pack \
--target aarch64-darwin-apple \
--profile release
This:
-
Packages the already-built library for that
targetandprofile. -
Produces a binary
.tar.gzcontaining:lib<name>.a<name>.zxhzynx.json
-
Adds a
kind: "binary"artifact entry to the samerelease.jsonused for the source artifact.
Notes:
- Each publish run creates artifacts for one target/profile pair.
- Publishing is a packaging step; it assumes the library for that target/profile has already been built.
Practical Recommendations
- Check in
zynx-lock.jsonto ensure reproducible builds across machines and CI. - Prefer
--artifact autoin most development and CI workflows:- You get fast binary installs when available, with safe fallback to source.
- Use
--artifact sourcewhen:- Debugging build issues for dependencies.
- Verifying that a package builds cleanly on a new platform.
- Use
--artifact binaryfor:- Strictly controlled, fully prebuilt deployments.
- Environments where local compilation is undesirable or impossible.
- Keep C interop local via
ffi:- Treat C integration as part of your workspace.
- Rely on packages only for Zynx libraries.
By keeping the package model small (single kind, single lockfile, and explicit artifact selection), Zynx aims to provide a package system that is easy to reason about, robust in CI, and friendly to both source-based and binary-centric workflows.