Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Module Monitor | |
| on: | |
| pull_request_target: | |
| paths: | |
| - 'zephyr/module.yml' | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| issues: write | |
| jobs: | |
| module-monitor: | |
| runs-on: ubuntu-24.04 | |
| name: Monitor Module Changes | |
| steps: | |
| - name: Checkout the code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Fetch PR head | |
| run: | | |
| git fetch origin pull/${{ github.event.number }}/head:pr-head | |
| - name: Setup sdk-nrfxlib repository | |
| run: | | |
| git clone --depth=1000 https://github.com/nrfconnect/sdk-nrfxlib.git sdk-nrfxlib-repo | |
| cd sdk-nrfxlib-repo | |
| git fetch --depth=1000 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: 3.12 | |
| - name: Install PyYAML | |
| run: | | |
| pip install pyyaml | |
| - name: Get module.yml changes and blob info | |
| id: changes | |
| env: | |
| GITHUB_EVENT_NAME: ${{ github.event_name }} | |
| GITHUB_BASE_SHA: ${{ github.event.pull_request_target.base.sha || '' }} | |
| GITHUB_HEAD_SHA: ${{ github.event.pull_request_target.head.sha || '' }} | |
| run: | | |
| # Compare with base branch for pull requests | |
| BASE_REF="$GITHUB_BASE_SHA" | |
| HEAD_REF="$GITHUB_HEAD_SHA" | |
| echo "DEBUG: Comparing $BASE_REF to $HEAD_REF" >&2 | |
| echo "DEBUG: Current HEAD is $(git rev-parse HEAD)" >&2 | |
| echo "DEBUG: Available commits:" >&2 | |
| git log --oneline -5 >&2 | |
| # Get old and new module.yml content | |
| echo "DEBUG: Getting old module.yml from $BASE_REF" >&2 | |
| git show $BASE_REF:zephyr/module.yml > old_module.yml | |
| echo "DEBUG: Getting new module.yml from pr-head" >&2 | |
| git show pr-head:zephyr/module.yml > new_module.yml | |
| echo "DEBUG: Old module.yml content:" >&2 | |
| head -5 old_module.yml >&2 | |
| echo "DEBUG: New module.yml content:" >&2 | |
| head -5 new_module.yml >&2 | |
| # Parse YAML and generate comparison table | |
| echo "DEBUG: Starting Python script..." >&2 | |
| python3 << 'PYTHON_SCRIPT_EOF' | |
| import yaml | |
| import sys | |
| import os | |
| import re | |
| from urllib.parse import urlparse | |
| def extract_commit_from_url(url): | |
| """Extract commit hash from GitHub raw URL""" | |
| if 'raw/' in url: | |
| parts = url.split('raw/') | |
| if len(parts) > 1: | |
| commit_part = parts[1].split('/')[0] | |
| if len(commit_part) == 40: # Full SHA | |
| return commit_part | |
| elif len(commit_part) >= 7: # Short SHA | |
| return commit_part[:7] | |
| return "Unknown" | |
| def get_commit_info(commit_hash): | |
| """Get additional commit information like branch and PR references""" | |
| try: | |
| import subprocess | |
| import json | |
| import os | |
| # Use the cloned sdk-nrfxlib repository | |
| sdk_repo_dir = 'sdk-nrfxlib-repo' | |
| if not os.path.exists(sdk_repo_dir): | |
| return {'hash': commit_hash[:7] if commit_hash != "Unknown" else "Unknown"} | |
| # Get commit details from the sdk-nrfxlib repository | |
| result = subprocess.run(['git', 'log', '--format=%H|%s|%an|%ad', '--date=short', '-1', commit_hash], | |
| cwd=sdk_repo_dir, capture_output=True, text=True, timeout=10) | |
| if result.returncode == 0 and result.stdout.strip(): | |
| parts = result.stdout.strip().split('|') | |
| if len(parts) >= 4: | |
| return { | |
| 'hash': parts[0][:7], | |
| 'subject': parts[1], | |
| 'author': parts[2], | |
| 'date': parts[3] | |
| } | |
| except: | |
| pass | |
| # Fallback: return basic info | |
| return {'hash': commit_hash[:7] if commit_hash != "Unknown" else "Unknown"} | |
| def get_commit_status(commit_hash): | |
| """Get commit status and branch information""" | |
| try: | |
| import subprocess | |
| import os | |
| import re | |
| # First, check if we have PR metadata in the module.yml file | |
| try: | |
| with open('zephyr/module.yml', 'r') as f: | |
| content = f.read() | |
| # Look for PR metadata comment (more flexible pattern) | |
| pr_match = re.search(r'# Generated from PR #(\d+).*?commit: ([a-f0-9]+)', content, re.DOTALL) | |
| if pr_match and pr_match.group(2) == commit_hash: | |
| pr_number = pr_match.group(1) | |
| return { | |
| 'display': f"[{commit_hash[:7]}](PR #{pr_number})", | |
| 'is_pr': True, | |
| 'pr_number': pr_number, | |
| 'pr_url': f"https://github.com/nrfconnect/sdk-nrfxlib/pull/{pr_number}" | |
| } | |
| except: | |
| pass | |
| # Change to sdk-nrfxlib repository directory | |
| sdk_repo_dir = 'sdk-nrfxlib-repo' | |
| if not os.path.exists(sdk_repo_dir): | |
| return {'display': commit_hash[:7] if commit_hash != "Unknown" else "Unknown"} | |
| # Get all branches containing this commit | |
| result = subprocess.run(['git', 'branch', '-r', '--contains', commit_hash], | |
| cwd=sdk_repo_dir, capture_output=True, text=True, timeout=10) | |
| if result.returncode == 0: | |
| branches = result.stdout.strip().split('\n') | |
| branch_names = [] | |
| is_on_main = False | |
| for branch in branches: | |
| branch = branch.strip() | |
| if branch: | |
| branch_name = branch.replace('origin/', '') | |
| branch_names.append(branch_name) | |
| if branch_name in ['main', 'master']: | |
| is_on_main = True | |
| if is_on_main: | |
| return { | |
| 'display': f"{commit_hash[:7]} (main)" if commit_hash != "Unknown" else "Unknown (main)" | |
| } | |
| elif branch_names: | |
| # Show the first branch found | |
| return { | |
| 'display': f"{commit_hash[:7]} ({branch_names[0]})" if commit_hash != "Unknown" else f"Unknown ({branch_names[0]})" | |
| } | |
| except: | |
| pass | |
| # Default: show short commit hash | |
| return { | |
| 'display': commit_hash[:7] if commit_hash != "Unknown" else "Unknown" | |
| } | |
| def generate_diff_link(old_url, new_url): | |
| """Generate diff link for the source repository""" | |
| old_commit = extract_commit_from_url(old_url) | |
| new_commit = extract_commit_from_url(new_url) | |
| if old_commit != "Unknown" and new_commit != "Unknown" and old_commit != new_commit: | |
| return f"https://github.com/nrfconnect/sdk-nrfxlib/compare/{old_commit}...{new_commit}" | |
| elif new_commit != "Unknown": | |
| return f"https://github.com/nrfconnect/sdk-nrfxlib/commit/{new_commit}" | |
| return "N/A" | |
| try: | |
| print("DEBUG: Loading old_module.yml...", file=sys.stderr) | |
| # Load old and new module.yml | |
| with open('old_module.yml', 'r') as f: | |
| old_data = yaml.safe_load(f) | |
| print("DEBUG: Loading new_module.yml...", file=sys.stderr) | |
| with open('new_module.yml', 'r') as f: | |
| new_data = yaml.safe_load(f) | |
| old_blobs = {blob['path']: blob for blob in old_data.get('blobs', [])} | |
| new_blobs = {blob['path']: blob for blob in new_data.get('blobs', [])} | |
| # Generate comparison table | |
| table_rows = [] | |
| table_rows.append("| Variant | Old Version | New Version | Old Commit | New Commit | Diff Link |") | |
| table_rows.append("|---------|-------------|-------------|------------|------------|-----------|") | |
| all_paths = set(old_blobs.keys()) | set(new_blobs.keys()) | |
| for path in sorted(all_paths): | |
| old_blob = old_blobs.get(path, {}) | |
| new_blob = new_blobs.get(path, {}) | |
| old_version = old_blob.get('version', 'N/A') | |
| new_version = new_blob.get('version', 'N/A') | |
| old_commit_hash = extract_commit_from_url(old_blob.get('url', '')) | |
| new_commit_hash = extract_commit_from_url(new_blob.get('url', '')) | |
| # Get additional commit information | |
| old_commit_info = get_commit_info(old_commit_hash) | |
| new_commit_info = get_commit_info(new_commit_hash) | |
| # Check commit status (merged vs PR) | |
| old_commit_status = get_commit_status(old_commit_hash) | |
| new_commit_status = get_commit_status(new_commit_hash) | |
| # Format commit display - simplify for better table formatting | |
| old_commit_display = old_commit_hash[:7] if old_commit_hash != "Unknown" else "Unknown" | |
| new_commit_display = new_commit_hash[:7] if new_commit_hash != "Unknown" else "Unknown" | |
| diff_link = generate_diff_link(old_blob.get('url', ''), new_blob.get('url', '')) | |
| # Extract variant name from path | |
| variant = path.split('/')[-2] if '/' in path else path | |
| table_rows.append(f"| {variant} | {old_version} | {new_version} | {old_commit_display} | {new_commit_display} | {diff_link} |") | |
| # Check if there are any actual changes | |
| has_changes = False | |
| for path in sorted(all_paths): | |
| old_blob = old_blobs.get(path, {}) | |
| new_blob = new_blobs.get(path, {}) | |
| if (old_blob.get('version') != new_blob.get('version') or | |
| old_blob.get('sha256') != new_blob.get('sha256') or | |
| old_blob.get('url') != new_blob.get('url')): | |
| has_changes = True | |
| break | |
| # Output the table | |
| diff_content = [] | |
| if has_changes: | |
| diff_content.append("## Firmware Blob Changes") | |
| diff_content.append("") | |
| diff_content.extend(table_rows) | |
| else: | |
| diff_content.append("## No Changes Detected") | |
| diff_content.append("") | |
| diff_content.append("All firmware blobs remain unchanged in this PR.") | |
| diff_content.append("") | |
| diff_content.append("### Current Firmware Blob Summary:") | |
| # Create a simplified summary table without the comparison columns | |
| summary_rows = [] | |
| summary_rows.append("| Variant | Version | Commit |") | |
| summary_rows.append("|---------|---------|--------|") | |
| for path in sorted(all_paths): | |
| new_blob = new_blobs.get(path, {}) | |
| version = new_blob.get('version', 'N/A') | |
| commit_hash = extract_commit_from_url(new_blob.get('url', '')) | |
| commit_display = commit_hash[:7] if commit_hash != "Unknown" else "Unknown" | |
| # Extract variant name from path | |
| variant = path.split('/')[-2] if '/' in path else path | |
| summary_rows.append(f"| {variant} | {version} | {commit_display} |") | |
| diff_content.extend(summary_rows) | |
| # Write to file for GitHub Actions output | |
| print(f"DEBUG: Writing {len(diff_content)} lines to diff_output.txt", file=sys.stderr) | |
| with open('diff_output.txt', 'w') as f: | |
| f.write('\n'.join(diff_content)) | |
| print(f"DEBUG: Successfully wrote diff_output.txt", file=sys.stderr) | |
| except Exception as e: | |
| print(f"Error generating comparison: {e}", file=sys.stderr) | |
| print("DEBUG: Python script completed successfully", file=sys.stderr) | |
| PYTHON_SCRIPT_EOF | |
| - name: Set output variables | |
| id: set-output | |
| run: | | |
| echo "DEBUG: Checking if files exist..." >&2 | |
| ls -la *.txt >&2 || echo "No .txt files found" >&2 | |
| if [ -f diff_output.txt ]; then | |
| echo "DEBUG: diff_output.txt exists, size: $(wc -c < diff_output.txt)" >&2 | |
| # Use base64 encoding to avoid delimiter conflicts | |
| DIFF_CONTENT=$(cat diff_output.txt | base64 -w 0) | |
| echo "diff_output_encoded=$DIFF_CONTENT" >> $GITHUB_OUTPUT | |
| else | |
| echo "DEBUG: diff_output.txt not found" >&2 | |
| echo "diff_output_encoded=" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Create or update comment | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| // Decode the base64 content | |
| let diffOutput = 'No changes detected'; | |
| if (process.env.diff_output_encoded) { | |
| try { | |
| diffOutput = Buffer.from(process.env.diff_output_encoded, 'base64').toString('utf8'); | |
| } catch (e) { | |
| console.log('Error decoding diff_output:', e.message); | |
| diffOutput = 'Error decoding output'; | |
| } | |
| } | |
| console.log('DEBUG: diff_output length:', diffOutput.length); | |
| const commentBody = '## Module Monitor\n\nChanges detected in module.yml\n\n' + | |
| diffOutput + '\n\n' + | |
| 'This comment was automatically generated.'; | |
| try { | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number | |
| }); | |
| const existingComment = comments.find(comment => | |
| comment.body.includes('Module Monitor') | |
| ); | |
| if (existingComment) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existingComment.id, | |
| body: commentBody | |
| }); | |
| console.log('Successfully updated existing comment'); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: commentBody | |
| }); | |
| console.log('Successfully created new comment'); | |
| } | |
| } catch (error) { | |
| console.log('Could not create/update comment due to permissions.'); | |
| console.log('Error:', error.message); | |
| console.log('Comment body that would have been posted:'); | |
| console.log('---'); | |
| console.log(commentBody); | |
| console.log('---'); | |
| // Don't fail the workflow - the PR detection worked correctly | |
| console.log('Workflow completed successfully - PR detection and diff generation worked correctly.'); | |
| } | |
| env: | |
| diff_output_encoded: ${{ steps.set-output.outputs.diff_output_encoded }} | |
| - name: Output summary | |
| run: | | |
| echo "## Module Monitor Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Changes in zephyr/module.yml:" >> $GITHUB_STEP_SUMMARY | |
| echo '```diff' >> $GITHUB_STEP_SUMMARY | |
| if [ -n "${{ steps.set-output.outputs.diff_output_encoded }}" ]; then | |
| echo "${{ steps.set-output.outputs.diff_output_encoded }}" | base64 -d >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "No changes detected" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo '```' >> $GITHUB_STEP_SUMMARY |