@@ -9,49 +9,24 @@ import rmc
9
9
import rmc_flags
10
10
import os
11
11
import pathlib
12
+ import toml
12
13
13
14
MY_PATH = pathlib .Path (__file__ ).parent .parent .absolute ()
14
15
RMC_C_LIB = MY_PATH / "library" / "rmc" / "rmc_lib.c"
15
16
EXIT_CODE_SUCCESS = 0
16
17
CBMC_VERIFICATION_FAILURE_EXIT_CODE = 10
17
18
18
-
19
19
def main ():
20
- # Remove "rmc" from arg if invoked `cargo rmc ...`
21
- if len (sys .argv ) >= 2 and sys .argv [1 ] == "rmc" :
22
- del sys .argv [1 ]
23
-
24
- parser = argparse .ArgumentParser (prog = "cargo rmc" , description = "Verify a Rust crate. For more information, see https://github.com/model-checking/rmc." )
25
-
26
- crate_group = parser .add_argument_group ("Crate" , "You can pass in the rust crate positionally or with the --crate flag." )
27
- crate_group .add_argument ("crate" , help = "crate to verify" , nargs = "?" )
28
- crate_group .add_argument ("--crate" , help = "crate to verify" , dest = "crate_flag" , metavar = "CRATE" )
29
-
30
- exclude_flags = []
31
- rmc_flags .add_flags (parser , {"default-target" : "target" }, exclude_flags = exclude_flags )
32
- args = parser .parse_args ()
33
-
34
- if args .crate :
35
- rmc .ensure (args .crate_flag is None , "Please provide a single crate to verify." )
36
- else :
37
- rmc .ensure (args .crate_flag is not None , "Please provide a crate to verify." )
38
- args .crate = args .crate_flag
39
-
40
- if args .quiet :
41
- args .verbose = False
20
+ args = parse_args ()
42
21
43
22
rmc .ensure_dependencies_in_path ()
44
23
45
- # Add some CBMC flags by default unless `--no-default-checks` is being used
46
- if not args .no_default_checks :
47
- rmc .add_selected_default_cbmc_flags (args )
48
-
49
24
rmc .cargo_build (args .crate , args .target_dir ,
50
25
args .verbose , args .debug , args .mangler , args .dry_run , [])
51
26
52
27
pattern = os .path .join (args .target_dir , "debug" , "deps" , "*.json" )
53
28
jsons = glob .glob (pattern )
54
- rmc .ensure (len (jsons ) == 1 , "Unexpected number of json outputs. " )
29
+ rmc .ensure (len (jsons ) == 1 , f "Unexpected number of json outputs: { len ( jsons ) } " )
55
30
56
31
cbmc_filename = os .path .join (args .target_dir , "cbmc.out" )
57
32
c_filename = os .path .join (args .target_dir , "cbmc.c" )
@@ -89,6 +64,162 @@ def main():
89
64
90
65
return retcode
91
66
67
+ def parse_args ():
68
+ # Create parser
69
+ def create_parser ():
70
+ parser = argparse .ArgumentParser (prog = "cargo rmc" , description = "Verify a Rust crate. For more information, see https://github.com/model-checking/rmc." )
71
+
72
+ crate_group = parser .add_argument_group ("Crate" , "You can pass in the rust crate positionally or with the --crate flag." )
73
+ crate_group .add_argument ("crate" , help = "crate to verify" , nargs = "?" )
74
+ crate_group .add_argument ("--crate" , help = "crate to verify" , dest = "crate_flag" , metavar = "CRATE" )
75
+
76
+ config_group = parser .add_argument_group ("Config" , "You can configure cargo rmc with your Cargo.toml file." )
77
+ config_group .add_argument ("--config-toml" , help = "Where to read configuration from; defaults to crate's Cargo.toml" )
78
+ config_group .add_argument ("--no-config-toml" , action = "store_true" , help = "Do not use any configuration toml" )
79
+
80
+ exclude_flags = []
81
+ rmc_flags .add_flags (parser , {"default-target" : "target" }, exclude_flags = exclude_flags )
82
+
83
+ return parser
84
+
85
+ # Determine what crate to analyze from flags
86
+ def get_crate_from_args (args ):
87
+ validate (args )
88
+ return args .crate or args .crate_flag or "."
89
+
90
+ # Combine the command line parameter to get a config file;
91
+ # return None if no_config_toml is set
92
+ def get_config_toml (no_config_toml , config_toml , args ):
93
+ crate = get_crate_from_args (args )
94
+ if no_config_toml :
95
+ return None
96
+ elif config_toml is not None :
97
+ # If config_toml is explicitly given, ensure it exists
98
+ rmc .ensure (pathlib .Path (config_toml ).is_file (), f"Invalid path to config file: { config_toml } " )
99
+ return config_toml
100
+ else :
101
+ # If no config flags are set, look for config toml in default location (<crate>/Cargo.toml)
102
+ cargo_toml = pathlib .Path (crate ).joinpath ("Cargo.toml" )
103
+ rmc .ensure (cargo_toml .is_file (), f"Cannot find configuration toml at expected location: { cargo_toml } " )
104
+ return cargo_toml
105
+
106
+ # Combine args set by config toml and provided on command line
107
+ def get_combined_args (parser , config_toml ):
108
+ # Extract flags from config_toml
109
+ toml_flags = extract_flags_from_toml (config_toml )
110
+
111
+ # Load args from toml flags
112
+ combined_args = argparse .Namespace ()
113
+ parser .parse_args (toml_flags , namespace = combined_args )
114
+
115
+ # Set relative paths to be rooted at config toml's directory rather than cwd
116
+ def fix_paths (value ):
117
+ if type (value ) == list :
118
+ return list (map (fix_paths , value ))
119
+ elif isinstance (value , pathlib .PurePath ):
120
+ return pathlib .Path (config_toml ).parent .joinpath (value )
121
+ else :
122
+ return value
123
+
124
+ ca_dict = combined_args .__dict__
125
+ for key in ca_dict :
126
+ ca_dict [key ] = fix_paths (ca_dict [key ])
127
+
128
+ # Load args from command line using toml args as default
129
+ parser .parse_args (sys .argv [1 :], namespace = combined_args )
130
+
131
+ return combined_args
132
+
133
+ # Check for conflicting flags
134
+ def validate (args ):
135
+ rmc .ensure (not (args .crate and args .crate_flag ), "Please provide a single crate to verify." )
136
+ rmc .ensure (not (args .no_config_toml and args .config_toml ), "Incompatible flags: --config-toml, --no-config-toml" )
137
+
138
+ # Fix up args before returning
139
+ def post_process (args ):
140
+ # Combine positional and flag argument for input
141
+ args .crate = get_crate_from_args (args )
142
+
143
+ # --quiet overrides --verbose
144
+ if args .quiet :
145
+ args .verbose = False
146
+
147
+ # Add some CBMC flags by default unless `--no-default-checks` is being used
148
+ if args .default_checks :
149
+ rmc .add_selected_default_cbmc_flags (args )
150
+
151
+ # Remove "rmc" from arg if invoked `cargo rmc ...`
152
+ if len (sys .argv ) >= 2 and sys .argv [1 ] == "rmc" :
153
+ del sys .argv [1 ]
154
+
155
+ # Parse command line args to find crate and check for config flags
156
+ parser = create_parser ()
157
+ cl_args = parser .parse_args ()
158
+ validate (cl_args )
159
+
160
+ # Try to find the config_toml based on command line arguments
161
+ config_toml = get_config_toml (cl_args .no_config_toml , cl_args .config_toml , cl_args )
162
+
163
+ if config_toml is not None :
164
+ # If config_toml is found, combine configuration with command line arguments
165
+ combined_args = get_combined_args (parser , config_toml )
166
+ validate (combined_args )
167
+ post_process (combined_args )
168
+ return combined_args
169
+ else :
170
+ # If config_toml is missing, just use the parsed command line arguments
171
+ post_process (cl_args )
172
+ return cl_args
173
+
174
+ # Extract a list of args based on given config toml
175
+ def extract_flags_from_toml (path ):
176
+ # Load the flag data from toml
177
+ data = toml .load (path )
178
+
179
+ # Extract the rmc flags from the toml, if any present
180
+ if ("rmc" not in data ) or ("flags" not in data ["rmc" ]):
181
+ # If no flags are present, just return none
182
+ return []
183
+ rmc_data = data ["rmc" ]
184
+ flag_data = rmc_data ["flags" ]
185
+
186
+ # Extract nested flags
187
+ flags = dict ()
188
+ def find_flags (map ):
189
+ for key in map :
190
+ if type (map [key ]) == dict :
191
+ find_flags (map [key ])
192
+ else :
193
+ flags [key ] = map [key ]
194
+ find_flags (flag_data )
195
+
196
+ # Add config toml flags to flag list
197
+ success = True
198
+ flag_list = []
199
+ def add_flag (flag , value ):
200
+ if type (value ) == bool :
201
+ if value :
202
+ flag_list .append (f"--{ flag } " )
203
+ else :
204
+ if flag .startswith ("no-" ):
205
+ flag_list .append (f"--{ flag [3 :]} " )
206
+ else :
207
+ flag_list .append (f"--no-{ flag } " )
208
+ elif type (value ) == list :
209
+ flag_list .append (f"--{ flag } " )
210
+ assert all (map (lambda arg : type (arg ) == str , value )), f"ERROR: Invalid config: { flag } = { value } "
211
+ flag_list .extend (value )
212
+ elif type (value ) == str :
213
+ flag_list .append (f"--{ flag } " )
214
+ flag_list .append (value )
215
+ else :
216
+ print (f"ERROR: Invalid config: { flag } = { value } " )
217
+ success = False
218
+ for flag in flags :
219
+ add_flag (flag , flags [flag ])
220
+
221
+ rmc .ensure (success )
222
+ return flag_list
92
223
93
224
if __name__ == "__main__" :
94
225
sys .exit (main ())
0 commit comments