Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 97 additions & 59 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,51 +17,129 @@ concurrency:
jobs:
tests:
if: github.event_name != 'pull_request' || github.event.label.name == 'run-ci'
name: ${{ matrix.os }} / ${{ matrix.configuration }}
runs-on: ${{ matrix.os }}
name: Swift ${{ matrix.swift }} / ${{ matrix.platform }} / ${{ matrix.configuration }}
runs-on: ${{ matrix.runner }}
container: ${{ matrix.container }}
strategy:
fail-fast: false
matrix:
os: [macos-26, ubuntu-24.04]
swift: ['6.1', '6.2.3', '6.4.1']
platform: [macos, linux, windows]
configuration: [debug, release]
include:
- os: macos-26
swift-version: 6.2.4
- os: ubuntu-24.04
- platform: macos
runner: macos-26
- platform: linux
runner: ubuntu-24.04
- platform: windows
runner: windows-2022
- swift: '6.1'
platform: macos
runner: macos-15
xcode: '16.3'
- swift: '6.2.3'
platform: macos
xcode: '26.2'
- swift: '6.4.1'
platform: macos
xcode: '26.6'
- swift: '6.1'
platform: linux
container: swift:6.1.0
- swift: '6.2.3'
platform: linux
container: swift:6.2.3
swift-version: 6.2.3
- swift: '6.4.1'
platform: linux
container: swift:6.4.1
env:
SWIFT_VERSION: ${{ matrix.swift }}
BUILD_CONFIGURATION: ${{ matrix.configuration }}
steps:
- uses: actions/checkout@v6
- name: Select Xcode 26.3
- name: Select Xcode ${{ matrix.xcode }}
if: runner.os == 'macOS'
run: echo 'DEVELOPER_DIR=/Applications/Xcode_26.3.app/Contents/Developer' >> "$GITHUB_ENV"
- name: Verify Swift ${{ matrix.swift-version }}
env:
EXPECTED_SWIFT_VERSION: ${{ matrix.swift-version }}
XCODE_VERSION: ${{ matrix.xcode }}
run: echo "DEVELOPER_DIR=/Applications/Xcode_${XCODE_VERSION}.app/Contents/Developer" >> "$GITHUB_ENV"
- name: Install Swift ${{ matrix.swift }}
if: runner.os == 'Windows' || (runner.os == 'macOS' && matrix.swift == '6.4.1')
uses: compnerd/gha-setup-swift@86d1ddeaf915c282137601aeb0654ea0e7b7f17e
with:
swift-version: swift-${{ matrix.swift }}-release
swift-build: ${{ matrix.swift }}-RELEASE
- name: Verify Swift ${{ matrix.swift }}
if: runner.os != 'Windows'
run: |
swift --version
swift --version | grep -F "Swift version $EXPECTED_SWIFT_VERSION "
if [ "$SWIFT_VERSION" = '6.1' ]; then
swift --version | grep -Eq 'Swift version 6\.1(\.0)?[[:space:]]'
else
swift --version | grep -F "Swift version $SWIFT_VERSION "
fi
- name: Verify Swift ${{ matrix.swift }} for Windows x64
if: runner.os == 'Windows'
shell: pwsh
run: |
$version = (swift --version) -join "`n"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
Write-Output $version
$expectedVersion = [regex]::Escape($env:SWIFT_VERSION)
if ($env:SWIFT_VERSION -eq '6.1') { $expectedVersion += '(\.0)?' }
if ($version -notmatch "Swift version $expectedVersion(\s|$)") {
throw "Expected Swift $env:SWIFT_VERSION"
}
if ($version -notmatch 'Target: x86_64-unknown-windows-msvc') {
throw 'Expected the Windows x64 toolchain'
}
- name: Functional tests
run: swift test -c ${{ matrix.configuration }} --skip PerformanceTests
if: runner.os != 'Windows'
shell: bash
run: |
swiftFlags=()
if [ "$RUNNER_OS" = 'Linux' ] && [ "$SWIFT_VERSION" = '6.4.1' ]; then
# Avoid Swift 6.4.1's Swift Build planning crash on Linux.
swiftFlags+=(--build-system native)
fi
swift test -c "$BUILD_CONFIGURATION" "${swiftFlags[@]}" --skip PerformanceTests
- name: Windows functional tests
if: runner.os == 'Windows'
shell: pwsh
run: |
$vswhere = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe'
$vsPath = & $vswhere -latest -products '*' -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
if (-not $vsPath) { throw 'Visual Studio C++ tools not found' }
Import-Module (Join-Path $vsPath 'Common7\Tools\Microsoft.VisualStudio.DevShell.dll')
Enter-VsDevShell -VsInstallPath $vsPath -SkipAutomaticLocation -DevCmdArguments '-arch=x64 -host_arch=x64'
$swiftFlags = @()
if ($env:SWIFT_VERSION -eq '6.2.3' -or $env:SWIFT_VERSION -eq '6.4.1') {
# Negative integer generic arguments trip the compiler's debug-type check.
$swiftFlags += @('-Xswiftc', '-Xfrontend', '-Xswiftc', '-disable-round-trip-debug-types')
}
if ($env:SWIFT_VERSION -eq '6.4.1') {
# Work around Swift 6.4.1's build-system header lookup issue.
$swiftFlags += @('--build-system', 'native')
}
swift test -c $env:BUILD_CONFIGURATION @swiftFlags --skip PerformanceTests
exit $LASTEXITCODE
- name: Coverage gate tests
if: matrix.os == 'macos-26' && matrix.configuration == 'debug'
if: matrix.platform == 'macos' && matrix.swift == '6.2.3' && matrix.configuration == 'debug'
run: python3 -m unittest discover -s Scripts/tests
- name: Coverage
if: matrix.os == 'macos-26' && matrix.configuration == 'debug'
if: matrix.platform == 'macos' && matrix.swift == '6.2.3' && matrix.configuration == 'debug'
run: |
swift test --enable-code-coverage --skip PerformanceTests
cp "$(swift test --show-codecov-path)" coverage.json
python3 Scripts/check-coverage.py coverage.json --minimum 95
- name: Upload coverage
if: always() && matrix.os == 'macos-26' && matrix.configuration == 'debug' && hashFiles('coverage.json') != ''
if: always() && matrix.platform == 'macos' && matrix.swift == '6.2.3' && matrix.configuration == 'debug' && hashFiles('coverage.json') != ''
uses: actions/upload-artifact@v6
with:
name: coverage
path: coverage.json
if-no-files-found: error
- name: Build documentation
if: matrix.os == 'macos-26' && matrix.configuration == 'debug'
if: matrix.platform == 'macos' && matrix.swift == '6.2.3' && matrix.configuration == 'debug'
run: |
swift package dump-symbol-graph --minimum-access-level public
enigmatic_symbols="$(dirname "$(swift build --show-bin-path)")/symbolgraph"
Expand All @@ -78,53 +156,13 @@ jobs:
--hosting-base-path Enigmatic \
--warnings-as-errors
- name: Pack documentation
if: matrix.os == 'macos-26' && matrix.configuration == 'debug'
if: matrix.platform == 'macos' && matrix.swift == '6.2.3' && matrix.configuration == 'debug'
run: tar -czf Enigmatic.doccarchive.tar.gz -C .build Enigmatic.doccarchive
- name: Upload documentation
if: matrix.os == 'macos-26' && matrix.configuration == 'debug'
if: matrix.platform == 'macos' && matrix.swift == '6.2.3' && matrix.configuration == 'debug'
uses: actions/upload-artifact@v6
with:
name: documentation
path: Enigmatic.doccarchive.tar.gz
compression-level: 0
if-no-files-found: error

