From 123921169feb6ecb526ad8779f99d22d74be12b0 Mon Sep 17 00:00:00 2001 From: Tim Meusel Date: Fri, 12 Jun 2026 10:03:07 +0200 Subject: [PATCH] Add rake task for component diff between versions This adds a new rake task. It allows us to output the component updates between two provided versions. This is useful for consuming project like openbolt and openvox. They usually skip a few puppet-runtime releases. The new task allows those projects to diff their previous to new puppet-runtime version and get a component overview. example for the openbolt 5.5.0->5.6.0 release: ``` bundle exec rake release:changelog_components:diff[2026.04.20.1,2026.06.10.1,false] **Component Changes:** | Component | Old Version | New Version | |-----------|-------------|-------------| | curl | 8.19.0 | 8.20.0 | | libxml2 | 2.15.2 | 2.15.3 | | openssl-3.0 | 3.0.20 | 3.0.21 | | openssl-3.5 | | 3.5.7 | | ruby-4.0 | | 4.0.5 | | ruby-selinux | 3.9 | 3.10 | | ruby-shadow-patched | | bc7752a9ddbde06c1418734d003a9607bafcc6df | | rubygem-aws-partitions | 1.1240.0 | 1.1259.0 | | rubygem-aws-sdk-core | 3.245.0 | 3.251.0 | | rubygem-aws-sdk-ec2 | 1.612.0 | 1.622.0 | | rubygem-excon | 1.4.2 | 1.5.0 | | rubygem-faraday | 2.14.1 | 2.14.2 | | rubygem-faraday-net_http | 3.4.2 | 3.4.4 | | rubygem-openfact | 5.6.0 | 5.6.1 | | rubygem-openvox | 8.26.2 | 8.28.0 | | rubygem-yard | 0.9.43 | 0.9.44 | ``` And latest puppet-runtime releases: ``` $ bundle exec rake release:changelog_components:diff[2026.06.09.1,2026.06.10.1,false] **Component Changes:** | Component | Old Version | New Version | |-----------|-------------|-------------| | rubygem-aws-partitions | 1.1258.0 | 1.1259.0 | | rubygem-aws-sdk-ec2 | 1.621.0 | 1.622.0 | | rubygem-openvox | 8.27.0 | 8.28.0 | ``` We can also get a list of added components in various projects: ``` **Component Changes:** | Component | Old Version | New Version | |-----------|-------------|-------------| | curl | 8.19.0 | 8.20.0 | | libxml2 | 2.15.2 | 2.15.3 | | openssl-3.0 | 3.0.20 | 3.0.21 | | openssl-3.5 | | 3.5.7 | | ruby-4.0 | | 4.0.5 | | ruby-selinux | 3.9 | 3.10 | | ruby-shadow-patched | | bc7752a9ddbde06c1418734d003a9607bafcc6df | | rubygem-aws-partitions | 1.1240.0 | 1.1259.0 | | rubygem-aws-sdk-core | 3.245.0 | 3.251.0 | | rubygem-aws-sdk-ec2 | 1.612.0 | 1.622.0 | | rubygem-excon | 1.4.2 | 1.5.0 | | rubygem-faraday | 2.14.1 | 2.14.2 | | rubygem-faraday-net_http | 3.4.2 | 3.4.4 | | rubygem-openfact | 5.6.0 | 5.6.1 | | rubygem-openvox | 8.26.2 | 8.28.0 | | rubygem-yard | 0.9.43 | 0.9.44 | **Project component additions:** - openssl-3.5: agent-runtime-main - ruby-4.0: agent-runtime-main - ruby-shadow-patched: agent-runtime-main ``` This is required for the puppet-runtime changelog itself, but not so much in the individual projects, so it's configureable. Signed-off-by: Tim Meusel --- tasks/vox.rake | 100 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 71 insertions(+), 29 deletions(-) diff --git a/tasks/vox.rake b/tasks/vox.rake index 39b0400a..d7a2083c 100644 --- a/tasks/vox.rake +++ b/tasks/vox.rake @@ -52,6 +52,33 @@ class Version end end +def build_component_change_lines(prev_data, new_data, include_project_component_additions: true) + header_lines = ["\n**Component Changes:**\n", "| Component | Old Version | New Version |\n", "|-----------|-------------|-------------|\n"] + component_lines = [] + new_components = [] + + new_data['components'].sort.each do |comp, ver| + prev_ver = prev_data['components'][comp] + next if prev_ver == ver + + new_components << comp if prev_ver.nil? + component_lines << "| #{comp} | #{prev_ver} | #{ver} |\n" + end + component_lines = header_lines + component_lines unless component_lines.empty? + + if include_project_component_additions && !new_components.empty? + component_lines << "\n**Project component additions:**\n" + new_components.each do |component| + projects = new_data['projects'] + .select { |_, platforms| platforms.values.any? { |components| components.key?(component) } } + .keys + component_lines << "- #{component}: #{projects.join(', ')}\n" + end + end + + component_lines +end + desc 'Set the full version of the project' task 'vox:version:bump:full' do puts 'This project use the current date as version number. No bump needed.' @@ -107,49 +134,64 @@ else end # rubocop:enable Rake/DuplicateTask -desc 'Inject component change information into changelog' -task 'release:changelog_components', ['tag'] do |_, args| - abort 'You must provide the tag that will be used for this release.' if args[:tag].nil? || args[:tag].empty? +desc 'Show component change information between two tags from component_info.json' +task 'release:changelog_components:diff', %i[old_tag new_tag include_project_component_additions] do |_, args| + abort 'You must provide old_tag and new_tag, e.g. rake "release:changelog_components:diff[2026.06.11.1,2026.06.12.1,true]"' if args[:old_tag].to_s.empty? || args[:new_tag].to_s.empty? - changelog = File.expand_path('../CHANGELOG.md', __dir__) data_file = File.expand_path('../component_info.json', __dir__) data = JSON.parse(File.read(data_file)) - abort "No component information found for tag #{args[:tag]} in #{data_file}" unless data.key?(args[:tag]) - abort "Data for tag #{args[:tag]} does not appear at the top of the file." unless data.keys.first == args[:tag] + abort "No component information found for old_tag #{args[:old_tag]} in #{data_file}" unless data.key?(args[:old_tag]) + abort "No component information found for new_tag #{args[:new_tag]} in #{data_file}" unless data.key?(args[:new_tag]) - prev_data = data.values[1] - new_data = data.values[0] + include_project_component_additions = !%w[0 false no off].include?(args[:include_project_component_additions].to_s.downcase) - header_lines = ["\n**Component Changes:**\n", "| Component | Old Version | New Version |\n", "|-----------|-------------|-------------|\n"] - component_lines = [] - new_components = [] - data[args[:tag]]['components'].sort.each do |comp, ver| - prev_ver = prev_data['components'][comp] - next if prev_ver == ver + component_lines = build_component_change_lines( + data[args[:old_tag]], + data[args[:new_tag]], + include_project_component_additions: include_project_component_additions + ) - new_components << comp if prev_ver.nil? - component_lines << "| #{comp} | #{prev_data['components'][comp]} | #{ver} |\n" + if component_lines.empty? + puts "No component changes detected between #{args[:old_tag]} and #{args[:new_tag]}" + else + puts component_lines.join end - component_lines = header_lines + component_lines unless component_lines.empty? +end - unless new_components.empty? - component_lines << "\n**Project component additions:**\n" - new_components.each do |component| - projects = new_data['projects'] - .select { |_, platforms| platforms.values.any? { |components| components.key?(component) } } - .keys - component_lines << "- #{component}: #{projects.join(', ')}\n" - end - end +desc 'Inject component change information into changelog' +task 'release:changelog_components', ['tag'] do |_, args| + changelog = File.expand_path('../CHANGELOG.md', __dir__) + data_file = File.expand_path('../component_info.json', __dir__) + data = JSON.parse(File.read(data_file)) + + tag = args[:tag].to_s.empty? ? data.keys.first : args[:tag] + abort "No component information found for tag #{tag} in #{data_file}" unless data.key?(tag) + tag_index = data.keys.index(tag) + prev_tag = data.keys[tag_index + 1] + abort "No previous tag data found in #{data_file} to diff against #{tag}" if prev_tag.nil? + + prev_data = data[prev_tag] + new_data = data[tag] + component_lines = build_component_change_lines(prev_data, new_data) if component_lines.empty? - puts "No component changes detected for tag #{args[:tag]}" + puts "No component changes detected for tag #{tag}" else content = File.read(changelog) - new_content = content.sub('**Merged pull requests:**', "#{component_lines.join}\n**Merged pull requests:**") + + section_regex = /^## \[#{Regexp.escape(tag)}\].*?(?=^## \[|\z)/m + section_match = content.match(section_regex) + abort "Could not find release section for tag #{tag} in #{changelog}" if section_match.nil? + + section = section_match[0] + abort "Release section for tag #{tag} does not contain a Full Changelog line" unless section.match?(/^\[Full Changelog\]\(/) + + new_section = section.sub(/^\[Full Changelog\]\([^\n]+\)\n/, "\\0\n#{component_lines.join}\n") + new_content = content.sub(section, new_section) + File.write(changelog, new_content) - puts "Injected component change information into #{changelog} for tag #{args[:tag]}" + puts "Injected component change information into #{changelog} for tag #{tag}" end end