What is flyp?

flyp is the official package manager for Fly. It handles everything outside the compiler itself: creating projects, declaring and fetching dependencies, locking versions for reproducible builds, and invoking the fly compiler with the right flags and include paths.

flyp ships alongside the fly compiler as a separate binary in the same installation archive. If you have Fly installed, you have flyp.

$ flyp --version
flyp 0.1.0

Project structure

A Fly project managed by flyp has this layout:

my-project/
├── fly.toml        # manifest — project metadata and dependencies
├── fly.lock        # lockfile — exact resolved versions (commit to VCS)
└── src/
    └── main.fly    # entry point (auto-detected)

fly.toml is written by hand. fly.lock is generated by flyp and must never be edited manually.


fly.toml reference

fly.toml is the project manifest. It uses TOML syntax.

[package]

Required section that describes the project itself.

[package]
name        = "my-app"
version     = "0.1.0"
description = "A brief description of what this project does"
license     = "Apache-2.0"
fly-version = "0.1.0"
homepage    = "https://example.com"           # optional
repository  = "https://github.com/you/my-app" # optional
authors     = ["Your Name <[email protected]>"]  # optional
FieldRequiredDescription
nameyesPackage name. Must match [a-z0-9_-]+
versionyesSemantic version in MAJOR.MINOR.PATCH format
descriptionyesShort description
licenseyesSPDX license identifier
fly-versionyesMinimum Fly compiler version required
homepagenoProject website
repositorynoSource repository URL
authorsnoList of author strings

[targets]

Declares all build targets — binaries and libraries — in a single key→value table. The key is the unique identifier used by the CLI (flyp build --target key, flyp run --bin key) and the default output filename.

A target is a binary by default. Adding the lib field makes it a library:

[targets]
app      = { path = "src/main.fly" }                       # binary
cli-tool = { path = "src/cli.fly" }                        # binary
my-lib   = { path = "src/lib.fly",  lib = "static"  }      # static library → libmy-lib.a
dyn-lib  = { path = "src/dyn.fly",  lib = "dynamic" }      # shared library → libdyn-lib.so
both-lib = { path = "src/both.fly", lib = "both"    }      # both → libboth-lib.a + libboth-lib.so

The optional name field overrides the output filename (the key is still used for CLI lookup):

[targets]
app    = { name = "MyApp", path = "src/main.fly" }
my-lib = { name = "MyLib", path = "src/lib.fly", lib = "static" }

flyp build --target app → produces target/debug/MyApp.

FieldRequiredDescription
pathyesEntry-point source file, relative to the project root
libnoAbsent = binary. "static"lib<name>.a. "dynamic"lib<name>.so / .dylib / .dll. "both" → static archive and shared library.
namenoOutput filename override (defaults to the key)

If [targets] is absent, flyp auto-detects src/main.fly (→ binary) and src/lib.fly (→ static library) when they exist.

[hooks]

Build hooks run shell commands before and after compilation. Useful for code generation, asset processing, or post-build stripping.

[hooks]
pre-build  = "python scripts/codegen.py"    # runs before any target compiles
post-build = "strip target/release/my-app"  # runs after all targets compile successfully

Both fields are optional. post-build only runs if the build succeeds.

Each hook runs from the package root directory with these environment variables set:

VariableValue
FLYP_PROFILEActive profile name (debug, release, …)
FLYP_OUT_DIRAbsolute path to target/<profile>/
FLYP_ROOT_DIRAbsolute path to the package root
FLYP_TARGET_TRIPLECross-compilation triple, or empty for native
FLYP_PACKAGE_NAMEPackage name from [package]

If a hook exits with a non-zero code the build is aborted and an error is reported.

Links native C/C++ libraries into the compiled output. Each entry in libs is passed to the Fly compiler as --link-lib=NAME, which forwards it as -lNAME to the system linker.

[link]
libs = ["LLVM-20", "curl", "pthread"]
FieldTypeDescription
libsstring[]Library names without the -l prefix

Use this for projects that call into C libraries via the FFI bridge. The [link] section applies to all targets in the package.

