Release v0.6.4 — native C/C++ and Python blocks in .stlib - #225
Merged
Conversation
A library may ship function blocks whose bodies are C/C++ or Python. STruC++
does not compile those bodies — it transports them. The archive carries the
authored file verbatim and the consuming toolchain lowers it at its own build
time, against whatever native bridge that toolchain implements.
That distinction is the point. Compiling the bodies here would bake one fixed
bridge ABI into every archive, so a bridge change would break every library
published before it until each was rebuilt. Transporting the source keeps a
published library working across bridge revisions.
What is read is the ST header every such file already carries — the
FUNCTION_BLOCK line and its VAR_* blocks. That is ordinary ST, so the
interface is recovered by projecting the file down to a header-only block and
running it through the normal front end: one declaration parser, no second
implementation to drift, and a bad declaration in a .py file gets the same
diagnostics an .st file would. The body between the last END_VAR and the
closing END_FUNCTION_BLOCK never reaches the parser.
Native blocks land in `manifest.functionBlocks` looking like any other,
distinguished by `implementation: "cpp" | "python"` and pointing at their file
via `sourceFile`. They emit no chunk. Consumers that see `implementation` must
lower the source themselves; consumers that don't recognise the field find no
chunk either, so they fail loudly rather than silently linking nothing.
Also:
- A library whose every input is native no longer fails. There is nothing
for the compiler to do, so it is not called — previously this reached
`compile()` with an empty translation unit and died on "No source files
provided", which made an all-native library unbuildable. A library with no
inputs at all still fails, as before.
- `--compile-lib <dir>` discovers .cpp/.c/.cc/.cxx/.py alongside .st/.il, so
an author drops Block.py in the folder next to Other.st and both end up in
the archive.
- `--no-source` strips ST sources only. Native bodies are exempt and must
be: they have no chunk, so the source IS the deliverable and an archive
without it is unbuildable by anyone. A native block cannot be shipped
closed-source in this format.
Archives that contain no native blocks are unchanged, so the existing library
corpus and older editors are unaffected.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_018qaJbNUTA9kMLL9Jr5FjUU
Two review findings on the native-blocks change, both reproduced before fixing
and both of the same shape: the library built with `success: true` and handed
back an artifact that was quietly wrong. A published `.stlib` is immutable in
the field, so silence there costs more than a failure does.
B1 — a native FUNCTION was accepted, then dropped
-------------------------------------------------
`projectNativeHeaderToSt` rejected PROGRAM but accepted FUNCTION, projected its
header and closed it with END_FUNCTION. The result landed in the AST's function
list, which `compileNativeEntries` does not read, so:
success: true errors: []
manifest.functions: [] manifest.functionBlocks: []
A "successful" library with the block present nowhere, surfacing later as an
undefined type in someone else's project.
There is no such thing as a C/C++ or Python FUNCTION here: the bridge hands the
body a pointer to per-instance storage and calls `setup` once before `loop` on
every scan, which is function-block shape. A FUNCTION has no instance to hold
that state in. So this is now a compile error naming the file and saying why.
ST functions are untouched — they compile through the normal path and keep
their manifest entries and chunks, including alongside a native block. This is
only about a `.cpp`/`.py` file declaring FUNCTION, which the editor's own UI
already forbids; it takes a hand-authored project to reach.
`compileNativeEntries` no longer returns a `functions` list, since native
entries are always function blocks now, and the `LibraryFunctionEntry` import
goes with it.
B2 — one name, two exports, no error
------------------------------------
STruC++'s duplicate detection fires only within a single kind and a single
translation unit. Two `FUNCTION_BLOCK Foo` in one compile is caught; `FUNCTION
Foo` beside `FUNCTION_BLOCK Foo` is not. And native headers compile in a
separate unit, so nothing there was ever compared against the ST — an author
moving a block from ST to C++ and forgetting to delete the `.st` got two `Foo`
entries with different interfaces, one with a chunk and one without.
`findDuplicateExports` now runs over the assembled manifest — functions,
function blocks, types and globals together, case-insensitively — and fails
naming the symbol and both kinds. That is deliberately wider than the reported
case: a library exports one thing per name, whatever those things are. It also
closes three collisions that predate this feature and were equally silent
(function vs function block, type vs function block, global vs function block).
Nothing downstream validates against these entries today, so the old behaviour
went unnoticed; the point is that the next reader that does validate would
inherit the ambiguity from an archive nobody can change.
Editor-side half of B1: the orchestrator read native sources from a hardcoded
`pous/function-blocks`, so a native FUNCTION (which lives in `pous/functions/`)
died on a path guessed wrong rather than reaching strucpp's explanation. The
directory now follows the POU type.
C1 was referenced in the review but never filed; not addressed here.
Tests: 12 new in `native-blocks.test.ts` covering both rejections, the wider
duplicate matrix, case-insensitive matching, and that a library with distinct
names still builds. Full suite 2265 passed.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_018qaJbNUTA9kMLL9Jr5FjUU
…locks-in-stlib Enables DOPE-585. Consumer PRs: Autonomy-Logic/openplc-web#699 and Autonomy-Logic/openplc-editor#1042, both of which need binary-versions.json pointing at a release that carries this. Archives with no native blocks serialize unchanged, so the existing library corpus and older editors are unaffected.
Patch bump for the native-block support merged in #224: libraries can now ship C/C++ and Python function blocks, whose source the archive transports verbatim for the consumer to lower at its own build time. Additive and backward compatible — an archive with no native blocks serializes exactly as before, so the existing library corpus and older editors are unaffected. openplc-web and openplc-editor pin strucpp through `binary-versions.json` and need this version to consume the feature. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_018qaJbNUTA9kMLL9Jr5FjUU
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Promotes
developmenttomainfor the v0.6.4 release.What's in it
#224 — libraries can now ship function blocks whose bodies are C/C++ or Python. STruC++ does not compile those bodies; it transports them. The archive carries the authored file verbatim and the consuming toolchain lowers it at its own build time, against whatever native bridge that toolchain implements.
That distinction is the point: compiling the bodies here would bake one fixed bridge ABI into every archive, so a bridge change would break every library published before it until each was rebuilt.
Concretely:
--compile-lib <dir>discovers.cpp/.c/.cc/.cxx/.pyalongside.st/.il, routed into a separate bucket so a Python body never reaches the ST parser..pygets the same diagnostics an.stwould.manifest.functionBlocksmarkedimplementation: "cpp" | "python", pointing at their file viasourceFile, and emit no chunk.--no-sourcestrips ST sources only: a native block has no chunk, so its source is the deliverable.compile()with an empty translation unit and died onNo source files provided.Plus the two review findings from #224, both of which had the library building
success: truewith a quietly wrong artifact:FUNCTIONwas accepted then silently dropped from the manifest. Now a compile error explaining that a FUNCTION has no instance state for the bridge to hold. ST functions are unaffected — they keep their manifest entries and chunks.Compatibility
Additive. An archive with no native blocks serializes exactly as before, so the existing library corpus and older editors are unaffected. An archive that does carry native blocks is a newer artifact by definition: an older consumer finds no chunk for them and fails loudly at compile rather than silently linking nothing.
Verification
tsc --noEmitclean.strucpp --compile-lib, installed it through the editor's real install path, compiled and uploaded a consumer project to a device (slm-rp4, runtime v4.1.10) and read the values back — 42 and 142 as expected, plus a negative control on changed inputs giving 42 and 51.After merge
Tag
v0.6.4onmainto trigger the release workflow.openplc-webandopenplc-editorpin strucpp throughbinary-versions.jsonand need that release before openplc-web#699 and openplc-editor#1042 can consume the feature.🤖 Generated with Claude Code
https://claude.ai/code/session_018qaJbNUTA9kMLL9Jr5FjUU