A lightweight Semantic Versioning (SemVer 2.0.0) parser and checker for any platform with C++11 support (Arduino, ESP, etc.) featuring a zero-allocation architecture.
-
Zero-Allocation Architecture: Core logic uses a single internal buffer and pointer offsets, eliminating memory fragmentation.
-
Full SemVer 2.0.0 Support: Handles major, minor, patch, pre-release identifiers, and build metadata.
-
Arduino Printable Interface: Direct printing to Serial/LCD/OLED without String allocation.
-
Smart Compatibility Checking:
satisfies()implements caret range logic with 0.x.x handling. -
Safety First: Optional strict pre-release protection prevents accidental upgrades to unstable versions.
-
Version Comparison Helpers:
maximum()andminimum()static methods for finding latest/oldest versions. -
Configurable Buffer Size: Adjust via build flags for your project needs.
-
Robust Validation: Enforces strict SemVer rules (no leading zeros, character set validation) to prevent security issues.
-
Comparison Operators: Easy-to-use operators (
<,<=,==,>=,>,!=). -
Memory Efficient: Minimal footprint, suitable for extremely constrained embedded systems (AVR).
-
Portable: C++ core with optional Arduino wrappers (can be used in non-Arduino environments).
-
Security Focused: Built-in length limits (
MAX_VERSION_LEN) preventing buffer overflows.
- Search for SemVerChecker in the Library Manager.
- Click Install.
Alternatively, download the latest release from the Releases page and install via Sketch → Include Library → Add .ZIP Library...
Add the following to your platformio.ini:
lib_deps =
bkwoka/SemVerCheckerSemVer objects can be printed directly to any Print stream:
#include <SemVerChecker.h>
void setup() {
Serial.begin(115200);
SemVer version("2.5.3-rc.1+build.789");
// Direct print - zero allocation, 5-10x faster!
Serial.print("Current version: ");
Serial.println(version); // Outputs: 2.5.3-rc.1+build.789
// Works with LCD, OLED, File, WiFi, etc.
// lcd.print(version);
// client.println(version);
}Check if a version satisfies a requirement using SemVer caret range logic:
#include <SemVerChecker.h>
void setup() {
Serial.begin(115200);
SemVer installed("1.5.0");
SemVer required("1.2.0");
if (installed.satisfies(required)) {
// ✓ Compatible: same major, minor/patch can be higher
Serial.println("Library version is compatible!");
startApplication();
} else {
// ✗ Incompatible: breaking changes detected
Serial.println("ERROR: Incompatible library version");
showError();
}
}Special 0.x.x handling:
// 0.1.x - 0.9.x behavior (Minor is breaking)
SemVer("0.2.5").satisfies(SemVer("0.2.0")); // ✓ true (patch OK)
SemVer("0.3.0").satisfies(SemVer("0.2.0")); // ✗ false (minor breaking!)
// 0.0.x behavior (Strict: Patch is breaking)
SemVer("0.0.2").satisfies(SemVer("0.0.1")); // ✗ false (patch breaking in 0.0.x!)
SemVer("0.0.1+build").satisfies(SemVer("0.0.1")); // ✓ trueBy default, satisfies() enforces a Safety First policy: if the requirement is a stable version (e.g., 1.0.0), it will reject any pre-release versions (e.g., 1.1.0-beta), even if they are technically newer.
To allow pre-releases (e.g., for a "Beta" channel), pass true as the second argument:
SemVer requirement("1.0.0");
SemVer candidate("1.1.0-beta");
// Default: Safe for Production
if (candidate.satisfies(requirement)) {
// WON'T enter here (candidate rejected)
}
// Explicit: Beta Channel
if (candidate.satisfies(requirement, true)) {
// WILL enter here (candidate accepted)
}SemVer v1("1.2.3");
SemVer v2("1.5.0");
SemVer v3("1.3.2");
SemVer latest = SemVer::maximum(SemVer::maximum(v1, v2), v3); // 1.5.0
SemVer oldest = SemVer::minimum(SemVer::minimum(v1, v2), v3); // 1.2.3
Serial.print("Latest: ");
Serial.println(latest);#include <SemVerChecker.h>
void setup() {
Serial.begin(115200);
// Parse versions
SemVer currentVersion("1.2.3-beta.1");
SemVer minVersion("1.0.0");
if (currentVersion.isValid() && currentVersion >= minVersion) {
Serial.println(F("Version is compatible."));
}
// Pre-release precedence
SemVer v1("1.0.0-rc.1");
SemVer v2("1.0.0");
if (v1 < v2) {
Serial.println(F("Pre-release is older than stable release."));
}
}#include <SemVerChecker.h>
void checkForUpdate() {
String current = "1.0.0";
String available = "1.1.0";
if (SemVer::isUpgrade(current, available)) {
Serial.println(F("An update is available!"));
}
}By default, SemVer uses a 64-byte buffer. You can adjust this via build flags:
PlatformIO:
[env:myboard]
build_flags =
-DSEMVER_MAX_LENGTH=128 ; For longer version stringsArduino IDE: Add to your sketch before including the library:
#define SEMVER_MAX_LENGTH 128
#include <SemVerChecker.h>Trade-offs:
- Larger buffer = supports longer versions, uses more RAM
- Smaller buffer = saves RAM, may reject very long version strings
- Default 64 bytes handles 99% of real-world cases
SemVer(): Create invalid version (0.0.0, invalid flag set)SemVer(const char* versionString): Parse a version stringSemVer(const String& versionString): Arduino String variant (ifARDUINOdefined)
bool isValid() const: Check if the version string was parsed correctly
size_t printTo(Print& p) const: Print directly to any Print stream (Serial, LCD, etc.)String toString() const: Get Arduino String representation (ifARDUINOdefined)void toString(char* buffer, size_t len) const: Fill a buffer with string representation
const char* getPrerelease() const: Get pointer to pre-release stringconst char* getBuild() const: Get pointer to build metadata string
- Operators:
<,<=,==,!=,>=,> bool satisfies(const SemVer& requirement) const: Check compatibility (caret range logic)
static SemVer maximum(const SemVer& v1, const SemVer& v2): Return greater versionstatic SemVer minimum(const SemVer& v1, const SemVer& v2): Return lesser version
SemVer::DiffType diff(const SemVer& other) const: Returns type of difference (MAJOR,MINOR,PATCH,PRERELEASE,NONE)
void incMajor(): Increment major version (resets minor and patch to 0)void incMinor(): Increment minor version (resets patch to 0)void incPatch(): Increment patch version
-
static bool isUpgrade(const char* base, const char* next): Check ifnextis an upgrade overbase -
Arduino String variants available when
ARDUINOis defined
static const size_t MAX_VERSION_LEN: Maximum allowed length for a version string (default 64, configurable)
The library includes 5 comprehensive examples:
- BasicUsage - Start here! Covers parsing, validation, printing, and comparison
- PrintableIntegration - Deep dive into zero-allocation printing
- DependencyManagement - Using
satisfies()for library compatibility - VersionComparison - Finding latest/oldest versions with
maximum()/minimum() - FirmwareUpdate - Complete OTA update decision logic
Find them in: File → Examples → SemVerChecker
The project includes comprehensive unit tests covering:
- Strict SemVer 2.0.0 parsing
- Security edge cases (overflow, leading zeros)
- Precedence rules
- Pre-release handling
- All library features
# Using g++ (Linux/macOS)
g++ -DARDUINO=100 -Itests -Isrc tests/run_tests.cpp -o tests/run_tests && ./tests/run_tests
# Or via WSL on Windows
wsl -e bash -c "g++ -DARDUINO=100 -Itests -Isrc tests/run_tests.cpp -o tests/run_tests && ./tests/run_tests"Object size: 82 bytes per SemVer instance
uint32_t major, minor, patch: 12 byteschar _buffer[MAX_VERSION_LEN + 1]: 65 bytes (default 64 char + null terminator)- Metadata (
_preOffset,_buildOffset,_valid): 5 bytes
Code size: ~8-9KB Flash (with all features)
- With
-ffunction-sections -Wl,--gc-sections: only used methods included - Typical impact on ATmega328P (32KB): ~2-3%
- On ESP32 (4MB): negligible
Zero heap allocation - all operations use stack or internal buffer
Tested and working on:
- ✅ Arduino Uno/Nano (AVR)
- ✅ Arduino Mega
- ✅ ESP8266
- ✅ ESP32
- ✅ STM32
- ✅ Native Linux/macOS (for testing)
- ✅ Any platform with C++11 support
This project is licensed under the MIT License - see the LICENSE file for details.
bkwoka - GitHub