0
0
Fork 0
Commit graph

8 commits

Author SHA1 Message Date
Karstein Phobic Nyvold Kvistad
04b46fb49a fix(read_running_version_online): document the real root cause + fix
Three changes after deeper investigation against PLATEA Win V3:

1. The 'symbol' := 'read' attribute experiment didn't help. Reverted
   bump_project_version's GVL template to plain VAR_GLOBAL +
   qualified_only (matches what shipped originally, minus CONSTANT).
   Comment updated to explain both why CONSTANT is wrong (compile-time
   inlining) and why the symbol attribute alone wasn't enough (it
   requires a Symbol Configuration object to do anything).

2. Real root cause for read_running_version_online's 'Invalid expression':
   CODESYS strips unreferenced GVLs from the online symbol table at
   compile time, regardless of attribute pragmas. The version anchor by
   definition has no IEC code reading it, so the optimizer drops it.
   GVL_Test.bRun reads fine despite no references because GVL_Test has
   OTHER referenced variables; entire-GVL retention seems to be the
   stripping unit, not per-variable.
   Verified end-to-end: adding 'sVersionTag := _MCP_PROJECT_VERSION
   .sVersion;' in PLC_PRG made the read return '1.4.1.0' on PLATEA.

3. Updated the read_running_version_online error message to surface
   BOTH the (now-rare) CONSTANT case AND the (common) unreferenced-GVL
   case, with the exact 2-line code snippet a user needs to paste into
   their main program. The bump tool intentionally does NOT auto-inject
   this -- modifying user code on every release was deemed too invasive.
   Documented the requirement in TEST_OVERVIEW.md alongside the v5
   sweep notes.

37/37 tests still green. v1.4.2.0 of MCPTest2 carries the working
PLC_PRG reference as the canonical demonstration.
2026-04-26 20:13:25 +02:00
Karstein Phobic Nyvold Kvistad
ef259c8ee3 fix(online tools): auto-login + non-CONSTANT version GVL
Two related v5-sweep fixes for the online/runtime tool family:

1. Auto-login helper for headless mode

   In headless mode each MCP call spawns a fresh CODESYS --noUI process,
   so the login state established by connect_to_device dies before the
   next call. Pre-fix, only connect_to_device and download_to_device did
   their own login(); the other four (start_stop_application,
   read_variable, write_variable, read_running_version_online) silently
   failed in headless with 'Application not logged in.' (start/stop) or
   'Invalid expression' (read/write). They worked in persistent mode
   only because the login carried across calls.

   Added ensure_logged_in(online_app, login_wait_seconds=30) to
   ensure_online_connection.py. Idempotent: short-circuits via
   online_app.is_logged_in (persistent mode is a no-op, no extra login
   roundtrip). When not logged in, runs the same enum-probe + call-shape
   probe + STABLE_STATES settle-wait pattern as connect_to_device.py.
   Added to start_stop_application.py, read_variable.py,
   write_variable.py, read_running_version_online.py.

2. _MCP_PROJECT_VERSION GVL emitted as plain VAR_GLOBAL, not CONSTANT

   CODESYS inlines VAR_GLOBAL CONSTANT scalars at compile time and
   strips them from the online symbol table. The whole point of
   _MCP_PROJECT_VERSION.sVersion is to be readable live from the
   running PLC, so CONSTANT was the wrong storage class.
   read_running_version_online failed against EVERY project bumped via
   the old template -- 'Invalid expression' on the runtime read.

   Dropped CONSTANT from VERSION_GVL_DECLARATION_TEMPLATE in
   bump_project_version.py. Existing projects auto-migrate on the next
   bump because maintain_version_gvl()'s existing-GVL branch overwrites
   textual_declaration with the (now non-CONSTANT) template. The string
   is still effectively read-only at runtime -- only the bump tool
   updates it.

   read_running_version_online.py also got a more precise error message
   that explicitly fingerprints the 'Invalid expression' failure mode
   and points at the CONSTANT root cause. Useful for any user landing
   on a project that pre-dates this fix.

Verified end-to-end against local CODESYS Control Win V3 (PLATEA, port
11740) on MCPTest2 v1.3.4.0:
- connect_to_device, get_application_state, download_to_device,
  start_stop_application (both directions), read_variable
  (PLC_PRG.watchdog1 = 225 ticking), write_variable (200 -> 204 in 4s
  proves write took), disconnect_from_device: all 7 PASS.
- read_running_version_online failure reproduced (CONSTANT inlined),
  fix landed -- next bump on MCPTest2 will validate.

