Skip to content

Commit

Permalink
Add automated repository generation and version updates (#344)
Browse files Browse the repository at this point in the history
* Add automated repository generation and version updates

Implemented a new Makefile target for updating versions and generating repositories. Added a Python script for automating kodi repository XML generation. Enhanced GitHub Actions to support automatic version updates, build, and deployment to GitHub Pages.

* Add Claude API integration for automated release notes
  • Loading branch information
mhdzumair authored Nov 6, 2024
1 parent 61b9e7b commit 80d3235
Show file tree
Hide file tree
Showing 6 changed files with 256 additions and 32 deletions.
106 changes: 97 additions & 9 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,53 @@ on:
types: [ created ]

jobs:
update_version:
runs-on: ubuntu-latest
outputs:
previous_version: ${{ steps.extract_versions.outputs.PREVIOUS_VERSION }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Extract versions
id: extract_versions
run: |
# Extract current version from release body
BODY="${{ github.event.release.body }}"
if ! PREVIOUS_VERSION=$(echo "$BODY" | grep -o '/compare/.*\.\.\.' | sed 's/\/compare\///' | sed 's/\.\.\.//' || true); then
echo "Error: Failed to extract previous version from release body"
exit 1
fi
VERSION="${GITHUB_REF#refs/tags/v}"
echo "PREVIOUS_VERSION=${PREVIOUS_VERSION}" >> "$GITHUB_OUTPUT"
echo "VERSION=${VERSION}" >> "$GITHUB_OUTPUT"
- name: Update version numbers
if: "!github.event.release.prerelease"
run: |
make update-version VERSION_NEW=${{ steps.extract_versions.outputs.VERSION }}
- name: Commit and push version updates
if: "!github.event.release.prerelease"
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add -A
git commit -m "chore: update version to ${{ steps.extract_versions.outputs.VERSION }}"
git push origin HEAD:main
mediafusion_docker_build:
needs: update_version
if: "!github.event.release.prerelease"
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: main # Use main branch with updated versions

- name: Set up QEMU
uses: docker/setup-qemu-action@v3
Expand All @@ -34,18 +75,22 @@ jobs:
push: true
build-args: VERSION=${{ github.ref_name }}
tags: |
mhdzumair/mediafusion:v${{ github.ref_name }}
mhdzumair/mediafusion:${{ github.ref_name }}
mhdzumair/mediafusion:latest
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}

kodi_build:
needs: update_version
if: "!github.event.release.prerelease"
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: main # Use main branch with updated versions

- name: Set up Python
uses: actions/setup-python@v5
Expand All @@ -57,16 +102,59 @@ jobs:
python -m pip install --upgrade pip
pip install setuptools wheel
- name: Install zip
run: sudo apt-get install -y zip
- name: Install required packages
run: sudo apt-get install -y zip xmlstarlet

- name: Build addon and repository
run: |
make -C kodi
# Validate build artifacts
for file in kodi/plugin.video.mediafusion-*.zip kodi/repository.mediafusion-*.zip; do
if [ ! -f "$file" ]; then
echo "Error: Build artifact $file not found"
exit 1
fi
done
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: kodi/dist
destination_dir: kodi/dist
keep_files: true
enable_jekyll: false
force_orphan: true

- name: Upload Release Assets
uses: softprops/action-gh-release@v2
with:
files: |
kodi/plugin.video.mediafusion-*.zip
kodi/repository.mediafusion-*.zip
prerelease: false
token: ${{ secrets.GITHUB_TOKEN }}

generate_release_notes:
needs: update_version
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Build addon
run: make -C kodi
- name: Install jq
run: sudo apt-get install -y jq

- name: List build directory
run: ls -la kodi
- name: Generate Release Notes
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
make generate-notes VERSION_OLD=${{ needs.update_version.outputs.previous_version }} VERSION_NEW=${{ github.ref_name }} > release_notes.md
- name: Upload Release Asset
- name: Update Release Notes
uses: softprops/action-gh-release@v2
with:
files: kodi/plugin.video.mediafusion-*.zip
body_path: release_notes.md
token: ${{ secrets.GITHUB_TOKEN }}
57 changes: 48 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,33 @@ VERSION_OLD ?=
VERSION_NEW ?=
CONTRIBUTORS ?=

.PHONY: build tag push prompt
# Claude API settings
CLAUDE_MODEL ?= claude-3-5-sonnet-20241022
MAX_TOKENS ?= 1024
ANTHROPIC_VERSION ?= 2023-06-01

.PHONY: build tag push prompt update-version generate-notes

build:
docker build --build-arg VERSION=$(VERSION) -t $(DOCKER_IMAGE) -f deployment/Dockerfile .

update-version:
ifndef VERSION_NEW
@echo "Error: VERSION_NEW is not set. Please set it like: make update-version VERSION_NEW=4.1.0"
@exit 1
endif
@echo "Updating version to $(VERSION_NEW)..."
# Update main addon.xml
@sed -i -e "/<addon\s*id=\"plugin\.video\.mediafusion\"/s/version=\"[^\"]*\"/version=\"$(VERSION_NEW)\"/" kodi/plugin.video.mediafusion/addon.xml
# Update repository addon.xml
@sed -i -e "/<addon\s*id=\"repository\.mediafusion\"/s/version=\"[^\"]*\"/version=\"$(VERSION_NEW)\"/" kodi/repository.mediafusion/addon.xml
# Update docker-compose.yml
@sed -i 's|image: $(DOCKER_REPO)/$(IMAGE_NAME):v[0-9.]*|image: $(DOCKER_REPO)/$(IMAGE_NAME):v$(VERSION_NEW)|g' deployment/docker-compose/docker-compose.yml
# Update k8s deployment
@sed -i 's|image: $(DOCKER_REPO)/$(IMAGE_NAME):v[0-9.]*|image: $(DOCKER_REPO)/$(IMAGE_NAME):v$(VERSION_NEW)|g' deployment/k8s/local-deployment.yaml
@echo "Version updated to $(VERSION_NEW) in all files"


build-multi:
@if ! docker buildx ls | grep -q $(BUILDER_NAME); then \
echo "Creating new builder $(BUILDER_NAME)"; \
Expand All @@ -49,7 +71,6 @@ push:
docker push $(DOCKER_IMAGE)

prompt:
# Check if necessary variables are set and generate prompt
ifndef VERSION_OLD
@echo "Error: VERSION_OLD is not set. Please set it like: make prompt VERSION_OLD=x.x.x VERSION_NEW=y.y.y CONTRIBUTORS='@user1, @user2'"
@exit 1
Expand All @@ -58,11 +79,7 @@ ifndef VERSION_NEW
@echo "Error: VERSION_NEW is not set. Please set it like: make prompt VERSION_OLD=x.x.x VERSION_NEW=y.y.y CONTRIBUTORS='@user1, @user2'"
@exit 1
endif
ifndef CONTRIBUTORS
@echo "Warning: CONTRIBUTORS not set. Continuing without contributors."
endif

@echo "Generate a release note for MediaFusion $(VERSION_NEW) by analyzing the following changes. Organize the release note by importance rather than by commit order. highlight the most significant updates first, and streamline the content to focus on what adds the most value to the user. Ensure to dynamically create sections for New Features & Enhancements, Bug Fixes, and Documentation updates only if relevant based on the types of changes listed. Use emojis relevantly at the start of each item to enhance readability and engagement. Keep the format straightforward & shorter, List down the contributors, and provide a direct link to the detailed list of changes:\n"
@echo "Generate a release note for MediaFusion $(VERSION_NEW) by analyzing the following changes. Organize the release note by importance rather than by commit order. highlight the most significant updates first, and streamline the content to focus on what adds the most value to the user. Ensure to dynamically create sections for New Features & Enhancements, Bug Fixes, and Documentation updates only if relevant based on the types of changes listed. Use emojis relevantly at the start of each item to enhance readability and engagement. Keep the format straightforward & shorter, provide a direct link to the detailed list of changes:\n"
@echo "## 🚀 MediaFusion $(VERSION_NEW) Released\n"
@echo "### Commit Messages and Descriptions:\n"
@git log --pretty=format:'%s%n%b' $(VERSION_OLD)..$(VERSION_NEW) | awk 'BEGIN {RS="\n\n"; FS="\n"} { \
Expand All @@ -75,8 +92,30 @@ endif
if (message != "") print "- " message; \
if (description != "") printf "%s", description; \
}'
@echo "\n### 🤝 Contributors: $(CONTRIBUTORS)\n"
@echo "### 📄 Full Changelog:\n- https://github.com/mhdzumair/MediaFusion/compare/$(VERSION_OLD)...$(VERSION_NEW)"
@echo "--- \n### 🤝 Contributors: $(CONTRIBUTORS)\n\n### 📄 Full Changelog:\nhttps://github.com/mhdzumair/MediaFusion/compare/$(VERSION_OLD)...$(VERSION_NEW)";