[test]

Configures test suite discovery and execution. suites accepts glob patterns relative to the project root.

[test]
suites    = ["src/**/*Suite.fly", "tests/**/*.fly"]
parallel  = false   # run suites concurrently
fail_fast = false   # stop after the first failing suite
FieldTypeDefaultDescription
suitesstring[]Glob patterns for suite source files
parallelboolfalseRun suites concurrently
fail_fastboolfalseStop after the first failing suite

Each suite is compiled with the --test flag and run as a standalone binary. Compiled test binaries are written to target/test/<suite_name> (always profile-independent).

See Fly Testing for the full test system reference.

[repo]

Declares named registry aliases. Each alias maps a short name to a registry URL. All references to registries elsewhere in the file use these names — never raw URLs.

[repo]
local  = "http://localhost:5000"
remote = "https://registry.flylang.org"

[package].registry

The default registry alias used by flyp deploy and flyp publish:

[package]
name     = "my-lib"
version  = "1.0.0"
registry = "local"   # alias from [repo]

[dependencies]

Runtime dependencies. Three source types are supported:

Git dependency — fetched directly from a git repository:

[dependencies]
mylib   = { git = "https://github.com/example/mylib.git", tag = "v1.2.0" }
core    = { git = "https://github.com/example/core.git",  branch = "main" }
patched = { git = "https://github.com/example/utils.git", rev = "a3f1c29d" }

Registry dependency — fetched from a named registry (alias from [repo]). The map key is the local alias; name is the package name on the registry (defaults to the key); version is a semver range:

[dependencies]
fly-std    = { registry = "local",  version = "^0.14.0" }
io-helpers = { registry = "remote", name = "fly-io", version = "~1.2" }
utils      = { registry = "local",  version = ">=1.0.0,<2.0.0" }

Semver range syntax:

SyntaxMeaningExample
*Any version (latest)"*"
1.2.3Exact version"1.2.3"
^1.2.3Compatible: >=1.2.3, <2.0.0 (same major)"^1.2.3"
^0.2.3>=0.2.3, <0.3.0 (major=0: same minor)"^0.2.3"
^0.0.3>=0.0.3, <0.0.4 (minor=0: same patch)"^0.0.3"
~1.2.3Patch-level: >=1.2.3, <1.3.0"~1.2.3"
~1.2>=1.2.0, <1.3.0"~1.2"
~1>=1.0.0, <2.0.0"~1"
>=1.0.0At least">=1.0.0"
<2.0.0Strictly less"<2.0.0"
>=1.0.0,<2.0.0Range (comma = AND)">=1.0.0,<2.0.0"
1.x / 1.*Wildcard: >=1.0.0, <2.0.0"1.x"

flyp selects the highest available version that satisfies the range.

Path dependency — references a local workspace member:

[dependencies]
core = { path = "../libs/core" }

For git dependencies, specify exactly one of:

KeyDescription
tagA Git tag — recommended for released versions
branchA Git branch — always fetches the latest commit on that branch
revA full 40-character commit hash — the most reproducible option

[dev-dependencies]

Dependencies used only for tests and development tools. Not fetched in release builds and not propagated to packages that depend on yours.

[dev-dependencies]
test-utils = { git = "https://github.com/example/test-utils.git", tag = "v0.3.0" }

[profiles]

Build profiles control how the Fly compiler optimises and instruments the output. All profiles are declared in a single [profiles] table as inline key→value entries.

[profiles]
debug   = { opt-level = 0, debug-info = true,  assertions = true,  lto = false, strip = false }
release = { opt-level = 3, debug-info = false, assertions = false, lto = false, strip = false }
ci      = { opt-level = 2, debug-info = true,  assertions = false, lto = false, strip = false }

debug and release are built-in profiles with the defaults shown above. They do not need to be declared explicitly — flyp injects them automatically if the [profiles] section is absent or does not define them.

Any number of custom profiles can be added. Select a profile with --profile:

$ flyp build --profile ci
$ flyp test  --profile ci

