* [PATCH 1/2] devtool: upgrade: write changelog metadata as JSON sidecar
@ 2026-09-18 12:01 daniel.turull
2026-09-18 12:01 ` [PATCH 2/2] devtool: upgrade: clean up changelog content for commit messages daniel.turull
0 siblings, 1 reply; 2+ messages in thread
From: daniel.turull @ 2026-09-18 12:01 UTC (permalink / raw)
To: openembedded-core; +Cc: Daniel Turull
From: Daniel Turull <daniel.turull@ericsson.com>
Add changelogs/<pn>.json alongside the existing changelogs/<pn>.txt,
to include metadata. This makes the upgrade/changelog metadata
machine-readable without having to parse it out of the .txt file.
The .txt file no longer has the 'Changelog for ...'/'Source: ...'
header lines, now containing just the changelog body; that
information moved to the JSON file instead. devtool reset now cleans
up both files.
AI-Generated: Uses Kiro with Claude-sonnet-5
Signed-off-by: Daniel Turull <daniel.turull@ericsson.com>
---
.../python3-guessing-game_git.bb.changelog | 2 --
.../devtool-upgrade-test1_1.5.3.bb.changelog | 3 ---
.../devtool-upgrade-test2_git.bb.changelog | 2 --
.../devtool-upgrade-test3_1.5.3.bb.changelog | 3 ---
.../devtool-upgrade-test4_1.5.3.bb.changelog | 3 ---
.../devtool-upgrade-test5_git.bb.changelog | 2 --
meta/lib/oeqa/selftest/cases/devtool.py | 17 +++++++++++++--
scripts/lib/devtool/standard.py | 9 +++++---
scripts/lib/devtool/upgrade.py | 21 +++++++++++++++----
9 files changed, 38 insertions(+), 24 deletions(-)
diff --git a/meta-selftest/recipes-devtools/python/python3-guessing-game_git.bb.changelog b/meta-selftest/recipes-devtools/python/python3-guessing-game_git.bb.changelog
index 571220b2a4..ef0052160b 100644
--- a/meta-selftest/recipes-devtools/python/python3-guessing-game_git.bb.changelog
+++ b/meta-selftest/recipes-devtools/python/python3-guessing-game_git.bb.changelog
@@ -1,3 +1 @@
-Changelog for python3-guessing-game: 0.1.0 -> 0.2.0
-
40cf004 Sync with maturin tutorial source
diff --git a/meta-selftest/recipes-test/devtool/devtool-upgrade-test1_1.5.3.bb.changelog b/meta-selftest/recipes-test/devtool/devtool-upgrade-test1_1.5.3.bb.changelog
index a37f796a0d..15d7baeeb4 100644
--- a/meta-selftest/recipes-test/devtool/devtool-upgrade-test1_1.5.3.bb.changelog
+++ b/meta-selftest/recipes-test/devtool/devtool-upgrade-test1_1.5.3.bb.changelog
@@ -1,6 +1,3 @@
-Changelog for devtool-upgrade-test1: 1.5.3 -> 1.6.0
-Source: doc/NEWS
-
1.6.0 - 15 March 2015
- fix lstat64 support when unavailable - separate patches supplied by
Ganael Laplanche and Peter Korsgaard
diff --git a/meta-selftest/recipes-test/devtool/devtool-upgrade-test2_git.bb.changelog b/meta-selftest/recipes-test/devtool/devtool-upgrade-test2_git.bb.changelog
index b7f0019d56..ac133c483f 100644
--- a/meta-selftest/recipes-test/devtool/devtool-upgrade-test2_git.bb.changelog
+++ b/meta-selftest/recipes-test/devtool/devtool-upgrade-test2_git.bb.changelog
@@ -1,3 +1 @@
-Changelog for devtool-upgrade-test2: 0.1+git -> 0.1+git
-
6cc6077 dbus-wait.c: Fix typo
diff --git a/meta-selftest/recipes-test/devtool/devtool-upgrade-test3_1.5.3.bb.changelog b/meta-selftest/recipes-test/devtool/devtool-upgrade-test3_1.5.3.bb.changelog
index aaab809736..15d7baeeb4 100644
--- a/meta-selftest/recipes-test/devtool/devtool-upgrade-test3_1.5.3.bb.changelog
+++ b/meta-selftest/recipes-test/devtool/devtool-upgrade-test3_1.5.3.bb.changelog
@@ -1,6 +1,3 @@
-Changelog for devtool-upgrade-test3: 1.5.3 -> 1.6.0
-Source: doc/NEWS
-
1.6.0 - 15 March 2015
- fix lstat64 support when unavailable - separate patches supplied by
Ganael Laplanche and Peter Korsgaard
diff --git a/meta-selftest/recipes-test/devtool/devtool-upgrade-test4_1.5.3.bb.changelog b/meta-selftest/recipes-test/devtool/devtool-upgrade-test4_1.5.3.bb.changelog
index 0ce6547c77..15d7baeeb4 100644
--- a/meta-selftest/recipes-test/devtool/devtool-upgrade-test4_1.5.3.bb.changelog
+++ b/meta-selftest/recipes-test/devtool/devtool-upgrade-test4_1.5.3.bb.changelog
@@ -1,6 +1,3 @@
-Changelog for devtool-upgrade-test4: 1.5.3 -> 1.6.0
-Source: doc/NEWS
-
1.6.0 - 15 March 2015
- fix lstat64 support when unavailable - separate patches supplied by
Ganael Laplanche and Peter Korsgaard
diff --git a/meta-selftest/recipes-test/devtool/devtool-upgrade-test5_git.bb.changelog b/meta-selftest/recipes-test/devtool/devtool-upgrade-test5_git.bb.changelog
index 7c5ecf3505..8077da3e21 100644
--- a/meta-selftest/recipes-test/devtool/devtool-upgrade-test5_git.bb.changelog
+++ b/meta-selftest/recipes-test/devtool/devtool-upgrade-test5_git.bb.changelog
@@ -1,3 +1 @@
-Changelog for devtool-upgrade-test5: 0.1+git -> 0.1+git
-
0a60d6a Add dummy commit on tip for testing
diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py
index 84e020cd3d..dda59a3459 100644
--- a/meta/lib/oeqa/selftest/cases/devtool.py
+++ b/meta/lib/oeqa/selftest/cases/devtool.py
@@ -2337,21 +2337,33 @@ class DevtoolUpgradeTests(DevtoolBase):
except:
self.skip("Git user.name and user.email must be set")
- def _check_changelog(self, recipe, oldrecipefile):
+ def _check_changelog(self, recipe, oldrecipefile, old_ver=None, new_ver=None):
"""Compare extracted changelog against reference data."""
changelog_ref = oldrecipefile + '.changelog'
self.assertExists(changelog_ref, 'Changelog reference file must exist for %s' % recipe)
changelog_file = os.path.join(self.workspacedir, 'changelogs', '%s.txt' % recipe)
+ metadata_file = os.path.join(self.workspacedir, 'changelogs', '%s.json' % recipe)
with open(changelog_ref, 'r') as f:
expected = f.read()
if not expected:
self.assertNotExists(changelog_file,
'Changelog file should not exist when reference is empty')
+ self.assertNotExists(metadata_file,
+ 'Changelog metadata file should not exist when reference is empty')
else:
self.assertExists(changelog_file, 'Changelog file should exist after upgrade')
with open(changelog_file, 'r') as f:
actual = f.read()
self.assertEqual(expected, actual)
+ self.assertExists(metadata_file, 'Changelog metadata file should exist after upgrade')
+ with open(metadata_file, 'r') as f:
+ metadata = json.load(f)
+ self.assertEqual(metadata.get('package'), recipe)
+ if old_ver:
+ self.assertEqual(metadata.get('old_version'), old_ver)
+ if new_ver:
+ self.assertEqual(metadata.get('new_version'), new_ver)
+ self.assertEqual(metadata.get('changelog_file'), '%s.txt' % recipe)
def test_devtool_upgrade(self):
# Check preconditions
@@ -2364,6 +2376,7 @@ class DevtoolUpgradeTests(DevtoolBase):
self.assertIn(param, result.output)
# For the moment, we are using a real recipe.
recipe = 'devtool-upgrade-test1'
+ old_version = '1.5.3'
version = '1.6.0'
oldrecipefile = get_bb_var('FILE', recipe)
tempdir = tempfile.mkdtemp(prefix='devtoolqa')
@@ -2392,7 +2405,7 @@ class DevtoolUpgradeTests(DevtoolBase):
newlines = f.readlines()
self.assertEqual(desiredlines, newlines)
# Check changelog
- self._check_changelog(recipe, oldrecipefile)
+ self._check_changelog(recipe, oldrecipefile, old_version, version)
# Check devtool reset recipe
result = runCmd('devtool reset %s -n' % recipe)
result = runCmd('devtool status')
diff --git a/scripts/lib/devtool/standard.py b/scripts/lib/devtool/standard.py
index ac9e7cd61a..0d933e68b1 100644
--- a/scripts/lib/devtool/standard.py
+++ b/scripts/lib/devtool/standard.py
@@ -2074,11 +2074,14 @@ def _reset(recipes, no_clean, remove_work, config, basepath, workspace):
# Clean up changelog if present
changelog_file = os.path.join(config.workspace_path, 'changelogs', '%s.txt' % pn)
+ metadata_file = os.path.join(config.workspace_path, 'changelogs', '%s.json' % pn)
if os.path.exists(changelog_file):
os.remove(changelog_file)
- changelog_dir = os.path.dirname(changelog_file)
- if not os.listdir(changelog_dir):
- os.rmdir(changelog_dir)
+ if os.path.exists(metadata_file):
+ os.remove(metadata_file)
+ changelog_dir = os.path.join(config.workspace_path, 'changelogs')
+ if os.path.exists(changelog_dir) and not os.listdir(changelog_dir):
+ os.rmdir(changelog_dir)
def reset(args, config, basepath, workspace):
"""Entry point for the devtool 'reset' subcommand"""
diff --git a/scripts/lib/devtool/upgrade.py b/scripts/lib/devtool/upgrade.py
index 58165541f6..cdc85fe7ae 100644
--- a/scripts/lib/devtool/upgrade.py
+++ b/scripts/lib/devtool/upgrade.py
@@ -9,6 +9,7 @@
import os
import sys
import re
+import json
import shlex
import shutil
import subprocess
@@ -730,13 +731,23 @@ def _extract_changelog(srctree, pn, old_ver, new_ver, old_tag, new_tag, workspac
bb.utils.mkdirhier(changelog_dir)
changelog_path = os.path.join(changelog_dir, '%s.txt' % pn)
with open(changelog_path, 'w') as f:
- f.write('Changelog for %s: %s -> %s\n' % (pn, old_ver, new_ver))
- if changelog_fname:
- f.write('Source: %s\n' % changelog_fname)
- f.write('\n')
f.write(changelog_content)
f.write('\n')
+ # Metadata sidecar file: keeps upgrade/changelog details machine-readable
+ # while the changelog text itself stays in the .txt file.
+ metadata = {
+ 'package': pn,
+ 'old_version': old_ver,
+ 'new_version': new_ver,
+ 'changelog_file': os.path.basename(changelog_path),
+ 'changelog_source': changelog_fname.split(', ') if changelog_fname else None,
+ }
+ metadata_path = os.path.join(changelog_dir, '%s.json' % pn)
+ with open(metadata_path, 'w') as f:
+ json.dump(metadata, f, indent=4)
+ f.write('\n')
+
return changelog_path
def upgrade(args, config, basepath, workspace):
@@ -831,6 +842,8 @@ def upgrade(args, config, basepath, workspace):
config.workspace_path, is_git)
if changelog_file:
logger.info('Changelog extracted to %s' % changelog_file)
+ metadata_file = os.path.join(os.path.dirname(changelog_file), '%s.json' % pn)
+ logger.info('Changelog metadata written to %s' % metadata_file)
if license_diff:
logger.info('License checksums have been updated in the new recipe; please refer to it for the difference between the old and the new license texts.')
^ permalink raw reply related [flat|nested] 2+ messages in thread* [PATCH 2/2] devtool: upgrade: clean up changelog content for commit messages
2026-09-18 12:01 [PATCH 1/2] devtool: upgrade: write changelog metadata as JSON sidecar daniel.turull
@ 2026-09-18 12:01 ` daniel.turull
0 siblings, 0 replies; 2+ messages in thread
From: daniel.turull @ 2026-09-18 12:01 UTC (permalink / raw)
To: openembedded-core; +Cc: Daniel Turull
From: Daniel Turull <daniel.turull@ericsson.com>
Strip noise that has no value in a commit message:
- GitHub PR references: trailing "(#NNN)" and full pull/issue URLs.
- Commit hash prefixes from "git log --oneline"-style content,
including the git-log fallback used when no changelog file exists.
- GNU ChangeLog noise: "YYYY-MM-DD Name <email>" entry headers, and
"* file (func):" / "+ commit <hash>" bullets plus their wrapped
continuation lines, down to the next blank line, keeping only the
human-readable summary of each entry. Remaining tabs are stripped.
- NEWS-style "Changes in X.Y.Z, DATE" release headers and their "==="
underline (libportal/glib-family projects).
Lines exceeding 80 chars are wrapped, preserving existing indentation.
AI-Generated: Uses Kiro with Claude Sonnet 5
Signed-off-by: Daniel Turull <daniel.turull@ericsson.com>
---
.../python3-guessing-game_git.bb.changelog | 2 +-
.../devtool-upgrade-test2_git.bb.changelog | 2 +-
.../devtool-upgrade-test5_git.bb.changelog | 2 +-
scripts/lib/devtool/upgrade.py | 63 ++++++++++++++++++-
4 files changed, 65 insertions(+), 4 deletions(-)
diff --git a/meta-selftest/recipes-devtools/python/python3-guessing-game_git.bb.changelog b/meta-selftest/recipes-devtools/python/python3-guessing-game_git.bb.changelog
index ef0052160b..6c7bf9f713 100644
--- a/meta-selftest/recipes-devtools/python/python3-guessing-game_git.bb.changelog
+++ b/meta-selftest/recipes-devtools/python/python3-guessing-game_git.bb.changelog
@@ -1 +1 @@
-40cf004 Sync with maturin tutorial source
+Sync with maturin tutorial source
diff --git a/meta-selftest/recipes-test/devtool/devtool-upgrade-test2_git.bb.changelog b/meta-selftest/recipes-test/devtool/devtool-upgrade-test2_git.bb.changelog
index ac133c483f..4699b1e89e 100644
--- a/meta-selftest/recipes-test/devtool/devtool-upgrade-test2_git.bb.changelog
+++ b/meta-selftest/recipes-test/devtool/devtool-upgrade-test2_git.bb.changelog
@@ -1 +1 @@
-6cc6077 dbus-wait.c: Fix typo
+dbus-wait.c: Fix typo
diff --git a/meta-selftest/recipes-test/devtool/devtool-upgrade-test5_git.bb.changelog b/meta-selftest/recipes-test/devtool/devtool-upgrade-test5_git.bb.changelog
index 8077da3e21..3196cc13bd 100644
--- a/meta-selftest/recipes-test/devtool/devtool-upgrade-test5_git.bb.changelog
+++ b/meta-selftest/recipes-test/devtool/devtool-upgrade-test5_git.bb.changelog
@@ -1 +1 @@
-0a60d6a Add dummy commit on tip for testing
+Add dummy commit on tip for testing
diff --git a/scripts/lib/devtool/upgrade.py b/scripts/lib/devtool/upgrade.py
index cdc85fe7ae..ba7348664f 100644
--- a/scripts/lib/devtool/upgrade.py
+++ b/scripts/lib/devtool/upgrade.py
@@ -14,6 +14,7 @@ import shlex
import shutil
import subprocess
import tempfile
+import textwrap
import logging
import argparse
import scriptutils
@@ -634,6 +635,66 @@ def _diff_git_log_changelog(old_content, new_content):
return '\n'.join(subjects) if subjects else None
+# GitHub PR references: trailing (#123) at end of line, or full URLs
+_GITHUB_PR_RE = re.compile(r'\s*\(#[0-9]+\)\s*$|\s*https?://github\.com/[^/]+/[^/]+/(pull|issues)/[0-9]+\s*')
+# Commit hash prefixes from git log --oneline: "abc1234 "
+_COMMIT_HASH_RE = re.compile(r'^[0-9a-f]{7,40}\s+')
+# A GNU ChangeLog entry: starts at either an entry header
+# ("2026-08-24 Werner Koch <wk@gnupg.org>") or a technical bullet
+# ("* file.c (func): text" or "+ commit <hash>"), and extends through all
+# wrapped continuation lines up to (but not including) the next blank line.
+_GNU_CHANGELOG_ENTRY_RE = re.compile(
+ r'^(?:\d{4}-\d{2}-\d{2}\s+.+<.+@.+>|\s*(?:\*\s|\+ commit [0-9a-f]{7,40}).*)'
+ r'(?:\n(?!\s*$).*)*\n?',
+ re.MULTILINE)
+# NEWS-style release header + underline (glib/gtk family):
+# "Changes in 0.11.0, 2026-09-12\n===============================\n"
+_NEWS_RELEASE_HEADER_RE = re.compile(r'^Changes in .+,\s*\d{4}-\d{2}-\d{2}\s*\n=+\s*$', re.MULTILINE)
+
+def _join_single_line_paragraphs(text):
+ """Drop the blank line between two adjacent one-line paragraphs (each
+ surrounded by blank lines), so lines left behind by GNU ChangeLog entry
+ stripping read as one entry per line instead of double-spaced."""
+ paragraphs = text.split('\n\n')
+ out = [paragraphs[0]]
+ prev_is_single_line = '\n' not in paragraphs[0]
+ for para in paragraphs[1:]:
+ this_is_single_line = '\n' not in para
+ if prev_is_single_line and this_is_single_line:
+ out[-1] += '\n' + para
+ else:
+ out.append(para)
+ prev_is_single_line = this_is_single_line
+ return '\n\n'.join(out)
+
+
+def _cleanup_changelog(content):
+ """Strip GitHub PR refs, commit hashes, GNU ChangeLog entries (headers
+ and file/function bullets, plus their wrapped continuation lines) and
+ NEWS-style 'Changes in X.Y.Z, DATE' release headers with their
+ underline. Wrap lines over 80 chars, preserving indentation."""
+ content = _NEWS_RELEASE_HEADER_RE.sub('', content)
+ content = _GNU_CHANGELOG_ENTRY_RE.sub('', content)
+ out = []
+ for line in content.splitlines():
+ line = _GITHUB_PR_RE.sub(' ', line)
+ line = _COMMIT_HASH_RE.sub('', line)
+ line = line.replace('\t', '').rstrip()
+ stripped = line.lstrip()
+ indent = line[:len(line) - len(stripped)]
+ if len(line) > 80:
+ if stripped.startswith('- '):
+ indent += ' '
+ line = textwrap.fill(line, width=80, subsequent_indent=indent)
+ out.append(line)
+ text = '\n'.join(out)
+ # Entries removed above can leave 3+ blank newlines where a blank-line
+ # separator butted up against a removed entry's own blank line;
+ # collapse any such run down to a single blank line (2 newlines).
+ text = re.sub(r'\n{3,}', '\n\n', text).strip()
+ return _join_single_line_paragraphs(text)
+
+
def _extract_changelog(srctree, pn, old_ver, new_ver, old_tag, new_tag, workspace_path, is_git_source):
"""Extract changelog between old and new version using devtool git tags."""
changelog_content = None
@@ -723,7 +784,7 @@ def _extract_changelog(srctree, pn, old_ver, new_ver, old_tag, new_tag, workspac
changelog_content = ''.join(filtered)
# Clean up content for readability and commit message use
- changelog_content = re.sub(r'\n{3,}', '\n\n', changelog_content).strip()
+ changelog_content = _cleanup_changelog(changelog_content)
if not changelog_content:
return None
^ permalink raw reply related [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-09-18 12:02 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-18 12:01 [PATCH 1/2] devtool: upgrade: write changelog metadata as JSON sidecar daniel.turull
2026-09-18 12:01 ` [PATCH 2/2] devtool: upgrade: clean up changelog content for commit messages daniel.turull
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox