Skip to content

meshmy/tool-stm32flash

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

tool-stm32flash (reconstructed build recipe)

This repository reverse-engineers PlatformIO's tool-stm32flash package into a buildable recipe that produces an equivalent stm32flash binary from source, and keeps it up to date automatically by tracking upstream's master branch (see "Automation" below) instead of a single fixed release.

Using this in a PlatformIO project

Grab the exact filename for your platform from the latest release, then in platformio.ini:

[env:your_env]
platform_packages =
  tool-stm32flash @ https://github.com/meshmy/tool-stm32flash/releases/download/<tag>/tool-stm32flash-<system>-<version>.tar.gz

<system> is one of darwin_arm64, darwin_x86_64, linux_x86_64, linux_aarch64, linux_armv7l, windows_amd64 -- pick the one matching the machine that will run PlatformIO (dev box or CI runner). The name=uri form (tool-stm32flash @ <url>) makes PlatformIO install it under the tool-stm32flash directory name regardless of what platform (ststm32, etc.) would otherwise pull in. There's no per-machine auto-detection with this form, so pick the URL per [env:] section, or set it via PLATFORMIO_PLATFORM_PACKAGES in CI.

Prefer automatic per-machine resolution instead (like the stock package gets)? Publish these builds under your own PlatformIO account:

pio account register   # one-time
pio pkg publish --type tool --owner <you> --release tool-stm32flash-darwin_arm64-<version>.tar.gz
# repeat per downloaded system archive

then:

platform_packages =
  <you>/tool-stm32flash@<version>

resolves per-machine automatically, same as platformio/tool-stm32flash does today, just under your own namespace (publishing as platformio isn't possible without owning that account).

Automation

This repo tracks stm32flash's upstream master branch rather than a single fixed tag, and cuts a new release automatically -- roughly every time upstream goes quiet for a few weeks, not on a fixed calendar schedule. Two pieces work together:

  • renovate.json + renovate.yml run self-hosted Renovate on a schedule. It opens/updates a PR the moment upstream gets a new commit -- purely for visibility, so you can see incoming upstream changes as a diff before they ship. It can't do more than that: Renovate's git-refs datasource has no commit-timestamp data, so its usual "wait N days before updating" feature (minimumReleaseAge) has nothing to gate on here.
  • auto-release.yml runs on its own schedule and asks GitLab's commit API directly (which does have timestamps) whether upstream's HEAD has been sitting still for 21 days. Once it has -- and only then -- it bumps STM32FLASH_GIT_COMMIT in scripts/versions.sh, commits straight to main, tags the result, and closes out whatever Renovate PR was tracking that same commit (now superseded).

So a new commit landing upstream shows up as a Renovate PR right away, but nothing actually ships until that commit has been stable for 3 weeks. release.yml (build + publish) is unchanged by any of this -- it just reacts to whatever tag shows up, same as a manually pushed one would.

Because upstream's own VERSION macro is a hardcoded "0.7" that never moves, this repo derives its own PlatformIO package version from the pinned commit instead: 0.7.<commit-date:YYYYMMDD>+<short-sha>, computed by lib.sh's package_version(). It's a normal, monotonically increasing semver as far as PlatformIO/npm-style comparisons are concerned (verified against PlatformIO's actual semantic_version-based comparator) -- the +<short-sha> is build metadata for traceability, not part of ordering.

Provenance

Unlike tool-openocd (repackaged xPack build with no first-party attribution), this one barely needed any detective work: the registry API itself already tells you where the code comes from.

curl -s https://api.registry.platformio.org/v3/packages/platformio/tool/tool-stm32flash

returns, directly:

  • "repository_url": "https://gitlab.com/stm32flash/stm32flash.git"
  • "license": "GPL-2.0-or-later"
  • "homepage": "https://sourceforge.net/projects/stm32flash"
  • "dependencies_count": 0

So PlatformIO's package is the public stm32flash project (mirrored from its original home on SourceForge), repackaged as-is, with nothing bundled or patched in.

The original pin: reconstructing PlatformIO's own v0.7.0 build

Before this repo switched to tracking master (see "Automation" above), it pinned the exact commit PlatformIO's own 0.7.0 registry release was built from, recovered like this:

stm32flash's VERSION is a hardcoded macro in main.c (#define VERSION "0.7"), not a git describe string, so the version banner alone doesn't distinguish the tagged release from any later commit. The exact commit was instead recovered by diffing dev_table.c device-table entries embedded in the shipped binary against the upstream git history:

  • The v0.7 tag (5ac0ffb0a33d88cd4dbb2ac1971720dcce7f2be1, the commit the annotated tag object be36c67d9ba52fe79e93c5ea7332a61794980d93 points to) was cut 2022-03-27.
  • ~19 commits landed after it before this package was built, adding device table entries for STM32C011xx, STM32H72xxx/73xxx, STM32H7A3xx/B3xx, STM32L4P5xx/Q5xx, STM32L552xx/562xx, STM32WB10xx/15xx, STM32WB30xx/50xx, and others.
  • None of those device-name strings appear anywhere in the shipped binaries (strings ... | grep STM32) -- only the ones already active in dev_table.c at the v0.7 tag. The commented-out placeholder entries for those later-added chips are present in v0.7's own dev_table.c (as /* ... */), so their absence as live strings in the binary is conclusive: it was built from v0.7 exactly, not master.

