| 1 |
# Design Document |
| 2 |
|
| 3 |
## Overview |
| 4 |
|
| 5 |
This design implements a GitHub Actions workflow that automatically builds and publishes binary packages when version tags are pushed to the repository. The workflow leverages the existing test infrastructure, builds optimized binaries for five target platforms, packages them according to platform conventions, and uploads them as GitHub release assets. |
| 6 |
|
| 7 |
The solution uses a single workflow file that orchestrates testing, building, packaging, and publishing steps. It reuses the existing test workflow to ensure quality gates are met before creating releases. |
| 8 |
|
| 9 |
## Architecture |
| 10 |
|
| 11 |
### Workflow Structure |
| 12 |
|
| 13 |
The release workflow consists of three main stages: |
| 14 |
|
| 15 |
1. **Validation Stage**: Triggers existing test workflow and waits for completion |
| 16 |
2. **Build Stage**: Compiles release binaries for all target platforms in parallel |
| 17 |
3. **Publish Stage**: Creates packages, generates checksums, and uploads to GitHub release |
| 18 |
|
| 19 |
### Workflow Trigger |
| 20 |
|
| 21 |
```yaml |
| 22 |
on: |
| 23 |
push: |
| 24 |
tags: |
| 25 |
- '[0-9]+.[0-9]+.[0-9]+' |
| 26 |
``` |
| 27 |
|
| 28 |
This pattern matches semantic version tags like `1.5.0`, `2.0.1`, etc. |
| 29 |
|
| 30 |
### Build Matrix |
| 31 |
|
| 32 |
The workflow uses a matrix strategy to build for multiple platforms: |
| 33 |
|
| 34 |
|
| 35 |
|
| 36 |
| x86_64-unknown-linux-gnu | ubuntu-22.04 | .tar.gz | Native x86_64 Linux runner, explicit version for stability | |
| 37 |
| aarch64-unknown-linux-gnu | ubuntu-22.04-arm | .tar.gz | Native ARM64 Linux runner, explicit version for stability | |
| 38 |
| aarch64-apple-darwin | macos-14 | .tar.gz | Native Apple Silicon runner (M1/M2), explicit version for stability | |
| 39 |
| x86_64-pc-windows-msvc | windows-2022 | .zip | Native x86_64 Windows runner, explicit version for stability | |
| 40 |
| aarch64-pc-windows-msvc | windows-11-arm | .zip | Native ARM64 Windows runner (only ARM64 option available) | |
| 41 |
|
| 42 |
**Runner Selection Rationale**: |
| 43 |
|
| 44 |
#### General Principles |
| 45 |
1. **Native Compilation**: Each runner matches the target architecture for optimal performance and to leverage architecture-specific CPU features (AVX-512, NEON, etc.) |
| 46 |
2. **Binary Compatibility**: Binaries built on older OS versions are forward-compatible with newer versions, but not backward-compatible |
| 47 |
3. **Single Binary Per Architecture**: One binary package per architecture is sufficient due to OS forward compatibility |
| 48 |
4. **Explicit Versions**: Using explicit runner versions (ubuntu-22.04, windows-2022, macos-14) instead of -latest provides stability and predictability, preventing unexpected changes when GitHub updates their -latest pointers |
| 49 |
|
| 50 |
#### Platform-Specific Decisions |
| 51 |
|
| 52 |
**Linux (ubuntu-22.04 for x86_64, ubuntu-22.04-arm for aarch64)**: |
| 53 |
- Explicit version (22.04) ensures consistent build environment over time |
| 54 |
- Ubuntu 22.04 uses glibc 2.35, providing good compatibility with modern distributions |
| 55 |
- Ubuntu binaries have excellent forward compatibility across distributions (Ubuntu, Debian, RHEL, etc.) |
| 56 |
- When ubuntu-latest moves to 24.04 or newer, our release builds remain stable on 22.04 |
| 57 |
- No need for multiple Linux versions; one binary per architecture covers the ecosystem |
| 58 |
- Using explicit versions allows intentional upgrades rather than automatic changes |
| 59 |
|
| 60 |
**macOS (macos-14 for aarch64)**: |
| 61 |
- `macos-14` is the first stable Apple Silicon (M1/M2) runner |
| 62 |
- macOS binaries built on older versions run on newer versions (forward compatible) |
| 63 |
- `macos-15` and `macos-latest` would work but offer no compatibility advantage |
| 64 |
- Using `macos-14` provides maximum compatibility (works on macOS 14, 15, and future versions) |
| 65 |
- No x86_64 macOS build needed: Apple Silicon Macs can run x86_64 binaries via Rosetta 2, but native ARM64 is preferred |
| 66 |
- Single ARM64 binary covers all Apple Silicon Macs (M1, M2, M3, M4, etc.) |
| 67 |
|
| 68 |
**Windows (windows-2022 for x86_64, windows-11-arm for aarch64)**: |
| 69 |
- Explicit version (2022) ensures consistent build environment over time |
| 70 |
- Windows Server 2022 binaries have excellent forward compatibility (run on Windows 11, future versions) |
| 71 |
- MSVC runtime is statically linked in Rust release builds, eliminating runtime dependencies |
| 72 |
- When windows-latest moves to Server 2025 or newer, our release builds remain stable on 2022 |
| 73 |
- `windows-11-arm` is the only ARM64 Windows runner available (no version choice) |
| 74 |
- Using explicit versions allows intentional upgrades rather than automatic changes |
| 75 |
- No need for multiple Windows versions; one binary per architecture covers the ecosystem |
| 76 |
|
| 77 |
#### Why Not Multiple OS Versions? |
| 78 |
|
| 79 |
**Rejected Approach**: Building separate packages for each OS version (e.g., macos-14, macos-15, ubuntu-22.04, ubuntu-24.04) |
| 80 |
|
| 81 |
**Reasons**: |
| 82 |
1. **Forward Compatibility**: Binaries built on older OS versions work on newer versions |
| 83 |
2. **Maintenance Burden**: Multiple packages per architecture increases complexity without benefit |
| 84 |
3. **User Confusion**: Users would need to choose between multiple packages for the same architecture |
| 85 |
4. **Storage Costs**: More packages means more storage and bandwidth |
| 86 |
5. **Testing Overhead**: The existing test workflow already validates across multiple OS versions (macos-14, macos-15, ubuntu-22.04, ubuntu-24.04, etc.), proving binary compatibility |
| 87 |
|
| 88 |
**Conclusion**: One binary package per architecture provides maximum compatibility with minimum complexity. The test workflow validates compatibility across OS versions, while the release workflow builds on the oldest supported version for maximum forward compatibility. |
| 89 |
|
| 90 |
## Components and Interfaces |
| 91 |
|
| 92 |
### 1. Release Workflow File |
| 93 |
|
| 94 |
**Location**: `.github/workflows/release.yml` |
| 95 |
|
| 96 |
**Responsibilities**: |
| 97 |
- Trigger on version tags |
| 98 |
- Wait for test workflow completion |
| 99 |
- Build release binaries for all targets |
| 100 |
- Create platform-specific packages |
| 101 |
- Generate SHA256 checksums |
| 102 |
- Upload packages and checksums to GitHub release |
| 103 |
|
| 104 |
**Key Jobs**: |
| 105 |
|
| 106 |
#### Job: `validate` |
| 107 |
- Waits for the existing test workflow to complete for the tagged commit |
| 108 |
- Uses an action like `lewagon/wait-on-check-action` to poll test workflow status |
| 109 |
- Fails the release if tests don't pass |
| 110 |
- Includes timeout to prevent infinite waiting (e.g., 60 minutes) |
| 111 |
- Runs quality checks (fmt, clippy) after tests pass |
| 112 |
|
| 113 |
#### Job: `build` (matrix) |
| 114 |
- Depends on `validate` job |
| 115 |
- Checks out code |
| 116 |
- Sets up Rust toolchain (stable) |
| 117 |
- Runs quality checks (fmt, clippy) |
| 118 |
- Builds release binaries with `cargo build --release` (LTO and stripping handled by Cargo.toml profile) |
| 119 |
- Builds CLI binaries (checksum, arch-check, get-custom-params) |
| 120 |
- Stages files in platform-specific directory structure |
| 121 |
- Creates compressed package |
| 122 |
- Generates SHA256 checksum |
| 123 |
- Uploads package and checksum as artifacts |
| 124 |
|
| 125 |
#### Job: `publish` |
| 126 |
- Depends on all `build` jobs |
| 127 |
- Downloads all artifacts |
| 128 |
- Checks if GitHub release already exists for the tag |
| 129 |
- If release exists, uploads packages and checksums to existing release |
| 130 |
- If release doesn't exist, creates new release and uploads assets |
| 131 |
- Allows manual release creation with custom notes before workflow runs |
| 132 |
|
| 133 |
### 2. Package Structure |
| 134 |
|
| 135 |
#### Linux Packages (x86_64 and aarch64) |
| 136 |
|
| 137 |
``` |
| 138 |
crc-fast-{version}-linux-{arch}/ |
| 139 |
├── lib/ |
| 140 |
│ ├── libcrc_fast.so |
| 141 |
│ └── libcrc_fast.a |
| 142 |
├── include/ |
| 143 |
│ └── libcrc_fast.h |
| 144 |
├── bin/ |
| 145 |
│ ├── checksum |
| 146 |
│ ├── arch-check |
| 147 |
│ └── get-custom-params |
| 148 |
├── LICENSE-MIT |
| 149 |
├── LICENSE-Apache |
| 150 |
├── VERSION |
| 151 |
└── README.txt |
| 152 |
``` |
| 153 |
|
| 154 |
#### macOS Package (aarch64) |
| 155 |
|
| 156 |
``` |
| 157 |
crc-fast-{version}-macos-aarch64/ |
| 158 |
├── lib/ |
| 159 |
│ ├── libcrc_fast.dylib |
| 160 |
│ └── libcrc_fast.a |
| 161 |
├── include/ |
| 162 |
│ └── libcrc_fast.h |
| 163 |
├── bin/ |
| 164 |
│ ├── checksum |
| 165 |
│ ├── arch-check |
| 166 |
│ └── get-custom-params |
| 167 |
├── LICENSE-MIT |
| 168 |
├── LICENSE-Apache |
| 169 |
├── VERSION |
| 170 |
└── README.txt |
| 171 |
``` |
| 172 |
|
| 173 |
#### Windows Packages (x86_64 and aarch64) |
| 174 |
|
| 175 |
``` |
| 176 |
crc-fast-{version}-windows-{arch}/ |
| 177 |
├── bin/ |
| 178 |
│ ├── crc_fast.dll |
| 179 |
│ ├── checksum.exe |
| 180 |
│ ├── arch-check.exe |
| 181 |
│ └── get-custom-params.exe |
| 182 |
├── lib/ |
| 183 |
│ ├── crc_fast.dll.lib (import library for linking against DLL) |
| 184 |
│ └── crc_fast.lib (static library) |
| 185 |
├── include/ |
| 186 |
│ └── libcrc_fast.h |
| 187 |
├── LICENSE-MIT |
| 188 |
├── LICENSE-Apache |
| 189 |
├── VERSION |
| 190 |
└── README.txt |
| 191 |
``` |
| 192 |
|
| 193 |
**Windows Library Files Explained**: |
| 194 |
- `crc_fast.dll`: The dynamic library containing the actual code |
| 195 |
- `crc_fast.dll.lib`: Import library needed to link against the DLL at compile time |
| 196 |
- `crc_fast.lib`: Static library for static linking (standalone, doesn't need DLL) |
| 197 |
|
| 198 |
### 3. Package Metadata Files |
| 199 |
|
| 200 |
#### VERSION File |
| 201 |
Contains the version number extracted from the git tag: |
| 202 |
``` |
| 203 |
1.5.0 |
| 204 |
``` |
| 205 |
|
| 206 |
#### README.txt File |
| 207 |
Provides basic installation and usage instructions: |
| 208 |
``` |
| 209 |
crc-fast Binary Distribution |
| 210 |
Version: {version} |
| 211 |
Platform: {platform}-{arch} |
| 212 |
|
| 213 |
Contents: |
| 214 |
- Library files (dynamic and static) |
| 215 |
- Header file for C/C++ integration |
| 216 |
- Command-line utilities |
| 217 |
|
| 218 |
Installation: |
| 219 |
[Platform-specific instructions] |
| 220 |
|
| 221 |
Usage: |
| 222 |
See https://github.com/awesomized/crc-fast-rust for documentation |
| 223 |
|
| 224 |
License: MIT OR Apache-2.0 |
| 225 |
``` |
| 226 |
|
| 227 |
### 4. Checksum Files |
| 228 |
|
| 229 |
Each package has an accompanying `.sha256` file: |
| 230 |
``` |
| 231 |
{sha256_hash} crc-fast-{version}-{platform}-{arch}.{ext} |
| 232 |
``` |
| 233 |
|
| 234 |
## Data Models |
| 235 |
|
| 236 |
### Build Artifact Structure |
| 237 |
|
| 238 |
```yaml |
| 239 |
artifacts: |
| 240 |
- name: "crc-fast-{version}-{platform}-{arch}.{ext}" |
| 241 |
type: "package" |
| 242 |
checksum: "{sha256_hash}" |
| 243 |
- name: "crc-fast-{version}-{platform}-{arch}.{ext}.sha256" |
| 244 |
type: "checksum" |
| 245 |
``` |
| 246 |
|
| 247 |
### Matrix Configuration |
| 248 |
|
| 249 |
```yaml |
| 250 |
matrix: |
| 251 |
include: |
| 252 |
- target: x86_64-unknown-linux-gnu |
| 253 |
os: ubuntu-22.04 |
| 254 |
platform: linux |
| 255 |
arch: x86_64 |
| 256 |
ext: tar.gz |
| 257 |
|
| 258 |
- target: aarch64-unknown-linux-gnu |
| 259 |
os: ubuntu-22.04-arm |
| 260 |
platform: linux |
| 261 |
arch: aarch64 |
| 262 |
ext: tar.gz |
| 263 |
|
| 264 |
- target: aarch64-apple-darwin |
| 265 |
os: macos-14 |
| 266 |
platform: macos |
| 267 |
arch: aarch64 |
| 268 |
ext: tar.gz |
| 269 |
|
| 270 |
- target: x86_64-pc-windows-msvc |
| 271 |
os: windows-2022 |
| 272 |
platform: windows |
| 273 |
arch: x86_64 |
| 274 |
ext: zip |
| 275 |
|
| 276 |
- target: aarch64-pc-windows-msvc |
| 277 |
os: windows-11-arm |
| 278 |
platform: windows |
| 279 |
arch: aarch64 |
| 280 |
ext: zip |
| 281 |
``` |
| 282 |
|
| 283 |
## Error Handling |
| 284 |
|
| 285 |
### Test Failure |
| 286 |
- If the test workflow fails, the `validate` job fails |
| 287 |
- The `build` jobs don't execute (dependency chain) |
| 288 |
- No release is created |
| 289 |
- GitHub Actions shows clear failure status |
| 290 |
|
| 291 |
### Build Failure |
| 292 |
- If any platform build fails, that specific matrix job fails |
| 293 |
- Other platform builds continue |
| 294 |
- The `publish` job waits for all builds |
| 295 |
- If any build failed, `publish` job is skipped |
| 296 |
- Partial releases are prevented |
| 297 |
|
| 298 |
### Quality Check Failure |
| 299 |
- If `cargo fmt --check` fails, build stops |
| 300 |
- If `cargo clippy` produces warnings, build stops |
| 301 |
- Ensures all released binaries meet quality standards |
| 302 |
|
| 303 |
### Missing Files |
| 304 |
- Build script validates all expected files exist before packaging |
| 305 |
- Fails with clear error message if files are missing |
| 306 |
- Prevents incomplete packages |
| 307 |
|
| 308 |
### Upload Failure |
| 309 |
- If GitHub release creation fails, workflow fails |
| 310 |
- If asset upload fails, workflow retries (GitHub Actions default) |
| 311 |
- All artifacts remain available for manual inspection |
| 312 |
|
| 313 |
## Testing Strategy |
| 314 |
|
| 315 |
### Pre-Release Testing |
| 316 |
1. The existing test workflow must pass completely |
| 317 |
2. All platforms in the test matrix must succeed |
| 318 |
3. Format and clippy checks must pass |
| 319 |
|
| 320 |
### Build Verification |
| 321 |
1. Verify all expected files are present in target/release |
| 322 |
2. Check file sizes are reasonable (not empty) |
| 323 |
3. Validate binary executables are actually executable |
| 324 |
4. Confirm library files have correct extensions |
| 325 |
|
| 326 |
### Package Verification |
| 327 |
1. Verify package structure matches specification |
| 328 |
2. Check all metadata files are included |
| 329 |
3. Validate checksums are generated correctly |
| 330 |
4. Ensure package names follow naming convention |
| 331 |
|
| 332 |
### Integration Testing |
| 333 |
1. Test workflow on a feature branch with test tags |
| 334 |
2. Verify packages can be downloaded |
| 335 |
3. Test that libraries can be linked |
| 336 |
4. Verify CLI binaries execute correctly |
| 337 |
5. Validate checksums match package contents |
| 338 |
|
| 339 |
### Manual Testing Checklist |
| 340 |
Before first production release: |
| 341 |
- [ ] Create test tag on feature branch |
| 342 |
- [ ] Verify workflow triggers correctly |
| 343 |
- [ ] Download each platform package |
| 344 |
- [ ] Extract and verify contents |
| 345 |
- [ ] Test linking dynamic library |
| 346 |
- [ ] Test linking static library |
| 347 |
- [ ] Run each CLI binary |
| 348 |
- [ ] Verify checksums |
| 349 |
- [ ] Test on actual target platforms |
| 350 |
|
| 351 |
## Design Decisions |
| 352 |
|
| 353 |
### Decision 1: Single Package Per Platform |
| 354 |
**Rationale**: Combining libraries and CLI tools in one package simplifies distribution and ensures users get everything they need. Separate packages would create confusion about which package to download. |
| 355 |
|
| 356 |
**Alternative Considered**: Separate library and CLI packages. Rejected because it adds complexity for minimal benefit. |
| 357 |
|
| 358 |
### Decision 2: Include Both Dynamic and Static Libraries |
| 359 |
**Rationale**: Different projects have different linking requirements. Providing both gives maximum flexibility without requiring users to build from source. |
| 360 |
|
| 361 |
**Alternative Considered**: Separate packages for dynamic and static. Rejected because the size overhead is minimal and user experience is worse. |
| 362 |
|
| 363 |
### Decision 3: Platform-Specific Directory Layouts |
| 364 |
**Rationale**: Following platform conventions makes integration easier for users familiar with their platform's standards. Windows expects DLLs in bin/, while Unix expects shared libraries in lib/. |
| 365 |
|
| 366 |
**Alternative Considered**: Unified layout across all platforms. Rejected because it would be non-standard on all platforms. |
| 367 |
|
| 368 |
### Decision 4: Reuse Existing Test Workflow |
| 369 |
**Rationale**: The existing test workflow is comprehensive and already validates all target platforms. Duplicating tests would be wasteful and create maintenance burden. |
| 370 |
|
| 371 |
**Alternative Considered**: Inline tests in release workflow. Rejected because it duplicates logic and increases workflow complexity. |
| 372 |
|
| 373 |
### Decision 5: Matrix Build Strategy |
| 374 |
**Rationale**: Building all platforms in parallel maximizes speed and allows independent failure handling. GitHub Actions provides excellent matrix support. |
| 375 |
|
| 376 |
**Alternative Considered**: Sequential builds. Rejected because it would significantly increase release time. |
| 377 |
|
| 378 |
### Decision 6: SHA256 Checksums |
| 379 |
**Rationale**: SHA256 is the industry standard for verifying download integrity. It's widely supported and provides strong security guarantees. |
| 380 |
|
| 381 |
**Alternative Considered**: MD5 or SHA1. Rejected because they're cryptographically weak. Multiple checksums rejected as unnecessary. |
| 382 |
|
| 383 |
### Decision 7: Separate Checksum Files |
| 384 |
**Rationale**: Separate files allow users to download and verify checksums independently. This is the standard practice for software distribution. |
| 385 |
|
| 386 |
**Alternative Considered**: Checksums in release notes. Rejected because it's less convenient for automated verification. |
| 387 |
|
| 388 |
### Decision 8: Tag Pattern Without 'v' Prefix |
| 389 |
**Rationale**: The project already uses tags like `1.5.0` without the 'v' prefix. Maintaining consistency with existing practice is important. |
| 390 |
|
| 391 |
**Alternative Considered**: Requiring 'v' prefix. Rejected because it would break existing tagging convention. |
| 392 |
|
| 393 |
### Decision 9: Update Existing Release Rather Than Create |
| 394 |
**Rationale**: Allowing manual release creation before the workflow runs gives maintainers control over release notes, descriptions, and other metadata. The workflow focuses on building and uploading binaries, not managing release content. |
| 395 |
|
| 396 |
**Workflow**: |
| 397 |
1. Maintainer creates release manually with desired notes |
| 398 |
2. Maintainer creates and pushes tag |
| 399 |
3. Workflow builds binaries and uploads to existing release |
| 400 |
4. If no release exists, workflow creates a minimal one |
| 401 |
|
| 402 |
**Alternative Considered**: Workflow always creates release. Rejected because it forces auto-generated release notes and removes maintainer control over release presentation. |