add environment inventory
This commit is contained in:
parent
b9241780d0
commit
f1a33fc1c0
|
|
@ -0,0 +1,620 @@
|
|||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
class EnvDiscovery:
|
||||
def __init__(self):
|
||||
# Structured results dictionary.
|
||||
self.results = {
|
||||
"os": {},
|
||||
"cli_tools": {},
|
||||
"python": {"installations": [], "env_tools": {}},
|
||||
"package_managers": {},
|
||||
"libraries": {},
|
||||
"node": {}
|
||||
}
|
||||
# Common CLI tools. Added additional critical dev tools.
|
||||
self._cli_tool_names = [
|
||||
"fd", "rg", "fzf", "git", "g++", "gcc", "clang", "cmake", "make",
|
||||
"pkg-config", "ninja", "autoconf", "automake", "libtool", "meson", "scons"
|
||||
]
|
||||
# Python environment tools.
|
||||
self._py_env_tools = {
|
||||
"virtualenv": "virtualenv",
|
||||
"uv": "uv",
|
||||
"pipenv": "pipenv",
|
||||
"poetry": "poetry",
|
||||
"conda": "conda",
|
||||
"pyenv": "pyenv",
|
||||
"pipx": "pipx"
|
||||
}
|
||||
# Package managers.
|
||||
self._package_managers = [
|
||||
"apt", "apt-get", "dnf", "yum", "pacman", "paru", "zypper",
|
||||
"brew", "winget", "choco"
|
||||
]
|
||||
# Expanded libraries detection list.
|
||||
# Each entry maps a library key to a dict with possible keys:
|
||||
# - 'pkg': pkg-config name if available.
|
||||
# - 'headers': list of header paths relative to common include directories.
|
||||
self._libraries = {
|
||||
# Graphics & Game Dev:
|
||||
"SDL2": {"pkg": "sdl2", "headers": ["SDL2/SDL.h", "SDL.h"]},
|
||||
"OpenGL": {"pkg": "gl", "headers": ["GL/gl.h", "OpenGL/gl.h"]},
|
||||
"Vulkan": {"pkg": "vulkan", "headers": ["vulkan/vulkan.h"]},
|
||||
"DirectX": {"headers": []}, # Windows only; detection via headers is non-trivial.
|
||||
"GLFW": {"pkg": "glfw3", "headers": ["GLFW/glfw3.h"]},
|
||||
"Raylib": {"pkg": "raylib", "headers": ["raylib.h"]},
|
||||
"SFML": {"headers": ["SFML/Graphics.hpp", "SFML/Window.hpp"]},
|
||||
"Allegro": {"pkg": "allegro", "headers": ["allegro5/allegro.h"]},
|
||||
"OGRE": {"headers": ["OGRE/Ogre.h"]},
|
||||
"Irrlicht": {"headers": ["irrlicht.h"]},
|
||||
"bgfx": {"headers": ["bgfx/bgfx.h"]},
|
||||
"Magnum": {"headers": ["Magnum/Platform/GlfwApplication.h"]},
|
||||
"Assimp": {"pkg": "assimp", "headers": ["assimp/Importer.hpp"]},
|
||||
"DearImGui": {"headers": ["imgui.h"]},
|
||||
"Cairo": {"pkg": "cairo", "headers": ["cairo.h"]},
|
||||
"NanoVG": {"headers": ["nanovg.h"]},
|
||||
# Physics Engines:
|
||||
"Bullet": {"headers": ["bullet/btBulletDynamicsCommon.h"]},
|
||||
"PhysX": {"headers": []},
|
||||
"ODE": {"pkg": "ode", "headers": ["ode/ode.h"]},
|
||||
"Box2D": {"pkg": "box2d", "headers": ["box2d/box2d.h"]},
|
||||
"JoltPhysics": {"headers": ["Jolt/Jolt.h"]},
|
||||
"MuJoCo": {"headers": ["mujoco.h"]},
|
||||
"Newton": {"pkg": "newton", "headers": ["Newton/Newton.h"]},
|
||||
# Math & Linear Algebra:
|
||||
"Eigen": {"headers": ["Eigen/Dense"]},
|
||||
"GLM": {"headers": ["glm/glm.hpp"]},
|
||||
"Armadillo": {"pkg": "armadillo", "headers": ["armadillo"]},
|
||||
"BLAS": {"headers": []},
|
||||
"LAPACK": {"headers": []},
|
||||
"OpenBLAS": {"headers": []},
|
||||
"IntelMKL": {"headers": []},
|
||||
"Boost_uBLAS": {"headers": ["boost/numeric/ublas/matrix.hpp"]},
|
||||
"Blaze": {"headers": ["blaze/Blaze.h"]},
|
||||
"Blitz++": {"headers": ["blitz/array.h"]},
|
||||
"xtensor": {"headers": ["xtensor/xarray.hpp"]},
|
||||
"GSL": {"pkg": "gsl", "headers": ["gsl/gsl_errno.h"]},
|
||||
# Machine Learning & AI:
|
||||
"TensorFlow": {"pkg": "tensorflow", "headers": ["tensorflow/c/c_api.h"]},
|
||||
"PyTorch": {"pkg": "torch", "headers": []},
|
||||
"ONNX": {"pkg": "onnx", "headers": []},
|
||||
"OpenCV": {"pkg": "opencv", "headers": ["opencv2/opencv.hpp"]},
|
||||
"scikit-learn": {"headers": []},
|
||||
"Caffe": {"headers": ["caffe/caffe.hpp"]},
|
||||
"MXNet": {"headers": ["mxnet-cpp/MxNetCpp.h"]},
|
||||
"XGBoost": {"pkg": "xgboost", "headers": []},
|
||||
"LightGBM": {"headers": []},
|
||||
"dlib": {"pkg": "dlib", "headers": ["dlib/dlib.h"]},
|
||||
"OpenVINO": {"headers": []},
|
||||
"TensorRT": {"headers": []},
|
||||
# Networking & Communication:
|
||||
"Boost_Asio": {"headers": ["boost/asio.hpp"]},
|
||||
"libcurl": {"pkg": "libcurl", "headers": ["curl/curl.h"]},
|
||||
"ZeroMQ": {"pkg": "libzmq", "headers": ["zmq.h"]},
|
||||
"gRPC": {"pkg": "grpc", "headers": ["grpc/grpc.h"]},
|
||||
"Thrift": {"headers": ["thrift/Thrift.h"]},
|
||||
"libevent": {"pkg": "libevent", "headers": ["event2/event.h"]},
|
||||
"libuv": {"pkg": "libuv", "headers": ["uv.h"]},
|
||||
"Boost_Beast": {"headers": ["boost/beast.hpp"]},
|
||||
"libwebsockets": {"pkg": "libwebsockets", "headers": ["libwebsockets.h"]},
|
||||
"MQTT": {"pkg": "paho-mqtt3c", "headers": ["MQTTClient.h"]},
|
||||
"APR": {"pkg": "apr-1", "headers": ["apr.h"]},
|
||||
"nng": {"pkg": "nng", "headers": ["nng/nng.h"]},
|
||||
# Compression & Encoding:
|
||||
"zlib": {"pkg": "zlib", "headers": ["zlib.h"]},
|
||||
"LZ4": {"pkg": "lz4", "headers": ["lz4.h"]},
|
||||
"Zstd": {"pkg": "zstd", "headers": ["zstd.h"]},
|
||||
"Brotli": {"pkg": "brotli", "headers": ["brotli/decode.h"]},
|
||||
"bzip2": {"pkg": "bzip2", "headers": ["bzlib.h"]},
|
||||
"xz": {"pkg": "liblzma", "headers": ["lzma.h"]},
|
||||
"Snappy": {"pkg": "snappy", "headers": ["snappy.h"]},
|
||||
"libpng": {"pkg": "libpng", "headers": ["png.h"]},
|
||||
"libjpeg": {"pkg": "libjpeg", "headers": ["jpeglib.h"]},
|
||||
"libtiff": {"pkg": "libtiff-4", "headers": ["tiffio.h"]},
|
||||
"libwebp": {"pkg": "libwebp", "headers": ["webp/encode.h"]},
|
||||
"FFmpeg": {"pkg": "libavcodec", "headers": ["libavcodec/avcodec.h"]},
|
||||
"GStreamer": {"pkg": "gstreamer-1.0", "headers": ["gst/gst.h"]},
|
||||
"libogg": {"pkg": "libogg", "headers": ["ogg/ogg.h"]},
|
||||
"libvorbis": {"pkg": "vorbis", "headers": ["vorbis/codec.h"]},
|
||||
"libFLAC": {"pkg": "flac", "headers": ["FLAC/stream_encoder.h"]},
|
||||
# Databases & Data Storage:
|
||||
"SQLite": {"pkg": "sqlite3", "headers": ["sqlite3.h"]},
|
||||
"PostgreSQL": {"pkg": "libpq", "headers": ["libpq-fe.h"]},
|
||||
"MySQL": {"pkg": "mysqlclient", "headers": ["mysql.h"]},
|
||||
"Redis": {"headers": []},
|
||||
"LevelDB": {"headers": ["leveldb/db.h"]},
|
||||
"RocksDB": {"headers": ["rocksdb/db.h"]},
|
||||
"BerkeleyDB": {"headers": ["db.h"]},
|
||||
"HDF5": {"pkg": "hdf5", "headers": ["hdf5.h"]},
|
||||
# Parallel Computing & GPU:
|
||||
"OpenMP": {"headers": []},
|
||||
"MPI": {"pkg": "mpi", "headers": ["mpi.h"]},
|
||||
"CUDA": {"pkg": "cuda", "headers": ["cuda.h"]},
|
||||
"OpenCL": {"pkg": "OpenCL", "headers": ["CL/cl.h"]},
|
||||
"oneAPI": {"headers": []},
|
||||
"HIP": {"headers": []},
|
||||
"OpenACC": {"headers": []},
|
||||
"TBB": {"pkg": "tbb", "headers": ["tbb/tbb.h"]},
|
||||
"cuDNN": {"headers": []},
|
||||
"MicrosoftMPI": {"headers": []},
|
||||
# Cryptography & Security:
|
||||
"OpenSSL": {"pkg": "openssl", "headers": ["openssl/ssl.h"]},
|
||||
"LibreSSL": {"pkg": "openssl", "headers": ["openssl/ssl.h"]},
|
||||
"BoringSSL": {"headers": []},
|
||||
"libsodium": {"pkg": "sodium", "headers": ["sodium.h"]},
|
||||
"Crypto++": {"headers": ["cryptopp/cryptlib.h"]},
|
||||
"Botan": {"headers": ["botan/botan.h"]},
|
||||
"GnuTLS": {"pkg": "gnutls", "headers": ["gnutls/gnutls.h"]},
|
||||
"mbedTLS": {"pkg": "mbedtls", "headers": ["mbedtls/ssl.h"]},
|
||||
"wolfSSL": {"pkg": "wolfssl", "headers": ["wolfssl/options.h"]},
|
||||
# Scripting & Embedding:
|
||||
"Python_C_API": {"headers": ["Python.h"]},
|
||||
"Lua": {"pkg": "lua", "headers": ["lua.h"]},
|
||||
"LuaJIT": {"pkg": "luajit", "headers": ["luajit.h"]},
|
||||
"V8": {"headers": ["v8.h"]},
|
||||
"Duktape": {"headers": ["duktape.h"]},
|
||||
"SpiderMonkey": {"headers": ["jsapi.h"]},
|
||||
"JavaScriptCore": {"headers": ["JavaScriptCore/JavaScript.h"]},
|
||||
"ChakraCore": {"headers": ["ChakraCore.h"]},
|
||||
"Tcl": {"pkg": "tcl", "headers": ["tcl.h"]},
|
||||
"Guile": {"headers": ["libguile.h"]},
|
||||
"Mono": {"headers": ["mono/jit/jit.h"]},
|
||||
# Audio & Multimedia:
|
||||
"OpenAL": {"pkg": "openal", "headers": ["AL/al.h"]},
|
||||
"PortAudio": {"pkg": "portaudio-2.0", "headers": ["portaudio.h"]},
|
||||
"FMOD": {"headers": []},
|
||||
"SoLoud": {"headers": ["soloud.h"]},
|
||||
"RtAudio": {"headers": ["RtAudio.h"]},
|
||||
"SDL_mixer": {"pkg": "SDL2_mixer", "headers": ["SDL2/SDL_mixer.h"]},
|
||||
"OpenAL_Soft": {"pkg": "openal", "headers": ["AL/al.h"]},
|
||||
"libsndfile": {"pkg": "sndfile", "headers": ["sndfile.h"]},
|
||||
"Jack": {"pkg": "jack", "headers": ["jack/jack.h"]},
|
||||
# Dev Utilities & Frameworks:
|
||||
"Boost": {"headers": ["boost/config.hpp"]},
|
||||
"Qt": {"headers": ["QtCore/QtCore"]},
|
||||
"wxWidgets": {"headers": ["wx/wx.h"]},
|
||||
"GTK": {"pkg": "gtk+-3.0", "headers": ["gtk/gtk.h"]},
|
||||
"ncurses": {"pkg": "ncurses", "headers": ["ncurses.h"]},
|
||||
"Poco": {"headers": ["Poco/Foundation.h"]},
|
||||
"ICU": {"pkg": "icu-uc", "headers": ["unicode/utypes.h"]},
|
||||
"RapidJSON": {"headers": ["rapidjson/document.h"]},
|
||||
"nlohmann_json": {"headers": ["nlohmann/json.hpp"]},
|
||||
"json-c": {"pkg": "json-c", "headers": ["json-c/json.h"]},
|
||||
"YAML_cpp": {"headers": ["yaml-cpp/yaml.h"]},
|
||||
"spdlog": {"headers": ["spdlog/spdlog.h"]},
|
||||
"log4cxx": {"headers": ["log4cxx/logger.h"]},
|
||||
"glog": {"headers": ["glog/logging.h"]},
|
||||
"GoogleTest": {"headers": ["gtest/gtest.h"]},
|
||||
"BoostTest": {"headers": ["boost/test/unit_test.hpp"]},
|
||||
"pkg-config": {"headers": []},
|
||||
"CMake": {"headers": []},
|
||||
"GLib": {"pkg": "glib-2.0", "headers": ["glib.h"]}
|
||||
}
|
||||
# List of common include directories to search for headers.
|
||||
# Expanded to cover multiple common Homebrew paths on macOS and Linuxbrew.
|
||||
self._include_paths = [
|
||||
Path("/usr/include"),
|
||||
Path("/usr/local/include"),
|
||||
Path("/opt/homebrew/include"),
|
||||
Path("/home/linuxbrew/.linuxbrew/include"),
|
||||
Path("/usr/local/Homebrew/include")
|
||||
]
|
||||
# Linux distribution info.
|
||||
self._distro = {}
|
||||
if platform.system() == "Linux":
|
||||
self._distro = self._get_linux_distro()
|
||||
|
||||
def _get_linux_distro(self):
|
||||
distro = {}
|
||||
try:
|
||||
with open("/etc/os-release") as f:
|
||||
for line in f:
|
||||
if "=" not in line:
|
||||
continue
|
||||
key, val = line.strip().split("=", 1)
|
||||
distro[key] = val.strip('"')
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
return distro
|
||||
|
||||
def discover(self):
|
||||
self._detect_os()
|
||||
self._detect_cli_tools()
|
||||
self._detect_python()
|
||||
self._detect_python_env_tools()
|
||||
self._detect_package_managers()
|
||||
self._detect_libraries()
|
||||
self._detect_node()
|
||||
return self.results
|
||||
|
||||
def _detect_os(self):
|
||||
os_type = platform.system()
|
||||
os_info = {}
|
||||
if os_type == "Windows":
|
||||
os_info["name"] = "Windows"
|
||||
os_info["wsl"] = False
|
||||
elif os_type == "Linux":
|
||||
release = platform.uname().release
|
||||
if "Microsoft" in release or release.lower().endswith("microsoft"):
|
||||
os_info["name"] = "Linux (WSL)"
|
||||
os_info["wsl"] = True
|
||||
else:
|
||||
os_info["name"] = "Linux"
|
||||
os_info["wsl"] = False
|
||||
if self._distro:
|
||||
name = self._distro.get("PRETTY_NAME") or self._distro.get("NAME")
|
||||
version = self._distro.get("VERSION_ID") or self._distro.get("VERSION")
|
||||
if name:
|
||||
os_info["distro"] = name
|
||||
if version:
|
||||
os_info["distro_version"] = version
|
||||
elif os_type == "Darwin":
|
||||
os_info["name"] = "macOS"
|
||||
os_info["wsl"] = False
|
||||
else:
|
||||
os_info["name"] = os_type
|
||||
os_info["wsl"] = False
|
||||
self.results["os"] = os_info
|
||||
|
||||
def _detect_cli_tools(self):
|
||||
tools_found = {}
|
||||
for tool in self._cli_tool_names:
|
||||
path = shutil.which(tool)
|
||||
if path:
|
||||
version = None
|
||||
if tool in ("g++", "gcc", "clang", "git"):
|
||||
try:
|
||||
out = subprocess.check_output([tool, "--version"], text=True, stderr=subprocess.STDOUT, timeout=1)
|
||||
version = out.splitlines()[0].strip()
|
||||
except Exception:
|
||||
version = None
|
||||
tools_found[tool] = {"found": True}
|
||||
if version:
|
||||
tools_found[tool]["version"] = version
|
||||
else:
|
||||
tools_found[tool] = {"found": False}
|
||||
self.results["cli_tools"] = tools_found
|
||||
|
||||
def _detect_python(self):
|
||||
installations = []
|
||||
if platform.system() == "Windows":
|
||||
launcher = shutil.which("py")
|
||||
if launcher:
|
||||
try:
|
||||
out = subprocess.check_output([launcher, "-0p"], text=True, timeout=2)
|
||||
for line in out.splitlines():
|
||||
line = line.strip()
|
||||
if not line or not line.startswith("-V:"):
|
||||
continue
|
||||
after = line.split(":", 1)[1]
|
||||
parts = after.strip().split(None, 1)
|
||||
ver_str = parts[0].lstrip("v")
|
||||
py_path = parts[1] if len(parts) > 1 else ""
|
||||
installations.append({"version": ver_str, "path": py_path})
|
||||
except subprocess.CalledProcessError:
|
||||
pass
|
||||
if not installations:
|
||||
try:
|
||||
out = subprocess.check_output(["where", "python"], text=True, timeout=2)
|
||||
for path in out.splitlines():
|
||||
path = path.strip()
|
||||
if path and Path(path).name.lower().startswith("python"):
|
||||
ver = self._get_python_version(path)
|
||||
installations.append({"version": ver, "path": path})
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
common_names = ["python3", "python", "python2"]
|
||||
for major in [2, 3]:
|
||||
for minor in range(0, 15):
|
||||
common_names.append(f"python{major}.{minor}")
|
||||
seen_paths = set()
|
||||
for name in common_names:
|
||||
path = shutil.which(name)
|
||||
if path and path not in seen_paths:
|
||||
seen_paths.add(path)
|
||||
ver = self._get_python_version(path)
|
||||
installations.append({"version": ver, "path": path})
|
||||
installations = sorted(installations, key=lambda x: x.get("version", ""))
|
||||
self.results["python"]["installations"] = installations
|
||||
|
||||
def _get_python_version(self, python_path):
|
||||
try:
|
||||
out = subprocess.check_output([python_path, "--version"], stderr=subprocess.STDOUT, text=True, timeout=1)
|
||||
ver = out.strip().split()[1]
|
||||
return ver
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def _detect_python_env_tools(self):
|
||||
env_tools_status = {}
|
||||
venv_available = any(inst for inst in self.results["python"]["installations"]
|
||||
if inst.get("version") and inst["version"][0] == '3')
|
||||
env_tools_status["venv"] = {"available": venv_available, "built_in": True}
|
||||
for tool, display_name in self._py_env_tools.items():
|
||||
found_path = shutil.which(tool)
|
||||
if found_path:
|
||||
version = None
|
||||
try:
|
||||
if tool == "pyenv":
|
||||
out = subprocess.check_output([tool, "--version"], text=True, timeout=1)
|
||||
version = out.strip().split()[-1]
|
||||
elif tool in ("pipenv", "poetry", "conda", "pipx", "uv"):
|
||||
out = subprocess.check_output([tool, "--version"], text=True, timeout=2)
|
||||
version = out.strip().split()[-1]
|
||||
elif tool == "virtualenv":
|
||||
out = subprocess.check_output([tool, "--version"], text=True, timeout=2)
|
||||
version = out.strip()
|
||||
except Exception:
|
||||
version = None
|
||||
env_tools_status[display_name] = {"installed": True}
|
||||
if version:
|
||||
env_tools_status[display_name]["version"] = version
|
||||
else:
|
||||
env_tools_status[display_name] = {"installed": False}
|
||||
self.results["python"]["env_tools"] = env_tools_status
|
||||
|
||||
def _detect_package_managers(self):
|
||||
pkg_status = {}
|
||||
for mgr in self._package_managers:
|
||||
if platform.system() == "Windows":
|
||||
if mgr in ("apt", "apt-get", "dnf", "yum", "pacman", "paru", "zypper", "brew"):
|
||||
continue
|
||||
if platform.system() == "Darwin":
|
||||
if mgr in ("apt", "apt-get", "dnf", "yum", "pacman", "paru", "zypper", "winget", "choco"):
|
||||
continue
|
||||
if platform.system() == "Linux" and self._distro:
|
||||
distro_id = self._distro.get("ID", "").lower()
|
||||
if distro_id:
|
||||
if distro_id in ("debian", "ubuntu", "linuxmint"):
|
||||
if mgr in ("pacman", "paru", "yum", "dnf", "zypper"):
|
||||
continue
|
||||
if distro_id in ("fedora", "centos", "rhel", "rocky", "alma"):
|
||||
if mgr in ("apt", "apt-get", "pacman", "paru", "zypper"):
|
||||
continue
|
||||
if distro_id in ("arch", "manjaro", "endeavouros"):
|
||||
if mgr in ("apt", "apt-get", "dnf", "yum", "zypper"):
|
||||
continue
|
||||
if distro_id in ("opensuse", "suse"):
|
||||
if mgr in ("apt", "apt-get", "dnf", "yum", "pacman", "paru"):
|
||||
continue
|
||||
path = shutil.which(mgr)
|
||||
pkg_status[mgr] = {"found": bool(path)}
|
||||
if path:
|
||||
version = None
|
||||
try:
|
||||
if mgr in ("brew", "winget", "choco"):
|
||||
out = subprocess.check_output([mgr, "--version"], text=True, timeout=3)
|
||||
version_line = out.splitlines()[0].strip()
|
||||
version = version_line
|
||||
elif mgr in ("apt", "apt-get", "pacman", "paru", "dnf", "yum", "zypper"):
|
||||
out = subprocess.check_output([mgr, "--version"], text=True, timeout=2)
|
||||
version_line = out.splitlines()[0].strip()
|
||||
version = version_line
|
||||
except Exception:
|
||||
version = None
|
||||
if version:
|
||||
pkg_status[mgr]["version"] = version
|
||||
self.results["package_managers"] = pkg_status
|
||||
|
||||
def _detect_libraries(self):
|
||||
libs_found = {}
|
||||
have_pkg_config = bool(shutil.which("pkg-config"))
|
||||
for lib, info in self._libraries.items():
|
||||
lib_info = {"found": False}
|
||||
found = False
|
||||
ver = None
|
||||
cflags = None
|
||||
libs_flags = None
|
||||
header_paths = []
|
||||
if have_pkg_config and info.get("pkg"):
|
||||
pkg_name = info["pkg"]
|
||||
try:
|
||||
subprocess.check_output(["pkg-config", "--exists", pkg_name],
|
||||
stderr=subprocess.DEVNULL, timeout=1)
|
||||
found = True
|
||||
try:
|
||||
ver = subprocess.check_output(
|
||||
["pkg-config", "--modversion", pkg_name],
|
||||
text=True, timeout=1
|
||||
).strip()
|
||||
except Exception:
|
||||
ver = None
|
||||
try:
|
||||
cflags = subprocess.check_output(
|
||||
["pkg-config", "--cflags", pkg_name],
|
||||
text=True, timeout=1
|
||||
).strip()
|
||||
except Exception:
|
||||
cflags = None
|
||||
try:
|
||||
libs_flags = subprocess.check_output(
|
||||
["pkg-config", "--libs", pkg_name],
|
||||
text=True, timeout=1
|
||||
).strip()
|
||||
except Exception:
|
||||
libs_flags = None
|
||||
except subprocess.CalledProcessError:
|
||||
found = False
|
||||
if not found and info.get("headers"):
|
||||
for header in info["headers"]:
|
||||
for inc_dir in self._include_paths:
|
||||
header_file = inc_dir / header
|
||||
if header_file.exists():
|
||||
found = True
|
||||
header_paths.append(str(header_file))
|
||||
lib_info["found"] = found
|
||||
if ver:
|
||||
lib_info["version"] = ver
|
||||
if cflags:
|
||||
lib_info["cflags"] = cflags
|
||||
if libs_flags:
|
||||
lib_info["libs"] = libs_flags
|
||||
if header_paths:
|
||||
lib_info["header_paths"] = header_paths
|
||||
libs_found[lib] = lib_info
|
||||
self.results["libraries"] = libs_found
|
||||
|
||||
def _detect_node(self):
|
||||
node_info = {}
|
||||
node_path = shutil.which("node")
|
||||
if node_path:
|
||||
try:
|
||||
out = subprocess.check_output(["node", "--version"], text=True, timeout=1)
|
||||
node_info["node_version"] = out.strip()
|
||||
except Exception:
|
||||
node_info["node_version"] = "found"
|
||||
else:
|
||||
node_info["node_version"] = None
|
||||
npm_path = shutil.which("npm")
|
||||
if npm_path:
|
||||
try:
|
||||
out = subprocess.check_output(["npm", "--version"], text=True, timeout=1)
|
||||
node_info["npm_version"] = out.strip()
|
||||
except Exception:
|
||||
node_info["npm_version"] = "found"
|
||||
else:
|
||||
node_info["npm_version"] = None
|
||||
nvm_installed = False
|
||||
nvm_version = None
|
||||
if platform.system() == "Windows":
|
||||
if shutil.which("nvm"):
|
||||
nvm_installed = True
|
||||
try:
|
||||
out = subprocess.check_output(["nvm", "version"], text=True, timeout=2)
|
||||
nvm_version = out.strip()
|
||||
except Exception:
|
||||
nvm_version = None
|
||||
else:
|
||||
if os.environ.get("NVM_DIR") or Path.home().joinpath(".nvm").exists():
|
||||
nvm_installed = True
|
||||
node_info["nvm_installed"] = nvm_installed
|
||||
if nvm_version:
|
||||
node_info["nvm_version"] = nvm_version
|
||||
self.results["node"] = node_info
|
||||
|
||||
def format_markdown(self):
|
||||
os_info = self.results.get("os", {})
|
||||
lines = []
|
||||
# OS Section
|
||||
os_section = f"**Operating System:** {os_info.get('name')}"
|
||||
if os_info.get("distro"):
|
||||
os_section += f" ({os_info['distro']}"
|
||||
if os_info.get("distro_version"):
|
||||
os_section += f" {os_info['distro_version']}"
|
||||
os_section += ")"
|
||||
lines.append(os_section)
|
||||
if os_info.get("wsl"):
|
||||
lines.append("- Running under WSL")
|
||||
lines.append("")
|
||||
# CLI Tools Section - output as one list.
|
||||
cli_found = []
|
||||
for tool, status in self.results.get("cli_tools", {}).items():
|
||||
if status.get("found"):
|
||||
if status.get("version"):
|
||||
cli_found.append(f"{tool} ({status['version']})")
|
||||
else:
|
||||
cli_found.append(tool)
|
||||
if cli_found:
|
||||
lines.append("**Found CLI developer tools:** " + ", ".join(cli_found))
|
||||
else:
|
||||
lines.append("**Found CLI developer tools:** None")
|
||||
lines.append("")
|
||||
# Python Section
|
||||
py_installs = self.results.get("python", {}).get("installations", [])
|
||||
env_tools = self.results.get("python", {}).get("env_tools", {})
|
||||
lines.append("**Python Environments:**")
|
||||
if py_installs:
|
||||
for py in py_installs:
|
||||
ver = py.get("version") or "unknown version"
|
||||
path = py.get("path")
|
||||
lines.append(f"- Python {ver} at `{path}`")
|
||||
else:
|
||||
lines.append("- No Python interpreter found")
|
||||
for tool, info in env_tools.items():
|
||||
if tool == "venv":
|
||||
available = info.get("available", False)
|
||||
lines.append(f"- venv (builtin): {'available' if available else 'not available'}")
|
||||
else:
|
||||
installed = info.get("installed", False)
|
||||
ver = info.get("version")
|
||||
if installed:
|
||||
if ver:
|
||||
lines.append(f"- {tool}: installed (version {ver})")
|
||||
else:
|
||||
lines.append(f"- {tool}: installed")
|
||||
else:
|
||||
lines.append(f"- {tool}: not installed")
|
||||
lines.append("")
|
||||
# Package Managers Section
|
||||
pkg_mgrs = self.results.get("package_managers", {})
|
||||
lines.append("**Package Managers:**")
|
||||
any_pkg = False
|
||||
for mgr, info in pkg_mgrs.items():
|
||||
if not info.get("found"):
|
||||
continue
|
||||
any_pkg = True
|
||||
ver = info.get("version")
|
||||
if ver:
|
||||
lines.append(f"- {mgr}: found ({ver})")
|
||||
else:
|
||||
lines.append(f"- {mgr}: found")
|
||||
if not any_pkg:
|
||||
lines.append("- *(No common package managers found)*")
|
||||
lines.append("")
|
||||
# Libraries Section
|
||||
libs = self.results.get("libraries", {})
|
||||
lines.append("**Developer Libraries:**")
|
||||
found_libs = []
|
||||
not_found_libs = []
|
||||
for lib, info in libs.items():
|
||||
if info.get("found"):
|
||||
line = f"- {lib}: installed"
|
||||
if info.get("version"):
|
||||
line += f" (version {info['version']})"
|
||||
if info.get("cflags"):
|
||||
line += f", cflags: `{info['cflags']}`"
|
||||
if info.get("libs"):
|
||||
line += f", libs: `{info['libs']}`"
|
||||
if info.get("header_paths"):
|
||||
line += f", headers: {', '.join(info['header_paths'])}"
|
||||
found_libs.append(line)
|
||||
else:
|
||||
not_found_libs.append(lib)
|
||||
lines.extend(found_libs)
|
||||
if not_found_libs:
|
||||
lines.append(f"- Not found: {', '.join(sorted(not_found_libs))}")
|
||||
lines.append("")
|
||||
# Node.js Section
|
||||
node = self.results.get("node", {})
|
||||
lines.append("**Node.js and Related:**")
|
||||
node_ver = node.get("node_version")
|
||||
npm_ver = node.get("npm_version")
|
||||
nvm_inst = node.get("nvm_installed")
|
||||
nvm_ver = node.get("nvm_version")
|
||||
if node_ver:
|
||||
lines.append(f"- Node.js: {node_ver}")
|
||||
else:
|
||||
lines.append("- Node.js: not installed")
|
||||
if npm_ver:
|
||||
lines.append(f"- npm: version {npm_ver}")
|
||||
else:
|
||||
lines.append("- npm: not installed")
|
||||
if nvm_inst:
|
||||
if nvm_ver:
|
||||
lines.append(f"- nvm: installed (version {nvm_ver})")
|
||||
else:
|
||||
lines.append("- nvm: installed")
|
||||
else:
|
||||
lines.append("- nvm: not installed")
|
||||
lines.append("")
|
||||
return "\n".join(lines)
|
||||
|
||||
if __name__ == "__main__":
|
||||
env = EnvDiscovery()
|
||||
env.discover()
|
||||
print(env.format_markdown())
|
||||
Loading…
Reference in New Issue