mirror of
https://git.citron-emu.org/citron/emulator
synced 2026-02-01 07:13:33 +00:00
239 lines
9.7 KiB
Python
239 lines
9.7 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Generate release notes for Citron releases
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import subprocess
|
|
import json
|
|
from datetime import datetime
|
|
|
|
def run_git_command(cmd):
|
|
"""Run a git command and return its output"""
|
|
try:
|
|
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, check=True)
|
|
return result.stdout.strip()
|
|
except subprocess.CalledProcessError:
|
|
return None
|
|
|
|
def get_commits_since_last_tag():
|
|
"""Get commits since the last tag"""
|
|
try:
|
|
# Get the last tag
|
|
last_tag = run_git_command("git describe --tags --abbrev=0")
|
|
if last_tag:
|
|
# Get commits since last tag
|
|
commits = run_git_command(f"git log {last_tag}..HEAD --oneline --pretty=format:'%s'")
|
|
return commits.split('\n') if commits else []
|
|
else:
|
|
# No tags, get last 20 commits
|
|
commits = run_git_command("git log --oneline --max-count=20 --pretty=format:'%s'")
|
|
return commits.split('\n') if commits else []
|
|
except:
|
|
return []
|
|
|
|
def categorize_commits(commits):
|
|
"""Categorize commits by type"""
|
|
categories = {
|
|
'Features': [],
|
|
'Bug Fixes': [],
|
|
'Performance': [],
|
|
'UI/UX': [],
|
|
'Audio': [],
|
|
'Graphics': [],
|
|
'Input': [],
|
|
'Network': [],
|
|
'Build/CI': [],
|
|
'Documentation': [],
|
|
'Other': []
|
|
}
|
|
|
|
for commit in commits:
|
|
commit_lower = commit.lower()
|
|
|
|
# Categorize based on commit message content
|
|
if any(word in commit_lower for word in ['feat', 'feature', 'add', 'implement']):
|
|
categories['Features'].append(commit)
|
|
elif any(word in commit_lower for word in ['fix', 'bug', 'issue', 'crash', 'error']):
|
|
categories['Bug Fixes'].append(commit)
|
|
elif any(word in commit_lower for word in ['perf', 'optimize', 'speed', 'fast']):
|
|
categories['Performance'].append(commit)
|
|
elif any(word in commit_lower for word in ['ui', 'gui', 'interface', 'dialog', 'window']):
|
|
categories['UI/UX'].append(commit)
|
|
elif any(word in commit_lower for word in ['audio', 'sound', 'music']):
|
|
categories['Audio'].append(commit)
|
|
elif any(word in commit_lower for word in ['graphics', 'render', 'vulkan', 'opengl', 'gpu']):
|
|
categories['Graphics'].append(commit)
|
|
elif any(word in commit_lower for word in ['input', 'controller', 'keyboard', 'mouse']):
|
|
categories['Input'].append(commit)
|
|
elif any(word in commit_lower for word in ['network', 'multiplayer', 'online']):
|
|
categories['Network'].append(commit)
|
|
elif any(word in commit_lower for word in ['build', 'ci', 'cmake', 'docker']):
|
|
categories['Build/CI'].append(commit)
|
|
elif any(word in commit_lower for word in ['doc', 'readme', 'comment']):
|
|
categories['Documentation'].append(commit)
|
|
else:
|
|
categories['Other'].append(commit)
|
|
|
|
# Remove empty categories
|
|
return {k: v for k, v in categories.items() if v}
|
|
|
|
def generate_release_notes(version_info, output_format='markdown'):
|
|
"""Generate release notes"""
|
|
|
|
# Get version information
|
|
version_name = version_info['version_name']
|
|
git_commit_short = version_info['git_commit_short']
|
|
git_commit_full = version_info['git_commit_full']
|
|
git_branch = version_info['git_branch']
|
|
build_date = version_info['build_date']
|
|
build_time = version_info['build_time']
|
|
|
|
# Get project info from environment or defaults
|
|
project_url = os.environ.get('CI_PROJECT_URL', 'https://git.citron-emu.org/citron/emulator')
|
|
package_registry_url = os.environ.get('PACKAGE_REGISTRY_URL', f'{project_url}/-/packages')
|
|
package_dir = os.environ.get('PACKAGE_DIR', f'Citron-Canary/{version_name}')
|
|
|
|
# Get commits
|
|
commits = get_commits_since_last_tag()
|
|
categorized_commits = categorize_commits(commits)
|
|
|
|
if output_format == 'markdown':
|
|
return generate_markdown_release_notes(
|
|
version_name, git_commit_short, git_commit_full, git_branch,
|
|
build_date, build_time, project_url, package_registry_url,
|
|
package_dir, categorized_commits
|
|
)
|
|
elif output_format == 'json':
|
|
return generate_json_release_notes(
|
|
version_name, git_commit_short, git_commit_full, git_branch,
|
|
build_date, build_time, project_url, package_registry_url,
|
|
package_dir, categorized_commits
|
|
)
|
|
else:
|
|
raise ValueError(f"Unknown output format: {output_format}")
|
|
|
|
def generate_markdown_release_notes(version_name, git_commit_short, git_commit_full,
|
|
git_branch, build_date, build_time, project_url,
|
|
package_registry_url, package_dir, categorized_commits):
|
|
"""Generate markdown release notes"""
|
|
|
|
notes = []
|
|
notes.append(f"# Citron {version_name}")
|
|
notes.append("")
|
|
notes.append(f"**Build Date:** {build_date} {build_time} UTC ")
|
|
notes.append(f"**Commit:** [{git_commit_short}]({project_url}/-/commit/{git_commit_full}) ")
|
|
notes.append(f"**Branch:** `{git_branch}`")
|
|
notes.append("")
|
|
|
|
# Downloads section
|
|
notes.append("## Downloads")
|
|
notes.append("")
|
|
notes.append("### Linux (AppImage)")
|
|
notes.append(f"- **x86_64 (Generic):** [Citron-{version_name}-anylinux-x86_64.AppImage]({package_registry_url}/{package_dir}/Citron-{version_name}-anylinux-x86_64.AppImage)")
|
|
notes.append(f"- **x86_64 (SteamDeck):** [Citron-{version_name}-steamdeck-x86_64.AppImage]({package_registry_url}/{package_dir}/Citron-{version_name}-steamdeck-x86_64.AppImage)")
|
|
notes.append(f"- **x86_64 (Compatibility):** [Citron-{version_name}-compat-x86_64.AppImage]({package_registry_url}/{package_dir}/Citron-{version_name}-compat-x86_64.AppImage)")
|
|
notes.append("")
|
|
notes.append("### Android")
|
|
notes.append(f"- **ARM64:** [Citron-{version_name}-android-arm64.apk]({package_registry_url}/{package_dir}/Citron-{version_name}-android-arm64.apk)")
|
|
notes.append("")
|
|
notes.append("### Source Code")
|
|
notes.append(f"- **Source Archive:** [citron-{version_name}-source.tar.gz]({package_registry_url}/{package_dir}/citron-{version_name}-source.tar.gz)")
|
|
notes.append("")
|
|
|
|
# Installation instructions
|
|
notes.append("## Installation Instructions")
|
|
notes.append("")
|
|
notes.append("### Linux")
|
|
notes.append("1. Download the appropriate AppImage for your system")
|
|
notes.append("2. Make it executable: `chmod +x Citron-*.AppImage`")
|
|
notes.append("3. Run: `./Citron-*.AppImage`")
|
|
notes.append("")
|
|
notes.append("### Android")
|
|
notes.append("1. Enable \"Install from Unknown Sources\" in your device settings")
|
|
notes.append("2. Download and install the APK file")
|
|
notes.append("")
|
|
|
|
# Changes section
|
|
if categorized_commits:
|
|
notes.append("## Changes")
|
|
notes.append("")
|
|
|
|
for category, commits in categorized_commits.items():
|
|
if commits:
|
|
notes.append(f"### {category}")
|
|
for commit in commits[:10]: # Limit to 10 commits per category
|
|
notes.append(f"- {commit}")
|
|
notes.append("")
|
|
|
|
# Footer
|
|
notes.append("---")
|
|
notes.append(f"*This is an automated build from commit [{git_commit_short}]({project_url}/-/commit/{git_commit_full})*")
|
|
|
|
return '\n'.join(notes)
|
|
|
|
def generate_json_release_notes(version_name, git_commit_short, git_commit_full,
|
|
git_branch, build_date, build_time, project_url,
|
|
package_registry_url, package_dir, categorized_commits):
|
|
"""Generate JSON release notes"""
|
|
|
|
return json.dumps({
|
|
'version': version_name,
|
|
'build_date': build_date,
|
|
'build_time': build_time,
|
|
'git_commit_short': git_commit_short,
|
|
'git_commit_full': git_commit_full,
|
|
'git_branch': git_branch,
|
|
'project_url': project_url,
|
|
'package_registry_url': package_registry_url,
|
|
'package_dir': package_dir,
|
|
'changes': categorized_commits,
|
|
'downloads': {
|
|
'linux': {
|
|
'generic': f'Citron-{version_name}-anylinux-x86_64.AppImage',
|
|
'steamdeck': f'Citron-{version_name}-steamdeck-x86_64.AppImage',
|
|
'compatibility': f'Citron-{version_name}-compat-x86_64.AppImage'
|
|
},
|
|
'android': {
|
|
'arm64': f'Citron-{version_name}-android-arm64.apk'
|
|
},
|
|
'source': f'citron-{version_name}-source.tar.gz'
|
|
}
|
|
}, indent=2)
|
|
|
|
def main():
|
|
"""Main function"""
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser(description='Generate release notes for Citron')
|
|
parser.add_argument('--format', choices=['markdown', 'json'], default='markdown',
|
|
help='Output format (default: markdown)')
|
|
parser.add_argument('--version-info', help='JSON file with version information')
|
|
parser.add_argument('--output', help='Output file (default: stdout)')
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Get version information
|
|
if args.version_info and os.path.exists(args.version_info):
|
|
with open(args.version_info, 'r') as f:
|
|
version_info = json.load(f)
|
|
else:
|
|
# Import version script
|
|
sys.path.insert(0, os.path.dirname(__file__))
|
|
from get_version import get_version_info
|
|
version_info = get_version_info()
|
|
|
|
# Generate release notes
|
|
notes = generate_release_notes(version_info, args.format)
|
|
|
|
# Output
|
|
if args.output:
|
|
with open(args.output, 'w') as f:
|
|
f.write(notes)
|
|
else:
|
|
print(notes)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|