generate-notes:
ifndef VERSION_OLD
@echo "Error: VERSION_OLD is not set"
@exit 1
endif
ifndef VERSION_NEW
@echo "Error: VERSION_NEW is not set"
@exit 1
endif
ifndef CONTRIBUTORS
@echo "Warning: CONTRIBUTORS not set. Continuing without contributors."
endif
ifndef ANTHROPIC_API_KEY
@echo "Error: ANTHROPIC_API_KEY is not set"
@exit 1
endif
@PROMPT_CONTENT=`make prompt VERSION_OLD=$(VERSION_OLD) VERSION_NEW=$(VERSION_NEW) | tr '\n' ' ' | sed 's/"/\\\\"/g'`; \
curl -s https://api.anthropic.com/v1/messages \
--header "x-api-key: $(ANTHROPIC_API_KEY)" \
--header "anthropic-version: $(ANTHROPIC_VERSION)" \
--header "content-type: application/json" \
--data "{\"model\":\"$(CLAUDE_MODEL)\",\"max_tokens\":$(MAX_TOKENS),\"messages\":[{\"role\":\"user\",\"content\":\"$$PROMPT_CONTENT\"}]}" \
| jq -r '.content[] | select(.type=="text") | .text';

all: build-multi
48 changes: 34 additions & 14 deletions kodi/Makefile
Original file line number Diff line number Diff line change
@@ -1,37 +1,57 @@
# Define the addon directory, build directory, and zip file name
ADDON_DIR = plugin.video.mediafusion
BUILD_DIR = build/$(ADDON_DIR)
LIB_DIR = $(BUILD_DIR)/lib
REPO_DIR = repository.mediafusion
BUILD_DIR = build
DIST_DIR = dist
VERSION := $(shell sed -ne "s/.*version=\"\([0-9a-z\.\-]*\)\"\sname=.*/\1/p" $(ADDON_DIR)/addon.xml)
RANDOM_STRING = $(shell head /dev/urandom | tr -dc A-Za-z0-9 | head -c 8)
NEW_VERSION := $(VERSION)-$(RANDOM_STRING)
ZIP_FILE := $(ADDON_DIR)-$(VERSION).zip
DEV_ZIP_FILE := $(ADDON_DIR)-dev-$(NEW_VERSION).zip
REPO_ZIP_FILE := $(REPO_DIR)-$(VERSION).zip

