tlaplus-cli 0.4.2__tar.gz → 0.5.0__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.
- {tlaplus_cli-0.4.2/src/tlaplus_cli.egg-info → tlaplus_cli-0.5.0}/PKG-INFO +15 -3
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/README.md +14 -2
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/pyproject.toml +1 -1
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/cli.py +1 -1
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/cmd/modules/__init__.py +1 -1
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/cmd/modules/build.py +12 -1
- tlaplus_cli-0.5.0/src/tlaplus_cli/cmd/modules/install.py +75 -0
- tlaplus_cli-0.5.0/src/tlaplus_cli/cmd/modules/path.py +72 -0
- tlaplus_cli-0.5.0/src/tlaplus_cli/cmd/tlc.py +139 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/cmd/tools/meta/sync.py +2 -1
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/cmd/tools/uninstall.py +3 -2
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/config/schema.py +1 -0
- tlaplus_cli-0.5.0/src/tlaplus_cli/java/classpath.py +174 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/resources/default_config.yaml +4 -0
- tlaplus_cli-0.5.0/src/tlaplus_cli/tlc/codes.py +314 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/tlc/compiler.py +18 -25
- tlaplus_cli-0.5.0/src/tlaplus_cli/tlc/models.py +130 -0
- tlaplus_cli-0.5.0/src/tlaplus_cli/tlc/parser.py +256 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/tlc/runner.py +54 -29
- tlaplus_cli-0.5.0/src/tlaplus_cli/tlc/sany.py +94 -0
- tlaplus_cli-0.5.0/src/tlaplus_cli/tlc/values.py +186 -0
- tlaplus_cli-0.5.0/src/tlaplus_cli/ui.py +21 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/versioning/api.py +9 -6
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/versioning/downloader.py +15 -18
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/versioning/paths.py +10 -4
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0/src/tlaplus_cli.egg-info}/PKG-INFO +15 -3
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli.egg-info/SOURCES.txt +12 -1
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/tests/test_build_both.py +4 -3
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/tests/test_modules_build_lib.py +4 -4
- tlaplus_cli-0.5.0/tests/test_modules_build_show_command.py +27 -0
- tlaplus_cli-0.5.0/tests/test_modules_install_community.py +86 -0
- tlaplus_cli-0.5.0/tests/test_modules_path_resolved.py +33 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/tests/test_tlc_modules_path.py +11 -4
- tlaplus_cli-0.5.0/tests/test_tlc_show_command.py +25 -0
- tlaplus_cli-0.4.2/src/tlaplus_cli/cmd/modules/path.py +0 -39
- tlaplus_cli-0.4.2/src/tlaplus_cli/cmd/tlc.py +0 -51
- tlaplus_cli-0.4.2/src/tlaplus_cli/ui.py +0 -6
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/LICENSE +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/MANIFEST.in +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/setup.cfg +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/__init__.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/cache/__init__.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/cache/github.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/cmd/__init__.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/cmd/check_java.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/cmd/config/__init__.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/cmd/config/edit.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/cmd/config/list.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/cmd/fetch_cache/__init__.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/cmd/fetch_cache/clear.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/cmd/modules/lib.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/cmd/tools/__init__.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/cmd/tools/dir.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/cmd/tools/install.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/cmd/tools/list.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/cmd/tools/meta/__init__.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/cmd/tools/path.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/cmd/tools/pin.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/cmd/tools/upgrade.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/config/__init__.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/config/loader.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/java/__init__.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/java/inspector.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/project/__init__.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/project/core.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/py.typed +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/resources/__init__.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/tlc/__init__.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/versioning/__init__.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/versioning/metadata.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/versioning/resolver.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli/versioning/schema.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli.egg-info/dependency_links.txt +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli.egg-info/entry_points.txt +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli.egg-info/requires.txt +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/src/tlaplus_cli.egg-info/top_level.txt +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/tests/test_build_modules_path.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/tests/test_cli.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/tests/test_config.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/tests/test_config_suite.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/tests/test_modules_lib.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/tests/test_modules_path.py +0 -0
- {tlaplus_cli-0.4.2 → tlaplus_cli-0.5.0}/tests/test_settings.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tlaplus-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: TLA+ tools: download TLC, compile custom modules, run model checker
|
|
5
5
|
Author-email: Denis Nikolskiy <codeomatics@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -156,6 +156,12 @@ To check the currently pinned `tla2tools.jar` path and its TLC version:
|
|
|
156
156
|
tla tlc --version
|
|
157
157
|
```
|
|
158
158
|
|
|
159
|
+
To inspect the exact Java command that will be executed without running it:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
tla tlc <spec_name> --show-command
|
|
163
|
+
```
|
|
164
|
+
|
|
159
165
|
### Compile Custom Java Modules
|
|
160
166
|
|
|
161
167
|
Java modules (overrides) are compiled using the pinned version of the toolset.
|
|
@@ -177,8 +183,9 @@ tla modules path /path/to/custom/modules
|
|
|
177
183
|
|
|
178
184
|
View or reset the custom modules path:
|
|
179
185
|
```bash
|
|
180
|
-
tla modules path
|
|
181
|
-
tla modules path none
|
|
186
|
+
tla modules path # View current path
|
|
187
|
+
tla modules path none # Reset to default
|
|
188
|
+
tla modules path --resolved # Show fully resolved paths for current context
|
|
182
189
|
```
|
|
183
190
|
|
|
184
191
|
Configure a persistent custom modules dependencies (lib) path:
|
|
@@ -200,6 +207,11 @@ Verbose output:
|
|
|
200
207
|
tla modules build --verbose
|
|
201
208
|
```
|
|
202
209
|
|
|
210
|
+
Show compilation command:
|
|
211
|
+
```bash
|
|
212
|
+
tla modules build --show-command
|
|
213
|
+
```
|
|
214
|
+
|
|
203
215
|
The build command:
|
|
204
216
|
1. Resolves the project root (defaults to `workspace.root` from config).
|
|
205
217
|
2. Includes `lib/*.jar` files from the project root in the `javac` classpath.
|
|
@@ -127,6 +127,12 @@ To check the currently pinned `tla2tools.jar` path and its TLC version:
|
|
|
127
127
|
tla tlc --version
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
+
To inspect the exact Java command that will be executed without running it:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
tla tlc <spec_name> --show-command
|
|
134
|
+
```
|
|
135
|
+
|
|
130
136
|
### Compile Custom Java Modules
|
|
131
137
|
|
|
132
138
|
Java modules (overrides) are compiled using the pinned version of the toolset.
|
|
@@ -148,8 +154,9 @@ tla modules path /path/to/custom/modules
|
|
|
148
154
|
|
|
149
155
|
View or reset the custom modules path:
|
|
150
156
|
```bash
|
|
151
|
-
tla modules path
|
|
152
|
-
tla modules path none
|
|
157
|
+
tla modules path # View current path
|
|
158
|
+
tla modules path none # Reset to default
|
|
159
|
+
tla modules path --resolved # Show fully resolved paths for current context
|
|
153
160
|
```
|
|
154
161
|
|
|
155
162
|
Configure a persistent custom modules dependencies (lib) path:
|
|
@@ -171,6 +178,11 @@ Verbose output:
|
|
|
171
178
|
tla modules build --verbose
|
|
172
179
|
```
|
|
173
180
|
|
|
181
|
+
Show compilation command:
|
|
182
|
+
```bash
|
|
183
|
+
tla modules build --show-command
|
|
184
|
+
```
|
|
185
|
+
|
|
174
186
|
The build command:
|
|
175
187
|
1. Resolves the project root (defaults to `workspace.root` from config).
|
|
176
188
|
2. Includes `lib/*.jar` files from the project root in the `javac` classpath.
|
|
@@ -4,17 +4,28 @@ from pathlib import Path
|
|
|
4
4
|
import typer
|
|
5
5
|
|
|
6
6
|
from tlaplus_cli.cmd.modules import app
|
|
7
|
-
from tlaplus_cli.tlc.compiler import compile_modules
|
|
7
|
+
from tlaplus_cli.tlc.compiler import build_compile_command, compile_modules
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
@app.command(name="build")
|
|
11
11
|
def build(
|
|
12
12
|
path: str | None = typer.Argument(None, help="Project root directory (defaults to workspace root)."),
|
|
13
13
|
verbose: bool = typer.Option(False, "--verbose", "-v", help="Show compilation output."),
|
|
14
|
+
show_command: bool = typer.Option(False, "--show-command", help="Print the command instead of executing it."),
|
|
14
15
|
) -> None:
|
|
15
16
|
"""Compile custom Java modules."""
|
|
16
17
|
base_dir = Path(path).resolve() if path is not None else None
|
|
17
18
|
|
|
19
|
+
if show_command:
|
|
20
|
+
try:
|
|
21
|
+
cmd = build_compile_command(base_dir)
|
|
22
|
+
except FileNotFoundError as e:
|
|
23
|
+
typer.echo(f"Error: {e}", err=True)
|
|
24
|
+
raise typer.Exit(1) from None
|
|
25
|
+
if cmd:
|
|
26
|
+
typer.echo(" ".join(cmd))
|
|
27
|
+
return
|
|
28
|
+
|
|
18
29
|
typer.echo("Compiling Java files ...")
|
|
19
30
|
try:
|
|
20
31
|
classes_dir = compile_modules(base_dir, verbose)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import typer
|
|
3
|
+
|
|
4
|
+
from tlaplus_cli import ui
|
|
5
|
+
from tlaplus_cli.cmd.modules import app
|
|
6
|
+
from tlaplus_cli.config.loader import load_config
|
|
7
|
+
from tlaplus_cli.versioning import (
|
|
8
|
+
download_version,
|
|
9
|
+
download_version_from_url,
|
|
10
|
+
fetch_remote_versions,
|
|
11
|
+
is_url,
|
|
12
|
+
)
|
|
13
|
+
from tlaplus_cli.versioning.paths import get_modules_dir
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@app.command(name="install-community")
|
|
17
|
+
def install_community(
|
|
18
|
+
version: str = typer.Argument(
|
|
19
|
+
None, help="Version tag (e.g. 'v202412171801') or a direct URL to CommunityModules.jar."
|
|
20
|
+
),
|
|
21
|
+
force: bool = typer.Option(False, "--force", "-f", help="Re-download if already installed."),
|
|
22
|
+
) -> None:
|
|
23
|
+
"""Download and install a specific version of CommunityModules."""
|
|
24
|
+
base_dir = get_modules_dir()
|
|
25
|
+
jar_name = "CommunityModules.jar"
|
|
26
|
+
|
|
27
|
+
if version and is_url(version):
|
|
28
|
+
try:
|
|
29
|
+
version_dir = download_version_from_url(version, base_dir=base_dir, jar_name=jar_name)
|
|
30
|
+
except (requests.RequestException, OSError, ValueError) as e:
|
|
31
|
+
ui.error(f"Failed to download: {e}")
|
|
32
|
+
raise typer.Exit(1) from e
|
|
33
|
+
else:
|
|
34
|
+
ui.success(f"Successfully installed from URL to {version_dir}")
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
config = load_config()
|
|
38
|
+
if not config.tla.community_modules:
|
|
39
|
+
ui.error("Community modules URLs not configured in settings.")
|
|
40
|
+
raise typer.Exit(1)
|
|
41
|
+
|
|
42
|
+
versions, status = fetch_remote_versions(
|
|
43
|
+
config.tla.community_modules.tags,
|
|
44
|
+
config.tla.community_modules.releases,
|
|
45
|
+
config.tla.community_modules.per_page,
|
|
46
|
+
asset_name=jar_name,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
if not versions:
|
|
50
|
+
ui.error(f"Could not fetch remote versions (status: {status.value})")
|
|
51
|
+
raise typer.Exit(1)
|
|
52
|
+
|
|
53
|
+
target = None
|
|
54
|
+
if version:
|
|
55
|
+
target = next((v for v in versions if v.name == version), None)
|
|
56
|
+
if not target:
|
|
57
|
+
ui.error(f"Version {version} not found.")
|
|
58
|
+
raise typer.Exit(1)
|
|
59
|
+
else:
|
|
60
|
+
# Default to latest non-prerelease
|
|
61
|
+
target = next((v for v in versions if not v.prerelease), versions[0])
|
|
62
|
+
ui.info(f"No version specified, selecting latest stable release: {target.name}")
|
|
63
|
+
|
|
64
|
+
# Check if already installed
|
|
65
|
+
target_dir = base_dir / f"{target.name}-{target.short_sha}"
|
|
66
|
+
if target_dir.exists() and not force:
|
|
67
|
+
ui.info(f"Version {target.name} is already installed at {target_dir}")
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
version_dir = download_version(target, force=force, base_dir=base_dir, jar_name=jar_name)
|
|
72
|
+
ui.success(f"Successfully installed {target.name} to {version_dir}")
|
|
73
|
+
except (requests.RequestException, OSError) as e:
|
|
74
|
+
ui.error(f"Failed to download: {e}")
|
|
75
|
+
raise typer.Exit(1) from e
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from tlaplus_cli.cmd.modules import app
|
|
6
|
+
from tlaplus_cli.config.loader import load_config, save_config, workspace_root
|
|
7
|
+
from tlaplus_cli.java.classpath import ClasspathResolver, ResolveMode, SubdirType
|
|
8
|
+
from tlaplus_cli.tlc.compiler import get_tlc_jar_path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@app.command(name="path")
|
|
12
|
+
def set_modules_path(
|
|
13
|
+
path: str | None = typer.Argument(None, help="Path to the custom Java modules directory, or 'none' to reset."),
|
|
14
|
+
resolved: bool = typer.Option(False, "--resolved", help="Show the actual resolved paths for the current context."),
|
|
15
|
+
) -> None:
|
|
16
|
+
"""Configure or view custom Java modules source path."""
|
|
17
|
+
config_obj = load_config()
|
|
18
|
+
|
|
19
|
+
if resolved:
|
|
20
|
+
resolver = ClasspathResolver(config_obj, project_root=workspace_root(), tool_jar=get_tlc_jar_path())
|
|
21
|
+
|
|
22
|
+
# Source path (modules)
|
|
23
|
+
tla_library = resolver.get_tla_library_property()
|
|
24
|
+
typer.echo(f"Source Path: {tla_library or 'None'}")
|
|
25
|
+
|
|
26
|
+
# Classes path
|
|
27
|
+
classes_dir = resolver.get_project_path(SubdirType.CLASSES)
|
|
28
|
+
typer.echo(f"Classes Path: {classes_dir or 'None'}")
|
|
29
|
+
|
|
30
|
+
# Library path (JARs)
|
|
31
|
+
libs = resolver.resolve(ResolveMode.RUNTIME)
|
|
32
|
+
# Filter out classes_dir and tool_jar to show only libs
|
|
33
|
+
tool_jar = get_tlc_jar_path()
|
|
34
|
+
only_libs = [p for p in libs if p != str(classes_dir) and p != str(tool_jar)]
|
|
35
|
+
|
|
36
|
+
lib_str = "None"
|
|
37
|
+
if only_libs:
|
|
38
|
+
lib_str = f"{len(only_libs)} JARs"
|
|
39
|
+
|
|
40
|
+
typer.echo(f"Library Path: {lib_str}")
|
|
41
|
+
if only_libs:
|
|
42
|
+
for lib in only_libs:
|
|
43
|
+
typer.echo(f" - {lib}")
|
|
44
|
+
|
|
45
|
+
# Tool Jar
|
|
46
|
+
typer.echo(f"Tool Jar: {tool_jar}")
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
if path is None:
|
|
50
|
+
if config_obj.module_path:
|
|
51
|
+
typer.echo(f"Current modules path: {config_obj.module_path}")
|
|
52
|
+
else:
|
|
53
|
+
default_path = workspace_root() / config_obj.workspace.modules_dir
|
|
54
|
+
typer.echo("Custom modules path is not set.")
|
|
55
|
+
typer.echo(f"Defaulting to: {default_path}")
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
if path.lower() == "none":
|
|
59
|
+
config_obj.module_path = None
|
|
60
|
+
save_config(config_obj)
|
|
61
|
+
typer.echo("Custom modules path reset to default.")
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
p = Path(path).resolve()
|
|
65
|
+
|
|
66
|
+
if not p.is_dir():
|
|
67
|
+
typer.echo(f"Error: Path does not exist or is not a directory: {p}", err=True)
|
|
68
|
+
raise typer.Exit(1)
|
|
69
|
+
|
|
70
|
+
config_obj.module_path = str(p)
|
|
71
|
+
save_config(config_obj)
|
|
72
|
+
typer.echo(f"Modules path updated to: {p}")
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
from tlaplus_cli import ui
|
|
4
|
+
from tlaplus_cli.config.loader import load_config
|
|
5
|
+
from tlaplus_cli.java.classpath import ClasspathResolver
|
|
6
|
+
from tlaplus_cli.project import find_project_root
|
|
7
|
+
from tlaplus_cli.tlc.compiler import get_tlc_jar_path
|
|
8
|
+
from tlaplus_cli.tlc.models import CheckState, CheckStatus, ModelCheckResult
|
|
9
|
+
from tlaplus_cli.tlc.runner import (
|
|
10
|
+
build_tlc_command,
|
|
11
|
+
get_tlc_version,
|
|
12
|
+
resolve_spec_file,
|
|
13
|
+
run_tlc,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def version_callback(value: bool) -> None:
|
|
18
|
+
if value:
|
|
19
|
+
jar_path = get_tlc_jar_path()
|
|
20
|
+
typer.echo(f"tla2tools.jar path: {jar_path}")
|
|
21
|
+
|
|
22
|
+
if not jar_path.exists():
|
|
23
|
+
ui.error(f"tla2tools.jar not found at {jar_path}")
|
|
24
|
+
typer.echo("Run 'tla tools install' first.", err=True)
|
|
25
|
+
raise typer.Exit(1)
|
|
26
|
+
|
|
27
|
+
version_str = get_tlc_version()
|
|
28
|
+
if version_str:
|
|
29
|
+
typer.echo(version_str)
|
|
30
|
+
|
|
31
|
+
raise typer.Exit(0)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def print_progress(result: ModelCheckResult) -> None:
|
|
35
|
+
"""Callback to print progress from ModelCheckResult."""
|
|
36
|
+
if result.status == CheckStatus.SuccessorStatesComputing and result.initial_states_stat:
|
|
37
|
+
last_stat = result.initial_states_stat[-1]
|
|
38
|
+
# Use carriage return to update the same line for progress
|
|
39
|
+
msg = f"Progress: {last_stat.total} states generated, {last_stat.distinct} distinct states found"
|
|
40
|
+
typer.echo(f"\r{msg}", err=True, nl=False)
|
|
41
|
+
elif result.status == CheckStatus.Finished:
|
|
42
|
+
# Final newline after progress
|
|
43
|
+
typer.echo("", err=True)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _show_diagnostic_info(spec: str, spec_name: str) -> None:
|
|
47
|
+
"""Show diagnostic info before running TLC."""
|
|
48
|
+
version_str = get_tlc_version() or "unknown"
|
|
49
|
+
typer.echo(f"Running TLC ({version_str}) on {spec_name} ...")
|
|
50
|
+
|
|
51
|
+
# Check if we have TLA-Library
|
|
52
|
+
try:
|
|
53
|
+
config = load_config()
|
|
54
|
+
spec_file, _ = resolve_spec_file(spec)
|
|
55
|
+
project_root = find_project_root(
|
|
56
|
+
spec_file,
|
|
57
|
+
modules_dir=config.workspace.modules_dir,
|
|
58
|
+
classes_dir=config.workspace.classes_dir,
|
|
59
|
+
)
|
|
60
|
+
jar_path = get_tlc_jar_path()
|
|
61
|
+
resolver = ClasspathResolver(config, project_root=project_root, tool_jar=jar_path)
|
|
62
|
+
tla_library = resolver.get_tla_library_property()
|
|
63
|
+
if tla_library:
|
|
64
|
+
ui.info(f"TLA-Library: {tla_library}")
|
|
65
|
+
except Exception:
|
|
66
|
+
pass # nosec
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# PLR0912: This command handler orchestrates multiple output types (errors, warnings, results, progress);
|
|
70
|
+
# splitting it would fragment the CLI's primary user-facing logic.
|
|
71
|
+
def tlc( # noqa: PLR0912
|
|
72
|
+
spec: str = typer.Argument(help="Name of the TLA+ specification (without .tla extension)."),
|
|
73
|
+
# Typer requires the parameter in the signature to register the CLI option;
|
|
74
|
+
# since the logic is in the eager callback, the value is not used here.
|
|
75
|
+
version: bool | None = typer.Option( # noqa: ARG001
|
|
76
|
+
None,
|
|
77
|
+
"--version",
|
|
78
|
+
help="Print the path to tla2tools.jar and its version.",
|
|
79
|
+
callback=version_callback,
|
|
80
|
+
is_eager=True,
|
|
81
|
+
),
|
|
82
|
+
show_command: bool = typer.Option(False, "--show-command", help="Print the command instead of executing it."),
|
|
83
|
+
) -> None:
|
|
84
|
+
"""Run TLC model checker on a TLA+ specification."""
|
|
85
|
+
# version argument is handled by version_callback (is_eager=True)
|
|
86
|
+
try:
|
|
87
|
+
_, spec_name = resolve_spec_file(spec)
|
|
88
|
+
except FileNotFoundError as e:
|
|
89
|
+
ui.error(str(e))
|
|
90
|
+
raise typer.Exit(1) from None
|
|
91
|
+
|
|
92
|
+
if show_command:
|
|
93
|
+
try:
|
|
94
|
+
cmd = build_tlc_command(spec)
|
|
95
|
+
except FileNotFoundError as e:
|
|
96
|
+
ui.error(str(e))
|
|
97
|
+
raise typer.Exit(1) from None
|
|
98
|
+
typer.echo(" ".join(cmd))
|
|
99
|
+
raise typer.Exit(0)
|
|
100
|
+
|
|
101
|
+
_show_diagnostic_info(spec, spec_name)
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
# We need the parser to get the final result, so we'll wrap run_tlc
|
|
105
|
+
# or capture the result from the last callback.
|
|
106
|
+
final_result: list[ModelCheckResult] = []
|
|
107
|
+
|
|
108
|
+
def wrapped_callback(res: ModelCheckResult) -> None:
|
|
109
|
+
print_progress(res)
|
|
110
|
+
if not final_result:
|
|
111
|
+
final_result.append(res)
|
|
112
|
+
else:
|
|
113
|
+
final_result[0] = res
|
|
114
|
+
|
|
115
|
+
exit_code = run_tlc(spec, callback=wrapped_callback)
|
|
116
|
+
|
|
117
|
+
if final_result:
|
|
118
|
+
res = final_result[0]
|
|
119
|
+
if res.state == CheckState.Success:
|
|
120
|
+
ui.success("Model checking completed successfully.")
|
|
121
|
+
elif res.state == CheckState.Error:
|
|
122
|
+
ui.error(f"Model checking failed with {len(res.errors)} error(s).")
|
|
123
|
+
for err in res.errors:
|
|
124
|
+
for line in err.lines:
|
|
125
|
+
typer.echo(f" {line}", err=True)
|
|
126
|
+
|
|
127
|
+
if res.warnings:
|
|
128
|
+
ui.warn(f"Found {len(res.warnings)} warning(s).")
|
|
129
|
+
for warn_info in res.warnings:
|
|
130
|
+
for line in warn_info.lines:
|
|
131
|
+
typer.echo(f" {line}", err=True)
|
|
132
|
+
|
|
133
|
+
if res.duration:
|
|
134
|
+
ui.info(f"Duration: {res.duration}ms")
|
|
135
|
+
except FileNotFoundError as e:
|
|
136
|
+
ui.error(str(e))
|
|
137
|
+
raise typer.Exit(1) from None
|
|
138
|
+
|
|
139
|
+
raise typer.Exit(exit_code)
|
|
@@ -2,6 +2,7 @@ import typer
|
|
|
2
2
|
|
|
3
3
|
from tlaplus_cli.cmd.tools.meta import app
|
|
4
4
|
from tlaplus_cli.config.loader import load_config
|
|
5
|
+
from tlaplus_cli.ui import warn
|
|
5
6
|
from tlaplus_cli.versioning import (
|
|
6
7
|
fetch_remote_versions,
|
|
7
8
|
list_local_versions,
|
|
@@ -28,6 +29,6 @@ def meta_sync() -> None:
|
|
|
28
29
|
write_version_metadata(lv.path, target)
|
|
29
30
|
typer.echo(f"Synced metadata for {lv.path.name}")
|
|
30
31
|
else:
|
|
31
|
-
|
|
32
|
+
warn(f"Could not find remote data for {lv.name}")
|
|
32
33
|
|
|
33
34
|
typer.echo("Metadata sync complete.")
|
|
@@ -5,6 +5,7 @@ import typer
|
|
|
5
5
|
|
|
6
6
|
from tlaplus_cli.cmd.tools import app
|
|
7
7
|
from tlaplus_cli.config.loader import cache_dir
|
|
8
|
+
from tlaplus_cli.ui import warn
|
|
8
9
|
from tlaplus_cli.versioning import (
|
|
9
10
|
clear_pin,
|
|
10
11
|
get_pinned_version_dir,
|
|
@@ -24,12 +25,12 @@ def _resolve_uninstall_targets(versions: list[str]) -> list[Path]:
|
|
|
24
25
|
if legacy.exists():
|
|
25
26
|
targets.append(legacy)
|
|
26
27
|
else:
|
|
27
|
-
|
|
28
|
+
warn("Default (legacy) tla2tools.jar not found.")
|
|
28
29
|
continue
|
|
29
30
|
|
|
30
31
|
matching = sorted([lv for lv in local_versions if lv.name == v], key=lambda x: x.path.name)
|
|
31
32
|
if not matching:
|
|
32
|
-
|
|
33
|
+
warn(f"Version {v} not found locally.")
|
|
33
34
|
continue
|
|
34
35
|
|
|
35
36
|
if len(matching) > 1:
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from enum import Enum, auto
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from tlaplus_cli.config.schema import Settings
|
|
6
|
+
from tlaplus_cli.versioning.paths import get_modules_dir
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ResolveMode(Enum):
|
|
10
|
+
"""Modes for classpath resolution."""
|
|
11
|
+
|
|
12
|
+
RUNTIME = auto() # For 'java -cp ... tlc2.TLC' (classes first)
|
|
13
|
+
COMPILE = auto() # For 'javac -cp ...' (tool jar first, no output classes)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SubdirType(Enum):
|
|
17
|
+
"""Types of project-local subdirectories."""
|
|
18
|
+
|
|
19
|
+
MODULES = auto()
|
|
20
|
+
CLASSES = auto()
|
|
21
|
+
LIB = auto()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ClasspathResolver:
|
|
25
|
+
"""
|
|
26
|
+
Centralized component for resolving Java classpaths and system properties.
|
|
27
|
+
Follows a strict Specific-to-General hierarchy: CLI > Project > Config > Global.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
config: Settings,
|
|
33
|
+
project_root: Path | None = None,
|
|
34
|
+
tool_jar: Path | None = None,
|
|
35
|
+
extra_libs: list[str] | None = None,
|
|
36
|
+
extra_modules: list[str] | None = None,
|
|
37
|
+
):
|
|
38
|
+
"""
|
|
39
|
+
Initialize the resolver.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
config: The global settings.
|
|
43
|
+
project_root: The root of the TLA+ project (where .tla file is).
|
|
44
|
+
tool_jar: Path to tla2tools.jar.
|
|
45
|
+
extra_libs: Explicit library paths from CLI.
|
|
46
|
+
extra_modules: Explicit module paths from CLI.
|
|
47
|
+
"""
|
|
48
|
+
self.config = config
|
|
49
|
+
self.project_root = project_root
|
|
50
|
+
self.tool_jar = tool_jar
|
|
51
|
+
self.extra_libs = extra_libs or []
|
|
52
|
+
self.extra_modules = extra_modules or []
|
|
53
|
+
|
|
54
|
+
def get_project_path(self, subdir: SubdirType) -> Path | None:
|
|
55
|
+
"""Helper to find a project-local directory."""
|
|
56
|
+
if not self.project_root:
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
# 1. Try explicit workspace config mapping
|
|
60
|
+
if subdir == SubdirType.MODULES:
|
|
61
|
+
path = self.project_root / self.config.workspace.modules_dir
|
|
62
|
+
if path.is_dir():
|
|
63
|
+
return path
|
|
64
|
+
elif subdir == SubdirType.CLASSES:
|
|
65
|
+
path = self.project_root / self.config.workspace.classes_dir
|
|
66
|
+
if path.is_dir():
|
|
67
|
+
return path
|
|
68
|
+
|
|
69
|
+
# 2. Try direct subdir (new standard)
|
|
70
|
+
subdir_name = {
|
|
71
|
+
SubdirType.MODULES: "modules",
|
|
72
|
+
SubdirType.CLASSES: "classes",
|
|
73
|
+
SubdirType.LIB: "lib",
|
|
74
|
+
}[subdir]
|
|
75
|
+
path = self.project_root / subdir_name
|
|
76
|
+
if path.is_dir():
|
|
77
|
+
return path
|
|
78
|
+
|
|
79
|
+
# 3. Try legacy/nested 'lib' inside modules_dir
|
|
80
|
+
if subdir == SubdirType.LIB:
|
|
81
|
+
path = self.project_root / self.config.workspace.modules_dir / "lib"
|
|
82
|
+
if path.is_dir():
|
|
83
|
+
return path
|
|
84
|
+
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
def _resolve_paths(self, cli_paths: list[str], subdir: SubdirType, config_path: str | None) -> list[str]:
|
|
88
|
+
"""
|
|
89
|
+
Resolves a set of paths following the hierarchy.
|
|
90
|
+
Deduplicates while preserving order.
|
|
91
|
+
"""
|
|
92
|
+
raw_paths = []
|
|
93
|
+
|
|
94
|
+
# 1. CLI Arguments
|
|
95
|
+
raw_paths.extend(cli_paths)
|
|
96
|
+
|
|
97
|
+
# 2. Project-Local
|
|
98
|
+
project_path = self.get_project_path(subdir)
|
|
99
|
+
if project_path:
|
|
100
|
+
raw_paths.append(str(project_path.absolute()))
|
|
101
|
+
|
|
102
|
+
# 3. Config
|
|
103
|
+
if config_path:
|
|
104
|
+
raw_paths.append(str(Path(config_path).absolute()))
|
|
105
|
+
|
|
106
|
+
# 4. Managed Modules (Global Cache)
|
|
107
|
+
managed_dir = get_modules_dir()
|
|
108
|
+
if managed_dir.is_dir():
|
|
109
|
+
# Add all subdirectories of managed_dir to raw_paths so they are expanded for JARs
|
|
110
|
+
for item in managed_dir.iterdir():
|
|
111
|
+
if item.is_dir():
|
|
112
|
+
raw_paths.append(str(item.absolute()))
|
|
113
|
+
|
|
114
|
+
# Deduplicate while preserving order
|
|
115
|
+
seen = set()
|
|
116
|
+
deduped = []
|
|
117
|
+
for p in raw_paths:
|
|
118
|
+
# Expand directories to include JARs if it's a lib path
|
|
119
|
+
path_obj = Path(p)
|
|
120
|
+
if path_obj.is_dir() and subdir == SubdirType.LIB:
|
|
121
|
+
for jar in sorted(path_obj.glob("*.jar")):
|
|
122
|
+
jar_abs = str(jar.absolute())
|
|
123
|
+
if jar_abs not in seen:
|
|
124
|
+
seen.add(jar_abs)
|
|
125
|
+
deduped.append(jar_abs)
|
|
126
|
+
else:
|
|
127
|
+
p_abs = str(path_obj.absolute())
|
|
128
|
+
if p_abs not in seen:
|
|
129
|
+
seen.add(p_abs)
|
|
130
|
+
deduped.append(p_abs)
|
|
131
|
+
|
|
132
|
+
return deduped
|
|
133
|
+
|
|
134
|
+
def resolve(self, mode: ResolveMode) -> list[str]:
|
|
135
|
+
"""
|
|
136
|
+
Resolve all necessary paths for the classpath based on the requested mode.
|
|
137
|
+
Deduplicates results while preserving the Specific-to-General order
|
|
138
|
+
and mode-specific requirements.
|
|
139
|
+
"""
|
|
140
|
+
classpath = []
|
|
141
|
+
|
|
142
|
+
# In RUNTIME mode, custom classes shadow everything
|
|
143
|
+
if mode == ResolveMode.RUNTIME:
|
|
144
|
+
classes_dir = self.get_project_path(SubdirType.CLASSES)
|
|
145
|
+
if classes_dir:
|
|
146
|
+
classpath.append(str(classes_dir.absolute()))
|
|
147
|
+
|
|
148
|
+
# Tool JAR
|
|
149
|
+
if self.tool_jar:
|
|
150
|
+
classpath.append(str(self.tool_jar.absolute()))
|
|
151
|
+
|
|
152
|
+
# Libraries (CLI > Project > Config)
|
|
153
|
+
libs = self._resolve_paths(self.extra_libs, SubdirType.LIB, self.config.module_lib_path)
|
|
154
|
+
classpath.extend(libs)
|
|
155
|
+
|
|
156
|
+
# Final deduplication (e.g. if tool_jar is also in libs)
|
|
157
|
+
seen = set()
|
|
158
|
+
final_cp = []
|
|
159
|
+
for p in classpath:
|
|
160
|
+
if p not in seen:
|
|
161
|
+
seen.add(p)
|
|
162
|
+
final_cp.append(p)
|
|
163
|
+
|
|
164
|
+
return final_cp
|
|
165
|
+
|
|
166
|
+
def get_tla_library_property(self) -> str | None:
|
|
167
|
+
"""
|
|
168
|
+
Resolve paths for -DTLA-Library system property.
|
|
169
|
+
Deduplicates, orders, and joins multiple valid directories.
|
|
170
|
+
"""
|
|
171
|
+
modules = self._resolve_paths(self.extra_modules, SubdirType.MODULES, self.config.module_path)
|
|
172
|
+
if not modules:
|
|
173
|
+
return None
|
|
174
|
+
return os.pathsep.join(modules)
|
|
@@ -3,6 +3,10 @@ tla:
|
|
|
3
3
|
tags: https://api.github.com/repos/tlaplus/tlaplus/tags
|
|
4
4
|
releases: https://api.github.com/repos/tlaplus/tlaplus/releases
|
|
5
5
|
per_page: 30
|
|
6
|
+
community_modules:
|
|
7
|
+
tags: https://api.github.com/repos/tlaplus/CommunityModules/tags
|
|
8
|
+
releases: https://api.github.com/repos/tlaplus/CommunityModules/releases
|
|
9
|
+
per_page: 30
|
|
6
10
|
|
|
7
11
|
# Path to the TLA+ workspace (specs, custom modules, compiled classes).
|
|
8
12
|
# Relative paths are resolved from the current working directory.
|