• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

chrjabs / rustsat / 17126001466

21 Aug 2025 10:07AM UTC coverage: 60.443% (+0.5%) from 59.961%
17126001466

push

github

chrjabs
chore(ci): switch CI to nix

13173 of 21794 relevant lines covered (60.44%)

134798.23 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/codegen/src/main.rs
1
use std::io::Write;
2

3
use minijinja::{context, Environment};
4
use similar::{ChangeTag, TextDiff};
5
use tempfile::NamedTempFile;
6

7
fn main() {
×
8
    // Check if current directory has a Cargo.toml with [workspace]
9
    let cargo_toml_path = std::env::current_dir().unwrap().join("Cargo.toml");
×
10
    let cargo_toml_content =
×
11
        std::fs::read_to_string(cargo_toml_path).expect("Failed to read Cargo.toml");
×
12
    if !cargo_toml_content.contains("[workspace]") {
×
13
        panic!(
×
14
            "Cargo.toml does not contain [workspace] (you must run codegen from the workspace root)"
×
15
        );
16
    }
×
17

18
    let do_check = std::env::args().any(|arg| arg == "--check");
×
19
    let mut has_changes = false;
×
20

21
    let templates = template_env();
×
22

23
    let am1_encs = [
×
24
        Am1 {
×
25
            name: "Pairwise",
×
26
            id: "pairwise",
×
27
            wrapped: false,
×
28
            n_vars: 4,
×
29
            n_clauses: 6,
×
30
        },
×
31
        Am1 {
×
32
            name: "Ladder",
×
33
            id: "ladder",
×
34
            wrapped: false,
×
35
            n_vars: 7,
×
36
            n_clauses: 8,
×
37
        },
×
38
        Am1 {
×
39
            name: "Bitwise",
×
40
            id: "bitwise",
×
41
            wrapped: false,
×
42
            n_vars: 6,
×
43
            n_clauses: 8,
×
44
        },
×
45
        Am1 {
×
46
            name: "Commander",
×
47
            id: "commander",
×
48
            wrapped: true,
×
49
            n_vars: 5,
×
50
            n_clauses: 10,
×
51
        },
×
52
        Am1 {
×
53
            name: "Bimander",
×
54
            id: "bimander",
×
55
            wrapped: true,
×
56
            n_vars: 5,
×
57
            n_clauses: 10,
×
58
        },
×
59
    ];
×
60
    let path = "capi/src/encodings/am1.rs";
×
61
    let generated = rustfmt(capi_enc_bindings("capi-am1.rs.j2", &am1_encs, &templates));
×
62
    if do_check {
×
63
        has_changes |= diff(path, &generated);
×
64
    } else {
×
65
        write!(file(path), "{generated}").unwrap();
×
66
    }
×
67
    has_changes |= capi_tests("am1", &am1_encs, &templates, do_check);
×
68

69
    let card_encs = [Card {
×
70
        name: "Totalizer",
×
71
        id: "tot",
×
72
        ub: true,
×
73
        lb: true,
×
74
        n_vars: 12,
×
75
        n_clauses: 28,
×
76
    }];
×
77
    let path = "capi/src/encodings/card.rs";
×
78
    let generated = rustfmt(capi_enc_bindings("capi-card.rs.j2", &card_encs, &templates));
×
79
    if do_check {
×
80
        has_changes |= diff(path, &generated);
×
81
    } else {
×
82
        write!(file(path), "{generated}").unwrap();
×
83
    }
×
84
    has_changes |= capi_tests("card", &card_encs, &templates, do_check);
×
85

86
    let pb_encs = [
×
87
        Pb {
×
88
            name: "GeneralizedTotalizer",
×
89
            id: "gte",
×
90
            ub: true,
×
91
            lb: false,
×
92
            extend: true,
×
93
            n_vars: 24,
×
94
            n_vars_reserve: 24,
×
95
            n_clauses: 25,
×
96
            skip_reserve: false,
×
97
        },
×
98
        Pb {
×
99
            name: "BinaryAdder",
×
100
            id: "bin_adder",
×
101
            ub: true,
×
102
            lb: true,
×
103
            extend: true,
×
104
            n_vars: 20,
×
105
            n_vars_reserve: 20,
×
106
            n_clauses: 53,
×
107
            skip_reserve: true,
×
108
        },
×
109
        Pb {
×
110
            name: "DynamicPolyWatchdog",
×
111
            id: "dpw",
×
112
            ub: true,
×
113
            lb: false,
×
114
            extend: false,
×
115
            n_vars: 19,
×
116
            n_vars_reserve: 19,
×
117
            n_clauses: 21,
×
118
            skip_reserve: false,
×
119
        },
×
120
    ];
×
121
    let path = "capi/src/encodings/pb.rs";
×
122
    let generated = rustfmt(capi_enc_bindings("capi-pb.rs.j2", &pb_encs, &templates));
×
123
    if do_check {
×
124
        has_changes |= diff(path, &generated);
×
125
    } else {
×
126
        write!(file(path), "{generated}").unwrap();
×
127
    }
×
128
    has_changes |= capi_tests("pb", &pb_encs, &templates, do_check);
×
129

130
    has_changes |= capi_header(do_check);
×
131

132
    if has_changes && do_check {
×
133
        std::process::exit(1);
×
134
    }
×
135
}
×
136

