55import click
66from packaging .requirements import InvalidRequirement , Requirement
77
8- from fromager import requirements_file
8+ from fromager import bootstrapper , context , progress , requirements_file
9+ from fromager .log import requirement_ctxvar
10+ from fromager .requirements_file import RequirementType
911
1012logger = logging .getLogger (__name__ )
1113
1719 required = True ,
1820 type = click .Path (exists = False , path_type = pathlib .Path ),
1921)
20- def lint_requirements (input_files_path : list [pathlib .Path ]) -> None :
22+ @click .pass_obj
23+ def lint_requirements (
24+ wkctx : context .WorkContext , input_files_path : list [pathlib .Path ]
25+ ) -> None :
2126 """
2227 Command to lint the constraints.txt and requirements.txt files
2328 This command takes a single wildcard path string for constraints.txt and requirements.txt.
2429 It checks the formatting of these files and reports issues if found. Files with names that
2530 end with constraints.txt (e.g. constraints.txt, global-constraints.txt, etc.) are not allowed
26- to contain extra dependencies.
31+ to contain extra dependencies. Additionally, it resolves valid input requirements to ensure
32+ we can find a matching version of each package.
2733 """
2834
2935 if len (input_files_path ) == 0 :
@@ -32,6 +38,15 @@ def lint_requirements(input_files_path: list[pathlib.Path]) -> None:
3238
3339 flag = True
3440
41+ # Create bootstrapper for requirement resolution
42+ bt = bootstrapper .Bootstrapper (
43+ ctx = wkctx ,
44+ progressbar = progress .Progressbar (None ),
45+ prev_graph = None ,
46+ cache_wheel_server_url = None ,
47+ sdist_only = True ,
48+ )
49+
3550 for path in input_files_path :
3651 parsed_lines = requirements_file .parse_requirements_file (path )
3752 unique_entries : dict [str , Requirement ] = {}
@@ -47,6 +62,24 @@ def lint_requirements(input_files_path: list[pathlib.Path]) -> None:
4762 raise InvalidRequirement (
4863 "Constraints files cannot contain extra dependencies"
4964 )
65+
66+ # Resolve the requirement to ensure it can be found
67+ # Skip resolution for constraints files as they should only specify versions
68+ if not path .name .endswith ("constraints.txt" ):
69+ token = requirement_ctxvar .set (requirement )
70+ try :
71+ _ , version = bt .resolve_version (
72+ req = requirement ,
73+ req_type = RequirementType .TOP_LEVEL ,
74+ )
75+ logger .info (f"{ requirement } resolves to { version } " )
76+ except Exception as resolve_err :
77+ logger .error (
78+ f"{ path } : { line } : Failed to resolve requirement: { resolve_err } "
79+ )
80+ flag = False
81+ finally :
82+ requirement_ctxvar .reset (token )
5083 except InvalidRequirement as err :
5184 logger .error (f"{ path } : { line } : { err } " )
5285 flag = False
0 commit comments