1010import tomli
1111from packageurl import PackageURL
1212from packaging .requirements import InvalidRequirement , Requirement
13+ from packaging .specifiers import InvalidSpecifier
1314from packaging .utils import InvalidWheelFilename , parse_wheel_filename
1415
1516from macaron .build_spec_generator .build_command_patcher import CLI_COMMAND_PATCHES , patch_commands
@@ -110,14 +111,15 @@ def resolve_fields(self, purl: PackageURL) -> None:
110111
111112 pypi_package_json = pypi_registry .find_or_create_pypi_asset (purl .name , purl .version , registry_info )
112113 patched_build_commands : list [list [str ]] = []
114+ build_requires_set : set [str ] = set ()
115+ build_backends_set : set [str ] = set ()
116+ parsed_build_requires : dict [str , str ] = {}
117+ python_version_set : set [str ] = set ()
118+ wheel_name_python_version_list : list [str ] = []
119+ wheel_name_platforms : set [str ] = set ()
113120
114121 if pypi_package_json is not None :
115122 if pypi_package_json .package_json or pypi_package_json .download (dest = "" ):
116- requires_array : list [str ] = []
117- build_backends : dict [str , str ] = {}
118- python_version_set : set [str ] = set ()
119- wheel_name_python_version_list : list [str ] = []
120- wheel_name_platforms : set [str ] = set ()
121123
122124 # Get the Python constraints from the PyPI JSON response.
123125 json_releases = pypi_package_json .get_releases ()
@@ -135,59 +137,62 @@ def resolve_fields(self, purl: PackageURL) -> None:
135137 wheel_contents , metadata_contents = self .read_directory (pypi_package_json .wheel_path , purl )
136138 generator , version = self .read_generator_line (wheel_contents )
137139 if generator != "" :
138- build_backends [generator ] = "==" + version
139- if generator != "setuptools" :
140- # Apply METADATA heuristics to determine setuptools version.
141- if "License-File" in metadata_contents :
142- build_backends ["setuptools" ] = "==" + defaults .get (
143- "heuristic.pypi" , "setuptools_version_emitting_license"
144- )
145- elif "Platform: UNKNOWN" in metadata_contents :
146- build_backends ["setuptools" ] = "==" + defaults .get (
147- "heuristic.pypi" , "setuptools_version_emitting_platform_unknown"
148- )
149- else :
150- build_backends ["setuptools" ] = "==" + defaults .get (
151- "heuristic.pypi" , "default_setuptools"
152- )
140+ parsed_build_requires [generator ] = "==" + version .replace (" " , "" )
141+ # Apply METADATA heuristics to determine setuptools version.
142+ elif "License-File" in metadata_contents :
143+ parsed_build_requires ["setuptools" ] = "==" + defaults .get (
144+ "heuristic.pypi" , "setuptools_version_emitting_license"
145+ )
146+ elif "Platform: UNKNOWN" in metadata_contents :
147+ parsed_build_requires ["setuptools" ] = "==" + defaults .get (
148+ "heuristic.pypi" , "setuptools_version_emitting_platform_unknown"
149+ )
153150 except SourceCodeError :
154151 logger .debug ("Could not find pure wheel matching this PURL" )
155152
156153 logger .debug ("From .dist_info:" )
157- logger .debug (build_backends )
154+ logger .debug (parsed_build_requires )
158155
159156 try :
160157 with pypi_package_json .sourcecode ():
161158 try :
162159 pyproject_content = pypi_package_json .get_sourcecode_file_contents ("pyproject.toml" )
163160 content = tomli .loads (pyproject_content .decode ("utf-8" ))
164- build_system : dict [str , list [str ]] = content .get ("build-system" , {})
165- requires_array = build_system .get ("requires" , [])
161+ requires = json_extract (content , ["build-system" , "requires" ], list )
162+ if requires :
163+ build_requires_set .update (elem .replace (" " , "" ) for elem in requires )
164+ backend = json_extract (content , ["build-system" , "build-backend" ], str )
165+ if backend :
166+ build_backends_set .add (backend .replace (" " , "" ))
166167
167168 python_version_constraint = json_extract (content , ["project" , "requires-python" ], str )
168169 if python_version_constraint :
169170 python_version_set .add (python_version_constraint .replace (" " , "" ))
170- logger .debug ("From pyproject.toml:" )
171- logger .debug (requires_array )
172- except SourceCodeError :
173- logger .debug ("No pyproject.toml found" )
174- except SourceCodeError :
175- logger .debug ("No source distribution found" )
176-
177- # Merge in pyproject.toml information only when the wheel dist_info does not contain the same
171+ logger .debug (
172+ "After analyzing pyproject.toml from the sdist: build-requires: %s, build_backend: %s" ,
173+ build_requires_set ,
174+ build_backends_set ,
175+ )
176+ except TypeError as error :
177+ logger .debug (
178+ "Found a type error while reading the pyproject.toml file from the sdist: %s" , error
179+ )
180+ except tomli .TOMLDecodeError as error :
181+ logger .debug ("Failed to read the pyproject.toml file from the sdist: %s" , error )
182+ except SourceCodeError as error :
183+ logger .debug ("No pyproject.toml found: %s" , error )
184+ except SourceCodeError as error :
185+ logger .debug ("No source distribution found: %s" , error )
186+
187+ # Merge in pyproject.toml information only when the wheel dist_info does not contain the same.
178188 # Hatch is an interesting example of this merge being required.
179- for requirement in requires_array :
189+ for requirement in build_requires_set :
180190 try :
181191 parsed_requirement = Requirement (requirement )
182- if parsed_requirement .name not in build_backends :
183- build_backends [parsed_requirement .name ] = str (parsed_requirement .specifier )
184- except InvalidRequirement :
185- logger .debug ("Malformed requirement encountered:" )
186- logger .debug (requirement )
187-
188- logger .debug ("Combined:" )
189- logger .debug (build_backends )
190- self .data ["build_backends" ] = build_backends
192+ if parsed_requirement .name not in parsed_build_requires :
193+ parsed_build_requires [parsed_requirement .name ] = str (parsed_requirement .specifier )
194+ except (InvalidRequirement , InvalidSpecifier ) as error :
195+ logger .debug ("Malformed requirement encountered %s : %s" , requirement , error )
191196
192197 try :
193198 # Get information from the wheel file name.
@@ -206,23 +211,33 @@ def resolve_fields(self, purl: PackageURL) -> None:
206211 if "any" in wheel_name_platforms :
207212 patched_build_commands = self .get_default_build_commands (self .data ["build_tools" ])
208213
209- if not patched_build_commands :
210- # Resolve and patch build commands.
211- selected_build_commands = self . data [ "build_commands" ] or self . get_default_build_commands (
212- self . data [ "build_tools" ]
213- )
214+ # If we were not able to find any build and backends, use the default setuptools.
215+ if not parsed_build_requires :
216+ parsed_build_requires [ "setuptools" ] = "==" + defaults . get ( "heuristic.pypi" , "default_setuptools" )
217+ if not build_backends_set :
218+ build_backends_set . add ( "setuptools.build_meta" )
214219
215- patched_build_commands = (
216- patch_commands (
217- cmds_sequence = selected_build_commands ,
218- patches = CLI_COMMAND_PATCHES ,
219- )
220- or []
220+ logger .debug ("Combined build-requires: %s" , parsed_build_requires )
221+ self .data ["build_requires" ] = parsed_build_requires
222+ self .data ["build_backends" ] = list (build_backends_set )
223+
224+ if not patched_build_commands :
225+ # Resolve and patch build commands.
226+ selected_build_commands = self .data ["build_commands" ] or self .get_default_build_commands (
227+ self .data ["build_tools" ]
228+ )
229+
230+ patched_build_commands = (
231+ patch_commands (
232+ cmds_sequence = selected_build_commands ,
233+ patches = CLI_COMMAND_PATCHES ,
221234 )
222- if not patched_build_commands :
223- raise GenerateBuildSpecError (f"Failed to patch command sequences { selected_build_commands } ." )
235+ or []
236+ )
237+ if not patched_build_commands :
238+ raise GenerateBuildSpecError (f"Failed to patch command sequences { selected_build_commands } ." )
224239
225- self .data ["build_commands" ] = patched_build_commands
240+ self .data ["build_commands" ] = patched_build_commands
226241
227242 def read_directory (self , wheel_path : str , purl : PackageURL ) -> tuple [str , str ]:
228243 """
@@ -286,5 +301,6 @@ def read_generator_line(self, wheel_contents: str) -> tuple[str, str]:
286301 for line in wheel_contents .splitlines ():
287302 if line .startswith ("Generator:" ):
288303 split_line = line .split (" " )
289- return split_line [1 ], split_line [2 ]
304+ if len (split_line ) > 2 :
305+ return split_line [1 ], split_line [2 ]
290306 return "" , ""
0 commit comments