tlaplus-cli 0.2.1__tar.gz → 0.3.2__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.
Files changed (38) hide show
  1. {tlaplus_cli-0.2.1/src/tlaplus_cli.egg-info → tlaplus_cli-0.3.2}/PKG-INFO +67 -30
  2. tlaplus_cli-0.3.2/README.md +218 -0
  3. {tlaplus_cli-0.2.1 → tlaplus_cli-0.3.2}/pyproject.toml +3 -1
  4. tlaplus_cli-0.3.2/src/tlaplus_cli/__init__.py +6 -0
  5. {tlaplus_cli-0.2.1 → tlaplus_cli-0.3.2}/src/tlaplus_cli/build_tlc_module.py +13 -14
  6. {tlaplus_cli-0.2.1 → tlaplus_cli-0.3.2}/src/tlaplus_cli/cli.py +9 -6
  7. tlaplus_cli-0.3.2/src/tlaplus_cli/project.py +35 -0
  8. {tlaplus_cli-0.2.1 → tlaplus_cli-0.3.2}/src/tlaplus_cli/run_tlc.py +38 -19
  9. tlaplus_cli-0.2.1/src/tlaplus_cli/tlc_manager.py → tlaplus_cli-0.3.2/src/tlaplus_cli/tools_manager.py +130 -76
  10. {tlaplus_cli-0.2.1 → tlaplus_cli-0.3.2}/src/tlaplus_cli/version_manager.py +95 -20
  11. {tlaplus_cli-0.2.1 → tlaplus_cli-0.3.2/src/tlaplus_cli.egg-info}/PKG-INFO +67 -30
  12. {tlaplus_cli-0.2.1 → tlaplus_cli-0.3.2}/src/tlaplus_cli.egg-info/SOURCES.txt +5 -2
  13. {tlaplus_cli-0.2.1 → tlaplus_cli-0.3.2}/src/tlaplus_cli.egg-info/requires.txt +1 -0
  14. tlaplus_cli-0.3.2/tests/test_build_tlc_module.py +161 -0
  15. {tlaplus_cli-0.2.1 → tlaplus_cli-0.3.2}/tests/test_java_module_naming.py +8 -7
  16. tlaplus_cli-0.3.2/tests/test_project.py +46 -0
  17. {tlaplus_cli-0.2.1 → tlaplus_cli-0.3.2}/tests/test_run_tlc.py +10 -10
  18. tlaplus_cli-0.3.2/tests/test_spec_resolution.py +200 -0
  19. tlaplus_cli-0.3.2/tests/test_tools_manager.py +636 -0
  20. tlaplus_cli-0.3.2/tests/test_version_manager.py +193 -0
  21. tlaplus_cli-0.2.1/README.md +0 -182
  22. tlaplus_cli-0.2.1/src/tlaplus_cli/__init__.py +0 -0
  23. tlaplus_cli-0.2.1/tests/test_build_tlc_module.py +0 -77
  24. tlaplus_cli-0.2.1/tests/test_tlc_manager.py +0 -355
  25. tlaplus_cli-0.2.1/tests/test_version_manager.py +0 -70
  26. {tlaplus_cli-0.2.1 → tlaplus_cli-0.3.2}/LICENSE +0 -0
  27. {tlaplus_cli-0.2.1 → tlaplus_cli-0.3.2}/MANIFEST.in +0 -0
  28. {tlaplus_cli-0.2.1 → tlaplus_cli-0.3.2}/setup.cfg +0 -0
  29. {tlaplus_cli-0.2.1 → tlaplus_cli-0.3.2}/src/tlaplus_cli/check_java.py +0 -0
  30. {tlaplus_cli-0.2.1 → tlaplus_cli-0.3.2}/src/tlaplus_cli/config.py +0 -0
  31. {tlaplus_cli-0.2.1 → tlaplus_cli-0.3.2}/src/tlaplus_cli/py.typed +0 -0
  32. {tlaplus_cli-0.2.1 → tlaplus_cli-0.3.2}/src/tlaplus_cli/resources/default_config.yaml +0 -0
  33. {tlaplus_cli-0.2.1 → tlaplus_cli-0.3.2}/src/tlaplus_cli/settings.py +0 -0
  34. {tlaplus_cli-0.2.1 → tlaplus_cli-0.3.2}/src/tlaplus_cli.egg-info/dependency_links.txt +0 -0
  35. {tlaplus_cli-0.2.1 → tlaplus_cli-0.3.2}/src/tlaplus_cli.egg-info/entry_points.txt +0 -0
  36. {tlaplus_cli-0.2.1 → tlaplus_cli-0.3.2}/src/tlaplus_cli.egg-info/top_level.txt +0 -0
  37. {tlaplus_cli-0.2.1 → tlaplus_cli-0.3.2}/tests/test_check_java.py +0 -0
  38. {tlaplus_cli-0.2.1 → tlaplus_cli-0.3.2}/tests/test_cli.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tlaplus-cli
3
- Version: 0.2.1
3
+ Version: 0.3.2
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
@@ -24,6 +24,7 @@ Requires-Dist: requests<3.0.0,>=2.31.0
24
24
  Requires-Dist: pyyaml<7.0,>=6.0
25
25
  Requires-Dist: platformdirs<5.0,>=4.0
26
26
  Requires-Dist: pydantic<3.0.0,>=2.12.5
27
+ Requires-Dist: truststore<1.0.0,>=0.9.0
27
28
  Dynamic: license-file
28
29
 
29
30
  # TLA+ CLI
@@ -50,99 +51,135 @@ uv tool uninstall tlaplus-cli
50
51
 
51
52
  ## Usage
52
53
 
53
- ### Managing TLC Versions
54
+ ### Managing TLA+ Tools
54
55
 
55
- The `tla tlc` command group allows you to download and manage multiple versions of the TLC model checker directly from GitHub releases.
56
+ The `tla tools` command group allows you to download and manage multiple versions of the TLA+ toolset (TLC, SANY, TLATeX) directly from GitHub releases.
56
57
 
57
- List available and installed TLC versions:
58
+ List available and installed toolset versions:
58
59
  ```bash
59
- tla tlc list
60
+ tla tools list
60
61
  ```
61
62
 
62
- Install the latest TLC version:
63
+ Install the latest toolset version:
63
64
  ```bash
64
- tla tlc install
65
+ tla tools install
65
66
  ```
66
67
 
67
- Install a specific TLC version:
68
+ > [!TIP]
69
+ > The first version you install is automatically "pinned" as the default. Subsequent installs won't change your pin unless you manually use `tla tools pin`.
70
+
71
+ Install a specific toolset version:
68
72
  ```bash
69
- tla tlc install v1.8.0
73
+ tla tools install v1.8.0
70
74
  ```
71
75
 
72
76
  Pin a specific version to be used by default:
73
77
  ```bash
74
- tla tlc pin v1.8.0
78
+ tla tools pin v1.8.0
75
79
  ```
76
80
 
77
81
  Upgrade the pinned version (or a specific version) to a newer commit:
78
82
  ```bash
79
- tla tlc upgrade
83
+ tla tools upgrade
80
84
  ```
81
85
 
82
- Show the TLC2 version string and absolute path to the pinned version's `tla2tools.jar`:
86
+ > [!NOTE]
87
+ > If the target version to upgrade is not yet installed locally, the CLI will automatically download it.
88
+
89
+ Show the absolute path to the pinned version's `tla2tools.jar`:
83
90
  ```bash
84
- tla tlc path
91
+ tla tools path
85
92
  ```
86
93
 
87
94
  Or for a specific version:
88
95
  ```bash
89
- tla tlc path v1.8.0
96
+ tla tools path v1.8.0
90
97
  ```
91
98
 
92
99
  Example output:
93
100
  ```text
94
- TLC2 Version 2.19 of 08 August 2024 (rev: 5a47802)
95
- /home/bob/.cache/tla/tlc/v1.8.0-5a47802/tla2tools.jar
101
+ /home/bob/.cache/tla/tools/v1.8.0-5a47802/tla2tools.jar
96
102
  ```
97
103
 
98
- Show the TLC versions directory and all installed version directories:
104
+ Show the toolset versions directory and all installed version directories:
99
105
  ```bash
100
- tla tlc dir
106
+ tla tools dir
101
107
  ```
102
108
 
103
109
  Example output:
104
110
  ```text
105
- /home/bob/.cache/tla/tlc
111
+ /home/bob/.cache/tla/tools
106
112
  v1.7.0-abc1234
107
113
  v1.8.0-5a47802
108
114
  ```
109
115
 
110
116
  Uninstall a specific version (or use 'default' to remove legacy jars):
111
117
  ```bash
112
- tla tlc uninstall v1.8.0
118
+ tla tools uninstall v1.8.0
113
119
  ```
114
120
 
121
+ > [!TIP]
122
+ > Use `--all` to remove all installed tags for a specific version name without interactive prompts.
123
+
124
+ > [!NOTE]
125
+ > If you uninstall the currently pinned version, the CLI will automatically "fall back" to the next best installed version (ranked by semver, then release date).
126
+
115
127
  ### Run TLC
116
128
 
117
- Run the TLC model checker on a specification. This uses the currently pinned TLC version.
129
+ Run the TLC model checker on a specification. This uses the currently pinned toolset version.
118
130
 
119
131
  ```bash
120
- tla run <spec_name>
132
+ tla tlc <spec_name>
121
133
  ```
122
134
 
123
135
  For example (runs `queue.tla`):
124
136
 
125
137
  ```bash
126
- tla run queue
138
+ tla tlc queue
127
139
  ```
128
140
 