137
fn template_env() -> Environment<'static> {
×
138
    let mut env = Environment::new();
×
139
    env.set_loader(minijinja::path_loader("codegen/templates"));
×
140
    env
×
141
}
×
142

143
fn file(path: &str) -> impl std::io::Write {
×
144
    std::io::BufWriter::new(std::fs::File::create(path).expect("could not open file"))
×
145
}
×
146

147
/// Runs `rustfmt` on a generated string
148
fn rustfmt(generated: String) -> String {
×
149
    let mut fmt = std::process::Command::new("rustfmt")
×
150
        .stdin(std::process::Stdio::piped())
×
151
        .stdout(std::process::Stdio::piped())
×
152
        .spawn()
×
153
        .expect("Failed to spawn rustfmt");
×
154

155
    fmt.stdin
×
156
        .take()
×
157
        .expect("Failed to get stdin")
×
158
        .write_all(generated.as_bytes())
×
159
        .expect("Failed to write to rustfmt stdin");
×
160

161
    let formatted_output = fmt.wait_with_output().expect("Failed to wait for rustfmt");
×
162
    if !formatted_output.status.success() {
×
163
        eprintln!("rustfmt failed with exit code: {}", formatted_output.status);
×
164
        std::process::exit(1);
×
165
    }
×
166

167
    String::from_utf8(formatted_output.stdout).unwrap()
×
168
}
×
169

170
/// Runs `clang-format` on a generated string
171
fn clang_format(generated: String) -> String {
×
172
    let mut fmt = std::process::Command::new("clang-format")
×
173
        .stdin(std::process::Stdio::piped())
×
174
        .stdout(std::process::Stdio::piped())
×
175
        .spawn()
×
176
        .expect("Failed to spawn clang-format");
×
177

178
    fmt.stdin
×
179
        .take()
×
180
        .expect("Failed to get stdin")
×
181
        .write_all(generated.as_bytes())
×
182
        .expect("Failed to write to clang-format stdin");
×
183

184
    let formatted_output = fmt
×
185
        .wait_with_output()
×
186
        .expect("Failed to wait for clang-format");
×
187
    if !formatted_output.status.success() {
×
188
        eprintln!(
×
189
            "clang-format failed with exit code: {}",
×
190
            formatted_output.status
191
        );
192
        std::process::exit(1);
×
193
    }
×
194

195
    String::from_utf8(formatted_output.stdout).unwrap()
×
196
}
×
197

198
fn diff(path: &str, generated: &str) -> bool {
×
199
    let Ok(old) = std::fs::read(path) else {
×
200
        eprintln!("Would create {path}");
×
201
        return true;
×
202
    };
203
    let old = std::str::from_utf8(&old).unwrap();
×
204
    if old == generated {
×
205
        return false;
×
206
    }
×
207
    let diff = TextDiff::from_lines(old, generated);
×
208
    eprintln!("Diff for {path}:");
×
209
    for change in diff.iter_all_changes() {
×
210
        let sign = match change.tag() {
×
211
            ChangeTag::Delete => "-",
×
212
            ChangeTag::Insert => "+",
×
213
            ChangeTag::Equal => " ",
×
214
        };
215
        eprint!("{}{}", sign, change);
×
216
    }
217
    true
×
218
}
×
219

220
fn capi_enc_bindings<E: Enc>(
×
221
    template: &str,
×
222
    encs: &[E],
×
223
    templates: &Environment<'static>,
×
224
) -> String {
×
225
    let tmpl = templates.get_template(template).expect("missing template");
×
226
    let ub = encs.iter().any(|enc| enc.ub());
×
227
    let lb = encs.iter().any(|enc| enc.lb());
×
228
    tmpl.render(context!(encodings => encs, ub => ub, lb => lb))
×
229
        .expect("missing template context")
×
230
}
×
231