windows:
if: github.event_name != 'pull_request' || github.event.label.name == 'run-ci'
name: windows-2022 / ${{ matrix.configuration }}
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
configuration: [debug, release]
defaults:
run:
shell: pwsh
steps:
- uses: actions/checkout@v6
- name: Install Swift 6.4.1
uses: compnerd/gha-setup-swift@86d1ddeaf915c282137601aeb0654ea0e7b7f17e
with:
swift-version: swift-6.4.1-release
swift-build: 6.4.1-RELEASE
- name: Verify Swift 6.4.1 for Windows x64
run: |
$version = (swift --version) -join "`n"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
Write-Output $version
if ($version -notmatch 'Swift version 6\.4\.1(\s|$)') {
throw 'Expected Swift 6.4.1'
}
if ($version -notmatch 'Target: x86_64-unknown-windows-msvc') {
throw 'Expected the Windows x64 toolchain'
}
- name: Functional tests
run: |
$vswhere = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe'
$vsPath = & $vswhere -latest -products '*' -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
if (-not $vsPath) { throw 'Visual Studio C++ tools not found' }
Import-Module (Join-Path $vsPath 'Common7\Tools\Microsoft.VisualStudio.DevShell.dll')
Enter-VsDevShell -VsInstallPath $vsPath -SkipAutomaticLocation -DevCmdArguments '-arch=x64 -host_arch=x64'
# Work around Swift 6.4.1's build-system and debug-type assertion issues.
swift test -c ${{ matrix.configuration }} --build-system native -Xswiftc -Xfrontend -Xswiftc -disable-round-trip-debug-types --skip PerformanceTests
exit $LASTEXITCODE
8 changes: 5 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# Project guidance

