---
title: Build and verify a Metal package
description: Check the native toolchain, validate shaders, regenerate an owned Swift package, and detect stale or modified output.
---

# Build and verify a Metal package



Native tooling runs on the machine that generates the shaders. The Swift application consumes the
resulting package without running Node.js or translating WGSL at launch.

> Beta: The grammar, help, lazy dispatch, `doctor`,
> `check`, `build`, and `verify` are implemented. These commands have real local-tarball coverage with an offline
> installation and dependency install scripts disabled; build coverage publishes to an initially
> absent destination and independently checks the resulting files and hashes. Verification succeeds
> without compiler or temporary-directory prerequisites and leaves parent recovery files untouched.
> An external Swift consumer builds those published payloads, relocates its build tree, and executes
> the documented compute and render programs on this host. The companion is an optional public beta.
> See [Local installed qualification](/docs/native/macos/metal/tooling/publication#local-installed-qualification)
> for replacement, interruption and recovery coverage and the separate cold-cache RC candidate check.
> Broader platform compatibility and release support remain unqualified.

## Prepare the build machine

Keep build tooling in development dependencies and pin both packages to the same exact RC:

```bash
npm install --save-dev --save-exact vgpu@next @vgpu/native@next
npx vgpu native doctor
```

Check that both resolved versions match. The `0.0.1` native bootstrap has no functionality.
The worker is bundled in npm; consumers do not download or build Tint separately.
This beta uses an ad hoc signed worker without Developer ID signing or notarization.
Validate it on the intended Apple Silicon development/CI hosts without disabling system
security protections. Stable compatibility, Intel GPU support and a minimum-OS matrix
are not promised. APIs, generated Swift and toolchain requirements may change during beta.

`doctor` checks the supported Node version, selected Xcode/SDK and Swift tools, the authenticated
vgpu-owned Tint worker, and Apple's Metal compiler. It compiles and links a small Metal probe;
finding `xcrun` is not enough to prove that the downloadable Metal compiler component works.

The command needs no project configuration and writes no project output. It may use temporary
files for its probe, which it cleans up. It does not install components, change the selected Xcode,
download an unpinned compiler, or bypass macOS security settings. Missing prerequisites produce
a failing exit status with an actionable diagnostic.

A successful toolchain probe is not GPU execution coverage. Real shader tests and the release's
physical-device matrix remain separate checks.

See [Check the native toolchain](/docs/native/macos/metal/tooling/doctor) for the findings, selected-Xcode
behavior, and diagnostic boundaries.

## Validate before generating

After [configuring a package](/docs/native/macos/metal/tooling/configuration), run:

```bash
npx vgpu native check
```

`check` reads the configuration and resolved WGSL inputs, validates selected stages and supported
resource layouts, and runs the pinned semantic and Metal-translation boundary. It stops before
Apple's offline compiler and does not write the configured output.

Each invocation captures the configuration and complete imported source graph once. Validation
uses that captured input even if an editor changes a file while the compiler is running. A check
does not inspect, create, or repair the output directory; checking a project with no generated
package is valid.

The check uses the same supported compiler profile as `build`. It does not retry a rejected shader
with extra language features or infer missing application state. Reported locations distinguish
resolved WGSL from authored source; the tool does not invent an original line number when no mapping
is available.

A successful check writes a human-readable report to standard output. It identifies the module,
lists programs in name order with their selected stages, and reports the captured input fingerprint.
For the documented `AppShaders` configuration, the report has this shape; the actual fingerprint
is 64 lowercase hexadecimal characters:

```text
Native shaders: valid
Module: AppShaders
[ok] Count: compute
[ok] Gradient: vertex, fragment
Input fingerprint: ...
```

Success exits `0`. A failed check exits `1` with a diagnostic on standard error instead of a
successful report. Cancellation retains the CLI's signal exit statuses. Neither a successful
check nor its fingerprint claims that an output package exists, is intact, or is current.

When Tint supplies structured diagnostics, the failure report begins with `Native shaders: invalid`
and preserves each diagnostic's severity, code, phase, and message in its original order. Multiline
messages are indented beneath the first line. If a diagnostic includes a location, a separate
indented line identifies it explicitly as resolved WGSL. For example, the shape is:

```text
Native shaders: invalid
[error] VGPU-NATIVE-WGSL-INVALID (wgsl): ...
  Resolved WGSL: Intermediate/resolved.wgsl:<line>:<column>
```

The line and UTF-8 byte column are one-based positions in the virtual WGSL document passed to Tint,
not positions in an authored shader file. No authored filename or line is inferred from that range,
and no location line is added when the compiler supplies none. The report stays on standard error,
with empty standard output and exit status `1`; it does not print a successful program summary or
input fingerprint. Other failures retain their ordinary error diagnostic.

## Generate the package

```bash
npx vgpu native build
```

The build validates the inputs, translates every selected stage, compiles and links the Metal
library, and generates the Swift package. It completes a sibling staging directory before
publishing it to the configured output.

A WGSL rejection during preparation uses the same structured Tint failure report as `check`,
including severity, code, phase, message and any resolved-WGSL location. It exits `1` with that
diagnostic on standard error and no successful report. It does not label this compiler failure
as a publication outcome or report existing sibling recovery paths: preparation failed before
entering the publisher. The existing package and recovery files remain untouched.

The library, generated Swift, and ownership record come from the same captured project. Editing
a shader during compilation does not splice newer source into part of that package. A later
verification can report the captured generation as stale against those edits.

The build captures its selected tool environment before awaiting project work. Relative
`DEVELOPER_DIR` and `TMPDIR` selections keep their meaning from that invocation's working directory;
changing the process environment later does not switch the Apple compiler halfway through the build.
These host settings are not copied into the generated package or its logical input fingerprint.

A successful build writes a human-readable publication report to standard output and exits `0`.
The module and input fingerprint identify the captured project; the absolute output path and
ownership-record hash come from the checked publication receipt. The report has this shape:

```text
Native package: published
Module: AppShaders
Output: /absolute/path/to/Generated/AppShaders
Input fingerprint: ...
Record SHA-256: ...
```

Both hashes are 64 lowercase hexadecimal characters. Compilation or staging alone is not success:
the command reports publication only after the publisher returns its checked receipt. A failure
does not print this successful report; the diagnostic retains any known publication outcome.

A failed program, cancelled build before publication, or rejected output boundary leaves the last
valid package unchanged. Publishing replaces the directory as one operation: the output path names
a complete old or new package, never a partially written package. If the destination filesystem
cannot provide the required operation, the build fails without replacing the package.

Finish generation before starting a Swift build, and do not edit generated output concurrently.
Cancellation after the commit point may leave the complete new package in place. The diagnostic
must retain that publication outcome, including when cleanup fails or an interrupted helper leaves
the outcome unknown.

See [Publish generated packages](/docs/native/macos/metal/tooling/publication) for safe parent creation,
physical-directory locking, atomic replacement, and recovery. These operations do not silently
discard unrelated files or interrupted transactions.

Add the generated package to your Swift project and use its functions, packers, and bindings.
The application still creates its pipelines, buffers, textures, encoders, and submissions. A build
does not launch the application or prove that its native pipeline state is compatible.

## Verify an existing package

```bash
npx vgpu native verify
```

`verify` checks the ownership record, supported artifact format, generated file set and hashes,
and whether the current configuration and resolved source inputs match the recorded logical build
inputs. It does not regenerate output or invoke Tint, `metal`, or `metallib`.

Verification first captures the current project inputs, then checks the existing package's owner
and integrity, and finally compares its recorded fingerprint with the captured inputs. A package
can be intact and owned by the project but still stale. That result is a failure with a request to
build again, not permission to modify the record or a claim that verification generated anything.

A successful verification writes a human-readable report to standard output and exits `0`:

```text
Native package: current
Module: AppShaders
Output: /absolute/path/to/Generated/AppShaders
Input fingerprint: ...
```

The absolute output path, module and 64-character lowercase hexadecimal fingerprint come from the
verified package and captured project. `current` means the observed package passed ownership,
integrity and input-freshness checks; it does not mean this invocation published or executed it.
A failed verification exits `1` with a diagnostic on standard error and no successful report.
The command needs no compiler or temporary compiler directory and does not repair files. Recovery
records beside the output are not package contents: successful verification leaves them untouched
and does not establish an earlier publication outcome or authorize their removal.

Before inspecting the package, verification checks the original output path components as described
in [Configure a Metal package](/docs/native/macos/metal/tooling/configuration). A symlink hidden by `..`
is an invalid destination, even when a package at the simplified path would be intact. This path
check does not reopen captured shader inputs; freshness still uses the one captured graph.

The hidden `.vgpu-native-output.json` record belongs to the tool. It identifies the artifact format,
owning configuration relative to the package, logical input fingerprint, and exact generated file
hashes. Ownership does not change when you edit a shader: an unchanged old package can be replaced
by its owning configuration even when its input fingerprint is stale. Do not edit the record to
bypass a conflict. It is integrity metadata, not a signature or an application runtime dependency.
The record is versioned UTF-8 JSON no larger than 64 KiB; unsupported or malformed records fail
validation before their file list is used.

One publication accepts exactly `Package.swift`, generated Swift, `Shaders.metallib`, and that
ownership record, with a combined 128 MiB limit. Payload transport uses chunks no larger than
64 KiB; it does not encode the complete library into a control message. See
[Publish generated packages](/docs/native/macos/metal/tooling/publication) for staging verification and
transaction-record limits.

Changing an imported helper makes the output stale even if its entry file did not change. Editing
only a comment in that helper still changes the captured source bytes. If the owned package remains
intact, the diagnostic is `Generated output is stale; build the Metal package again` on standard
error, with exit status `1` and no successful report. Verification leaves both the changed source
and the old package untouched; it does not regenerate or repair either one.

Editing generated Swift, replacing the library, removing a generated file, or adding an unexpected file
also fails verification. The initial policy treats this directory as an immutable generated
package: build it as a dependency of your consuming Swift project, whose build products live
outside this directory. A `.build` directory created by building the generated package directly
is still an unexpected addition; the tool reports it instead of deleting it.

For example, modifying the generated Swift in `AppShaders` reports
`Generated file has changed: Sources/AppShaders/Shaders.generated.swift` on standard error with
exit status `1` and no successful report. This is an integrity failure even when the configured
WGSL inputs have not changed. Verification preserves the modified file and its original ownership
record; it does not rewrite either one to accept the edit.

The package must contain ordinary directories and regular, unlinked files. Symbolic links,
hard-linked files, special files, and unexpected empty directories fail verification too. The
verifier reads the limited ownership record before using its file list, then hashes payloads in
small chunks. Cancelling stops the inspection without changing the package. Verification is a
read-only observation, not a lock or permission to replace the directory: builds must repeat the
ownership checks inside their publication boundary.

Hash checks detect mismatches. They are not a signature proving who authored a package, and a
library hash does not promise byte-identical Apple compiler output across toolchain versions.
Verification also does not execute shaders or validate the application's resource contents.

Commit the generated package when another build machine must consume it without Node.js or the
Metal compiler. Regenerate it intentionally when changing shaders or compiler versions, and review
the generated diff together with the source change.

## Consume the generated package

Add the generated local package as a dependency of your Swift project and select its library
product. Use the [function loaders](/docs/native/macos/metal/functions) and binding helpers with your
own Metal device, resources, pipelines, encoders, and command buffers. Build the consuming project,
not the immutable generated directory, and distribute its complete SwiftPM resource bundle.

The local installed test uses the actual published `Package.swift`, generated Swift, and
`Shaders.metallib` together in an external Swift consumer, checking their hashes and sizes before
copying them. It does not substitute output from a separate source-level compiler call. The
ownership record and recovery files stay in the original build project, whose files and retained
identities remain unchanged after consumption.

For the imported `Count` shader in [Resolve shader inputs](/docs/native/macos/metal/tooling/sources),
one `(2, 1, 1)` workgroup writes `[100, 101]`. This is distinct from the runtime-array example in
the compute-dispatch guide. The documented `Gradient` uniforms produce an RGBA pixel approximately
`[0.28, 0.44, 0.8, 1]`. Checking these GPU results connects the installed tooling's output to the
application-facing Swift API, beyond merely validating files and hashes.

A local test that builds and relocates the consumer and guards generation-tool lookup through
`PATH` is still not a clean-machine test: it does not block absolute executable paths or SDK
discovery. The generated package has no external Swift dependencies, but the consumer build still
requires Swift and system frameworks. Release support and execution on a machine without the
generation toolchain require their own qualification.

## Use the commands in automation

Run `vgpu native` or `vgpu native --help` to list the four commands. Each command accepts `--help`
or `-h`. Help does not load the native companion package, inspect a project, or require a working
Metal toolchain.

The `vgpu` command loads the optional `@vgpu/native` companion only for a valid native operation.
An absent or incompatible companion fails with an installation or compatibility diagnostic.
Other vgpu commands do not load it. Native execution uses the supported Node 22 build-host profile;
displaying help does not enforce that toolchain profile.

Project commands accept one `--config <file>`; use the same file for `check`, `build`, and `verify`.
Relative config arguments are resolved from the invocation's working directory. Omitting the
option selects `vgpu.native.json` in that directory, without searching ancestors. `doctor` does
not accept `--config`.

Unknown commands or options, repeated `--config`, missing values, and positional arguments are
usage errors, including when combined with `--help`. The initial commands have no `--target`,
`--json`, `--force`, or custom-worker option.

A successful command exits `0`. Usage errors exit `2`; missing prerequisites, unsupported shaders,
stale output, and verification failures exit `1`. An interrupted invocation waits for its cleanup
and exits `130` for SIGINT or `143` for SIGTERM. A post-publication interruption still reports that
the new package was published; it does not imply that the old package remains in place.

The initial command surface is deliberately four operations. Watching files, scaffold generation,
pixel-comparison commands, editor plugins, and automated publication are separate integrations.
The native test suite still compares real GPU results even without a public comparison command.


---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)