37/37 unit/integration tests green. TEST_OVERVIEW.md updated with the
v5 device sweep, with the headless-mode deep-dive, and with the
broken-by-design notes on read_running_version_online.
2026-04-26 19:54:13 +02:00
Karstein Kvistad
b42e10411f fix(bump_project_version): cross-check pi.version against GVL, take the max
Recurring "GVL misread" bug in release_project_version: when
Project Information.Version drifts BEHIND the runtime-anchor GVL
(_MCP_PROJECT_VERSION.sVersion), the bump used the stale pi.version
as the resume point and silently regressed the version, often
colliding with an existing v* tag.

Observed twice on the MCPTest2 sandbox:

  1. v1.0.4.0 (2026-04-26): bump from on-disk 1.2.0.0 read pi.version
     as 1.0.3.0 -> revision -> 1.0.4.0. Tag deleted; recovered as
     v1.2.1.0 via manual finish script.

  2. v1.1.0.0 collision (2026-04-26): bump from on-disk 1.2.1.0 read
     pi.version as 1.0.0.0 -> minor -> 1.1.0.0. Tag already existed,
     git tag step failed, release pipeline aborted. Recovered by
     two manual minor bumps (1.1.0.0 -> 1.2.0.0 -> 1.3.0.0) and an
     amended commit, released as v1.3.0.0.

Root cause: the MCPTest2 v1.2.0.0 and v1.2.1.0 releases were
finished by external (non-MCP) Node scripts that updated the GVL
via inject-once but never wrote pi.version back through the
bump_project_version pathway. So pi.version stayed pinned at
whatever value the LAST true bump_project_version run left it at
(in MCPTest2's case, ~1.0.0.0), while the GVL kept moving forward.

Fix: in the pi-present branch, read both pi.version and the GVL
sVersion, parse both as 4-tuples, and take the max as the resume
point. The max is always safe: both sides only ever move forward
in the normal case, so the higher of the two is by construction
the true latest version. When a drift is detected (pi behind GVL),
emit a WARNING and self-heal pi.version forward to the GVL value
before the bump so the warning doesn't recur on the next call.

The pi-missing branch is unchanged (still falls back to GVL).

Documented inline in the function with the regression scenario for
future maintainers. No new test (the affected logic runs inside
CODESYS's IronPython and doesn't have a unit-test scaffold here);
the inline comment + this commit message are the regression record.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 16:19:27 +02:00
Karstein Phobic Nyvold Kvistad
5cbd540fde bump_project_version: resume from GVL when Project Information missing
Previously, when a project had no Project Information node (e.g. one
created from the Standard template via create_project), the bump
flow read pi.version as None, the seed-check fired, and every call
re-seeded to 1.0.0.0 -- subsequent revision/minor/major bumps were
no-ops because the script never saw the actual current version.

Surfaced on MCPTest2 today: bumping revision after editing PLC_PRG
returned '1.0.0.0' instead of '1.0.1.0' because the seed kept firing.

Fix: when Project Information is missing, fall back to reading the
existing _MCP_PROJECT_VERSION.sVersion via the textual_declaration
of the GVL we ourselves maintain. So the source-of-truth chain is:

  pi.version (when Project Information exists)
    -> falls back to GVL.sVersion (when Project Information missing
                                    but the GVL has been written
                                    by a prior bump)
    -> falls back to seed at 1.0.0.0 (true first-run, no GVL yet)

Implementation: read_version_from_gvl(primary_project) walks the
active Application's children for the named GVL and parses the
sVersion := '...' literal out of its textual_declaration with a
4-part regex. Returns None if the GVL doesn't exist OR its decl
doesn't match the expected shape; caller treats None as "no prior
version, seed". Soft-fails on any access exception (the bump is
the primary outcome, this is just resume-from-state).

This is the kind of "every arising problem fixed at the fork, not
worked around in one-offs" hygiene the user called out.
2026-04-26 00:53:10 +02:00
Karstein Phobic Nyvold Kvistad
b07c24559e bump_project_version: handle projects with no Project Information node
Standard-template projects (those created via create_project) often
don't have a Project Information node at all -- it's added lazily by
the IDE when the user opens Project menu -> Project Information for
the first time. Surfaced when running auto-bump against MCPTest2
(copied from MCPTest, which was created via create_project).

Two changes:

1. Use the documented is_project_info marker (ScriptProjectInfoMarker
   per the SP22 stub Stubs/scriptengine/ScriptObject.pyi) to find the
   node, instead of name-matching 'Project Information'. Also robust
   against localised IDE display names ('Projektinformation' in DE).
   Walks up to depth 4 from the root.

2. If still no node found, log a WARNING and SKIP the metadata write
   (Project Information.Version), but continue with the GVL
   maintenance. The GVL is the runtime source-of-truth anyway -- the
   running PLC reads _MCP_PROJECT_VERSION.sVersion, not the .project
   metadata. Subsequent bumps after the user adds Project Information
   manually (Project menu -> Project Information in the IDE) will
   pick up both sides.