Enigmatic is a dependency-free SwiftPM library for Codable value trees and
composable coding strategies. It requires Swift 6.2.3; Checks uses 6.2.4 on
macOS 26 and 6.2.3 on Ubuntu 24.04. Release uses 6.3.3 to build the previous
API baseline.
composable coding strategies. It requires Swift 6.1; generic Unix date strategies
require Swift 6.2.3. Checks separately tests the original Swift 6.1 release,
Swift 6.2.3, and Swift 6.4.1 on macOS, Linux, and Windows in Debug and Release.
Coverage and DocC use macOS with Swift 6.2.3. Release uses 6.3.3 to build the
previous API baseline.

Follow the existing two-space Swift style. Preserve typed throws, runtime
availability guards, and visibility required by `@inlinable` code. Update the
Expand Down
13 changes: 10 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
// swift-tools-version:6.2.3
// swift-tools-version:6.1
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

#if compiler(>=6.3) && compiler(<6.4)
let swiftSettings: [SwiftSetting] = [.enableExperimentalFeature("AnyAppleOSAvailability")]
#else
let swiftSettings: [SwiftSetting] = []
#endif

let package = Package(
name: "Enigmatic",
products: [
.library(name: "Enigmatic", targets: ["Enigmatic"]),
],
targets: [
.target(name: "Enigmatic"),
.target(name: "Enigmatic", swiftSettings: swiftSettings),
.testTarget(
name: "EnigmaticTests",
dependencies: ["Enigmatic"]
dependencies: ["Enigmatic"],
swiftSettings: swiftSettings
),
]
)
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![Checks](https://github.com/SwiftUtility/Enigmatic/actions/workflows/checks.yml/badge.svg?branch=main&event=push)](https://github.com/SwiftUtility/Enigmatic/actions/workflows/checks.yml)
[![Release](https://img.shields.io/github/v/release/SwiftUtility/Enigmatic)](https://github.com/SwiftUtility/Enigmatic/releases/latest)
[![Swift](https://img.shields.io/badge/Swift-6.2.3%2B-orange?logo=swift)](Package.swift)
[![Swift](https://img.shields.io/badge/Swift-6.1%2B-orange?logo=swift)](Package.swift)
[![SwiftPM](https://img.shields.io/badge/Swift_Package_Manager-compatible-brightgreen)](#install)
[![License](https://img.shields.io/github/license/SwiftUtility/Enigmatic)](LICENSE)
[![Coverage](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftutility.github.io%2FEnigmatic%2Fcoverage-badge.json)](https://github.com/SwiftUtility/Enigmatic/actions/workflows/release.yml)
Expand Down Expand Up @@ -34,12 +34,14 @@ Foundation's `JSONDecoder`. With that same decoder, Enigma and PotentCodables'

## Install

Requires Swift 6.2.3 or newer. Add
Requires Swift 6.1 or newer. Add
[SwiftUtility/Enigmatic](https://github.com/SwiftUtility/Enigmatic) in Xcode and
link the **Enigmatic** product to your target. For SwiftPM targets, use
`.product(name: "Enigmatic", package: "Enigmatic")`.

Some APIs require newer Apple OS versions; see the
The generic Unix date strategies require Swift 6.2.3 or newer; use the named
seconds/milliseconds strategies with older compilers. Some APIs also require
newer Apple OS versions; see the
[compatibility notes](https://swiftutility.github.io/Enigmatic/documentation/enigmatic/#Compatibility).

## Edit a model
Expand Down Expand Up @@ -107,7 +109,8 @@ models that encode as dictionaries with no overlapping keys.
the usual date/data handling in `PropertyListEncoder`. Data becomes a byte
array; Date becomes seconds since 2001-01-01. Use `@Codec.Box<Codec.Base64Data>`
for Base64 and `@Codec.Box<Codec.PlistDate>` for seconds since 2001-01-01, or
`Codec.UnixDate<0>` for Unix seconds on supported OS versions.
`Codec.UnixSecondsDate` for Unix seconds. With Swift 6.2.3 or newer, prefer
`Codec.UnixDate<0>` on supported OS versions.
- `Float` and `Double` can store `NaN` and positive or negative infinity in memory.
These values are not valid JSON numbers. Default JSON encoding and
`asJsonObject` reject them. When encoding or decoding the tree with your own
Expand Down
29 changes: 23 additions & 6 deletions Sources/Enigmatic/Enigmatic.docc/CodingStrategies.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ the present payload of an optional strategy.
`Base64Data` encodes a Base64 string and rejects malformed input. `StringURL`
uses Foundation's URL initializer, which permits relative URLs; it does not enforce
an HTTP scheme. `PathURL` turns filesystem paths into file URLs and encodes their
unescaped paths. Relative paths resolve against the process's working directory;
unescaped absolute paths. Relative paths resolve against the process's working directory;
the original relative spelling and URL base are not preserved. Encoding a
non-file URL still writes only its path.
On Windows, `PathURL` accepts drive and network paths with either slash style.
Expand Down Expand Up @@ -70,9 +70,9 @@ when choosing a specific representation.
> the exact behavior.

Apply `Codec.Box` strategies before building the tree. Use `Base64Data` for Data
and `PlistDate` for seconds since 2001-01-01; `UnixDate<0>` is an alternative for
Unix seconds on supported runtimes. For Float and Double, define a string strategy
that preserves special values explicitly:
and `PlistDate` for seconds since 2001-01-01; `UnixSecondsDate` is an alternative
for Unix seconds on all supported compilers. For Float and Double, define a string
strategy that preserves special values explicitly:

```swift
import Foundation
Expand Down Expand Up @@ -173,8 +173,25 @@ timestamp need not survive a decode/encode round trip unchanged. For example,
the stored interval is just below two milliseconds, and encoding truncates toward
zero. The named integer strategies have the same precision limitation.

The generic Unix strategies require Apple OS 26; the named seconds and
milliseconds strategies remain available for older deployment targets.
The generic Unix strategies require Swift 6.2.3 or newer. On Apple platforms,
they also require OS 26. With earlier compilers or older deployment targets, use
`UnixSecondsDate` / `UnixIntSecondsDate` or
`UnixMillisecondsDate` / `UnixIntMillisecondsDate`. The named strategies are
deprecated on Apple OS 26 only when compiled with Swift 6.2.3 or newer; earlier
compilers retain them without deprecation warnings. Other scales require the
generic strategies or a custom strategy.

For example, this model uses integer milliseconds on every supported compiler:

```swift
struct Event: Codable {
@Codec.Box<Codec.UnixIntMillisecondsDate> var timestamp: Date
}

let event = Event(timestamp: Date(timeIntervalSince1970: -123.25))
let tree = try Enigma(encode: event)
// {"timestamp": -123250}
```

## Complexity

Expand Down
Loading
Loading