# Default target
all: $(ZIP_FILE)
all: $(ZIP_FILE) repository

# Create the zip file
# Create the plugin zip file
$(ZIP_FILE): install_dependencies
@echo "Creating zip file for $(ADDON_DIR)..."
@cd build && zip -r ../$(ZIP_FILE) $(ADDON_DIR)
@echo "Zip file created: $(ZIP_FILE)"

# Install dependencies
install_dependencies: $(BUILD_DIR)
install_dependencies: prepare_build
@echo "Installing dependencies..."
@pip install -r requirements.txt -t $(LIB_DIR)
@pip install -r requirements.txt -t $(BUILD_DIR)/$(ADDON_DIR)/lib

# Prepare build directory
$(BUILD_DIR): clean
prepare_build: clean
@echo "Preparing build directory..."
@mkdir -p $(BUILD_DIR)
@cp -r $(ADDON_DIR)/* $(BUILD_DIR)
@mkdir -p $(BUILD_DIR)/$(ADDON_DIR)
@mkdir -p $(BUILD_DIR)/$(REPO_DIR)
@mkdir -p $(DIST_DIR)/zips/$(ADDON_DIR)
@mkdir -p $(DIST_DIR)/zips/$(REPO_DIR)
@cp -r $(ADDON_DIR)/* $(BUILD_DIR)/$(ADDON_DIR)

# Repository setup
repository: $(ZIP_FILE)
@echo "Setting up repository..."
@cp -r $(REPO_DIR)/* $(BUILD_DIR)/$(REPO_DIR)
@cd $(BUILD_DIR) && zip -r ../$(REPO_ZIP_FILE) $(REPO_DIR)

# Copy files to dist structure
@cp $(ZIP_FILE) $(DIST_DIR)/zips/$(ADDON_DIR)/
@cp $(ADDON_DIR)/addon.xml $(DIST_DIR)/zips/$(ADDON_DIR)/
@cp $(REPO_ZIP_FILE) $(DIST_DIR)/zips/$(REPO_DIR)/
@cp $(REPO_DIR)/addon.xml $(DIST_DIR)/zips/$(REPO_DIR)/

# Generate repository files
@python3 generate_repository.py

# Clean up generated files
clean:
@echo "Cleaning up..."
@rm -rf $(ADDON_DIR)-*.zip build
@rm -rf $(ADDON_DIR)-*.zip build dist
@rm -rf $(HOME)/Downloads/$(ADDON_DIR)-*.zip

# Dev target
Expand All @@ -46,10 +66,10 @@ $(DEV_ZIP_FILE): install_dev_dependencies
@echo "Dev zip file copied to $(HOME)/Downloads/$(DEV_ZIP_FILE)"

# Install dev dependencies
install_dev_dependencies: $(BUILD_DIR)
install_dev_dependencies: prepare_build
@echo "Installing dev dependencies..."
@pip install -r requirements.txt -t $(LIB_DIR)
@pip install -r dev-requirements.txt -t $(LIB_DIR)
@pip install -r requirements.txt -t $(BUILD_DIR)/$(ADDON_DIR)/lib
@pip install -r dev-requirements.txt -t $(BUILD_DIR)/$(ADDON_DIR)/lib

# Phony targets
.PHONY: all clean dev install_dependencies install_dev_dependencies
.PHONY: all clean dev install_dependencies install_dev_dependencies repository prepare_build
58 changes: 58 additions & 0 deletions kodi/generate_repository.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env python3
import hashlib
import xml.etree.ElementTree as ET
from pathlib import Path

DIST_DIR = Path("dist")
ZIPS_DIR = DIST_DIR / "zips"


def generate_md5(file_path):
"""Generate MD5 hash of a file."""
hash_md5 = hashlib.md5()
with open(file_path, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
hash_md5.update(chunk)
return hash_md5.hexdigest()


def generate_addons_xml():
"""Generate addons.xml file."""
addon_xmls = []
errors = []
# Get all addon.xml files from the zips directory
for addon_dir in ZIPS_DIR.iterdir():
if addon_dir.is_dir():
addon_xml_path = addon_dir / "addon.xml"
if addon_xml_path.exists():
try:
tree = ET.parse(addon_xml_path)
root = tree.getroot()
# Basic validation
if root.tag != "addon":
raise ValueError(f"Invalid root tag in {addon_xml_path}")
addon_xmls.append(root)
except (ET.ParseError, ValueError) as e:
errors.append(f"Error processing {addon_xml_path}: {e}")
if errors:
raise ValueError("\n".join(errors))
# Sort addons by ID for consistent output
addon_xmls.sort(key=lambda x: x.get("id", ""))
# Create addons.xml
xml_root = ET.Element("addons")
for addon in addon_xmls:
xml_root.append(addon)
# Use proper XML declaration with encoding
xml_str = ET.tostring(xml_root, encoding="utf-8", xml_declaration=True)
# Save addons.xml
addons_xml_path = ZIPS_DIR / "addons.xml"
with open(addons_xml_path, "wb") as f:
f.write(xml_str)
# Generate MD5
md5_path = ZIPS_DIR / "addons.xml.md5"
with open(md5_path, "w") as f:
f.write(generate_md5(addons_xml_path))


if __name__ == "__main__":
generate_addons_xml()
Loading

0 comments on commit 80d3235

Please sign in to comment.