Skip to content

Commit 0935d9f

Browse files
ehildenbrv-auditor
andauthored
Refactor options classes and CLI args (#548)
This PR refactors the options classes and CLI args to deduplicate some things, and use the options classes directly when invoking the prover. In particular: - All the classes `*Opts` are moved out of `__main__` and into `options`. - The function `Prove.prove_rs(...)` now takes a `ProveRSOpts` argument instead of all the individual arguments on their own. - We factor out a common `ProveOpts` class that `ProveRunOpts` and `ProveRSOpts` draw from instead of duplicating their entries. - We factor out a common `prove_args` CLI parser that `prove_rs_args` and `prove_run_args` pull from. - This also makes a small change where we make sure to update the `kmir/uv.lock` file on version bumps to keep that from happening locally afterward. --------- Co-authored-by: devops <devops@runtimeverification.com>
1 parent 3328431 commit 0935d9f

9 files changed

Lines changed: 153 additions & 129 deletions

File tree

.github/workflows/test.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,17 @@ jobs:
2121
run: |
2222
git config user.name devops
2323
git config user.email devops@runtimeverification.com
24+
- name: 'Install uv'
25+
uses: astral-sh/setup-uv@v5
26+
with:
27+
version: 0.7.2
2428
- name: 'Update version'
2529
run: |
2630
og_version=$(git show origin/${GITHUB_BASE_REF}:package/version)
2731
./package/version.sh bump ${og_version}
2832
./package/version.sh sub
2933
new_version=$(cat package/version)
34+
uv --directory kmir lock --no-upgrade
3035
git add --update && git commit --message "Set Version: $(cat package/version)" || true
3136
- name: 'Push updates'
3237
run: git push origin HEAD:${GITHUB_HEAD_REF}

kmir/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "kmir"
7-
version = "0.3.133"
7+
version = "0.3.134"
88
description = ""
99
requires-python = "~=3.10"
1010
dependencies = [

kmir/src/kmir/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
from typing import Final
22

3-
VERSION: Final = '0.3.133'
3+
VERSION: Final = '0.3.134'

kmir/src/kmir/__main__.py

Lines changed: 14 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,17 @@
33
import logging
44
import sys
55
from argparse import ArgumentParser
6-
from dataclasses import dataclass
76
from pathlib import Path
87
from typing import TYPE_CHECKING
98

10-
from pyk.cli.args import KCLIArgs, LoggingOptions
9+
from pyk.cli.args import KCLIArgs
1110
from pyk.kast.outer import KFlatModule, KImport
1211
from pyk.proof.reachability import APRProof, APRProver
1312
from pyk.proof.tui import APRProofViewer
1413

1514
from .build import HASKELL_DEF_DIR, LLVM_LIB_DIR, haskell_semantics, llvm_semantics
1615
from .kmir import KMIR, KMIRAPRNodePrinter
16+
from .options import GenSpecOpts, ProvePruneOpts, ProveRSOpts, ProveRunOpts, ProveViewOpts, RunOpts
1717
from .parse.parser import parse_json
1818
from .rust import CargoProject
1919

@@ -22,103 +22,12 @@
2222
from collections.abc import Sequence
2323
from typing import Final
2424

25+
from .options import KMirOpts
26+
2527
_LOGGER: Final = logging.getLogger(__name__)
2628
_LOG_FORMAT: Final = '%(levelname)s %(asctime)s %(name)s - %(message)s'
2729

2830

29-
@dataclass
30-
class KMirOpts(LoggingOptions): ...
31-
32-
33-
@dataclass
34-
class RunOpts(KMirOpts):
35-
bin: str | None
36-
file: str | None
37-
depth: int
38-
start_symbol: str
39-
haskell_backend: bool
40-
41-
42-
@dataclass
43-
class GenSpecOpts(KMirOpts):
44-
input_file: Path
45-
output_file: Path | None
46-
start_symbol: str
47-
48-
def __init__(self, input_file: Path, output_file: Path | str | None, start_symbol: str) -> None:
49-
self.input_file = input_file
50-
if output_file is None:
51-
self.output_file = None
52-
else:
53-
self.output_file = Path(output_file).resolve()
54-
self.start_symbol = start_symbol
55-
56-
57-
@dataclass
58-
class ProveRSOpts(KMirOpts):
59-
rs_file: Path
60-
bug_report: Path | None
61-
max_depth: int | None
62-
max_iterations: int | None
63-
64-
def __init__(
65-
self,
66-
rs_file: Path,
67-
bug_report: Path | None = None,
68-
max_depth: int | None = None,
69-
max_iterations: int | None = None,
70-
) -> None:
71-
self.rs_file = rs_file
72-
self.bug_report = bug_report
73-
self.max_depth = max_depth
74-
self.max_iterations = max_iterations
75-
76-
77-
@dataclass
78-
class ProveRunOpts(KMirOpts):
79-
spec_file: Path
80-
proof_dir: Path | None
81-
include_labels: tuple[str, ...] | None
82-
exclude_labels: tuple[str, ...] | None
83-
bug_report: Path | None
84-
max_depth: int | None
85-
max_iterations: int | None
86-
reload: bool
87-
88-
def __init__(
89-
self,
90-
spec_file: Path,
91-
proof_dir: Path | str | None,
92-
include_labels: str | None,
93-
exclude_labels: str | None,
94-
bug_report: Path | None = None,
95-
max_depth: int | None = None,
96-
max_iterations: int | None = None,
97-
reload: bool = False,
98-
) -> None:
99-
self.spec_file = spec_file
100-
self.proof_dir = Path(proof_dir).resolve() if proof_dir is not None else None
101-
self.include_labels = tuple(include_labels.split(',')) if include_labels is not None else None
102-
self.exclude_labels = tuple(exclude_labels.split(',')) if exclude_labels is not None else None
103-
self.bug_report = bug_report
104-
self.max_depth = max_depth
105-
self.max_iterations = max_iterations
106-
self.reload = reload
107-
108-
109-
@dataclass
110-
class ProveViewOpts(KMirOpts):
111-
proof_dir: Path
112-
id: str
113-
114-
115-
@dataclass
116-
class ProvePruneOpts(KMirOpts):
117-
proof_dir: Path
118-
id: str
119-
node_id: int
120-
121-
12231
def _kmir_run(opts: RunOpts) -> None:
12332
tools = haskell_semantics() if opts.haskell_backend else llvm_semantics()
12433

@@ -143,10 +52,8 @@ def _kmir_run(opts: RunOpts) -> None:
14352

14453

14554
def _kmir_prove_rs(opts: ProveRSOpts) -> None:
146-
if not opts.rs_file.is_file():
147-
raise ValueError(f'Rust spec file does not exist: {opts.rs_file}')
14855
kmir = KMIR(HASKELL_DEF_DIR, LLVM_LIB_DIR, bug_report=opts.bug_report)
149-
proof = kmir.prove_rs(opts.rs_file, max_depth=opts.max_depth, max_iterations=opts.max_iterations)
56+
proof = kmir.prove_rs(opts)
15057
print(str(proof.summary))
15158
if not proof.passed:
15259
sys.exit(1)
@@ -264,12 +171,19 @@ def _arg_parser() -> ArgumentParser:
264171
'--start-symbol', type=str, metavar='SYMBOL', default='main', help='Symbol name to begin execution from'
265172
)
266173

174+
prove_args = ArgumentParser(add_help=False)
175+
prove_args.add_argument('--bug-report', metavar='PATH', help='path to optional bug report')
176+
prove_args.add_argument('--max-depth', metavar='DEPTH', type=int, help='max steps to take between nodes in kcfg')
177+
prove_args.add_argument(
178+
'--max-iterations', metavar='ITERATIONS', type=int, help='max number of proof iterations to take'
179+
)
180+
267181
prove_parser = command_parser.add_parser(
268182
'prove', help='Utilities for working with proofs over SMIR', parents=[kcli_args.logging_args]
269183
)
270184
prove_command_parser = prove_parser.add_subparsers(dest='prove_command', required=True)
271185

272-
prove_run_parser = prove_command_parser.add_parser('run', help='Run the prover on a spec')
186+
prove_run_parser = prove_command_parser.add_parser('run', help='Run the prover on a spec', parents=[prove_args])
273187
prove_run_parser.add_argument('input_file', metavar='FILE', help='K File with the spec module')
274188
prove_run_parser.add_argument('--proof-dir', metavar='DIR', help='Proof directory')
275189
prove_run_parser.add_argument(
@@ -278,14 +192,7 @@ def _arg_parser() -> ArgumentParser:
278192
prove_run_parser.add_argument(
279193
'--exclude-labels', metavar='LABELS', help='Comma separated list of claim labels to exclude'
280194
)
281-
prove_run_parser.add_argument('--bug-report', metavar='PATH', help='path to optional bug report')
282-
prove_run_parser.add_argument(
283-
'--max-depth', metavar='DEPTH', type=int, help='max steps to take between nodes in kcfg'
284-
)
285195
prove_run_parser.add_argument('--reload', action='store_true', help='Force restarting proof')
286-
prove_run_parser.add_argument(
287-
'--max-iterations', metavar='ITERATIONS', type=int, help='max number of proof iterations to take'
288-
)
289196

290197
prove_view_parser = prove_command_parser.add_parser('view', help='View a saved proof')
291198
prove_view_parser.add_argument('id', metavar='PROOF_ID', help='The id of the proof to view')
@@ -299,18 +206,11 @@ def _arg_parser() -> ArgumentParser:
299206
prove_prune_parser.add_argument('node_id', metavar='NODE', type=int, help='The node to prune')
300207

301208
prove_rs_parser = command_parser.add_parser(
302-
'prove-rs', help='Prove a rust program', parents=[kcli_args.logging_args]
209+
'prove-rs', help='Prove a rust program', parents=[kcli_args.logging_args, prove_args]
303210
)
304211
prove_rs_parser.add_argument(
305212
'rs_file', type=Path, metavar='FILE', help='Rust file with the spec function (e.g. main)'
306213
)
307-
prove_rs_parser.add_argument('--bug-report', metavar='PATH', help='path to optional bug report')
308-
prove_rs_parser.add_argument(
309-
'--max-depth', metavar='DEPTH', type=int, help='max steps to take between nodes in kcfg'
310-
)
311-
prove_rs_parser.add_argument(
312-
'--max-iterations', metavar='ITERATIONS', type=int, help='max number of proof iterations to take'
313-
)
314214

315215
return parser
316216

kmir/src/kmir/kmir.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727

2828
from pyk.utils import BugReport
2929

30+
from .options import ProveRSOpts
31+
3032

3133
class KMIR(KProve, KRun):
3234
llvm_library_dir: Path | None
@@ -68,24 +70,21 @@ def apr_proof_from_kast(self, id: str, kmir_kast: KInner, sort: str = 'Generated
6870
target_node = kcfg.create_node(rhs)
6971
return APRProof(id, kcfg, [], init_node.id, target_node.id, {})
7072

71-
def prove_rs(
72-
self,
73-
rs_file: Path,
74-
max_depth: int | None = None,
75-
max_iterations: int | None = None,
76-
) -> APRProof:
77-
smir_json = cargo_get_smir_json(rs_file)
73+
def prove_rs(self, opts: ProveRSOpts) -> APRProof:
74+
if not opts.rs_file.is_file():
75+
raise ValueError(f'Rust spec file does not exist: {opts.rs_file}')
7876

77+
smir_json = cargo_get_smir_json(opts.rs_file)
7978
parser = Parser(self.definition)
8079
parse_result = parser.parse_mir_json(smir_json, 'Pgm')
8180
assert parse_result is not None
8281
kmir_kast, _ = parse_result
8382
assert isinstance(kmir_kast, KInner)
8483

85-
apr_proof = self.apr_proof_from_kast(str(rs_file.stem), kmir_kast)
84+
apr_proof = self.apr_proof_from_kast(str(opts.rs_file.stem), kmir_kast)
8685
with self.kcfg_explore('PROOF-TEST') as kcfg_explore:
87-
prover = APRProver(kcfg_explore, execute_depth=max_depth)
88-
prover.advance_proof(apr_proof, max_iterations=max_iterations)
86+
prover = APRProver(kcfg_explore, execute_depth=opts.max_depth)
87+
prover.advance_proof(apr_proof, max_iterations=opts.max_iterations)
8988
return apr_proof
9089

9190

kmir/src/kmir/options.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
from __future__ import annotations
2+
3+
import logging
4+
from dataclasses import dataclass
5+
from pathlib import Path
6+
from typing import TYPE_CHECKING
7+
8+
from pyk.cli.args import LoggingOptions
9+
10+
if TYPE_CHECKING:
11+
from typing import Final
12+
13+
_LOGGER: Final = logging.getLogger(__name__)
14+
_LOG_FORMAT: Final = '%(levelname)s %(asctime)s %(name)s - %(message)s'
15+
16+
17+
@dataclass
18+
class KMirOpts(LoggingOptions): ...
19+
20+
21+
@dataclass
22+
class RunOpts(KMirOpts):
23+
bin: str | None
24+
file: str | None
25+
depth: int
26+
start_symbol: str
27+
haskell_backend: bool
28+
29+
30+
@dataclass
31+
class ProveOpts(KMirOpts):
32+
bug_report: Path | None
33+
max_depth: int | None
34+
max_iterations: int | None
35+
36+
def __init__(
37+
self,
38+
bug_report: Path | None = None,
39+
max_depth: int | None = None,
40+
max_iterations: int | None = None,
41+
) -> None:
42+
self.bug_report = bug_report
43+
self.max_depth = max_depth
44+
self.max_iterations = max_iterations
45+
46+
47+
@dataclass
48+
class GenSpecOpts(KMirOpts):
49+
input_file: Path
50+
output_file: Path | None
51+
start_symbol: str
52+
53+
def __init__(self, input_file: Path, output_file: Path | str | None, start_symbol: str) -> None:
54+
self.input_file = input_file
55+
if output_file is None:
56+
self.output_file = None
57+
else:
58+
self.output_file = Path(output_file).resolve()
59+
self.start_symbol = start_symbol
60+
61+
62+
@dataclass
63+
class ProveRSOpts(ProveOpts):
64+
rs_file: Path
65+
66+
def __init__(
67+
self,
68+
rs_file: Path,
69+
bug_report: Path | None = None,
70+
max_depth: int | None = None,
71+
max_iterations: int | None = None,
72+
) -> None:
73+
self.rs_file = rs_file
74+
self.bug_report = bug_report
75+
self.max_depth = max_depth
76+
self.max_iterations = max_iterations
77+
78+
79+
@dataclass
80+
class ProveRunOpts(ProveOpts):
81+
spec_file: Path
82+
proof_dir: Path | None
83+
include_labels: tuple[str, ...] | None
84+
exclude_labels: tuple[str, ...] | None
85+
reload: bool
86+
87+
def __init__(
88+
self,
89+
spec_file: Path,
90+
proof_dir: Path | str | None,
91+
include_labels: str | None,
92+
exclude_labels: str | None,
93+
bug_report: Path | None = None,
94+
max_depth: int | None = None,
95+
max_iterations: int | None = None,
96+
reload: bool = False,
97+
) -> None:
98+
self.spec_file = spec_file
99+
self.proof_dir = Path(proof_dir).resolve() if proof_dir is not None else None
100+
self.include_labels = tuple(include_labels.split(',')) if include_labels is not None else None
101+
self.exclude_labels = tuple(exclude_labels.split(',')) if exclude_labels is not None else None
102+
self.bug_report = bug_report
103+
self.max_depth = max_depth
104+
self.max_iterations = max_iterations
105+
self.reload = reload
106+
107+
108+
@dataclass
109+
class ProveViewOpts(KMirOpts):
110+
proof_dir: Path
111+
id: str
112+
113+
114+
@dataclass
115+
class ProvePruneOpts(KMirOpts):
116+
proof_dir: Path
117+
id: str
118+
node_id: int

0 commit comments

Comments
 (0)