Compile your Swift package's Git identity into the target that needs it.
VersionInfo.swift is a small Swift Package Manager plugin and library for answering a deceptively annoying question:
What version of this code is actually running?
Attach the plugin to a target, and SwiftPM generates a tiny Swift file before
that target compiles. Your code then reads a normal versions value. No shell
scripts in your app, no runtime git calls, no platform-specific build phase.
Real Swift projects often need build identity in places where Git is unavailable or should not be touched:
- command line
--versionoutput - server health and diagnostics endpoints
- crash reports and support bundles
- release automation checks
- debug screens in apps
- test fixtures that need to assert the exact built commit
The awkward part is not the data. Git already has the data. The awkward part is getting it into Swift code in a way that is native to SwiftPM, reusable across packages, friendly to cross-platform builds, and boring at runtime.
VersionInfo.swift does that by moving the Git lookup to build time and compiling the result into your target.
The package ships three products:
VersionInfoPlugin: a SwiftPM build tool plugin that generatesversions.VersionInfo: a library withSemanticVersionand the sharedVersiontuple shape.version-info-gen: the executable used by the plugin. Most users never call it directly.
Use the plugin by itself when you only need Git refs. Add the library when you also want strict semantic version parsing, comparison, encoding, and decoding.
Add VersionInfo.swift to your package dependencies:
dependencies: [
.package(
url: "https://github.com/adam-rocska/VersionInfo.swift.git",
from: "1.0.0"
)
]Attach the plugin to the target that needs build information:
.executableTarget(
name: "MyTool",
plugins: [
.plugin(name: "VersionInfoPlugin", package: "VersionInfo.swift")
]
)Then read versions from normal Swift code in that target:
print("built from \(versions.head.name)")
print("commit \(versions.head.hash)")That is the whole plugin-only path.
You do not import VersionInfo for this. The plugin generates source directly
inside the target that declares the plugin.
The plugin generates a Swift file with this shape:
typealias Version = (name: String, hash: String)
typealias Versions = (head: Version, heads: [Version], tags: [Version])
let versions: VersionsFor a repository whose HEAD points at main, versions.head might be:
(name: "main", hash: "1111111111111111111111111111111111111111")The generated value records:
versions.head: the currentHEAD, either a branch name or detachedHEAD.versions.heads: local branch refs found in the repository metadata.versions.tags: tag refs found in the repository metadata.
VersionInfoPlugin is attached to a target in Package.swift. SwiftPM invokes
the plugin as part of that target's build plan.
The plugin registers a build tool command. SwiftPM runs that command before it compiles the target whenever the generated source is needed or one of the declared Git metadata inputs changes.
The command runs version-info-gen, reads the package checkout's .git
metadata, and writes a generated VersionInfo.swift source file into SwiftPM's
plugin work directory.
SwiftPM then compiles that generated file into the target. From your code's
point of view, versions is just another top-level Swift value.
Because the generated file is built into your binary:
- your app does not need Git at runtime
- deployed artifacts keep the build identity they were compiled with
- the plugin can be used without linking the
VersionInfolibrary - changed Git metadata inputs are picked up by SwiftPM's build planning
Use this when you only need generated Git refs:
.target(
name: "App",
plugins: [
.plugin(name: "VersionInfoPlugin", package: "VersionInfo.swift")
]
)let head = versions.head
let tagNames = versions.tags.map(\.name)
print("built from \(head.name) at \(head.hash)")
print("visible tags: \(tagNames)")Use this when you want semantic version behavior without generated Git information:
.target(
name: "App",
dependencies: [
.product(name: "VersionInfo", package: "VersionInfo.swift")
]
)import VersionInfo
let current = SemanticVersion("1.2.0")!
let minimum = SemanticVersion("1.1.0")!
print(current > minimum)SemanticVersion supports:
major.minor.patch- prerelease identifiers, such as
1.0.0-rc.1 - build metadata, such as
1.0.0+build.42 CodableComparableHashableRawRepresentableLosslessStringConvertibleExpressibleByStringLiteralSendable
Use both products when your Git tags are semantic versions and your Swift code needs to reason about them:
.target(
name: "App",
dependencies: [
.product(name: "VersionInfo", package: "VersionInfo.swift")
],
plugins: [
.plugin(name: "VersionInfoPlugin", package: "VersionInfo.swift")
]
)import VersionInfo
let semanticTags = versions.tags.compactMap(SemanticVersion.init)
let latest = semanticTags.max()
if let latest {
print("latest semantic tag: \(latest)")
}You can also parse a specific generated ref:
if let tag = versions.tags.first(where: { $0.name == "1.0.0" }),
let version = SemanticVersion(tag)
{
print("tag \(version) points at \(tag.hash)")
}SemanticVersion follows SemVer precedence rules:
let prerelease = SemanticVersion("1.0.0-rc.1")!
let release = SemanticVersion("1.0.0")!
print(prerelease < release)
// trueNumeric prerelease identifiers are compared numerically without converting them
to Int, so very large identifiers compare correctly without overflow.
Build metadata is retained for display, encoding, and decoding, but it is not part of equality, hashing, or ordering:
let local = SemanticVersion("1.0.0+local.1")!
let ci = SemanticVersion("1.0.0+ci.2")!
print(local == ci)
// trueThat mirrors SemVer precedence: build metadata describes a build, not a different release version.
The generator reads Git metadata directly from files. It supports the checkout shapes commonly produced by Git:
- loose refs in
.git/refs/headsand.git/refs/tags - packed refs in
.git/packed-refs .gitfiles containinggitdir: ...- symbolic
HEAD - detached
HEAD - nested branch and tag names
VersionInfo.swift uses Swift tools version 6.0.
The public VersionInfo library and the generated Swift source use Swift
language and standard library features only. They are intended for:
- Apple platforms declared in
Package.swift: macOS 14, iOS 16, watchOS 9, tvOS 17, and visionOS 1 - Linux for server-side Swift
- Android through the official Swift Android SDK
- WebAssembly/WASI through the official Swift Wasm SDK
The plugin and version-info-gen executable run on the build host. They use
SwiftPM, Foundation file APIs, and a filesystem-backed Git checkout. When
cross-compiling, the host toolchain runs the plugin and the target SDK compiles
your target.
For 1.0.0 release confidence, macOS and Linux builds/tests should pass, and the library target should be cross-built with the official Android and Wasm Swift SDKs before tagging.
Most users should use the plugin. The executable product is public because the plugin needs it, and it can be useful for debugging:
swift run version-info-gen .gitTo write the generated Swift file to a directory:
swift run version-info-gen .git --output-dir GeneratedDocC documentation is included in the VersionInfo target. It covers:
- adding the plugin to a target
- how SwiftPM includes the generated source
- the generated
versionsAPI - semantic version parsing and comparison
- cross-platform expectations
- the design choices behind the tuple bridge
Generate or preview the docs with SwiftPM:
swift package generate-documentation --target VersionInfo
swift package --disable-sandbox preview-documentation --target VersionInfoThe package is tested with Swift Testing. The suite covers:
- unit behavior for semantic versions and Git ref parsing
- integration behavior for the command line generator
- user acceptance flows with temporary consumer packages
- plugin behavior across loose refs, packed refs,
.gitfiles, detachedHEAD, nested ref names, and regenerated Git metadata
For local confidence, run:
swift test
swift test -c release
swift test --enable-code-coverageLinux CI is configured with official Swift Docker images for Swift 6.0, 6.1,
and 6.2. Android and Wasm should be verified with official Swift SDKs by
building the VersionInfo library target before tagging 1.0.0:
swift sdk list
swift build --swift-sdk <android-sdk-id> --target VersionInfo
swift build --swift-sdk <wasm-sdk-id> --target VersionInfoVersionInfo.swift is released under the MIT License. See LICENSE.