Package Manager Guide
Create, build, and manage Iron projects and their dependencies.
This guide covers iron package manager commands, which are unchanged in v3. For the current language syntax, including methods inside object blocks and the pub visibility model, see the language reference.
Overview
Iron ships a two-binary toolchain inspired by Rust's Cargo/rustc split:
| Binary | Role | Analogy |
|---|---|---|
iron | Package manager — projects, dependencies, builds | Cargo |
ironc | Raw compiler — compiles single .iron files to native binaries | rustc |
For most workflows, you only interact with iron. It reads your project manifest (iron.toml), resolves dependencies, and invokes ironc behind the scenes.
The Toolchain
iron and ironc are installed side by side in ~/.iron/bin/. The iron binary discovers ironc automatically as a sibling binary — no configuration needed.
When to use which
| Use case | Command |
|---|---|
| Create a new project | iron init |
| Build a project | iron build |
| Compile a single file | ironc build hello.iron |
| Quick script | iron run script.iron |
iron also transparently forwards single-file commands to ironc. If you run iron build hello.iron (with a .iron file argument), it silently delegates to ironc.
Creating a Project
Binary project
$ iron init
Created binary project `my-project`
$ tree
.
├── iron.toml
├── .gitignore
└── src/
└── main.iron
Library project
$ iron init --lib
Created library project `my-lib`
$ tree
.
├── iron.toml
├── .gitignore
└── src/
└── lib.iron
iron init works in non-empty directories. It creates files that don't exist and skips files that already do — safe to run in an existing repo.
Project Structure
my-game/
├── iron.toml # Project manifest
├── iron.lock # Lockfile (auto-generated, commit to VCS)
├── .gitignore # Ignores target/
├── src/
│ └── main.iron # Entry point (bin) or lib.iron (lib)
├── tests/
│ └── test_math.iron # Test files discovered by iron test
└── target/
├── my-game # Built binary
└── combined.iron # Concatenated source (when deps exist)
Entry point is determined by convention: src/main.iron for binaries, src/lib.iron for libraries. No entry field needed.
iron.toml — Manifest Format
The manifest file describes your package and its dependencies.
[package]
[package]
name = "my-game"
version = "0.1.0"
type = "bin" # "bin" (default) or "lib"
description = "A cool project" # optional
| Field | Required | Description |
|---|---|---|
name | Yes | Package name (used for binary output filename) |
version | Yes | Semantic version string |
type | No | "bin" (default) or "lib" |
description | No | Short description of the package |
[dependencies]
[dependencies]
raylib = true # built-in
iron-ecs = { git = "user/iron-ecs", version = "0.1.0" } # GitHub dep
Dependencies are specified as inline TOML tables with two fields:
| Field | Format | Description |
|---|---|---|
git | "owner/repo" | GitHub repository in short form |
version | "X.Y.Z" | Version tag to resolve (tries vX.Y.Z then X.Y.Z) |
During alpha, dependencies are resolved exclusively from GitHub. The git field uses the short owner/repo format — Iron prepends https://github.com/ automatically.
iron init
Create a new Iron project in the current directory.
$ iron init # binary project
$ iron init --lib # library project
Creates iron.toml, src/main.iron (or src/lib.iron), and .gitignore. Runs git init if not already in a git repo. Skips existing files.
iron build
Build the current package.
$ iron build
Downloading iron-ecs v0.1.0 (user/iron-ecs)
Compiling my-game v0.1.0
Finished dev [unoptimized] in 0.42s
Reads iron.toml, resolves and fetches dependencies, concatenates source files, and invokes ironc. Output binary is placed in target/.
| Flag | Description |
|---|---|
--verbose | Show generated C code (passed through to ironc) |
iron run
Build and immediately execute the package binary.
$ iron run
Compiling my-game v0.1.0
Finished dev [unoptimized] in 0.38s
Running target/my-game
Hello, Iron!
Arguments after -- are passed to the built binary:
$ iron run -- --port 8080
iron check
Type-check the package without producing a binary. Fast feedback loop.
$ iron check
Checking my-game v0.1.0
Finished check completed
iron test
Discover and run all .iron files in the tests/ directory.
$ iron test
Testing my-game v0.1.0
Running test_math.iron
Running test_strings.iron
Finished 2 test(s) passed
Adding Dependencies
Edit iron.toml to add a dependency:
[dependencies]
iron-math = { git = "user/iron-math", version = "0.2.0" }
On the next iron build, Iron will:
- Resolve the version tag to a commit SHA via the GitHub API
- Download and extract the source tarball into
~/.iron/cache/ - Concatenate dependency source files with your project source
- Write the resolved SHA to
iron.lock
How Resolution Works
Tag resolution
Iron resolves version tags to 40-character commit SHAs using the GitHub REST API:
- Try
v{version}tag first (e.g.,v0.2.0) - Fall back to
{version}(e.g.,0.2.0) - Handle annotated tags with a two-step API dereference
The resolved SHA (never a mutable tag name) is stored in iron.lock for reproducibility.
Build composition
Iron concatenates all source files into target/combined.iron in this order:
- Transitive deps (leaf dependencies first)
- Direct deps (in
iron.tomldeclaration order) - Project source (
src/*.ironalphabetically,main.ironlast)
Within each dependency, lib.iron is placed first (if it exists), then remaining files alphabetically.
The Lockfile
iron.lock pins every dependency to an exact commit SHA. It is auto-generated on first build and should be committed to version control.
# This file is auto-generated by iron. Do not edit manually.
lock_version = 1
[[package]]
name = "iron-ecs"
version = "0.1.0"
git = "user/iron-ecs"
sha = "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"
Lockfile behavior
| Scenario | What happens |
|---|---|
No iron.lock | All deps resolved via GitHub API, lockfile created |
| Lockfile exists | Locked SHAs used directly (no API calls) |
New dep in iron.toml | Only new dep resolved, existing entries preserved |
Dep removed from iron.toml | Entry pruned from lockfile on next build |
| Cache deleted | Re-fetched using exact SHAs from lockfile |
Cache
Downloaded dependency sources are stored globally in ~/.iron/cache/:
~/.iron/cache/
└── user/
└── iron-ecs@a1b2c3d4.../
├── iron.toml
└── src/
└── lib.iron
The cache is shared across all projects on the machine. A second project using the same dependency at the same version reuses the cached source without re-downloading.
To force a clean re-fetch, delete the cache: rm -rf ~/.iron/cache/
Transitive Dependencies
If your dependency has its own [dependencies] in its iron.toml, Iron resolves them recursively. All transitive deps are flattened into iron.lock.
Diamond dependencies
If A depends on B and C, and both B and C depend on D at the same version, D is included only once.
Version conflicts
If two dependencies require different versions of the same transitive dep, Iron refuses to build and shows which deps conflict:
error: version conflict for user/iron-math
iron-ecs requires v0.2.0
iron-physics requires v0.1.0
hint: align dependency versions in the respective iron.toml files
Circular dependencies
If A depends on B which depends on A, Iron detects the cycle and prints the full chain:
error: circular dependency detected
-> user/iron-ecs
-> user/iron-math
-> user/iron-ecs (cycle)
hint: remove the circular dependency from one of the packages
Environment Variables
| Variable | Description |
|---|---|
IRON_GITHUB_TOKEN | GitHub auth token for API requests (5000/hr vs 60/hr). Checked first. |
GITHUB_TOKEN | Fallback auth token (auto-set in GitHub Actions). Checked second. |
NO_COLOR | Disable colored output when set to any value. |
FORCE_COLOR | Force colored output even when not a TTY. |
In GitHub Actions, GITHUB_TOKEN is set automatically. No configuration needed for dependency resolution in CI.
Error Messages
| Error | Cause | Fix |
|---|---|---|
| tag 'X' not found for owner/repo | Version tag doesn't exist on GitHub | Check that the repo has the tag (with or without v prefix) |
| GitHub API rate limit exceeded | Too many unauthenticated requests | Set IRON_GITHUB_TOKEN for 5000 req/hr |
| circular dependency detected | Dep graph has a cycle | Remove the circular dep from one package |
| version conflict for ... | Two deps need different versions of the same transitive dep | Align versions across your dependency tree |
| has no iron.toml | A dependency is not a valid Iron package | Ensure the dep has an iron.toml at its root |
| no iron.toml found | Not in a project directory | Run iron init or cd into a project |
FAQ
Do I commit iron.lock?
Yes. The lockfile ensures reproducible builds. Without it, teammates and CI may resolve different commits.
Can I use private repos?
Not yet. Alpha supports public GitHub repositories only. Private repo auth is planned for a future release.
What about semver ranges?
Iron pins to exact commit SHAs, not version ranges. The ecosystem is currently too small for version conflict resolution. Semver ranges may be added in a future release.
Where are deps downloaded?
To ~/.iron/cache/ (global, shared across all projects). Delete this directory to force a clean re-fetch.
Can I use local path dependencies?
Not yet. Local path dependencies (path = "../my-lib") are planned for a future release.
How do I update a dependency?
Delete the dep's entry from iron.lock and re-run iron build. It will re-resolve the tag to the latest commit SHA. An iron update command is planned.