Output line is also adjusted -- 'Project Information.Version: (skipped
-- node missing) -> 1.0.0.0' instead of pretending to have updated
something that doesn't exist.
2026-04-26 00:44:43 +02:00
Karstein Phobic Nyvold Kvistad
00d2dd8d96 bump_project_version: also maintain _MCP_PROJECT_VERSION GVL
Establishes the runtime-readable version anchor convention. Every
bump (manual or auto) now ALSO ensures the Application has a GVL
named '_MCP_PROJECT_VERSION' with:

  {attribute 'qualified_only'}
  VAR_GLOBAL CONSTANT
      sVersion : STRING := '<X.Y.Z.W>';
  END_VAR

Created on first bump; updated in place thereafter. Soft-fails if
the Application object can't be found or create_gvl() raises -- the
primary outcome (Project Information.Version updated and saved) has
already happened by the time GVL maintenance runs, so a GVL hiccup
is logged as a WARNING but doesn't fail the whole tool.

Why this matters:
  - Project Information.Version is metadata. The running PLC binary
    embeds it but exposing it at runtime requires the auto-generated
    Project_Info library helpers (GetVersion etc.), which not every
    project has wired up.
  - A plain VAR_GLOBAL CONSTANT in a known-name GVL is the simplest,
    most portable runtime anchor. Any IEC code can read it as
    `_MCP_PROJECT_VERSION.sVersion`. The future read_running_version_online
    tool will pull it via online connect + read_variable. The future
    SSH transport variant can pull it via libcmd-symbol-export or by
    grepping a debug log line that the project author can wire to
    write at startup.

qualified_only is set so the symbol can't accidentally shadow a
same-named local in user code.

The GVL convention will be exercised end-to-end on MCPTest running
on the local soft PLC (port 11740) once the read_running_version_online
tool ships -- that's the next ship in this sequence.
2026-04-26 00:31:26 +02:00
Karstein Phobic Nyvold Kvistad
75d77e2fb2 bump_project_version: seed at 1.0.0.0 on first run
Per user feedback. Previous behaviour treated 'no version yet' as
0.0.0.0 and bumped from there, so the very first call with level=build
produced 0.0.0.1 -- awkward as a canonical starting point. Most
projects start tracking at 1.0.0.0 the moment they turn on versioning.

New behaviour: if Project Information.version is None / empty / '0.0.0.0',
the tool seeds the value at 1.0.0.0 directly and ignores the level
argument for that one call. Subsequent calls bump per the level as
before.

Test cases:
  None     + level=build    -> 1.0.0.0  (seed)
  None     + level=major    -> 1.0.0.0  (seed)
  ''       + level=minor    -> 1.0.0.0  (seed)
  0.0.0.0  + level=revision -> 1.0.0.0  (seed)
  1.0.0.0  + level=build    -> 1.0.0.1
  1.0.0.0  + level=minor    -> 1.1.0.0
  1.2.3.4  + level=major    -> 2.0.0.0
2026-04-26 00:08:26 +02:00
Karstein Phobic Nyvold Kvistad
2ed3f17dc7 feat(bump_project_version): bump Project Information.Version one part
New MCP tool that increments one part of the 4-part
Project Information.Version field of the primary project, saves the
project, and reports the before/after.

Behaviour:
  - level=major   -> bump major,    reset minor/revision/build to 0
  - level=minor   -> bump minor,    reset revision/build to 0
  - level=revision-> bump revision, reset build to 0
  - level=build   -> bump build only

Convention follows the rest of CODESYS / 3S / WAGO library practice
(visible in any X33 library reference like 'WagoAppCanLayer2,
1.6.1.4 (WAGO)'):
  Major     -- incompatible API break (rename FB, change public
               signature, remove method).
  Minor     -- backward-compatible feature add.
  Revision  -- bug fix only, no API change.
  Build     -- internal / CI counter, often 0 for hand-released.

Implementation notes:
  - Project Information lives as the first child node of the project
    root. Its .version property is read/written directly; IronPython
    coerces strings like '1.2.3.4' to a System.Version on assignment,
    str(System.Version) gives the dotted form back. None / empty /
    unset is treated as '0.0.0.0'.
  - Verified live against X33 (MRCodesysX33_0021): set version to
    '1.0.0.0' from None, project.save() persisted it; reload via
    primary_project.get_children() found the same value. Probe done
    via the inject-once.mjs bridge against the live watcher in PID
    23056 before this commit landed.
  - project.save() is called after the bump so the new value sticks
    in the .project file. Soft-fails on save error (visible WARNING
    in DEBUG output but the bump itself is still reported as
    successful) so a save permission glitch doesn't mask the actual
    version change.

Used by the X33 GitLab project's "version in README header" workflow:
the version surfaces at the top of README.md (and at the top of the
library list once list_project_libraries gets enriched in a follow-up
commit).
2026-04-26 00:07:36 +02:00