This was corroborated independently: the macOS binaries embed a Mach-O debug map (nm -a, OSO stab entries) recording each .o's build mtime as Unix time 0x67f3d41a = 2025-04-07 13:33:14 UTC -- 43 minutes before the registry's own "added_at": "2025-04-07T14:16:10Z". PlatformIO checked out the v0.7 tag fresh that day and built it, rather than floating master. Every platform's binary embeds the same build-path fragment, .../fuzzy-octo-spoon/fuzzy-octo-spoon/stm32flash/... -- apparently an internal PlatformIO CI repo left at GitHub's own randomly-generated default repo name.

No bundled dependencies

dependencies_count: 0 in the registry API is confirmed at the binary level: every shipped binary's only linked libraries are the platform's own C runtime --

Platform otool -L / objdump -p NEEDED/imports
darwin (arm64 + x86_64) /usr/lib/libSystem.B.dylib only
linux (x86_64, aarch64, armv7l) libc.so.6 only
windows_amd64 KERNEL32.dll, msvcrt.dll only

so there's no equivalent of tool-openocd's build-deps.sh stage here at all -- stm32flash is a single self-contained C program.

What gets built

Component Source
stm32flash https://gitlab.com/stm32flash/stm32flash.git, master branch HEAD (see "Automation")

Pinned commit lives in one place: scripts/versions.sh.

It builds via stm32flash's own plain (non-autotools) root Makefile -- the path its own INSTALL file documents as primary ("A set of static makefiles is provided ... 1. Build executable: make"). No ./configure, no autoreconf: the Makefile only requires $CC/$AR to be set (it hard-errors otherwise), and serial_platform.c itself #includes either serial_posix.c or serial_w32.c based on __WIN32__/__CYGWIN__, so one unmodified Makefile already produces the right binary on every platform -- including automatically getting a .exe suffix under a mingw $CC.

Supported platforms

Matches the exact 6 systems the platformio/tool-stm32flash registry package ships (confirmed against the live registry API -- see "Releasing" below):

