1414import hashlib
1515import requests
1616import logging
17+ import os
1718from jinja2 import Environment , FileSystemLoader
18- from typing import Dict , Any , List
19+ from typing import Dict , Any , List , Optional
1920from collections import namedtuple
2021
2122WIFI_FW_BIN_NAME : str = "nrf70.bin"
@@ -71,14 +72,56 @@ def get_wifi_blob_info(name: str) -> BlobInfo:
7172logging .basicConfig (level = logging .INFO )
7273
7374
75+ def get_pr_head_commit (pr_number : str ) -> str :
76+ """Get the head commit SHA for a PR from GitHub API"""
77+ github_token = os .environ .get ('GITHUB_TOKEN' )
78+ headers = {}
79+ if github_token :
80+ headers ['Authorization' ] = f'token { github_token } '
81+
82+ url = f"https://api.github.com/repos/nrfconnect/sdk-nrfxlib/pulls/{ pr_number } "
83+
84+ try :
85+ response = requests .get (url , headers = headers , timeout = 30 )
86+ response .raise_for_status ()
87+
88+ pr_data = response .json ()
89+
90+ # Check if PR exists and is not closed/merged
91+ if pr_data .get ('state' ) == 'closed' :
92+ logger .warning (f"PR #{ pr_number } is closed" )
93+
94+ if pr_data .get ('merged' ):
95+ logger .warning (f"PR #{ pr_number } is already merged" )
96+
97+ # Get head commit
98+ head_commit = pr_data ['head' ]['sha' ]
99+ if not head_commit :
100+ raise ValueError (f"PR #{ pr_number } has no head commit" )
101+
102+ logger .info (f"PR #{ pr_number } head commit: { head_commit } " )
103+ return head_commit
104+
105+ except requests .exceptions .RequestException as e :
106+ if response .status_code == 404 :
107+ raise ValueError (f"PR #{ pr_number } not found in nrfconnect/sdk-nrfxlib repository" )
108+ elif response .status_code == 403 :
109+ raise ValueError (f"Access denied to PR #{ pr_number } . Check GITHUB_TOKEN permissions" )
110+ else :
111+ raise ValueError (f"Failed to fetch PR #{ pr_number } : { e } " )
112+ except KeyError as e :
113+ raise ValueError (f"Invalid response format for PR #{ pr_number } : missing { e } " )
114+ except Exception as e :
115+ raise ValueError (f"Unexpected error fetching PR #{ pr_number } : { e } " )
116+
74117def compute_sha256 (url : str ) -> str :
75118 response = requests .get (url )
76119 response .raise_for_status ()
77120 sha256_hash : str = hashlib .sha256 (response .content ).hexdigest ()
78121 return sha256_hash
79122
80123
81- def render_template (template_path : str , output_path : str , latest_sha : str ) -> None :
124+ def render_template (template_path : str , output_path : str , latest_sha : str , is_pr : bool = False , pr_number : Optional [ str ] = None ) -> None :
82125 # Load the Jinja2 template
83126 env : Environment = Environment (loader = FileSystemLoader ("." ))
84127 template = env .get_template (template_path )
@@ -97,24 +140,41 @@ def render_template(template_path: str, output_path: str, latest_sha: str) -> No
97140 blob_info ["doc_url" ] = f"{ blob .docpath } "
98141
99142 # Download the binary to compute SHA-256 and extract version
100- response = requests .get (blob_info ["url" ])
101- response .raise_for_status ()
102- binary_data = response .content
143+ try :
144+ response = requests .get (blob_info ["url" ], timeout = 60 )
145+ response .raise_for_status ()
146+ binary_data = response .content
147+
148+ blob_info ["sha256" ] = hashlib .sha256 (binary_data ).hexdigest ()
149+ blob_info ["description" ] = blob .description
103150
104- blob_info [ "sha256" ] = hashlib . sha256 ( binary_data ). hexdigest ()
105- blob_info ["description " ] = blob . description
151+ # Parse version from the actual binary
152+ blob_info ["version " ] = parse_version_from_binary ( binary_data )
106153
107- # Parse version from the actual binary
108- blob_info ["version" ] = parse_version_from_binary (binary_data )
154+ except requests .exceptions .RequestException as e :
155+ logger .error (f"Failed to download blob from { blob_info ['url' ]} : { e } " )
156+ raise ValueError (f"Failed to download blob for { blob .name } : { e } " )
157+ except Exception as e :
158+ logger .error (f"Unexpected error processing blob { blob .name } : { e } " )
159+ raise ValueError (f"Error processing blob { blob .name } : { e } " )
109160
110161 blobs [blob .name ] = blob_info
111162
112163 logger .debug (blobs )
113164 # Render the template with the provided context
114165 rendered_content : str = template .render (blobs = blobs , latest_sha = latest_sha )
115166
167+ # Add metadata comment at the top
168+ metadata_lines = []
169+ if is_pr and pr_number :
170+ metadata_lines .append (f"# Generated from PR #{ pr_number } (commit: { latest_sha } )" )
171+ else :
172+ metadata_lines .append (f"# Generated from commit: { latest_sha } " )
173+ metadata_lines .append ("" )
174+
116175 # Write the rendered content to the output file
117176 with open (output_path , "w" ) as output_file :
177+ output_file .writelines (metadata_lines )
118178 output_file .write (rendered_content )
119179
120180
@@ -137,8 +197,12 @@ def main() -> None:
137197 parser .add_argument (
138198 "-c" ,
139199 "--commit" ,
140- required = True ,
141- help = "The latest commit SHA for the nrfxlib repository." ,
200+ help = "The commit SHA for the nrfxlib repository (for merged commits)." ,
201+ )
202+ parser .add_argument (
203+ "-p" ,
204+ "--pr" ,
205+ help = "The PR number for the nrfxlib repository (for unmerged PRs)." ,
142206 )
143207 parser .add_argument (
144208 "-d" , "--debug" , action = "store_true" , help = "Enable debug logging."
@@ -149,8 +213,47 @@ def main() -> None:
149213 if args .debug :
150214 logger .setLevel (logging .DEBUG )
151215
152- # Render the template
153- render_template (args .template , args .output , args .commit )
216+ # Validate arguments
217+ if not args .commit and not args .pr :
218+ parser .error ("Either --commit or --pr must be specified" )
219+ if args .commit and args .pr :
220+ parser .error ("Only one of --commit or --pr can be specified" )
221+
222+ # Validate commit format if provided
223+ if args .commit :
224+ import re
225+ if not re .match (r'^[a-fA-F0-9]{7,40}$' , args .commit ):
226+ parser .error (f"Invalid commit hash format: { args .commit } . Expected 7-40 hex characters." )
227+
228+ # Validate PR number if provided
229+ if args .pr :
230+ if not args .pr .isdigit () or int (args .pr ) <= 0 :
231+ parser .error (f"Invalid PR number: { args .pr } . Expected a positive integer." )
232+
233+ # Determine the reference to use
234+ try :
235+ if args .pr :
236+ # For PRs, get the head commit from GitHub API
237+ logger .info (f"Processing PR #{ args .pr } " )
238+ reference = get_pr_head_commit (args .pr )
239+ is_pr = True
240+ pr_number = args .pr
241+ else :
242+ # For merged commits, use the commit hash directly
243+ logger .info (f"Processing commit { args .commit } " )
244+ reference = args .commit
245+ is_pr = False
246+ pr_number = None
247+
248+ # Render the template
249+ render_template (args .template , args .output , reference , is_pr , pr_number )
250+
251+ except ValueError as e :
252+ logger .error (f"Error: { e } " )
253+ exit (1 )
254+ except Exception as e :
255+ logger .error (f"Unexpected error: { e } " )
256+ exit (1 )
154257
155258
156259if __name__ == "__main__" :
0 commit comments