--release is an alias for --profile release. The default profile (no flag) is debug.

FieldTypeDescription
opt-levelint 0–3Compiler optimisation level
debug-infoboolEmit DWARF debug information
assertionsboolEnable runtime assertion checks
ltoboolLink-time optimisation
stripboolStrip symbols from output binary

Download: a fully annotated fly.toml template — including ci and bench custom profiles — is available at flylang.org/fly.toml.


Creating a project

$ flyp init

Creates fly.toml and src/main.fly in the current directory, using the directory name as the package name.

$ flyp init --name my-app

Creates a new subdirectory my-app/ with the same layout.

$ flyp init --name my-app --version 0.2.0

Specifies an initial version other than the default 0.1.0.

After flyp init:

my-app/
├── fly.toml
└── src/
    └── main.fly

Managing dependencies

Adding a dependency

$ flyp add mylib --git https://github.com/example/mylib.git --tag v1.2.0

flyp adds the entry to fly.toml, then immediately resolves and fetches all dependencies, writing a fresh fly.lock.

$ flyp add core --git https://github.com/example/core.git --branch main
$ flyp add patched --git https://github.com/example/utils.git --rev a3f1c29d8e2b...

Add a development-only dependency with --dev:

$ flyp add test-utils --git https://github.com/example/test-utils.git --tag v0.3.0 --dev

Removing a dependency

$ flyp remove mylib

Removes the entry from fly.toml (both [dependencies] and [dev-dependencies]) and regenerates fly.lock.

Updating dependencies

Update a single package to the latest commit on its configured branch or tag:

$ flyp update mylib

Update all dependencies at once:

$ flyp update

flyp update clears the affected cache entries and re-runs resolution, so it will pick up new commits on tracked branches or newer tags if you change fly.toml manually.

Upgrading dependencies

flyp upgrade updates tag-pinned git dependencies to the highest available semver tag, rewriting fly.toml and regenerating fly.lock:

$ flyp upgrade                # upgrade all tag-pinned deps
$ flyp upgrade fly-std        # upgrade one specific dep
$ flyp upgrade --dry-run      # preview changes without writing them

Example output:

  upgrading: fly-std  v0.12.0 → v0.14.0
  up to date: my-lib  v2.1.0
updated 1 dependency — re-locking...
Dep typeBehaviour
tag = "..."Upgraded to latest semver tag
branch = "..."Skipped — use flyp update
rev = "..."Skipped — pinned, change manually
Registry depSkipped — change version manually

The lockfile

fly.lock records the exact commit hash, checksum, and dependency tree for every package in the build. A minimal lockfile looks like this:

# fly.lock — generated automatically by flyp.
# Do not edit manually.
# Commit alongside fly.toml for reproducible builds.

version  = 1
flyp     = "0.1.0"
checksum = "sha256:e3b0c44298fc1c149afbf4c8996fb924..."

[[package]]
name         = "mylib"
version      = "1.2.0"
source       = "git+https://github.com/example/mylib.git"
rev          = "a3f1c29d8e2b4f1a7c3d5e6b8f9a0b1c2d3e4f5a"
tag          = "v1.2.0"
checksum     = "sha256:9f86d081884c7d659a2feaa0c55ad015..."
dependencies = []

Always commit fly.lock to version control. It guarantees that every developer and every CI run compiles the exact same code.

The lockfile is automatically regenerated whenever fly.toml changes. flyp detects staleness by storing a SHA-256 checksum of fly.toml inside the lockfile itself.


Version resolution (MVS)

flyp uses Minimum Version Selection (MVS), the same algorithm used by Go Modules. The rules are simple:

  1. Collect all version constraints from the root manifest and all transitive dependencies via breadth-first traversal.
  2. For each package, select the highest version among all constraints — this is the minimum version that satisfies everyone.
  3. If two constraints refer to the same package name but different Git URLs, resolution fails with error E001.

Example

root         → mylib v1.0.0
root         → framework v2.0.0
framework    → mylib v1.5.0   ← higher constraint

MVS selects mylib v1.5.0. No SAT solver, no backtracking — deterministic and fast.

