0
0
Fork 0
Codesys-MCP-SP21-plus/src/scripts/bump_project_version.py
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

118 lines
4.5 KiB
Python

import sys, scriptengine as script_engine, traceback
# Bumps one part of the 4-part Project Information.version field of the
# primary project. Convention (per CODESYS / 3S / WAGO library practice):
#
# Major -- bump on incompatible API break.
# Minor -- bump on backward-compatible feature add.
# Revision -- bump on bug fix only (no API change).
# 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).
#
# The Version field is read/written as a property on the "Project
# Information" node (first child of the primary project). IronPython
# coerces strings like "1.2.3.4" to System.Version automatically; we
# stringify on the read side because str(System.Version) gives
# the dotted form back. None / empty are treated as "0.0.0.0".
LEVEL = "{LEVEL}" # major | minor | revision | build
VALID_LEVELS = ('major', 'minor', 'revision', 'build')
def parse_version(v):
"""Parse a version-like value into a 4-tuple of ints, defaulting missing
parts to 0. Accepts None, '', '1.2', '1.2.3', '1.2.3.4', or a
System.Version. Raises ValueError on anything that can't be parsed."""
if v is None:
return (0, 0, 0, 0)
s = str(v).strip()
if not s or s == 'None':
return (0, 0, 0, 0)
parts = s.split('.')
if len(parts) > 4:
raise ValueError("version '%s' has more than 4 parts" % s)
nums = []
for p in parts:
try:
nums.append(int(p))
except ValueError:
raise ValueError("version '%s' has non-integer part '%s'" % (s, p))
while len(nums) < 4:
nums.append(0)
return tuple(nums)
def bump(parts, level):
major, minor, revision, build = parts
if level == 'major':
return (major + 1, 0, 0, 0)
if level == 'minor':
return (major, minor + 1, 0, 0)
if level == 'revision':
return (major, minor, revision + 1, 0)
if level == 'build':
return (major, minor, revision, build + 1)
raise ValueError("unknown bump level '%s' (must be one of %s)" % (level, ', '.join(VALID_LEVELS)))
try:
if LEVEL not in VALID_LEVELS:
raise ValueError("level must be one of %s, got '%s'" % (', '.join(VALID_LEVELS), LEVEL))
primary_project = ensure_project_open(PROJECT_FILE_PATH)
pi = None
for child in primary_project.get_children(False):
try:
if child.get_name() == 'Project Information':
pi = child
break
except Exception:
pass
if pi is None:
raise RuntimeError(
"Project Information node not found at the project root. "
"Every CODESYS project should have one as its first child; "
"if missing, recreate it via Project menu -> Project Information.")
before_raw = pi.version
before_str = str(before_raw) if before_raw is not None else None
# First-run convention: if no version is set yet, seed at 1.0.0.0 instead
# of treating "no version" as 0.0.0.0 + bump (which would give 0.0.0.1
# for level=build, awkward for a first canonical version). Most projects
# start tracking at 1.0.0.0 when they first turn on versioning, and the
# level argument is moot for the seed step.
seed_check = parse_version(before_raw)
if seed_check == (0, 0, 0, 0) and (before_raw is None or str(before_raw).strip() in ('', '0.0.0.0', 'None')):
after_parts = (1, 0, 0, 0)
after_str = '1.0.0.0'
print("DEBUG: bump_project_version: no prior version -- seeding to 1.0.0.0 (level=%s ignored on first run)" % LEVEL)
else:
before_parts = seed_check
after_parts = bump(before_parts, LEVEL)
after_str = '%d.%d.%d.%d' % after_parts
print("DEBUG: bump_project_version: level=%s before=%s -> after=%s" % (
LEVEL, before_str, after_str))
pi.version = after_str
try:
primary_project.save()
print("DEBUG: project.save() succeeded after version bump.")
except Exception as save_e:
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))
print("SCRIPT_SUCCESS: bump_project_version complete.")
sys.exit(0)
except Exception as e:
detailed = traceback.format_exc()
msg = "Error in bump_project_version for project '%s': %s\n%s" % (
PROJECT_FILE_PATH, e, detailed)
print(msg)
print("SCRIPT_ERROR: %s" % msg)
sys.exit(1)