simfix 0.1.3__tar.gz → 0.1.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {simfix-0.1.3/simfix.egg-info → simfix-0.1.5}/PKG-INFO +2 -1
- {simfix-0.1.3 → simfix-0.1.5}/pyproject.toml +2 -1
- {simfix-0.1.3 → simfix-0.1.5}/simfix/__init__.py +1 -1
- {simfix-0.1.3 → simfix-0.1.5}/simfix/analyzer.py +45 -17
- {simfix-0.1.3 → simfix-0.1.5}/simfix/cli.py +19 -0
- simfix-0.1.5/simfix/commands.py +130 -0
- simfix-0.1.5/simfix/cuda.py +133 -0
- simfix-0.1.5/simfix/dependency_discovery.py +139 -0
- simfix-0.1.5/simfix/install_commands.py +163 -0
- simfix-0.1.5/simfix/recommendation_types.py +16 -0
- simfix-0.1.5/simfix/recommendations.py +284 -0
- simfix-0.1.5/simfix/ros_environment.py +135 -0
- simfix-0.1.5/simfix/system_capabilities.py +46 -0
- simfix-0.1.5/simfix/vendor_dependencies.py +134 -0
- {simfix-0.1.3 → simfix-0.1.5/simfix.egg-info}/PKG-INFO +2 -1
- {simfix-0.1.3 → simfix-0.1.5}/simfix.egg-info/SOURCES.txt +7 -0
- {simfix-0.1.3 → simfix-0.1.5}/simfix.egg-info/requires.txt +3 -0
- {simfix-0.1.3 → simfix-0.1.5}/tests/test_basic.py +270 -5
- simfix-0.1.3/simfix/commands.py +0 -91
- simfix-0.1.3/simfix/recommendations.py +0 -131
- {simfix-0.1.3 → simfix-0.1.5}/LICENSE +0 -0
- {simfix-0.1.3 → simfix-0.1.5}/README.md +0 -0
- {simfix-0.1.3 → simfix-0.1.5}/setup.cfg +0 -0
- {simfix-0.1.3 → simfix-0.1.5}/simfix/cmake.py +0 -0
- {simfix-0.1.3 → simfix-0.1.5}/simfix/compatibility.py +0 -0
- {simfix-0.1.3 → simfix-0.1.5}/simfix/conda_environment.py +0 -0
- {simfix-0.1.3 → simfix-0.1.5}/simfix/conda_fixer.py +0 -0
- {simfix-0.1.3 → simfix-0.1.5}/simfix/cuda_docker.py +0 -0
- {simfix-0.1.3 → simfix-0.1.5}/simfix/docker_runner.py +0 -0
- {simfix-0.1.3 → simfix-0.1.5}/simfix/dockerfile.py +0 -0
- {simfix-0.1.3 → simfix-0.1.5}/simfix/fixer.py +0 -0
- {simfix-0.1.3 → simfix-0.1.5}/simfix/git_assets.py +0 -0
- {simfix-0.1.3 → simfix-0.1.5}/simfix/planner.py +0 -0
- {simfix-0.1.3 → simfix-0.1.5}/simfix/pypi.py +0 -0
- {simfix-0.1.3 → simfix-0.1.5}/simfix/pyproject.py +0 -0
- {simfix-0.1.3 → simfix-0.1.5}/simfix/python_requirements.py +0 -0
- {simfix-0.1.3 → simfix-0.1.5}/simfix/repo.py +0 -0
- {simfix-0.1.3 → simfix-0.1.5}/simfix/report.py +0 -0
- {simfix-0.1.3 → simfix-0.1.5}/simfix/ros_docker.py +0 -0
- {simfix-0.1.3 → simfix-0.1.5}/simfix/ros_package.py +0 -0
- {simfix-0.1.3 → simfix-0.1.5}/simfix/setup_py.py +0 -0
- {simfix-0.1.3 → simfix-0.1.5}/simfix/system.py +0 -0
- {simfix-0.1.3 → simfix-0.1.5}/simfix/system_docker.py +0 -0
- {simfix-0.1.3 → simfix-0.1.5}/simfix.egg-info/dependency_links.txt +0 -0
- {simfix-0.1.3 → simfix-0.1.5}/simfix.egg-info/entry_points.txt +0 -0
- {simfix-0.1.3 → simfix-0.1.5}/simfix.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: simfix
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5
|
|
4
4
|
Summary: A dependency checker and installation assistant for simulator repositories.
|
|
5
5
|
Author-email: Habib ur Rehmaan <h.rehmaan96@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -26,6 +26,7 @@ Requires-Dist: requests
|
|
|
26
26
|
Requires-Dist: packaging
|
|
27
27
|
Requires-Dist: pyyaml
|
|
28
28
|
Requires-Dist: uv
|
|
29
|
+
Requires-Dist: tomli; python_version < "3.11"
|
|
29
30
|
Provides-Extra: dev
|
|
30
31
|
Requires-Dist: pytest; extra == "dev"
|
|
31
32
|
Requires-Dist: pre-commit; extra == "dev"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "simfix"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.5"
|
|
8
8
|
description = "A dependency checker and installation assistant for simulator repositories."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -39,6 +39,7 @@ dependencies = [
|
|
|
39
39
|
"packaging",
|
|
40
40
|
"pyyaml",
|
|
41
41
|
"uv",
|
|
42
|
+
"tomli; python_version < '3.11'",
|
|
42
43
|
]
|
|
43
44
|
|
|
44
45
|
[project.optional-dependencies]
|
|
@@ -10,6 +10,7 @@ from simfix.pypi import normalize_requirement_name
|
|
|
10
10
|
from simfix.pyproject import PyProjectInfo, parse_pyproject
|
|
11
11
|
from simfix.python_requirements import parse_requirements_file
|
|
12
12
|
from simfix.ros_package import ROSPackageInfo, parse_ros_package
|
|
13
|
+
from simfix.dependency_discovery import discover_dependency_files
|
|
13
14
|
from simfix.setup_py import parse_setup_py_dependencies
|
|
14
15
|
|
|
15
16
|
|
|
@@ -89,33 +90,60 @@ def analyze_repo(repo_path: str | Path) -> RepoAnalysis:
|
|
|
89
90
|
if not path.is_dir():
|
|
90
91
|
raise NotADirectoryError(f"Repository path is not a directory: {path}")
|
|
91
92
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
discovered_files = discover_dependency_files(path)
|
|
94
|
+
|
|
95
|
+
requirements_path = (
|
|
96
|
+
discovered_files.requirements_txt[0]
|
|
97
|
+
if discovered_files.requirements_txt
|
|
98
|
+
else path / "requirements.txt"
|
|
99
|
+
)
|
|
100
|
+
environment_path = (
|
|
101
|
+
discovered_files.environment_files[0]
|
|
102
|
+
if discovered_files.environment_files
|
|
103
|
+
else path / "environment.yml"
|
|
104
|
+
)
|
|
105
|
+
dockerfile_path = (
|
|
106
|
+
discovered_files.dockerfiles[0]
|
|
107
|
+
if discovered_files.dockerfiles
|
|
108
|
+
else path / "Dockerfile"
|
|
109
|
+
)
|
|
110
|
+
package_xml_path = (
|
|
111
|
+
discovered_files.package_xml_files[0]
|
|
112
|
+
if discovered_files.package_xml_files
|
|
113
|
+
else path / "package.xml"
|
|
114
|
+
)
|
|
115
|
+
cmake_path = (
|
|
116
|
+
discovered_files.cmake_lists_files[0]
|
|
117
|
+
if discovered_files.cmake_lists_files
|
|
118
|
+
else path / "CMakeLists.txt"
|
|
119
|
+
)
|
|
120
|
+
pyproject_path = (
|
|
121
|
+
discovered_files.pyproject_toml[0]
|
|
122
|
+
if discovered_files.pyproject_toml
|
|
123
|
+
else path / "pyproject.toml"
|
|
124
|
+
)
|
|
125
|
+
setup_py_path = (
|
|
126
|
+
discovered_files.setup_py_files[0]
|
|
127
|
+
if discovered_files.setup_py_files
|
|
128
|
+
else path / "setup.py"
|
|
129
|
+
)
|
|
96
130
|
|
|
97
|
-
dockerfile_path = path / "Dockerfile"
|
|
98
|
-
package_xml_path = path / "package.xml"
|
|
99
|
-
cmake_path = path / "CMakeLists.txt"
|
|
100
|
-
pyproject_path = path / "pyproject.toml"
|
|
101
|
-
setup_py_path = path / "setup.py"
|
|
102
131
|
setup_py_dependencies = parse_setup_py_dependencies(setup_py_path)
|
|
103
132
|
|
|
104
133
|
return RepoAnalysis(
|
|
105
134
|
repo_path=path,
|
|
106
|
-
has_requirements_txt=
|
|
107
|
-
has_environment_yml=
|
|
108
|
-
or (path / "environment.yaml").exists(),
|
|
135
|
+
has_requirements_txt=discovered_files.has_requirements_txt,
|
|
136
|
+
has_environment_yml=discovered_files.has_environment_file,
|
|
109
137
|
python_requirements=parse_requirements_file(requirements_path),
|
|
110
138
|
conda_environment=parse_conda_environment(environment_path),
|
|
111
|
-
has_dockerfile=
|
|
139
|
+
has_dockerfile=discovered_files.has_dockerfile,
|
|
112
140
|
dockerfile_info=parse_dockerfile(dockerfile_path),
|
|
113
|
-
has_package_xml=
|
|
141
|
+
has_package_xml=discovered_files.has_ros_package,
|
|
114
142
|
ros_package_info=parse_ros_package(package_xml_path),
|
|
115
|
-
has_cmake=
|
|
143
|
+
has_cmake=discovered_files.has_cmake_lists,
|
|
116
144
|
cmake_info=parse_cmake_file(cmake_path),
|
|
117
|
-
has_pyproject_toml=
|
|
145
|
+
has_pyproject_toml=discovered_files.has_pyproject_toml,
|
|
118
146
|
pyproject_info=parse_pyproject(pyproject_path),
|
|
119
|
-
has_setup_py=
|
|
147
|
+
has_setup_py=discovered_files.has_setup_py,
|
|
120
148
|
setup_py_dependencies=setup_py_dependencies,
|
|
121
149
|
)
|
|
@@ -15,8 +15,11 @@ from simfix.planner import create_install_plan
|
|
|
15
15
|
from simfix.pypi import check_pypi_packages
|
|
16
16
|
from simfix.repo import clone_repo, is_git_url
|
|
17
17
|
from simfix.report import generate_markdown_report, write_markdown_report
|
|
18
|
+
from simfix.system_capabilities import detect_system_capabilities
|
|
18
19
|
from simfix.system import get_system_info
|
|
19
20
|
from simfix.recommendations import generate_recommendations
|
|
21
|
+
from simfix.cuda import detect_cuda_version_info
|
|
22
|
+
from simfix.ros_environment import detect_ros_environment_info
|
|
20
23
|
|
|
21
24
|
console = Console()
|
|
22
25
|
|
|
@@ -120,6 +123,9 @@ def recommendations(repo: str) -> None:
|
|
|
120
123
|
repo_recommendations = generate_recommendations(
|
|
121
124
|
dependencies=analysis.all_python_dependencies,
|
|
122
125
|
detected_ecosystems=analysis.detected_ecosystems,
|
|
126
|
+
system_capabilities=detect_system_capabilities(),
|
|
127
|
+
cuda_version_info=detect_cuda_version_info(repo_path),
|
|
128
|
+
ros_environment_info=detect_ros_environment_info(repo_path),
|
|
123
129
|
)
|
|
124
130
|
|
|
125
131
|
console.print("[bold]SimFix Recommendations[/bold]")
|
|
@@ -208,6 +214,7 @@ def doctor(
|
|
|
208
214
|
console.print(f"[bold]Detected ecosystem(s):[/bold] {ecosystems}")
|
|
209
215
|
|
|
210
216
|
python_dependencies = analysis.all_python_dependencies
|
|
217
|
+
pypi_results = []
|
|
211
218
|
|
|
212
219
|
if python_dependencies:
|
|
213
220
|
deps_table = Table(title="Python packages")
|
|
@@ -218,6 +225,15 @@ def doctor(
|
|
|
218
225
|
|
|
219
226
|
console.print(deps_table)
|
|
220
227
|
|
|
228
|
+
pypi_table = Table(title="PyPI check")
|
|
229
|
+
pypi_table.add_column("Package", style="cyan")
|
|
230
|
+
pypi_table.add_column("Status")
|
|
231
|
+
|
|
232
|
+
for result in pypi_results:
|
|
233
|
+
status = "found" if result.exists else "not found"
|
|
234
|
+
pypi_table.add_row(result.name, status)
|
|
235
|
+
|
|
236
|
+
console.print(pypi_table)
|
|
221
237
|
pypi_results = check_pypi_packages(python_dependencies)
|
|
222
238
|
|
|
223
239
|
pypi_table = Table(title="PyPI check")
|
|
@@ -405,6 +421,9 @@ def doctor(
|
|
|
405
421
|
repo_recommendations = generate_recommendations(
|
|
406
422
|
dependencies=analysis.all_python_dependencies,
|
|
407
423
|
detected_ecosystems=analysis.detected_ecosystems,
|
|
424
|
+
system_capabilities=detect_system_capabilities(),
|
|
425
|
+
cuda_version_info=detect_cuda_version_info(repo_path),
|
|
426
|
+
ros_environment_info=detect_ros_environment_info(repo_path),
|
|
408
427
|
)
|
|
409
428
|
|
|
410
429
|
if repo_recommendations:
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from simfix.analyzer import RepoAnalysis
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class CommandPlan:
|
|
10
|
+
"""Suggested shell commands for installing a repository."""
|
|
11
|
+
|
|
12
|
+
title: str
|
|
13
|
+
commands: list[str]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def create_command_plan(analysis: RepoAnalysis) -> CommandPlan:
|
|
17
|
+
"""Create suggested installation commands from repository analysis."""
|
|
18
|
+
ecosystems = analysis.detected_ecosystems
|
|
19
|
+
repo_name = analysis.repo_path.name.replace("_", "-").lower()
|
|
20
|
+
commands: list[str] = []
|
|
21
|
+
|
|
22
|
+
if "docker" in ecosystems:
|
|
23
|
+
commands.append(f"docker build -t {repo_name} .")
|
|
24
|
+
|
|
25
|
+
run_helper = analysis.repo_path / "run_simfix_docker.sh"
|
|
26
|
+
if run_helper.exists():
|
|
27
|
+
commands.append("./run_simfix_docker.sh")
|
|
28
|
+
else:
|
|
29
|
+
commands.append(f"docker run --rm -it {repo_name}")
|
|
30
|
+
|
|
31
|
+
if "ros" in ecosystems:
|
|
32
|
+
build_system = analysis.ros_package_info.build_system.lower()
|
|
33
|
+
|
|
34
|
+
commands.extend(
|
|
35
|
+
[
|
|
36
|
+
"rosdep update",
|
|
37
|
+
"rosdep install --from-paths . --ignore-src -r -y",
|
|
38
|
+
]
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
if build_system == "catkin":
|
|
42
|
+
commands.extend(
|
|
43
|
+
[
|
|
44
|
+
"catkin build",
|
|
45
|
+
"source devel/setup.bash",
|
|
46
|
+
]
|
|
47
|
+
)
|
|
48
|
+
elif build_system in {"ament", "ament_cmake", "ament_python"}:
|
|
49
|
+
commands.extend(
|
|
50
|
+
[
|
|
51
|
+
"colcon build",
|
|
52
|
+
"source install/setup.bash",
|
|
53
|
+
]
|
|
54
|
+
)
|
|
55
|
+
else:
|
|
56
|
+
commands.extend(
|
|
57
|
+
[
|
|
58
|
+
"catkin build # or: colcon build, depending on the ROS package type",
|
|
59
|
+
"source devel/setup.bash # or: source install/setup.bash",
|
|
60
|
+
]
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
if "conda" in ecosystems:
|
|
64
|
+
env_name = "simfix-env"
|
|
65
|
+
if analysis.conda_environment is not None and analysis.conda_environment.name:
|
|
66
|
+
env_name = analysis.conda_environment.name
|
|
67
|
+
|
|
68
|
+
commands.extend(
|
|
69
|
+
[
|
|
70
|
+
"conda env create -f environment.yml",
|
|
71
|
+
f"conda activate {env_name}",
|
|
72
|
+
]
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if "python" in ecosystems:
|
|
76
|
+
commands.extend(
|
|
77
|
+
[
|
|
78
|
+
"python -m venv .venv",
|
|
79
|
+
"source .venv/bin/activate",
|
|
80
|
+
"python -m pip install --upgrade pip",
|
|
81
|
+
]
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
if analysis.has_requirements_txt:
|
|
85
|
+
commands.append("python -m pip install -r requirements.txt")
|
|
86
|
+
|
|
87
|
+
if analysis.has_pyproject_toml or analysis.has_setup_py:
|
|
88
|
+
commands.extend(
|
|
89
|
+
[
|
|
90
|
+
"python -m pip install -e .",
|
|
91
|
+
"python -m pip install -e . --no-deps # use only if vendor/manual dependencies block normal install",
|
|
92
|
+
]
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
if "cmake/c++" in ecosystems and "ros" not in ecosystems:
|
|
96
|
+
commands.extend(
|
|
97
|
+
[
|
|
98
|
+
"cmake -S . -B build",
|
|
99
|
+
"cmake --build build -j",
|
|
100
|
+
]
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
if not commands:
|
|
104
|
+
commands.extend(
|
|
105
|
+
[
|
|
106
|
+
"# No common dependency file was detected.",
|
|
107
|
+
"# Read the README installation section manually.",
|
|
108
|
+
"# Check for install scripts such as install.sh or setup.sh.",
|
|
109
|
+
]
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return CommandPlan(
|
|
113
|
+
title="Suggested installation commands",
|
|
114
|
+
commands=_deduplicate_commands(commands),
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _deduplicate_commands(commands: list[str]) -> list[str]:
|
|
119
|
+
"""Return commands without duplicates while preserving order."""
|
|
120
|
+
seen: set[str] = set()
|
|
121
|
+
unique_commands: list[str] = []
|
|
122
|
+
|
|
123
|
+
for command in commands:
|
|
124
|
+
if command in seen:
|
|
125
|
+
continue
|
|
126
|
+
|
|
127
|
+
seen.add(command)
|
|
128
|
+
unique_commands.append(command)
|
|
129
|
+
|
|
130
|
+
return unique_commands
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""CUDA version detection helpers for SimFix recommendations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
import subprocess
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class CudaVersionInfo:
|
|
13
|
+
"""Detected CUDA version information."""
|
|
14
|
+
|
|
15
|
+
repo_cuda_version: tuple[int, int] | None
|
|
16
|
+
system_cuda_version: tuple[int, int] | None
|
|
17
|
+
repo_cuda_source: str | None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def detect_cuda_version_info(repo_path: Path) -> CudaVersionInfo:
|
|
21
|
+
"""Detect repository CUDA requirement and system CUDA capability."""
|
|
22
|
+
repo_cuda_version, repo_cuda_source = detect_repo_cuda_version(repo_path)
|
|
23
|
+
system_cuda_version = detect_system_cuda_version()
|
|
24
|
+
|
|
25
|
+
return CudaVersionInfo(
|
|
26
|
+
repo_cuda_version=repo_cuda_version,
|
|
27
|
+
system_cuda_version=system_cuda_version,
|
|
28
|
+
repo_cuda_source=repo_cuda_source,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def detect_repo_cuda_version(
|
|
33
|
+
repo_path: Path,
|
|
34
|
+
) -> tuple[tuple[int, int] | None, str | None]:
|
|
35
|
+
"""Detect CUDA version requested by repository files.
|
|
36
|
+
|
|
37
|
+
This is intentionally generic. It checks common simulator/deep-learning
|
|
38
|
+
signals such as Docker base images and CUDA-specific dependency names.
|
|
39
|
+
"""
|
|
40
|
+
dockerfile = repo_path / "Dockerfile"
|
|
41
|
+
if dockerfile.exists():
|
|
42
|
+
version = _detect_cuda_version_from_text(dockerfile.read_text(encoding="utf-8"))
|
|
43
|
+
if version is not None:
|
|
44
|
+
return version, "Dockerfile"
|
|
45
|
+
|
|
46
|
+
dependency_files = [
|
|
47
|
+
repo_path / "requirements.txt",
|
|
48
|
+
repo_path / "pyproject.toml",
|
|
49
|
+
repo_path / "setup.py",
|
|
50
|
+
repo_path / "environment.yml",
|
|
51
|
+
repo_path / "environment.yaml",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
for dependency_file in dependency_files:
|
|
55
|
+
if dependency_file.exists():
|
|
56
|
+
version = _detect_cuda_version_from_text(
|
|
57
|
+
dependency_file.read_text(encoding="utf-8")
|
|
58
|
+
)
|
|
59
|
+
if version is not None:
|
|
60
|
+
return version, dependency_file.name
|
|
61
|
+
|
|
62
|
+
return None, None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def detect_system_cuda_version() -> tuple[int, int] | None:
|
|
66
|
+
"""Detect CUDA version supported by the NVIDIA driver using nvidia-smi."""
|
|
67
|
+
try:
|
|
68
|
+
result = subprocess.run(
|
|
69
|
+
["nvidia-smi"],
|
|
70
|
+
check=False,
|
|
71
|
+
capture_output=True,
|
|
72
|
+
text=True,
|
|
73
|
+
timeout=10,
|
|
74
|
+
)
|
|
75
|
+
except (OSError, subprocess.SubprocessError):
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
return _parse_nvidia_smi_cuda_version(result.stdout + result.stderr)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def is_cuda_version_mismatch(
|
|
82
|
+
repo_cuda_version: tuple[int, int] | None,
|
|
83
|
+
system_cuda_version: tuple[int, int] | None,
|
|
84
|
+
) -> bool:
|
|
85
|
+
"""Return True if the repo appears to require newer CUDA than the system."""
|
|
86
|
+
if repo_cuda_version is None or system_cuda_version is None:
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
return repo_cuda_version > system_cuda_version
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _detect_cuda_version_from_text(text: str) -> tuple[int, int] | None:
|
|
93
|
+
"""Detect CUDA version from generic text."""
|
|
94
|
+
normalized = text.lower()
|
|
95
|
+
|
|
96
|
+
detected_versions: list[tuple[int, int]] = []
|
|
97
|
+
|
|
98
|
+
explicit_patterns = [
|
|
99
|
+
r"nvidia/cuda:(\d+)\.(\d+)",
|
|
100
|
+
r"cuda[-_]?(\d+)[\._-](\d+)",
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
for pattern in explicit_patterns:
|
|
104
|
+
for match in re.finditer(pattern, normalized):
|
|
105
|
+
detected_versions.append((int(match.group(1)), int(match.group(2))))
|
|
106
|
+
|
|
107
|
+
for match in re.finditer(r"cuda[-_]?(\d+)x", normalized):
|
|
108
|
+
detected_versions.append((int(match.group(1)), 0))
|
|
109
|
+
|
|
110
|
+
for match in re.finditer(r"cu(\d{2,3})", normalized):
|
|
111
|
+
detected_versions.append(_parse_compact_cuda_version(match.group(1)))
|
|
112
|
+
|
|
113
|
+
if not detected_versions:
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
return max(detected_versions)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _parse_nvidia_smi_cuda_version(text: str) -> tuple[int, int] | None:
|
|
120
|
+
"""Parse CUDA version from nvidia-smi output."""
|
|
121
|
+
match = re.search(r"cuda version:\s*(\d+)\.(\d+)", text.lower())
|
|
122
|
+
if match is None:
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
return int(match.group(1)), int(match.group(2))
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _parse_compact_cuda_version(version: str) -> tuple[int, int]:
|
|
129
|
+
"""Parse compact CUDA version strings such as cu118 or cu121."""
|
|
130
|
+
if len(version) == 2:
|
|
131
|
+
return int(version[0]), int(version[1])
|
|
132
|
+
|
|
133
|
+
return int(version[:-1]), int(version[-1])
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Generic dependency-file discovery for simulator repositories."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
IGNORED_DIRECTORY_NAMES = {
|
|
10
|
+
".git",
|
|
11
|
+
".hg",
|
|
12
|
+
".svn",
|
|
13
|
+
".venv",
|
|
14
|
+
"venv",
|
|
15
|
+
"env",
|
|
16
|
+
"__pycache__",
|
|
17
|
+
".mypy_cache",
|
|
18
|
+
".ruff_cache",
|
|
19
|
+
".pytest_cache",
|
|
20
|
+
"build",
|
|
21
|
+
"dist",
|
|
22
|
+
"install",
|
|
23
|
+
"log",
|
|
24
|
+
"node_modules",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
DEPENDENCY_FILE_NAMES = {
|
|
29
|
+
"requirements.txt",
|
|
30
|
+
"pyproject.toml",
|
|
31
|
+
"environment.yml",
|
|
32
|
+
"environment.yaml",
|
|
33
|
+
"Dockerfile",
|
|
34
|
+
"package.xml",
|
|
35
|
+
"CMakeLists.txt",
|
|
36
|
+
"setup.py",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True)
|
|
41
|
+
class DiscoveredDependencyFiles:
|
|
42
|
+
"""Dependency files discovered in a repository."""
|
|
43
|
+
|
|
44
|
+
requirements_txt: tuple[Path, ...]
|
|
45
|
+
pyproject_toml: tuple[Path, ...]
|
|
46
|
+
environment_files: tuple[Path, ...]
|
|
47
|
+
dockerfiles: tuple[Path, ...]
|
|
48
|
+
package_xml_files: tuple[Path, ...]
|
|
49
|
+
cmake_lists_files: tuple[Path, ...]
|
|
50
|
+
setup_py_files: tuple[Path, ...]
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def has_requirements_txt(self) -> bool:
|
|
54
|
+
return bool(self.requirements_txt)
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def has_pyproject_toml(self) -> bool:
|
|
58
|
+
return bool(self.pyproject_toml)
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def has_environment_file(self) -> bool:
|
|
62
|
+
return bool(self.environment_files)
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def has_dockerfile(self) -> bool:
|
|
66
|
+
return bool(self.dockerfiles)
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def has_ros_package(self) -> bool:
|
|
70
|
+
return bool(self.package_xml_files)
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def has_cmake_lists(self) -> bool:
|
|
74
|
+
return bool(self.cmake_lists_files)
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def has_setup_py(self) -> bool:
|
|
78
|
+
return bool(self.setup_py_files)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def discover_dependency_files(
|
|
82
|
+
repo_path: Path,
|
|
83
|
+
*,
|
|
84
|
+
max_depth: int = 4,
|
|
85
|
+
) -> DiscoveredDependencyFiles:
|
|
86
|
+
"""Discover dependency files in a repository up to a safe depth.
|
|
87
|
+
|
|
88
|
+
This is generic and does not assume a specific simulator layout.
|
|
89
|
+
It intentionally skips common build, cache, virtualenv, and VCS folders.
|
|
90
|
+
"""
|
|
91
|
+
discovered_files = _walk_dependency_files(repo_path, max_depth=max_depth)
|
|
92
|
+
|
|
93
|
+
return DiscoveredDependencyFiles(
|
|
94
|
+
requirements_txt=_filter_by_name(discovered_files, "requirements.txt"),
|
|
95
|
+
pyproject_toml=_filter_by_name(discovered_files, "pyproject.toml"),
|
|
96
|
+
environment_files=tuple(
|
|
97
|
+
path
|
|
98
|
+
for path in discovered_files
|
|
99
|
+
if path.name in {"environment.yml", "environment.yaml"}
|
|
100
|
+
),
|
|
101
|
+
dockerfiles=_filter_by_name(discovered_files, "Dockerfile"),
|
|
102
|
+
package_xml_files=_filter_by_name(discovered_files, "package.xml"),
|
|
103
|
+
cmake_lists_files=_filter_by_name(discovered_files, "CMakeLists.txt"),
|
|
104
|
+
setup_py_files=_filter_by_name(discovered_files, "setup.py"),
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _walk_dependency_files(repo_path: Path, *, max_depth: int) -> tuple[Path, ...]:
|
|
109
|
+
repo_path = repo_path.resolve()
|
|
110
|
+
found: list[Path] = []
|
|
111
|
+
|
|
112
|
+
for path in repo_path.rglob("*"):
|
|
113
|
+
if not path.is_file():
|
|
114
|
+
continue
|
|
115
|
+
|
|
116
|
+
relative_path = path.relative_to(repo_path)
|
|
117
|
+
|
|
118
|
+
if _should_skip_path(relative_path):
|
|
119
|
+
continue
|
|
120
|
+
|
|
121
|
+
if _depth(relative_path) > max_depth:
|
|
122
|
+
continue
|
|
123
|
+
|
|
124
|
+
if path.name in DEPENDENCY_FILE_NAMES:
|
|
125
|
+
found.append(path)
|
|
126
|
+
|
|
127
|
+
return tuple(sorted(found))
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _filter_by_name(paths: tuple[Path, ...], name: str) -> tuple[Path, ...]:
|
|
131
|
+
return tuple(path for path in paths if path.name == name)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _should_skip_path(relative_path: Path) -> bool:
|
|
135
|
+
return any(part in IGNORED_DIRECTORY_NAMES for part in relative_path.parts)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _depth(relative_path: Path) -> int:
|
|
139
|
+
return len(relative_path.parts) - 1
|