scripts/check-env.py (177 lines of code) (raw):

#!/usr/bin/env python3 # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. import platform import subprocess import sys from typing import Callable, Optional, Set, Tuple import click import psutil from packaging.version import InvalidVersion, Version class Requirement: def __init__( self, name: str, ideal_range: Tuple[Version, Version], supported_range: Tuple[Version, Version], req_type: str, command: str, version_post_process: Optional[Callable[[str], str]] = None, ): self.name = name self.ideal_range = ideal_range self.supported_range = supported_range self.req_type = req_type self.command = command self.version_post_process = version_post_process self.version = self.get_version() self.status = self.check_version() def get_version(self) -> Optional[str]: try: version = subprocess.check_output(self.command, shell=True).decode().strip() # noqa: S602 if self.version_post_process: version = self.version_post_process(version) return version.split()[-1] except subprocess.CalledProcessError: return None def check_version(self) -> str: if self.version is None: return "❌ Not Installed" try: version_number = Version(self.version) except InvalidVersion: return "❌ Invalid Version Format" ideal_min, ideal_max = self.ideal_range supported_min, supported_max = self.supported_range if ideal_min <= version_number <= ideal_max: return "✅ Ideal" elif supported_min <= version_number: return "🟡 Supported" else: return "❌ Unsupported" def format_result(self) -> str: ideal_range_str = f"{self.ideal_range[0]} - {self.ideal_range[1]}" supported_range_str = f"{self.supported_range[0]} - {self.supported_range[1]}" return f"{self.status.split()[0]} {self.name:<25} {self.version or 'N/A':<25} {ideal_range_str:<25} {supported_range_str:<25}" # noqa: E501 def check_memory(min_gb: int) -> str: total_memory = psutil.virtual_memory().total / (1024**3) if total_memory >= min_gb: return f"✅ Memory: {total_memory:.2f} GB" else: return f"❌ Memory: {total_memory:.2f} GB (Minimum required: {min_gb} GB)" def get_cpu_info() -> str: cpu_count = psutil.cpu_count(logical=True) cpu_freq = psutil.cpu_freq() cpu_info = ( f"{cpu_count} cores at {cpu_freq.current:.2f} MHz" if cpu_freq else f"{cpu_count} cores" ) return f"CPU: {cpu_info}" def get_docker_platform() -> str: try: output = ( subprocess.check_output( # noqa: S602 "docker info --format '{{.OperatingSystem}}'", # noqa: S607 shell=True, # noqa: S607 ) .decode() .strip() ) if "Docker Desktop" in output: return f"Docker Platform: {output} ({platform.system()})" return f"Docker Platform: {output}" except subprocess.CalledProcessError: return "Docker Platform: ❌ Not Detected" @click.command( help=""" This script checks the local environment for various software versions and other requirements, providing feedback on whether they are ideal, supported, or unsupported. """ # noqa: E501 ) @click.option( "--docker", is_flag=True, help="Check Docker and Docker Compose requirements" ) @click.option( "--frontend", is_flag=True, help="Check frontend requirements (npm, Node.js, memory)", ) @click.option("--backend", is_flag=True, help="Check backend requirements (Python)") def main(docker: bool, frontend: bool, backend: bool) -> None: # noqa: C901 requirements = [ Requirement( "python", (Version("3.10.0"), Version("3.10.999")), (Version("3.9.0"), Version("3.11.999")), "backend", "python --version", ), Requirement( "npm", (Version("10.0.0"), Version("999.999.999")), (Version("10.0.0"), Version("999.999.999")), "frontend", "npm -v", ), Requirement( "node", (Version("20.0.0"), Version("20.999.999")), (Version("20.0.0"), Version("20.999.999")), "frontend", "node -v", ), Requirement( "docker", (Version("20.10.0"), Version("999.999.999")), (Version("19.0.0"), Version("999.999.999")), "docker", "docker --version", lambda v: v.split(",")[0], ), Requirement( "docker-compose", (Version("2.28.0"), Version("999.999.999")), (Version("1.29.0"), Version("999.999.999")), "docker", "docker-compose --version", ), Requirement( "git", (Version("2.30.0"), Version("999.999.999")), (Version("2.20.0"), Version("999.999.999")), "backend", "git --version", ), ] print("==================") print("System Information") print("==================") print(f"OS: {platform.system()} {platform.release()}") print(get_cpu_info()) print(get_docker_platform()) print("\n") check_req_types: Set[str] = set() if docker: check_req_types.add("docker") if frontend: check_req_types.add("frontend") if backend: check_req_types.add("backend") if not check_req_types: check_req_types.update(["docker", "frontend", "backend"]) headers = ["Status", "Software", "Version Found", "Ideal Range", "Supported Range"] row_format = "{:<2} {:<25} {:<25} {:<25} {:<25}" print("=" * 100) print(row_format.format(*headers)) print("=" * 100) all_ok = True for requirement in requirements: if requirement.req_type in check_req_types: result = requirement.format_result() if "❌" in requirement.status: all_ok = False print(result) if "frontend" in check_req_types: memory_check = check_memory(12) if "❌" in memory_check: all_ok = False print(memory_check) if not all_ok: sys.exit(1) if __name__ == "__main__": main()