Why the highest version?

Because it is the minimum version that satisfies the most demanding constraint. Lower versions would break framework, and picking an arbitrary higher version would be non-deterministic.


Building

Compile all targets using the default (debug) profile:

$ flyp build

Compile with the release profile (--release is an alias for --profile release):

$ flyp build --release

Use a custom profile declared in [profiles]:

$ flyp build --profile ci
$ flyp build --profile bench

Build one or more specific targets by key:

$ flyp build --targets my-app
$ flyp build --targets my-app,my-lib

Omit --targets to build all declared targets.

Output goes to target/<profile>/ in the project root — for example target/debug/, target/release/, or target/ci/.

If fly.lock is stale when you run flyp build, flyp automatically re-locks before building.

Incremental builds

flyp skips recompiling a target if nothing has changed since the last successful build:

$ flyp build          # compiles all targets
$ flyp build          # "up to date: my-app" — nothing recompiled

A target is recompiled when any of the following change:

InputWhat triggers a rebuild
Source fileContent (SHA-256) of the entry-point .fly file
Build profileAny of opt-level, debug-info, assertions, lto, strip
Dependenciesfly.lock content (resolved revisions of all packages)
Cross-target--cross triple
Library typelib = "static" vs "dynamic"

Fingerprints are stored in target/<profile>/.flyp/<key>.fp. Running flyp clean removes them along with the build artifacts, forcing a full rebuild on the next invocation.

Cross-compilation

Build for a different CPU architecture or OS using --cross <triple>:

$ flyp build --cross aarch64-linux-musl
$ flyp build --cross x86_64-w64-mingw32
$ flyp build --cross wasm32-wasi

The triple is passed directly to the fly compiler as --target <triple>. Output goes to target/<profile>/<triple>/ to avoid collisions with native builds:

target/
├── debug/                        # native debug build
├── release/                      # native release build
└── release/aarch64-linux-musl/   # cross-compiled release

--cross can be combined with any profile:

$ flyp build --release --cross aarch64-linux-musl
$ flyp build --profile ci --cross wasm32-wasi

flyp run and flyp test refuse to execute a cross-compiled binary — use an emulator (e.g. qemu-aarch64) to run it manually.

Parallel compilation

Use -j N (or --jobs N) to control parallelism:

$ flyp build -j 4

With a single target all N threads are forwarded to the fly compiler as --jobs N, enabling LLVM's internal parallel optimisation and code generation pipeline.

With multiple targets flyp compiles them as N concurrent compiler processes (one per target), each running single-threaded, to avoid over-subscription.

0 (the default) auto-detects the number of CPU cores. 1 forces sequential compilation.

Offline mode

Build without network access using only the local dependency cache:

$ flyp build --offline

In offline mode flyp skips all network calls and verifies that every locked dependency is present in ~/.flyp/cache/. If any package is missing it prints a clear error and exits — run flyp lock (without --offline) first to populate the cache.

If fly.lock is stale in offline mode, flyp warns but continues using the existing lockfile.

The FLYP_OFFLINE=1 environment variable activates offline mode globally — useful in CI pipelines or air-gapped environments:

$ FLYP_OFFLINE=1 flyp build

--offline is also available on flyp run and flyp test.


Workspaces

A workspace groups multiple related packages under a single root, sharing one fly.lock and a unified build/test command.

Structure

my-workspace/
├── fly.toml          # workspace root — declares members
├── fly.lock          # single lockfile for all external deps
├── app/
│   ├── fly.toml      # member package
│   └── src/main.fly
└── libs/core/
    ├── fly.toml      # member package
    └── src/lib.fly

Workspace root fly.toml

[workspace]
members = ["app", "libs/core"]

The root manifest may omit [package] entirely if it contains only the workspace definition.

Member dependencies on other members

Use a path dependency to reference another workspace member:

# app/fly.toml
[package]
name    = "app"
version = "0.1.0"

[dependencies]
core = { path = "../libs/core" }

flyp resolves path deps at build time — core's output directory is automatically added as an include path when compiling app.

