An overview of how this repo is laid out and how its test suite is organized. See README.md for usage and CONTRIBUTING.md for the contributor workflow.
gleam/— the public rules (gleam_library,gleam_binary,gleam_test,gleam_format_test,gleam_release,gleam_standalone_release,gleam_package, all re-exported fromgleam/defs.bzl), the toolchain type and rule (gleam/toolchain.bzl), and the bzlmod module extension (gleam/extensions.bzl) that downstreamMODULE.bazelfiles use to register a Gleam toolchain and Hex packages.gleam/private/holds implementation details not meant to be depended on directly: toolchain/Hex-package repository rules (includinggleam_host, which re-exposes the host platform's toolchain repo under one unconditional name), version/manifest.toml parsers, and similar.erlang/— the Erlang/OTP toolchain type, split the same way: a hermetic repository rule that downloads a prebuilt OTP release (erlang/private/hermetic_erlang_repository.bzl, the default) and alocal_erlang_repository.bzlalternative that discovers Erlang/OTP on the host'sPATHinstead (opt-in viagleam.local_erlang_toolchain()).gleam_gazelle/— a Gazelle language extension (Go) that generatesgleam_package(...)targets fromgleam.toml+src/**/*.gleamdirectories, so BUILD files don't need to be hand-written.examples/— runnable, CI-exercised projects, each demonstrating one thing end-to-end (see Test taxonomy below, item 3).docs/—rules.md, generated fromgleam/defs.bzl's docstrings via Stardoc (bazel run //docs:updateregenerates it;bazel test //docs:rules_testchecks it's up to date).test/unit/— Starlark unit tests for private helpers (see below).
Four layers, each catching a different class of regression:
- Starlark unit tests (
test/unit/*, bazel-skylib'sunittest/analysistest) — test pure Starlark functions extracted from repository rules and macros (version parsing, manifest.toml parsing, the otp-tree/erl-invocation resolutiongleam_releasedepends on,gleam_host's host-platform detection) directly, without needing a full build or a real toolchain. Also used to prove afail()actually fires for a bad input, viaanalysistest.make(expect_failure = True)plusasserts.expect_failure(env, msg). These are the cheapest and fastest layer, and the first to write when adding logic to a repository rule or module extension — the majority of these functions were literally extracted from an existing rule implementation specifically to make them unit-testable in isolation. gleam_gazelle's own Go unit tests + golden files (gleam_gazelle/*_test.go,gleam_gazelle/testdata/golden/*) — since the Gazelle extension is Go, not Starlark, it's tested the same waybazel-gazelle's own language extensions are: each fixture directory undertestdata/golden/pairs agleam.toml+ sources with a checked-inBUILD.want, andgo test ./gleam_gazelle/... -run TestGolden -updateregenerates them after an intentional output change. This callsGenerateRulesdirectly and does not include theload(...)statement Gazelle's own resolver inserts — that's covered separately by a real compiledgazelle_binaryrun againstgleam_gazelle/testdata/sample_packagein CI (see below).- Example projects (
examples/*, exercised bybazel test //.../bazel build //...in CI on both Linux and macOS) — end-to-end proof that the public rules actually work together under real Bazel, real toolchains, and (for the Hex-dependent examples) real downloaded packages. Each example is scoped to demonstrate one thing:- smoke — the minimal
gleam_packageexample from the README's Quick start. - gazelle_smoke — wiring
gleam_gazelleinto a downstream project's owngazelle_binary. - nested_smoke — a package nested below the repo root, and the
one example that opts out of the hermetic Erlang toolchain via
gleam.local_erlang_toolchain()(so it needs a system Erlang onPATH; see the "Set up Erlang" CI steps). - web_service — a runnable HTTP service, a
manifest.toml-driven bulk Hex dependency declaration (gleam.hex_manifest(...)), and (Linux only) a Docker-based proof thatgleam_release's bundled OTP tree makes the result portable to a machine with no Erlang installed at all. - standalone_cli — same portability proof as
web_service, forgleam_standalone_release(which requires the hermetic toolchain outright). - hermetic_erlang — pins a specific, non-default
otp_versionviagleam.erlang_toolchain(...), proving a second OTP release works end-to-end (not just the built-in default). - expect_fail — deliberately broken fixtures (a failing test, a
misformatted file); CI asserts
bazel testactually fails on them, provinggleam_test/gleam_format_testdetect real failures rather than always passing. - hello_world — the smallest possible
gleam_binary(escript), including a check that the built escript actually runs.
- smoke — the minimal
- CI-only verification steps (
.github/workflows/ci.yaml, no correspondingbazel testtarget) — checks that don't fit cleanly into a hermetic Bazel test, either because they need a genuinely fresh environment (the Docker-based portability checks above) or because they exercise a real compiled binary rather than in-process Go code (thegazelle_binaryvs.testdata/sample_packagediff,bazel run @gleam_host//:gleam -- --version).
When adding a rule or macro with any non-trivial branching logic, prefer extracting the logic
into a plain function and covering it with layer 1 first — it's the cheapest to write and the
fastest to run. The macOS hermetic Erlang arch-naming bug and examples/hermetic_erlang's
never-actually-published OTP pin were both only caught after a real build broke, because the
logic at fault lived inside a repository rule's download/extract path rather than a
separately-testable function; gleam_host's own arm64→aarch64 arch-normalization mistake,
by contrast, was caught immediately by its layer-1 unit test the first time it ran under real
Bazel on a real macOS runner (repository_ctx.os.arch reports "arm64" there, not "aarch64")
-- exactly the kind of bug this layer exists to catch cheaply.