PlatformIO system GitHub Actions runner Notes
darwin_arm64 macos-15 Native. Validated locally against the real registry archive: identical symbol table (undefined + defined), identical embedded strings; only cosmetic differences (libSystem.B.dylib minor version, debug-info size from a different Xcode/clang point release)
darwin_x86_64 macos-15-intel Native. Also validated locally via clang -arch x86_64 cross-codegen (Apple clang can target x86_64 from an arm64 host without Rosetta) -- identical symbol table to the real registry archive
linux_x86_64 ubuntu-latest Native, validated only via CI
linux_aarch64 ubuntu-24.04-arm Native, validated only via CI
linux_armv7l ubuntu-latest (cross-compiled) No GitHub-hosted runner is 32-bit ARM (confirmed against the current actions/runner-images matrix -- all hosted ARM runners are arm64-only). Cross-compiled from an x86_64 host using Ubuntu's own crossbuild-essential-armhf package (arm-linux-gnueabihf-gcc/-ar), matching the real binary's ABI exactly: EABI5 hard-float (armhf), interpreter /lib/ld-linux-armhf.so.3. Smoke-tested in CI via qemu-user-static
windows_amd64 windows-latest + MSYS2 MINGW64 Also served for the windows_arm64 system tag (Windows on Arm runs the x86_64 build via its built-in x64 emulation; PlatformIO doesn't ship a separate arm64 binary either)

Per-platform packaging is trivial here compared to tool-openocd: every registry archive for this package is just the bare binary + package.json at the archive root (no bin/ subdirectory, no bundled libraries, no README/license/man page) -- confirmed by extracting and find-ing all 6 real archives.

Windows: MINGW64, not UCRT64

This is the one place this repo makes the opposite choice from the tool-openocd exercise, and it's worth calling out explicitly so it isn't "corrected" back by habit: the real shipped windows_amd64 archive's own stm32flash.exe imports msvcrt.dll (the legacy CRT), not the modern Universal CRT. Confirmed via objdump -p:

DLL Name: KERNEL32.dll
DLL Name: msvcrt.dll

and via strings embedded by the compiler itself -- GCC: (Rev2, Built by MSYS2 project) 14.2.0 and include paths like D:/a/msys64/mingw64/include (note mingw64, not ucrt64). So .github/workflows/release.yml deliberately configures msys2/setup-msys2 with msystem: MINGW64 and installs mingw-w64-x86_64-toolchain, not the UCRT64 equivalents. The general rule from the tool-openocd writeup stands -- confirm empirically per package via objdump -p against the real archive, don't assume either environment is "the" right one.

Building

Requires only a C compiler, ar, and GNU/BSD make -- no autotools, no third-party libraries.

# macOS: already present via Xcode Command Line Tools
xcode-select --install

# Linux (Debian/Ubuntu)
sudo apt-get install -y build-essential

# Windows: use MSYS2's MINGW64 shell (not UCRT64), see
# .github/workflows/release.yml for the exact package list.

# linux_armv7l cross-compile from an x86_64 Linux host:
sudo apt-get install -y crossbuild-essential-armhf

Then:

make            # fetch source, build, package, archive
# or step by step:
make fetch
make build
make package
make archive    # produces dist/tool-stm32flash-<system>-<version>.tar.gz

Cross-compiling (e.g. linux_armv7l from an x86_64 host) or pinning a specific compiler: set CC, AR, and TARGET_SYSTEM before invoking make, e.g.

CC=arm-linux-gnueabihf-gcc AR=arm-linux-gnueabihf-ar TARGET_SYSTEM=linux_armv7l make

Output lands in dist/tool-stm32flash/, laid out exactly like the real PlatformIO package -- just the binary and package.json, nothing else:

dist/tool-stm32flash/
  stm32flash            # or stm32flash.exe on Windows
  package.json          # includes "system": ["<platformio-system-tag>", ...]

make clean removes build artifacts but keeps fetched sources; make distclean removes everything including dist/.

Validation

Built locally on macOS arm64 (and cross-compiled for macOS x86_64) and diffed directly against the real registry archives for both:

  • strings ... | grep -E '\.c$' (embedded source file list) -- identical
  • strings ... | grep STM32 (device table names) -- identical
  • stm32flash 0.7 version banner -- identical
  • nm symbol table (undefined + all defined globals, addresses excluded) -- byte-for-byte identical, same count (172 symbols) on both arm64 and the x86_64 cross-build
  • otool -L -- identical (libSystem.B.dylib only), differing only in its reported minor version (a function of the build machine's OS/Xcode version, not a real difference -- same caveat noted in the tool-openocd exercise)

The other 4 platforms (linux_x86_64, linux_aarch64, linux_armv7l, windows_amd64) can't be built or run natively on this dev machine; validated via CI logs plus the same kind of archive diff instead, done once CI produces them (see commit history for that run's results).

Releasing

.github/workflows/release.yml builds all 6 platforms in a matrix and publishes a GitHub Release whenever a version tag is pushed. In normal operation you won't push that tag by hand -- auto-release.yml does it once upstream has gone quiet for 3 weeks (see "Automation" above). To force one manually anyway:

# scripts/versions.sh's STM32FLASH_GIT_COMMIT must already be the commit
# you want to release; the tag's version (after "v") must match what
# `source scripts/lib.sh && package_version` derives from it.
git tag "v$(source scripts/lib.sh && package_version)"
git push origin --tags

A check-version job fails the whole run fast if the pushed tag doesn't match the version derived from scripts/versions.sh's pinned commit, so the release version and the archives' package.json version can't drift apart. Each matrix leg uploads tool-stm32flash-<system>-<version>.tar.gz; the final release job collects all 6 and attaches them to the GitHub Release.

Known deviations from the shipped package

  • linux_armv7l is cross-compiled, not natively built. No GitHub-hosted runner offers 32-bit ARM; see the platform table above. Functionally equivalent (same ABI, same toolchain family) but not built on real armv7 hardware, unlike whatever build machine PlatformIO itself used for that archive.
  • Debug info is not stripped, matching the real shipped binaries exactly (stm32flash's own Makefile builds with -g and never runs strip; the Linux registry archives are themselves reported by file as "not stripped" with embedded debug_info). This is intentional parity, not an oversight -- don't add a stripping step expecting it to be an improvement.
  • No man page, README, or license file bundled in the archive, matching every real registry archive checked (all 6 contain only the binary and package.json). stm32flash.1 and gpl-2.0.txt exist in the upstream source tree but the shipped PlatformIO package doesn't carry them either.
  • Tracks upstream HEAD, not PlatformIO's registry release. After the first release (matching PlatformIO's own pinned v0.7 commit exactly, see "Provenance"), this repo diverges from whatever PlatformIO itself ships going forward -- it stays current with upstream master instead of whatever PlatformIO next decides to package (if anything).

License

stm32flash itself is GPL-2.0-or-later (see its own gpl-2.0.txt). The build scripts in this repository are original work reverse-engineering the build recipe, not the shipped binary's code itself.

About

Open, buildable recipe reproducing PlatformIO's tool-stm32flash package from public upstream source

Resources

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors