Skip to main content

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 zynx CLI.

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 specific target and profile.
  • Lockfilezynx-lock.json records exact versions and artifact choices.
  • Modules directorymodules/<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 version range (e.g., "^1.2.0").
  • Git-backed packages:
    • Use git to specify the repository URL.
    • Still use a SemVer version range, resolved against tags.
  • FFI:
    • Local C source and headers are configured separately as ffi modules.
    • ffi is not a package kind; it lives outside the package resolver.
  • 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.json
  • packages/<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 lock
  • zynx fetch
  • zynx install
  • zynx sync

Resolution and Locking

Resolution Rules

When you run a package operation (lock, fetch, install, sync):

  1. Manifest readzynx.json and zynx.modules are loaded.
  2. 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.
  3. Artifact selection:
    • Governed by --artifact mode as described above.
    • Git packages always resolve to source artifacts.

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, and profile.

The lockfile is meant to be checked into version control to ensure reproducible builds.


Install and Sync Flow

Two primary commands materialize dependencies:

  • zynx install
  • zynx sync

Both follow a three-step process:

  1. Resolve and lock

    • Compute dependency graph.
    • Select artifacts according to --artifact policy.
    • Write or update zynx-lock.json.
  2. Fetch artifacts

    • Download source or binary tarballs into a local cache, typically:
      • modules/.cache/src for source/binary bundles.
    • Use checksums from release.json to verify integrity.
  3. 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>/.

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:

  • package dependencies – Zynx libraries pulled from registries or git.
  • ffi modules – Local C sources and headers that are compiled into static archives with generated Zynx bindings.

package and ffi serve different purposes:

  • Use package when:
    • Consuming a Zynx library that adheres to the package registry/lockfile model.
  • Use ffi when:
    • Integrating locally maintained C code that should be compiled as part of the project.
    • Generating bindings via bindgen for 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.gz
  • release.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:

  1. Packages the already-built library for that target and profile.

  2. Produces a binary .tar.gz containing:

    • lib<name>.a
    • <name>.zxh
    • zynx.json
  3. Adds a kind: "binary" artifact entry to the same release.json used 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.json to ensure reproducible builds across machines and CI.
  • Prefer --artifact auto in most development and CI workflows:
    • You get fast binary installs when available, with safe fallback to source.
  • Use --artifact source when:
    • Debugging build issues for dependencies.
    • Verifying that a package builds cleanly on a new platform.
  • Use --artifact binary for:
    • 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.