Archives
Package binaries into tar.gz, zip, tar.xz, or tar.zst archives
The archive stage packages your compiled binaries into distributable archives.
Classification
Packager — builds distributable archives from compiled binaries. Required: not a publisher; always runs unless disabled.
Minimal config
crates:
- name: myapp
archives:
- name_template: "{{ ProjectName }}-{{ Version }}-{{ Os }}-{{ Arch }}"Full config reference
crates:
- name: myapp
archives:
- name_template: "{{ ProjectName }}-{{ Version }}-{{ Os }}-{{ Arch }}" # optional
formats: [tar.gz] # optional; one archive per listed format
# tar.gz | tar.xz | tar.zst | tar | zip | gz | xz | binary | none
# aliases: tgz | txz | tzst
format_overrides: # optional; per-OS format overrides
- os: windows
formats: [zip]
files: [] # optional; extra files to include
binaries: [] # optional; specific binaries (default: all)
wrap_in_directory: "" # optional; wrap contents in a subdirectoryAuthentication
Not applicable — archive generation is a local build step with no external service calls.
Common gotchas
- Format overrides:
format_overridesis matched by OS name (linux,darwin,windows). An unmatched override is silently ignored. wrap_in_directory: wrapping in a subdirectory changes the extraction path. Consumers expecting a flat archive will need to adjust their install scripts.archives: false: disables archiving entirely; binaries are distributed as raw files.
Republish / update behavior
Not applicable — this is a local packaging stage, not a publisher.
Archive config fields
| Field | Type | Default | Description |
|---|---|---|---|
name_template | string | {{ ProjectName }}-{{ Version }}-{{ Os }}-{{ Arch }} | Archive filename (without extension) |
formats | list | [tar.gz] | Archive formats; one archive is produced per entry. Values: tar.gz, tar.xz, tar.zst, tar, zip, gz, xz, binary, none (aliases: tgz, txz, tzst). The singular format is a deprecated alias folded into formats with a warning. |
format_overrides | list | none | Per-OS format overrides (each entry takes os plus formats) |
files | list | none | Extra files to include (e.g., LICENSE, README.md) |
binaries | list | all | Specific binaries to include (default: all from builds) |
wrap_in_directory | string | none | Wrap contents in a subdirectory |
Format overrides
Every fragment from here on shows the archives: block on its own. It sits
under crates[].archives: (or defaults.archives:), as the examples above
spell out.
Use different formats for different operating systems:
archives:
- name_template: "{{ ProjectName }}-{{ Version }}-{{ Os }}-{{ Arch }}"
formats: [tar.gz]
format_overrides:
- os: windows
formats: [zip]Including extra files
archives:
- name_template: "{{ ProjectName }}-{{ Version }}-{{ Os }}-{{ Arch }}"
files:
- LICENSE
- README.md
- config.example.yamlShell completions & man pages
Auto-generate (or harvest, or copy) shell completions and man pages and bundle
them into every archive. Three mutually-exclusive modes per block — set exactly
one of generate / from_build_out / copy:
archives:
- id: default
completions:
# Mode A — run the host-native binary once per shell, reuse for all targets.
generate: "{{ ArtifactPath }} completions {{ Shell }}"
shells: [bash, zsh, fish, powershell, nushell, elvish] # arbitrary list
dst: "completions/"
# Mode B — harvest a build.rs OUT_DIR (clap_complete) via a per-target glob:
# from_build_out: "**/out/{{ Binary }}.{bash,fish,zsh}"
# Mode C — copy committed files:
# copy: "contrib/completion/*"
manpages:
generate: "{{ ArtifactPath }} --man" # or from_build_out / copy
dst: "man/man1/"
Mode A generates once on the host-native target (completions/man pages do
not vary by architecture) and reuses the output for every target's archive. A
pure cross build with no host-native artifact errors clearly — use
from_build_out or copy instead, or add the host target.
Per-shell filenames follow the clap_complete convention so files drop straight
into the shell's lookup path: bash <bin>, zsh _<bin>, fish <bin>.fish,
powershell _<bin>.ps1, elvish <bin>.elv, nushell <bin>.nu. Man pages are
written as <bin>.1.
In from_build_out / copy globs, {{ Binary }} resolves to the
host-native binary's name — but on a pure cross build (no host artifact) it
falls back to the crate name. If your binary name differs from the crate
name, spell it literally in the glob instead of relying on {{ Binary }}.
Single source of truth for nfpm
Generated files are staged under the dist directory so the same files feed both the archive and any nfpm package — generate once, ship everywhere:
dist/.completions/<crate>/ # e.g. dist/.completions/rg/rg.fish
dist/.manpages/<crate>/ # e.g. dist/.manpages/rg/rg.1
Point an nfpm contents: entry at the staging dir to install them system-wide:
nfpm:
- contents:
- src: "dist/.completions/rg/*"
dst: /usr/share/bash-completion/completions/
- src: "dist/.manpages/rg/*"
dst: /usr/share/man/man1/Raw binary (no archive)
Use formats: [binary] to skip archiving and distribute the raw binaries:
archives:
- formats: [binary]
One output per binary, per target. Each is named by rendering name_template
with that binary's {{ .Binary }} — the default is
{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }} (plus the
micro-architecture suffix where one applies), so a crate shipping myapp and
myhelper for two targets produces four files:
$ anodizer release --snapshot
dist/myapp_1.0.0_linux_amd64
dist/myapp_1.0.0_darwin_arm64
dist/myhelper_1.0.0_linux_amd64
dist/myhelper_1.0.0_darwin_arm64
A template that omits {{ .Os }} / {{ .Arch }} renders one path for several
targets and is rejected, the same way it is for container formats.
Extra files are ignored under this format — a raw binary has no container to
carry a LICENSE in — so files:, templated_files: and the auto-included
LICENSE/README/CHANGELOG are all dropped. Configuring them explicitly is not an
error (--strict included); it earns a -v note:
archives:
- formats: [binary]
files:
- LICENSE • binary format ignores the files: entries for crate 'myapp' target 'x86_64-unknown-linux-gnu'
Windows targets keep the .exe suffix. before: / after: archive hooks do
not fire for binary, since there is no archive to post-process. An archives:
entry that selects no binaries at all (meta: true) produces nothing under
binary and says so:
• skipped archive for myapp/unknown — meta archive under format: binary carries no binariesNaming a binary output
Each binary output is named per binary from {{ .Binary }}. Setting
name_template: on the entry wins instead, so an entry shipping a single
binary can pin any name in one line:
archives:
- formats: [binary]
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
A template without {{ .Binary }} renders one path for every binary the entry
selects, so an entry shipping two or more binaries is rejected rather than
letting one overwrite the other. Every output path of the whole run — every
crate, build target, format and binary — is claimed before the first copy, so
the refusal leaves no archive or binary behind in dist/; only the run's own
bookkeeping remains — the effective config.yaml every run writes, plus
release-notes.md under --release-notes-tmpl and matrix.json under
--split — and a retry after fixing the template needs no --clean:
archives: name template '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}'
rendered the same binary 'myapp_1.0.0_linux_amd64' more than once for crate
'myapp', so one binary would silently overwrite another. Both binaries come
from the same build target 'x86_64-unknown-linux-gnu' and the same `archives`
entry, so no architecture variable can separate them: add '{{ .Binary }}' to
the `name_template` (e.g. "{{ .Binary }}_{{ .Os }}_{{ .Arch }}") so each binary
gets a distinct path.
The remedy names the one variable that separates the two outputs:
| The two outputs are… | Remedy |
|---|---|
| from different crates | {{ .CrateName }} |
from two archives entries of one crate on one build target | a distinct name_template per entry |
| two binaries of one entry on one build target | {{ .Binary }} |
| two amd64 micro-architecture variants of one target | {{ .Amd64 }} |
from two targets sharing OS and architecture (-gnu / -musl) | {{ .Target }} |
| from two targets of one architecture | {{ .Os }} |
| from two targets otherwise | {{ .Arch }} |
Only variables the stage exposes are ever advised: a package stage whose
naming context carries no {{ .Binary }} is told to give each config entry
a distinct name instead.
Re-running over a populated dist/
Archiving is idempotent. A re-run — release after release --prepare, a
retried release --merge, or any run over a dist/ a previous attempt already
populated — rewrites its own archives rather than refusing:
$ anodizer release --split # writes dist/myapp-1.0.0-linux-amd64.tar.gz
$ anodizer release --merge --verbose # converges over it
• replacing existing archive 'myapp-1.0.0-linux-amd64.tar.gz' left by an earlier run
• creating ./dist/myapp-1.0.0-linux-amd64.tar.gz
A name_template that renders the same filename twice within one run is
still a hard error — that is a config defect, not leftover state, and it is
caught in --dry-run and --snapshot too:
archives: name template '{{ .ProjectName }}' rendered the same archive
'myapp.tar.gz' more than once for crate 'myapp', so one archive would silently
overwrite another. The collision is between build targets
'aarch64-unknown-linux-gnu' and 'x86_64-unknown-linux-gnu': add '{{ .Arch }}'
to the `name_template` (e.g. "{{ .ProjectName }}_{{ .Arch }}") so each archive
gets a distinct path.Disabling archives
crates:
- name: myapp
archives: false # skip archiving entirelyFull example
crates:
- name: myapp
archives:
- name_template: "{{ ProjectName }}-{{ Version }}-{{ Os }}-{{ Arch }}"
formats: [tar.gz]
format_overrides:
- os: windows
formats: [zip]
files: [LICENSE, README.md]
wrap_in_directory: "{{ ProjectName }}-{{ Version }}"