Creating a workspace

$ flyp workspace init --name my-workspace --members app,core

Scaffolds the directory structure, one fly.toml per member, and the workspace root manifest.

Building and testing

From the workspace root:

$ flyp build          # builds all members in dependency order
$ flyp test           # tests all members that have [test] suites

Members are compiled in topological order based on path dependencies. A cycle between members is reported as an error.


Running

Build and immediately run the default binary:

$ flyp run

Pass arguments to the binary after --:

$ flyp run -- --port 8080 --verbose

Choose which binary to run, or which profile to use:

$ flyp run --bin cli-tool
$ flyp run --profile release --bin server
$ flyp run --release --bin server   # same as above

Testing

Build and run all test suites:

$ flyp test

Filter by suite name (or Suite::method or Suite::method::"label"):

$ flyp test --suite MathSuite
$ flyp test --suite MathSuite::classifyTest
$ flyp test --suite MathSuite::classifyTest::"positive"

Run tests with a specific profile:

$ flyp test --profile release
$ flyp test --profile ci

Test suites are discovered via the [test] section in fly.toml. Each suite is compiled with the --test flag and executed as a standalone binary. flyp reports pass/fail per suite.


Diagnosing your environment

flyp doctor checks that everything needed to build is in place:

$ flyp doctor

Compiler:
  ✓ fly compiler: 0.14.0

flyp:
  ✓ flyp 0.14.0

Manifest:
  ✓ fly.toml: /home/user/my-project/fly.toml
  ✓ fly-version satisfied (0.14.0 >= 0.14.0)

Lockfile:
  ✓ fly.lock up to date (3 packages)

Cache:
  ✓ cache root: /home/user/.flyp/cache
  ✓ all 3 packages in cache

Registries:
  ✓ local reachable: http://localhost:5000

✓ all checks passed

Checks performed:

CheckWhat it verifies
Compilerfly binary found — checks FLY_COMPILER env var, then sibling of the flyp binary, then PATH
fly-versionInstalled compiler satisfies fly-version in [package]
fly.tomlManifest exists and is valid
fly.lockLockfile exists and is not stale
CacheAll locked packages are present in ~/.flyp/cache/
RegistriesEach alias in [repo] responds to an HTTP probe

flyp doctor exits with code 0 if all checks pass, 1 if any fail.


Understanding the dependency graph

flyp why <package> explains why a package is included and which constraint caused it to be selected.

$ flyp why mylib

Example output:

mylib v1.5.0 is included because:

  root v0.1.0  (requires mylib tag v1.0.0)
  framework v2.0.0  (requires mylib tag v1.5.0)

resolved: mylib 1.5.0 satisfies all 2 constraint(s)

This makes it easy to understand diamond dependencies and trace unexpected version selections.


Resolving the lockfile manually

Run resolution explicitly without building:

$ flyp lock

Useful in CI pipelines to separate the "resolve" step from the "build" step, or to pre-populate the cache.


Cache management

flyp stores fetched packages in a local cache so they are not re-downloaded on every build.

Path (default)Description
~/.flyp/cache/<host>/<owner>/<repo>/<rev>/Cached source tree

Override the cache location with the environment variable $FLYP_HOME:

export FLYP_HOME=/opt/flyp

Inspect the cache

$ flyp cache stats
Cache: /home/user/.flyp/cache
  142 files, 3891 KB

Clear the cache

$ flyp cache clean
Cache cleared: /home/user/.flyp/cache

Registry

flyp supports a built-in REST package registry. You can run a local instance for offline builds, CI pipelines, or private packages.

Starting a local registry

$ flyp-registry --storage ~/.flyp/registry --port 5000
flyp-registry
  storage : /home/user/.flyp/registry
  listen  : 0.0.0.0:5000
  auth    : disabled

Options: --storage DIR, --port PORT, --host HOST.

Authentication

Authentication is optional. Without --token the registry is open — anyone who can reach it can publish. For shared or networked registries, protect publish operations with an API key:

$ flyp-registry --token mykey
flyp-registry
  storage : /home/user/.flyp/registry
  listen  : 0.0.0.0:5000
  auth    : Bearer token

The token can also be set via the FLYP_REGISTRY_TOKEN environment variable:

$ FLYP_REGISTRY_TOKEN=mykey flyp-registry

What requires auth: only POST (publish) operations. GET requests — downloads, version lists, search — are always public.

Providing the token when publishing:

$ flyp deploy --registry local --token mykey
$ flyp vendor --registry local --token mykey

Use the FLYP_TOKEN environment variable to avoid passing the token on the command line (e.g. in CI):

$ export FLYP_TOKEN=mykey
$ flyp deploy --registry local

--token on flyp deploy / flyp vendor takes precedence over FLYP_TOKEN.

Declaring registries in fly.toml

[repo]
local  = "http://localhost:5000"
remote = "https://registry.flylang.org"

[package]
name     = "my-lib"
version  = "1.0.0"
registry = "local"    # default registry for flyp deploy

Publishing a package

$ flyp deploy                      # uses [package].registry alias
$ flyp deploy --registry local     # explicit alias

Vendoring git dependencies to a local registry

Populate a local registry from all packages in fly.lock:

$ flyp vendor --registry local
  vendored: fly-std 0.14.0
  vendored: my-lib 1.2.0
  vendor complete: 2 packages pushed to 'local'

After vendoring, update fly.toml to use registry deps instead of git deps and run flyp lock — subsequent builds fetch from the local registry without internet access.

Searching packages

$ flyp search fly-
["fly-std","fly-io","fly-test"]

Error reference

flyp reports structured errors with a code, context, and a hint.

E001 — Dependency conflict

Two packages require the same dependency from different Git URLs. flyp cannot reconcile this automatically.

error[E001]: dependency conflict — cannot resolve

conflict chain:
  root → mylib  (git=https://github.com/example/mylib.git tag=v1.0.0)
  fork-lib → mylib  (git=https://github.com/fork/mylib.git tag=v1.0.0)

hint: run `flyp why mylib` to see all constraints

Resolution: ensure all packages in your dependency tree reference the same Git URL for a given package name, or fork one of the conflicting packages under a different name.

E002 — Package not found

The specified tag, branch, or commit hash does not exist in the remote repository.

error[E002]: package not found

package: mylib
source:  https://github.com/example/mylib.git
tag "v3.0.0" not found in repository
available: v1.0.0, v1.2.0, v2.0.0

hint: did you mean tag = "v2.0.0"?

Resolution: check the tag or branch name in fly.toml. Use flyp update after correcting it.


CLI reference

CommandDescription
flyp init [--name NAME] [--version VER]Create a new Fly project
flyp workspace init [--name NAME] [--members A,B,...]Create a new workspace
flyp build [--profile NAME|--release] [--targets KEY,...] [-j N] [--offline] [--cross TRIPLE]Build all or a subset of targets
flyp run [--profile NAME|--release] [--bin NAME] [--offline] [-- ARGS]Build and run a binary
flyp test [--profile NAME|--release] [--suite SUITE] [--offline]Build and run test suites
flyp add NAME --git URL (--tag|--branch|--rev) [--dev]Add a dependency
flyp remove NAMERemove a dependency
flyp update [NAME]Re-fetch one or all dependencies (same ref)
flyp upgrade [NAME] [--dry-run]Upgrade tag deps to latest semver tag
flyp doctorCheck compiler, manifest, lockfile, cache, registries
flyp clean [--profile NAME|--release]Remove build artifacts (target/ or one profile)
flyp lockResolve and write fly.lock
flyp why NAMEExplain why a package is included
flyp cache statsShow cache size
flyp cache cleanRemove all cached packages
flyp versionPrint flyp version
flyp deploy [--registry ALIAS] [--version VER] [--token KEY]Package and upload to a registry
flyp vendor [--registry ALIAS] [--token KEY]Push all locked deps to a registry (offline use)
flyp search QUERYSearch packages in the default registry
flyp publishAlias for flyp deploy