232
fn capi_tests<E: Enc>(
×
233
    id: &str,
×
234
    encs: &[E],
×
235
    templates: &Environment<'static>,
×
236
    do_check: bool,
×
237
) -> bool {
×
238
    let mut has_changes = false;
×
239
    for entry in std::fs::read_dir("codegen/templates/").expect("failed to iteratre over template")
×
240
    {
241
        let entry = entry.unwrap();
×
242
        let file_type = entry.file_type().unwrap();
×
243
        if file_type.is_file() {
×
244
            let filename = entry.file_name();
×
245
            let filename = filename.to_str().unwrap();
×
246
            if let Some(name) = filename.strip_prefix(&format!("capi-{id}-test-")) {
×
247
                let name = name.trim_end_matches(".j2");
×
248
                let tmpl = templates.get_template(filename).expect("missing template");
×
249
                for enc in encs {
×
250
                    if enc.skip(name) {
×
251
                        continue;
×
252
                    }
×
253
                    let path = format!("capi/tests/{}-{name}", enc.id());
×
254
                    let generated = clang_format(
×
255
                        tmpl.render(context!(enc => enc))
×
256
                            .expect("missing template context"),
×
257
                    );
258
                    if do_check {
×
259
                        has_changes |= diff(&path, &generated);
×
260
                    } else {
×
261
                        write!(file(&path), "{generated}").unwrap();
×
262
                    }
×
263
                }
264
            }
×
265
        }
×
266
    }
267
    has_changes
×
268
}
×
269

270
trait Enc: serde::Serialize {
271
    fn id(&self) -> &str;
272
    fn ub(&self) -> bool {
×
273
        false
×
274
    }
×
275
    fn lb(&self) -> bool {
×
276
        false
×
277
    }
×
278
    fn skip(&self, _key: &str) -> bool {
×
279
        false
×
280
    }
×
281
}
282

283
#[derive(serde::Serialize)]
284
struct Am1<'a> {
285
    name: &'a str,
286
    id: &'a str,
287
    wrapped: bool,
288
    n_vars: u32,
289
    n_clauses: usize,
290
}
291

292
impl Enc for Am1<'_> {
293
    fn id(&self) -> &str {
×
294
        self.id
×
295
    }
×
296
}
297

298
#[derive(serde::Serialize)]
299
struct Card<'a> {
300
    name: &'a str,
301
    id: &'a str,
302
    ub: bool,
303
    lb: bool,
304
    n_vars: u32,
305
    n_clauses: usize,
306
}
307

308
impl Enc for Card<'_> {
309
    fn id(&self) -> &str {
×
310
        self.id
×
311
    }
×
312
    fn ub(&self) -> bool {
×
313
        self.ub
×
314
    }
×
315
    fn lb(&self) -> bool {
×
316
        self.lb
×
317
    }
×
318
}
319

320
#[derive(serde::Serialize)]
321
struct Pb<'a> {
322
    name: &'a str,
323
    id: &'a str,
324
    ub: bool,
325
    lb: bool,
326
    extend: bool,
327
    n_vars: u32,
328
    n_vars_reserve: u32,
329
    n_clauses: usize,
330
    skip_reserve: bool,
331
}
332

333
impl Enc for Pb<'_> {
334
    fn id(&self) -> &str {
×
335
        self.id
×
336
    }
×
337
    fn ub(&self) -> bool {
×
338
        self.ub
×
339
    }
×
340
    fn lb(&self) -> bool {
×
341
        self.lb
×
342
    }
×
343
    fn skip(&self, key: &str) -> bool {
×
344
        if key == "reserve.c" {
×
345
            return self.skip_reserve;
×
346
        }
×
347
        false
×
348
    }
×
349
}
350

351
/// Generates the C-API header
352
fn capi_header(do_check: bool) -> bool {
×
353
    let mut temp_path = None;
×
354
    let path = if do_check {
×
355
        let path = NamedTempFile::new().unwrap().into_temp_path();
×
356
        std::fs::copy("capi/rustsat.h", &path).unwrap();
×
357
        temp_path = Some(path);
×
358
        temp_path.as_ref().unwrap().to_str().unwrap()
×
359
    } else {
360
        "capi/rustsat.h"
×
361
    };
362
    let changed = cbindgen::Builder::new()
×
363
        .with_config(
×
364
            cbindgen::Config::from_file("capi/cbindgen.toml")
×
365
                .expect("could not read cbindgen.toml"),
×
366
        )
×
367
        .with_crate("capi")
×
368
        .with_after_include(format!(
×
369
            r#"#define RUSTSAT_VERSION {version}
×
370
#define RUSTSAT_VERSION_MAJOR {major}
×
371
#define RUSTSAT_VERSION_MINOR {minor}
×
372
#define RUSTSAT_VERSION_PATCH {patch}"#,
×
373
            version = env!("CARGO_PKG_VERSION"),
×
374
            major = env!("CARGO_PKG_VERSION_MAJOR"),
×
375
            minor = env!("CARGO_PKG_VERSION_MINOR"),
×
376
            patch = env!("CARGO_PKG_VERSION_PATCH"),
×
377
        ))
×
378
        .generate()
×
379
        .expect("Unable to generate bindings")
×
380
        .write_to_file(path);
×
381
    if changed {
×
382
        let generated = std::fs::read(path).unwrap();
×
383
        let generated = std::str::from_utf8(&generated).unwrap();
×
384
        diff("capi/rustsat.h", generated);
×
385
    }
×
386
    drop(temp_path);
×
387
    changed
×
388
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc