@@ -93,6 +93,25 @@ def read_pyproject_toml():
9393 return pyproject_data
9494
9595
96+ def extract_python_version_from_pyproject ():
97+ """Extract Python version from pyproject.toml and return major.minor"""
98+ pyproject_data = read_pyproject_toml ()
99+ requires_python = pyproject_data ["project" ].get ("requires-python" , "" )
100+
101+ if requires_python :
102+ import re
103+
104+ # Match any version pattern: 2.7, 3.8, 3.13, 4.0, etc.
105+ version_match = re .search (r"(\d+)\.(\d+)" , requires_python )
106+ if version_match :
107+ major = version_match .group (1 )
108+ minor = version_match .group (2 )
109+ return f"{ major } .{ minor } "
110+
111+ # Default to current Python version if not specified
112+ return f"{ sys .version_info .major } .{ sys .version_info .minor } "
113+
114+
96115def extract_project_meta (pyproject_data ):
97116 # Extract project metadata from pyproject.toml data.
98117 project_data = pyproject_data ["project" ]
@@ -102,8 +121,26 @@ def extract_project_meta(pyproject_data):
102121 authors = project_data .get ("authors" )
103122 author_names = [author .get ("name" , "" ) for author in authors if "name" in author ]
104123 author_str = ", " .join (author_names )
105-
106- meta_dict = {"name" : name , "version" : version , "description" : description , "author" : author_str }
124+ requires_python = project_data .get ("requires-python" , "" )
125+ # Current system's python version
126+ python_version = f"{ sys .version_info .major } .{ sys .version_info .minor } "
127+
128+ if requires_python :
129+ import re
130+
131+ version_match = re .search (r"(\d+)(?:\.(\d+))?" , requires_python )
132+ if version_match :
133+ major = version_match .group (1 )
134+ minor = version_match .group (2 ) if version_match .group (2 ) else "0"
135+ python_version = f"{ major } .{ minor } "
136+
137+ meta_dict = {
138+ "name" : name ,
139+ "version" : version ,
140+ "description" : description ,
141+ "author" : author_str ,
142+ "requires_python" : python_version ,
143+ }
107144
108145 return meta_dict
109146
@@ -131,15 +168,15 @@ def extract_project_dependencies(pyproject_data):
131168 return dependencies_list
132169
133170
134- def extract_tags_from_url (url ):
171+ def extract_tags_from_url (url , major , minor ):
135172 """Extract tags from wheel URL"""
136173 tags = set ()
137174
138175 # Python version tags
139- if "cp313 " in url :
140- tags .add ("cp313 " )
141- if "py3 " in url :
142- tags .add ("py3 " )
176+ if f"py { major } " in url :
177+ tags .add (f"py { major } " )
178+ if f"cp { major } { minor } " in url :
179+ tags .add (f"cp { major } { minor } " )
143180
144181 # Platform tags
145182 if "manylinux" in url :
@@ -154,20 +191,20 @@ def extract_tags_from_url(url):
154191 return tags
155192
156193
157- def is_compatible_wheel (url ):
194+ def is_compatible_wheel (url , python_version ):
158195 """Check if wheel is compatible using tag matching"""
159- wheel_tags = extract_tags_from_url (url )
196+ major , minor = python_version .split ("." )
197+ wheel_tags = extract_tags_from_url (url , major , minor )
160198
161- # Define compatible tag combinations
162- compatible_python = {"cp313" , "py3" }
163- # Architecture-free or linux
164- compatible_platforms = {"manylinux" , "none" , "any" , "x86_64" }
199+ # Check Python compatibility
200+ has_required_python = any (tag in wheel_tags for tag in [f"py{ major } " , f"cp{ major } { minor } " ])
165201
166- # Check if wheel has required python version AND compatible platform
167- has_required_python = not wheel_tags .isdisjoint (compatible_python )
168- has_compatible_platform = not wheel_tags .isdisjoint (compatible_platforms )
202+ # Check platform
203+ has_allowed_platform = ("any" in wheel_tags or "x86_64" in wheel_tags ) and (
204+ "manylinux" in wheel_tags or "none" in wheel_tags
205+ )
169206
170- return has_required_python and has_compatible_platform
207+ return has_required_python and has_allowed_platform
171208
172209
173210def create_defualt_nix (dependencies_list , meta_dict ):
@@ -210,6 +247,7 @@ def create_defualt_nix(dependencies_list, meta_dict):
210247"""
211248 need_review_packages_list = []
212249 deps_size = len (dependencies_list )
250+ python_version = meta_dict ["requires_python" ]
213251 for idx , dep in enumerate (dependencies_list ):
214252 print ("Processing {}/{}: {}" .format (idx + 1 , deps_size , dep ["name" ]))
215253 name = dep ["name" ]
@@ -257,10 +295,11 @@ def create_defualt_nix(dependencies_list, meta_dict):
257295 url_section = data .get ("urls" , [])
258296 build_from_src = True
259297 package_added = False
298+
260299 for component in url_section :
261300 if component .get ("packagetype" ) == "bdist_wheel" :
262301 whl_url = component .get ("url" )
263- if not is_compatible_wheel (whl_url ):
302+ if not is_compatible_wheel (whl_url , python_version ):
264303 continue
265304 whl_sha256 = get_sha256_hash (whl_url )
266305 nix_content += " " + name + " = buildCustomPackage {\n "
@@ -362,12 +401,20 @@ def create_defualt_nix(dependencies_list, meta_dict):
362401 name = dep ["name" ]
363402 nix_content += " " + name + "\n "
364403
365- nix_content += """
404+ nix_content += (
405+ """
366406 ];
367407
368408 meta = with pkgs.lib; {
369- description = "Automate open source license compliance and ensure supply chain integrity";
370- license = "AGPL-3.0-only";
409+ description =\" """
410+ + meta_dict .get (
411+ "description" ,
412+ "Automate open source license compliance and ensure supply chain integrity." ,
413+ )
414+ + """\" ;
415+ license = \" """
416+ + meta_dict .get ("license" , "AGPL-3.0-only" )
417+ + """\" ;
371418 maintainers = ["AboutCode.org"];
372419 platforms = platforms.linux;
373420 };
@@ -382,6 +429,7 @@ def create_defualt_nix(dependencies_list, meta_dict):
382429 default = pythonApp;
383430}
384431"""
432+ )
385433 return nix_content , need_review_packages_list
386434
387435
@@ -493,6 +541,14 @@ def main():
493541 # Parse arguments
494542 args = parser .parse_args ()
495543
544+ # No argument is provided
545+ if len (sys .argv ) == 1 and Path ("default.nix" ).exists ():
546+ print ("Info: 'default.nix' exists and no arguments provided." )
547+ print ("Options:" )
548+ print (" --generate Re-generate default.nix" )
549+ print (" --test Test build with existing default.nix" )
550+ sys .exit (0 )
551+
496552 if args .generate or not Path ("default.nix" ).exists ():
497553 # Check if "nix-prefetch-url" is available
498554 if not shutil .which ("nix-prefetch-url" ):
@@ -502,6 +558,7 @@ def main():
502558 print ("Generating default.nix" )
503559 pyproject_data = read_pyproject_toml ()
504560 meta_dict = extract_project_meta (pyproject_data )
561+
505562 dependencies_list = extract_project_dependencies (pyproject_data )
506563 defualt_nix_content , need_review = create_defualt_nix (dependencies_list , meta_dict )
507564 with open ("default.nix" , "w" ) as file :
0 commit comments