# Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. import argparse import json import os import pathlib import sys from typing import Dict, List, Optional, Sequence, Tuple, Union LIB_ROOT = pathlib.Path(__file__).parent / "lib" / "python" sys.path.insert(0, os.fspath(LIB_ROOT)) import tomli from importlib_metadata import metadata from packaging.requirements import Requirement DEFAULT_SEVERITY = "3" # 'Hint' try: SEVERITY = int(os.getenv("VSCODE_MISSING_PGK_SEVERITY", DEFAULT_SEVERITY)) except ValueError: SEVERITY = int(DEFAULT_SEVERITY) def parse_args(argv: Optional[Sequence[str]] = None): if argv is None: argv = sys.argv[1:] parser = argparse.ArgumentParser( description="Check for installed packages against requirements" ) parser.add_argument("FILEPATH", type=str, help="Path to requirements.[txt, in]") return parser.parse_args(argv) def parse_requirements(line: str) -> Optional[Requirement]: try: req = Requirement(line.strip("\\")) if req.marker is None: return req elif req.marker.evaluate(): return req except Exception: pass return None def process_requirements(req_file: pathlib.Path) -> List[Dict[str, Union[str, int]]]: diagnostics = [] for n, line in enumerate(req_file.read_text(encoding="utf-8").splitlines()): if line.startswith(("#", "-", " ")) or line == "": continue req = parse_requirements(line) if req: try: # Check if package is installed metadata(req.name) except Exception: diagnostics.append( { "line": n, "character": 0, "endLine": n, "endCharacter": len(req.name), "package": req.name, "code": "not-installed", "severity": SEVERITY, } ) return diagnostics def get_pos(lines: List[str], text: str) -> Tuple[int, int, int, int]: for n, line in enumerate(lines): index = line.find(text) if index >= 0: return n, index, n, index + len(text) return (0, 0, 0, 0) def process_pyproject(req_file: pathlib.Path) -> List[Dict[str, Union[str, int]]]: diagnostics = [] try: raw_text = req_file.read_text(encoding="utf-8") pyproject = tomli.loads(raw_text) except Exception: return diagnostics lines = raw_text.splitlines() reqs = pyproject.get("project", {}).get("dependencies", []) for raw_req in reqs: req = parse_requirements(raw_req) n, start, _, end = get_pos(lines, raw_req) if req: try: # Check if package is installed metadata(req.name) except Exception: diagnostics.append( { "line": n, "character": start, "endLine": n, "endCharacter": end, "package": req.name, "code": "not-installed", "severity": SEVERITY, } ) return diagnostics def get_diagnostics(req_file: pathlib.Path) -> List[Dict[str, Union[str, int]]]: diagnostics = [] if not req_file.exists(): return diagnostics if req_file.name == "pyproject.toml": diagnostics = process_pyproject(req_file) else: diagnostics = process_requirements(req_file) return diagnostics def main(): args = parse_args() diagnostics = get_diagnostics(pathlib.Path(args.FILEPATH)) print(json.dumps(diagnostics, ensure_ascii=False)) if __name__ == "__main__": main()