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.
This commit is contained in:
parent
bd06fe76d7
commit
00d2dd8d96
2 changed files with 86 additions and 1 deletions
|
|
@ -21,6 +21,19 @@ LEVEL = "{LEVEL}" # major | minor | revision | build
|
|||
|
||||
VALID_LEVELS = ('major', 'minor', 'revision', 'build')
|
||||
|
||||
# Standard runtime-readable version anchor. Lives as a constant in a GVL
|
||||
# under Application so any IEC code can read it as
|
||||
# _MCP_PROJECT_VERSION.sVersion, and a future read_running_version_online
|
||||
# tool can pull it via online connect + read_variable. Kept as
|
||||
# qualified_only so it can't accidentally shadow a same-named local.
|
||||
VERSION_GVL_NAME = '_MCP_PROJECT_VERSION'
|
||||
VERSION_GVL_DECLARATION_TEMPLATE = (
|
||||
"{attribute 'qualified_only'}\n"
|
||||
"VAR_GLOBAL CONSTANT\n"
|
||||
" sVersion : STRING := '%s';\n"
|
||||
"END_VAR\n"
|
||||
)
|
||||
|
||||
|
||||
def parse_version(v):
|
||||
"""Parse a version-like value into a 4-tuple of ints, defaulting missing
|
||||
|
|
@ -45,6 +58,69 @@ def parse_version(v):
|
|||
return tuple(nums)
|
||||
|
||||
|
||||
def maintain_version_gvl(primary_project, version_str):
|
||||
"""Find or create the _MCP_PROJECT_VERSION GVL under the active
|
||||
Application, and set its declaration so the running PLC carries the
|
||||
project version as a constant string. Soft-fails on any error -- the
|
||||
primary outcome of the bump (Project Information.Version) has already
|
||||
succeeded by the time this is called, so a GVL creation failure is
|
||||
logged as a WARNING but does not fail the whole tool."""
|
||||
try:
|
||||
app = getattr(primary_project, 'active_application', None)
|
||||
except Exception:
|
||||
app = None
|
||||
if app is None:
|
||||
try:
|
||||
apps = primary_project.find('Application', True)
|
||||
if apps:
|
||||
app = apps[0]
|
||||
except Exception:
|
||||
pass
|
||||
if app is None:
|
||||
print("WARNING: no active Application found -- cannot maintain %s GVL" % VERSION_GVL_NAME)
|
||||
return False
|
||||
|
||||
decl = VERSION_GVL_DECLARATION_TEMPLATE % version_str
|
||||
|
||||
# Try to find existing GVL with this name
|
||||
existing = None
|
||||
try:
|
||||
for child in app.get_children(False):
|
||||
try:
|
||||
if child.get_name() == VERSION_GVL_NAME:
|
||||
existing = child
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
print("WARNING: walking Application children failed: %s" % e)
|
||||
|
||||
if existing is not None:
|
||||
try:
|
||||
existing.textual_declaration.replace(decl)
|
||||
print("DEBUG: updated %s -> sVersion := '%s'" % (VERSION_GVL_NAME, version_str))
|
||||
return True
|
||||
except Exception as e:
|
||||
print("WARNING: failed to update existing %s declaration: %s" % (VERSION_GVL_NAME, e))
|
||||
return False
|
||||
|
||||
# Create it
|
||||
if not hasattr(app, 'create_gvl'):
|
||||
print("WARNING: Application object doesn't expose create_gvl -- cannot create %s" % VERSION_GVL_NAME)
|
||||
return False
|
||||
try:
|
||||
new_gvl = app.create_gvl(name=VERSION_GVL_NAME)
|
||||
if new_gvl is None:
|
||||
print("WARNING: create_gvl returned None for %s" % VERSION_GVL_NAME)
|
||||
return False
|
||||
new_gvl.textual_declaration.replace(decl)
|
||||
print("DEBUG: created %s with sVersion := '%s'" % (VERSION_GVL_NAME, version_str))
|
||||
return True
|
||||
except Exception as e:
|
||||
print("WARNING: failed to create %s: %s" % (VERSION_GVL_NAME, e))
|
||||
return False
|
||||
|
||||
|
||||
def bump(parts, level):
|
||||
major, minor, revision, build = parts
|
||||
if level == 'major':
|
||||
|
|
@ -100,6 +176,11 @@ try:
|
|||
|
||||
pi.version = after_str
|
||||
|
||||
# Maintain the runtime-readable version anchor (_MCP_PROJECT_VERSION GVL)
|
||||
# so the running PLC carries the same string. Soft-fails so the primary
|
||||
# bump still reports success even if GVL creation hits an edge case.
|
||||
gvl_ok = maintain_version_gvl(primary_project, after_str)
|
||||
|
||||
try:
|
||||
primary_project.save()
|
||||
print("DEBUG: project.save() succeeded after version bump.")
|
||||
|
|
@ -107,6 +188,10 @@ try:
|
|||
print("WARNING: project.save() raised %s -- bump applied in memory but may not persist across IDE close." % save_e)
|
||||
|
||||
print("Project Information.Version: %s -> %s" % (before_str, after_str))
|
||||
if gvl_ok:
|
||||
print("Runtime anchor: %s.sVersion := '%s'" % (VERSION_GVL_NAME, after_str))
|
||||
else:
|
||||
print("Runtime anchor: %s NOT updated (see WARNING above)" % VERSION_GVL_NAME)
|
||||
print("SCRIPT_SUCCESS: bump_project_version complete.")
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -1487,7 +1487,7 @@ export async function startMcpServer(config: ServerConfig): Promise<void> {
|
|||
|
||||
s.tool(
|
||||
'bump_project_version',
|
||||
"Bumps one part of the 4-part Project Information.Version field of the primary project (Major.Minor.Revision.Build) and saves the project. Convention: major = incompatible API break (rename FB / change public signature / remove method); minor = backward-compatible feature add (new FB / GVL / method); revision = bug fix only; build = internal counter, often 0 for hand-released versions. Bumping a higher part resets all lower parts to 0 (e.g. bumping minor resets revision and build to 0). FIRST-RUN: if no version is set yet (None/empty/0.0.0.0), seeds at 1.0.0.0 regardless of level so a first-time bump gives a canonical starting point instead of 0.0.0.1. The Version is exposed at runtime via the Project Information library's GetVersion() helper, which IEC code can call to surface the running version. AUTO MODE: if level='auto', the tool diffs the project's mcp-mirror/ folder against the latest v* git tag and classifies the change (deletion/rename -> major; addition -> minor; modification -> revision; nothing or first-run -> build/seed). Requires the project's parent dir to be a git repo with at least one v* tag for full classification; otherwise falls back to 'build' (which seeds at 1.0.0.0 when the version is unset).",
|
||||
"Bumps one part of the 4-part Project Information.Version field of the primary project (Major.Minor.Revision.Build) and saves the project. Also maintains a `_MCP_PROJECT_VERSION` GVL under Application with the new version as `sVersion : STRING := '<X.Y.Z.W>'` so the running PLC carries the version at a known address (read it via the read_running_version_online tool). The GVL is created on first bump and updated in place thereafter. Convention: major = incompatible API break (rename FB / change public signature / remove method); minor = backward-compatible feature add (new FB / GVL / method); revision = bug fix only; build = internal counter, often 0 for hand-released versions. Bumping a higher part resets all lower parts to 0. FIRST-RUN: if no version is set yet (None/empty/0.0.0.0), seeds at 1.0.0.0 regardless of level so a first-time bump gives a canonical starting point instead of 0.0.0.1. AUTO MODE: if level='auto', the tool diffs the project's mcp-mirror/ folder against the latest v* git tag and classifies the change (deletion/rename -> major; addition -> minor; modification -> revision; no changes -> short-circuits with no bump; first-run -> seed at 1.0.0.0).",
|
||||
{
|
||||
projectFilePath: z.string().describe("Path to the project file."),
|
||||
level: z.enum(['major', 'minor', 'revision', 'build', 'auto']).describe("Which part of the 4-part version to bump. Major = incompatible API break. Minor = backward-compatible feature add. Revision = bug fix only. Build = internal / CI counter. AUTO classifies via git diff of mcp-mirror/ against the latest v* tag."),
|
||||
|
|
|
|||
Loading…
Reference in a new issue