129
- > **Note:** Starting from version `0.2.0`, the command structure has changed. To run a model, use `tla run <spec>`. For older versions (<0.2.0), the command was `tla tlc <spec>`.
141
+ #### Project-Aware Execution
142
+
143
+ The CLI intelligently resolves the specification file and its project structure. It will check the following locations:
144
+ 1. `<spec_name>` (if it's a direct path to a file, e.g., `models/my_model.tla`)
145
+ 2. `<spec_name>.tla`
146
+ 3. `spec/<spec_name>.tla` (checks an inner `spec/` folder relative to the spec's location)
147
+
148
+ Furthermore, it automatically discovers the **project root** by looking for `modules/`, `classes/`, or `lib/` directories adjacent to the spec file or its parent. When a project root is found:
149
+ - The project's `classes/` directory is added to the Java classpath.
150
+ - Any `*.jar` files found in the project's `lib/` directory are added to the Java classpath.
151
+ - The `-DTLA-Library` system property is set to the project's `modules/` directory, allowing TLC to find your custom Java overrides.
152
+
153
+ To check the currently pinned `tla2tools.jar` path and its TLC version:
154
+
155
+ ```bash
156
+ tla tlc --version
157
+ ```
130
158
 
131
159
  ### Compile Custom Java Modules
132
160
 
133
- Note that modules are compiled using the pinned version of TLC.
161
+ Java modules (overrides) are compiled using the pinned version of the toolset.
162
+
163
+ Compile modules in the current project:
164
+ ```bash
165
+ tla modules build
166
+ ```
134
167
 
135
- Compile modules:
168
+ Compile modules in a specific project path:
136
169
  ```bash
137
- tla build
170
+ tla modules build path/to/project
138
171
  ```
139
172
 
140
173
  Verbose output:
141
174
  ```bash
142
- tla build --verbose
175
+ tla modules build --verbose
143
176
  ```
144
177
 
145
- Compiles `.java` files from `workspace/modules/` into `workspace/classes/`.
178
+ The build command:
179
+ 1. Resolves the project root (defaults to `workspace.root` from config).
180
+ 2. Includes `lib/*.jar` files from the project root in the `javac` classpath.
181
+ 3. Compiles `.java` files from the project's `modules/` directory into its `classes/` directory.
182
+ 4. Generates the necessary Java service provider configuration for TLC overrides.
146
183
 
147
184
  ### Check Java Version
148
185
 
@@ -196,7 +233,7 @@ java:
196
233
  | Directory | Purpose | Location |
197
234
  |---|---|---|
198
235
  | Config | `config.yaml` | `~/.config/tla/` |
199
- | TLC Versions | Version dirs & `tlc-pinned-version.txt` file | `~/.cache/tla/tlc/` |
236
+ | Toolset Versions | Version dirs & `tools-pinned-version.txt` file | `~/.cache/tla/tools/` |
200
237
  | API Cache | `github_cache.json` | `~/.cache/tla/` |
201
238
  | Workspace | specs + modules + classes | Set via `workspace.root` in config |
202
239
 
@@ -0,0 +1,218 @@
1
+ # TLA+ CLI
2
+
3
+ Command-line tool for working with TLA+ specifications and the TLC model checker.
4
+
5
+ ## Installation
6
+
7
+ Install system-wide via uv tool:
8
+
9
+ ```bash
10
+ uv tool install tlaplus-cli
11
+ ```
12
+
13
+ Upgrade:
14
+ ```bash
15
+ uv tool upgrade tlaplus-cli
16
+ ```
17
+
18
+ Uninstall:
19
+ ```bash
20
+ uv tool uninstall tlaplus-cli
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ### Managing TLA+ Tools
26
+
27
+ The `tla tools` command group allows you to download and manage multiple versions of the TLA+ toolset (TLC, SANY, TLATeX) directly from GitHub releases.
28
+
29
+ List available and installed toolset versions:
30
+ ```bash
31
+ tla tools list
32
+ ```
33
+
34
+ Install the latest toolset version:
35
+ ```bash
36
+ tla tools install
37
+ ```
38
+
39
+ > [!TIP]
40
+ > The first version you install is automatically "pinned" as the default. Subsequent installs won't change your pin unless you manually use `tla tools pin`.
41
+
42
+ Install a specific toolset version:
43
+ ```bash
44
+ tla tools install v1.8.0
45
+ ```
46
+
47
+ Pin a specific version to be used by default:
48
+ ```bash
49
+ tla tools pin v1.8.0
50
+ ```
51
+
52
+ Upgrade the pinned version (or a specific version) to a newer commit:
53
+ ```bash
54
+ tla tools upgrade
55
+ ```
56
+
57
+ > [!NOTE]
58
+ > If the target version to upgrade is not yet installed locally, the CLI will automatically download it.
59
+
60
+ Show the absolute path to the pinned version's `tla2tools.jar`:
61
+ ```bash
62
+ tla tools path
63
+ ```
64
+
65
+ Or for a specific version:
66
+ ```bash
67
+ tla tools path v1.8.0
68
+ ```
69
+
70
+ Example output:
71
+ ```text
72
+ /home/bob/.cache/tla/tools/v1.8.0-5a47802/tla2tools.jar
73
+ ```
74
+
75
+ Show the toolset versions directory and all installed version directories:
76
+ ```bash
77
+ tla tools dir
78
+ ```
79
+
80
+ Example output:
81
+ ```text
82
+ /home/bob/.cache/tla/tools
83
+ v1.7.0-abc1234
84
+ v1.8.0-5a47802
85
+ ```
86
+
87
+ Uninstall a specific version (or use 'default' to remove legacy jars):
88
+ ```bash
89
+ tla tools uninstall v1.8.0
90
+ ```
91
+
92
+ > [!TIP]
93
+ > Use `--all` to remove all installed tags for a specific version name without interactive prompts.
94
+
95
+ > [!NOTE]
96
+ > If you uninstall the currently pinned version, the CLI will automatically "fall back" to the next best installed version (ranked by semver, then release date).
97
+
98
+ ### Run TLC
99
+
100
+ Run the TLC model checker on a specification. This uses the currently pinned toolset version.
101
+
102
+ ```bash
103
+ tla tlc <spec_name>
104
+ ```
105
+
106
+ For example (runs `queue.tla`):
107
+
108
+ ```bash
109
+ tla tlc queue
110
+ ```
111
+
112
+ #### Project-Aware Execution
113
+
114
+ The CLI intelligently resolves the specification file and its project structure. It will check the following locations:
115
+ 1. `<spec_name>` (if it's a direct path to a file, e.g., `models/my_model.tla`)
116
+ 2. `<spec_name>.tla`
117
+ 3. `spec/<spec_name>.tla` (checks an inner `spec/` folder relative to the spec's location)
118
+
119
+ Furthermore, it automatically discovers the **project root** by looking for `modules/`, `classes/`, or `lib/` directories adjacent to the spec file or its parent. When a project root is found:
120
+ - The project's `classes/` directory is added to the Java classpath.
121
+ - Any `*.jar` files found in the project's `lib/` directory are added to the Java classpath.
122
+ - The `-DTLA-Library` system property is set to the project's `modules/` directory, allowing TLC to find your custom Java overrides.
123
+
124
+ To check the currently pinned `tla2tools.jar` path and its TLC version:
125
+
126
+ ```bash
127
+ tla tlc --version
128
+ ```
129
+
130
+ ### Compile Custom Java Modules
131
+
132
+ Java modules (overrides) are compiled using the pinned version of the toolset.
133
+
134
+ Compile modules in the current project:
135
+ ```bash
136
+ tla modules build
137
+ ```
138
+
139
+ Compile modules in a specific project path:
140
+ ```bash
141
+ tla modules build path/to/project
142
+ ```
143
+
144
+ Verbose output:
145
+ ```bash
146
+ tla modules build --verbose
147
+ ```
148
+
149
+ The build command:
150
+ 1. Resolves the project root (defaults to `workspace.root` from config).
151
+ 2. Includes `lib/*.jar` files from the project root in the `javac` classpath.
152
+ 3. Compiles `.java` files from the project's `modules/` directory into its `classes/` directory.
153
+ 4. Generates the necessary Java service provider configuration for TLC overrides.
154
+
155
+ ### Check Java Version
156
+
157
+ ```bash
158
+ tla check-java
159
+ ```
160
+
161
+ ### Cache Management
162
+
163
+ The CLI caches GitHub API responses for 1 hour to prevent rate limiting. To clear this cache manually:
164
+
165
+ ```bash
166
+ tla fetch-cache clear
167
+ ```
168
+
169
+ ## Configuration
170
+
171
+ On first run, a default config is created at:
172
+
173
+ ```
174
+ ~/.config/tla/config.yaml
175
+ ```
176
+
177
+ Edit this file to set your workspace path and TLC options:
178
+
179
+ ```yaml
180
+ tla:
181
+ urls:
182
+ tags: https://api.github.com/repos/tlaplus/tlaplus/tags
183
+ releases: https://api.github.com/repos/tlaplus/tlaplus/releases
184
+
185
+ workspace:
186
+ root: . # Project root (relative to CWD)
187
+ spec_dir: spec # Directory containing .tla files
188
+ modules_dir: modules # Directory containing .java files
189
+ classes_dir: classes # Directory for compiled .class files
190
+
191
+ tlc:
192
+ java_class: tlc2.TLC
193
+ overrides_class: tlc2.overrides.TLCOverrides
194
+
195
+ java:
196
+ min_version: 11
197
+ opts:
198
+ - "-XX:+IgnoreUnrecognizedVMOptions"
199
+ - "-XX:+UseParallelGC"
200
+ ```
201
+
202
+ ### Directory Layout
203
+
204
+ | Directory | Purpose | Location |
205
+ |---|---|---|
206
+ | Config | `config.yaml` | `~/.config/tla/` |
207
+ | Toolset Versions | Version dirs & `tools-pinned-version.txt` file | `~/.cache/tla/tools/` |
208
+ | API Cache | `github_cache.json` | `~/.cache/tla/` |
209
+ | Workspace | specs + modules + classes | Set via `workspace.root` in config |
210
+
211
+ ## Note on Package Name
212
+
213
+ This package is distributed on PyPI as **`tlaplus-cli`** but imports as **`tla`**. There is a separate, unrelated [`tla`](https://pypi.org/project/tla/) package on PyPI (a TLA+ parser). If you have both installed, they will conflict. In practice this is unlikely since they serve different purposes, but be aware of it.
214
+
215
+ ## Dependencies
216
+
217
+ * **Java >= 11**: Required for TLC.
218
+ * [**uv**](https://docs.astral.sh/uv/getting-started/installation/): For installing the tool.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tlaplus-cli"
7
- version = "0.2.1"
7
+ version = "0.3.2"
8
8
  description = "TLA+ tools: download TLC, compile custom modules, run model checker"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -14,6 +14,7 @@ dependencies = [
14
14
  "pyyaml>=6.0,<7.0",
15
15
  "platformdirs>=4.0,<5.0",
16
16
  "pydantic>=2.12.5,<3.0.0",
17
+ "truststore>=0.9.0,<1.0.0",
17
18
  ]
18
19
  authors = [
19
20
  {name = "Denis Nikolskiy", email = "codeomatics@gmail.com"}
@@ -100,6 +101,7 @@ ignore = [
100
101
  [tool.ruff.lint.per-file-ignores]
101
102
  "tests/*" = [
102
103
  "ARG001", # Unused arguments are common in tests (fixtures)
104
+ "PLR0913", # Many arguments are common in tests (fixtures)
103
105
  ]
104
106
 
105
107
  [tool.poe.tasks]
@@ -0,0 +1,6 @@
1
+ import truststore
2
+
3
+ # Inject truststore as early as possible so that all standard library
4
+ # and third-party tools relying on `ssl` (such as `requests` and `urllib3`)
5
+ # utilize the native OS trust store.
6
+ truststore.inject_into_ssl()
@@ -1,6 +1,8 @@
1
1
  """Compile custom TLC modules (Java)."""
2
2
 
3
+ import os
3
4
  import subprocess
5
+ from pathlib import Path
4
6
 
5
7
  import typer
6
8
 
@@ -9,10 +11,12 @@ from tlaplus_cli.version_manager import get_pinned_version_dir
9
11
 
10
12
 
11
13
  def build(
14
+ path: str | None = typer.Argument(None, help="Project root directory (defaults to workspace root)."),
12
15
  verbose: bool = typer.Option(False, "--verbose", "-v", help="Show compilation output."),
13
16
  ) -> None:
14
17
  """Compile custom Java modules."""
15
18
  config = load_config()
19
+ base_dir = Path(path).resolve() if path is not None else workspace_root()
16
20
 
17
21
  # Fallback chain: pinned directory -> legacy jar
18
22
  pinned_dir = get_pinned_version_dir()
@@ -20,31 +24,29 @@ def build(
20
24
  legacy = cache_dir() / "tla2tools.jar"
21
25
  jar_path = pinned_jar if (pinned_jar and pinned_jar.exists()) else legacy
22
26
 
23
- ws_root = workspace_root()
24
- modules_dir = ws_root / config.workspace.modules_dir
25
- classes_dir = ws_root / config.workspace.classes_dir
26
-
27
27
  if not jar_path.exists():
28
- typer.echo("Error: tla2tools.jar not found.", err=True)
29
- typer.echo("Run 'tla tlc install' first.", err=True)
28
+ typer.echo("Error: tla2tools.jar not found.\nRun 'tla tools install' first.", err=True)
30
29
  raise typer.Exit(1)
31
30
 
31
+ modules_dir = base_dir / config.workspace.modules_dir
32
+ classes_dir = base_dir / config.workspace.classes_dir
33
+
34
+ lib_dir = base_dir / "lib"
35
+ lib_jars = sorted(lib_dir.glob("*.jar")) if lib_dir.is_dir() else []
36
+ classpath = os.pathsep.join([str(jar_path)] + [str(j) for j in lib_jars])
37
+
32
38
  if not modules_dir.exists():
33
39
  typer.echo(f"Error: modules directory not found: {modules_dir}", err=True)
34
40
  raise typer.Exit(1)
35
41
 
36
- # Find all .java files in modules dir
37
42
  java_files = list(modules_dir.rglob("*.java"))
38
43
  if not java_files:
39
44
  typer.echo(f"No Java source files found in {modules_dir}", err=True)
40
45
  return
41
46
 
42
47
  typer.echo(f"Compiling {len(java_files)} Java files from {modules_dir} ...")
43
-
44
- # Ensure output dir exists
45
48
  classes_dir.mkdir(parents=True, exist_ok=True)
46
-
47
- cmd = ["javac", "-cp", str(jar_path), "-d", str(classes_dir), *[str(f) for f in java_files]]
49
+ cmd = ["javac", "-cp", classpath, "-d", str(classes_dir), *[str(f) for f in java_files]]
48
50
 
49
51
  try:
50
52
  result = subprocess.run(cmd, check=True, capture_output=not verbose, text=True)
@@ -52,14 +54,11 @@ def build(
52
54
  if verbose and result.stdout:
53
55
  typer.echo(result.stdout)
54
56
 
55
- # Create META-INF/services/tlc2.overrides.ITLCOverrides
56
57
  meta_inf = classes_dir / "META-INF" / "services"
57
58
  meta_inf.mkdir(parents=True, exist_ok=True)
58
59
  service_file = meta_inf / "tlc2.overrides.ITLCOverrides"
59
-
60
60
  with service_file.open("w") as f:
61
61
  f.write(f"{config.tlc.overrides_class}\n")
62
-
63
62
  typer.echo(f"Created service file at {service_file}")
64
63
 
65
64
  except subprocess.CalledProcessError as e:
@@ -8,11 +8,11 @@ from tlaplus_cli.build_tlc_module import build as build_tlc_cmd
8
8
  from tlaplus_cli.check_java import check_java_version, get_java_version
9
9
  from tlaplus_cli.config import load_config
10
10
  from tlaplus_cli.run_tlc import tlc as run_tlc_cmd
11
- from tlaplus_cli.tlc_manager import fetch_cache_app, tlc_app
11
+ from tlaplus_cli.tools_manager import fetch_cache_app, tools_app
12
12
 
13
13
  app = typer.Typer(
14
14
  name="tla",
15
- help="TLA+ tools: download TLC, compile custom modules, run model checker.",
15
+ help="TLA+ tools: download TLA+ toolset distribution, compile custom modules, run model checker.",
16
16
  no_args_is_help=True,
17
17
  add_completion=False,
18
18
  )
@@ -47,12 +47,15 @@ def root(
47
47
 
48
48
  # --- Subcommands ---
49
49
 
50
- # Attach typer subgroups for TLC and fetch-cache
51
- app.add_typer(tlc_app, name="tlc")
50
+ # Attach typer subgroups
51
+ modules_app = typer.Typer(name="modules", help="Manage TLA+ Java modules.", no_args_is_help=True)
52
+ modules_app.command(name="build")(build_tlc_cmd)
53
+
54
+ app.add_typer(modules_app, name="modules")
55
+ app.add_typer(tools_app, name="tools")
52
56
  app.add_typer(fetch_cache_app, name="fetch-cache")
53
57
 
54
- app.command(name="run")(run_tlc_cmd)
55
- app.command(name="build")(build_tlc_cmd)
58
+ app.command(name="tlc")(run_tlc_cmd)
56
59
 
57
60
 
58
61
  @app.command(name="check-java")
@@ -0,0 +1,35 @@
1
+ """Project root discovery utilities."""
2
+
3
+ from pathlib import Path
4
+
5
+
6
+ def _is_project_root(directory: Path, modules_dir: Path | str, classes_dir: Path | str, lib_dir: Path | str) -> bool:
7
+ """Return True if directory contains at least one project structure marker."""
8
+ return (
9
+ (directory / classes_dir).is_dir()
10
+ or (directory / modules_dir).is_dir()
11
+ or (directory / lib_dir).is_dir()
12
+ )
13
+
14
+
15
+ def find_project_root(
16
+ spec_file: Path,
17
+ *,
18
+ modules_dir: Path | str,
19
+ classes_dir: Path | str,
20
+ lib_dir: Path | str = "lib",
21
+ ) -> Path | None:
22
+ """Resolve the project root relative to a .tla spec file.
23
+
24
+ Resolution strategy (in order):
25
+ 1. Check the spec file's own directory.
26
+ 2. Check the parent of the spec file's directory (handles spec/ and tests/ subdirs).
27
+ 3. Return None if neither directory qualifies.
28
+ """
29
+ spec_dir = spec_file.parent.resolve()
30
+
31
+ for candidate in (spec_dir, spec_dir.parent):
32
+ if _is_project_root(candidate, modules_dir, classes_dir, lib_dir):
33
+ return candidate
34
+
35
+ return None
@@ -2,11 +2,13 @@
2
2
 
3
3
  import os
4
4
  import subprocess
5
+ from pathlib import Path
5
6
 
6
7
  import typer
7
8
 
8
9
  from tlaplus_cli.check_java import check_java_version
9
- from tlaplus_cli.config import cache_dir, load_config, workspace_root
10
+ from tlaplus_cli.config import cache_dir, load_config
11
+ from tlaplus_cli.project import find_project_root
10
12
  from tlaplus_cli.version_manager import get_pinned_version_dir
11
13
 
12
14
 
@@ -21,7 +23,7 @@ def version_callback(value: bool) -> None:
21
23
 
22
24
  if not jar_path.exists():
23
25
  typer.echo(f"Error: tla2tools.jar not found at {jar_path}", err=True)
24
- typer.echo("Run 'tla tlc install' first.", err=True)
26
+ typer.echo("Run 'tla tools install' first.", err=True)
25
27
  raise typer.Exit(1)
26
28
 
27
29
  cmd = ["java", "-cp", str(jar_path), config.tlc.java_class]
@@ -62,25 +64,42 @@ def tlc(
62
64
  jar_path = pinned_jar if (pinned_jar and pinned_jar.exists()) else legacy
63
65
 
64
66
  if not jar_path.exists():
65
- typer.echo("Error: tla2tools.jar not found.", err=True)
66
- typer.echo("Run 'tla tlc install' first.", err=True)
67
+ typer.echo("Error: tla2tools.jar not found.\nRun 'tla tools install' first.", err=True)
67
68
  raise typer.Exit(1)
68
69
 
69
- ws_root = workspace_root()
70
- spec_dir = ws_root / config.workspace.spec_dir
71
- spec_file = spec_dir / f"{spec}.tla"
72
- if not spec_file.exists():
73
- typer.echo(f"Error: specification not found: {spec_file}", err=True)
74
- raise typer.Exit(1)
75
-
76
- classpath = str(jar_path)
77
- classes_path = ws_root / config.workspace.classes_dir
78
- if classes_path.exists():
79
- classpath = f"{classes_path}{os.pathsep}{classpath}"
70
+ # Spec resolution logic
71
+ spec_path = Path(spec)
72
+ candidates = [spec_path, spec_path.with_suffix(".tla"),
73
+ spec_path.parent / "spec" / (spec_path.name + ".tla")]
80
74
 
81
- cmd = ["java", *config.java.opts, "-cp", classpath, config.tlc.java_class, str(spec_file)]
75
+ spec_file = next((c for c in candidates if c.is_file()), None)
76
+ if not spec_file:
77
+ typer.echo("Error: Could not find a TLA+ spec file. Looked in the following locations:", err=True)
78
+ for c in candidates:
79
+ typer.echo(f"- {c}", err=True)
80
+ raise typer.Exit(1)
82
81
 
83
- typer.echo(f"Running TLC on {spec}.tla ...")
84
- typer.echo(f"Command: {' '.join(cmd)}")
85
- result = subprocess.run(cmd, cwd=str(spec_dir))
82
+ spec_file = spec_file.absolute()
83
+ project_root = find_project_root(
84
+ spec_file, modules_dir=config.workspace.modules_dir, classes_dir=config.workspace.classes_dir
85
+ )
86
+
87
+ classpath_parts = [str(jar_path)]
88
+ extra_jvm_opts: list[str] = []
89
+ if project_root:
90
+ classes_path = project_root / config.workspace.classes_dir
91
+ if classes_path.is_dir():
92
+ classpath_parts.insert(0, str(classes_path))
93
+ lib_dir = project_root / "lib"
94
+ if lib_dir.is_dir():
95
+ classpath_parts.extend(str(j) for j in sorted(lib_dir.glob("*.jar")))
96
+ modules_path = project_root / config.workspace.modules_dir
97
+ if modules_path.is_dir():
98
+ extra_jvm_opts.append(f"-DTLA-Library={modules_path}")
99
+
100
+ cmd = ["java", *config.java.opts, *extra_jvm_opts, "-cp", os.pathsep.join(classpath_parts),
101
+ config.tlc.java_class, spec_file.name]
102
+
103
+ typer.echo(f"Running TLC on {spec_file.name} ...\nCommand: {' '.join(cmd)}")
104
+ result = subprocess.run(cmd, cwd=str(spec_file.parent))
86
105
  raise typer.Exit(result.returncode)