From 87d6f2e80ee6c757f57a31864f7e9aa1a325c2ab Mon Sep 17 00:00:00 2001 From: Christoph Jabs Date: Tue, 19 Aug 2025 13:57:32 +0300 Subject: [PATCH 1/4] chore(tests): make paths relative to cargo manifest --- tests/compression.rs | 20 ++++++++++++-------- tests/maxsat.rs | 22 ++++++++++++++++------ tests/pb_encodings.rs | 8 ++++++-- tools/src/encodings/assignment.rs | 5 ++++- tools/src/encodings/facilitylocation.rs | 4 +++- tools/src/encodings/knapsack.rs | 9 +++++++-- 6 files changed, 48 insertions(+), 20 deletions(-) diff --git a/tests/compression.rs b/tests/compression.rs index cb131106..a36d2305 100644 --- a/tests/compression.rs +++ b/tests/compression.rs @@ -6,8 +6,9 @@ use rustsat::{ #[test] fn small_sat_instance_gzip() { + let manifest = std::env::var("CARGO_MANIFEST_DIR").unwrap(); let inst: SatInstance = - SatInstance::from_dimacs_path("./data/AProVE11-12.cnf.gz").unwrap(); + SatInstance::from_dimacs_path(format!("{manifest}/data/AProVE11-12.cnf.gz")).unwrap(); let mut solver = rustsat_minisat::core::Minisat::default(); solver.add_cnf(inst.into_cnf().0).unwrap(); let res = solver.solve().unwrap(); @@ -17,9 +18,10 @@ fn small_sat_instance_gzip() { #[test] #[ignore] fn small_unsat_instance_gzip() { - let inst: SatInstance = SatInstance::from_dimacs_path( - "./data/smtlib-qfbv-aigs-ext_con_032_008_0256-tseitin.cnf.gz", - ) + let manifest = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let inst: SatInstance = SatInstance::from_dimacs_path(format!( + "{manifest}/data/smtlib-qfbv-aigs-ext_con_032_008_0256-tseitin.cnf.gz" + )) .unwrap(); let mut solver = rustsat_minisat::core::Minisat::default(); solver.add_cnf(inst.into_cnf().0).unwrap(); @@ -29,8 +31,9 @@ fn small_unsat_instance_gzip() { #[test] fn small_sat_instance_bz2() { + let manifest = std::env::var("CARGO_MANIFEST_DIR").unwrap(); let inst: SatInstance = - SatInstance::from_dimacs_path("./data/AProVE11-12.cnf.bz2").unwrap(); + SatInstance::from_dimacs_path(format!("{manifest}/data/AProVE11-12.cnf.bz2")).unwrap(); let mut solver = rustsat_minisat::core::Minisat::default(); solver.add_cnf(inst.into_cnf().0).unwrap(); let res = solver.solve().unwrap(); @@ -40,9 +43,10 @@ fn small_sat_instance_bz2() { #[test] #[ignore] fn small_unsat_instance_bz2() { - let inst: SatInstance = SatInstance::from_dimacs_path( - "./data/smtlib-qfbv-aigs-ext_con_032_008_0256-tseitin.cnf.bz2", - ) + let manifest = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let inst: SatInstance = SatInstance::from_dimacs_path(format!( + "{manifest}/data/smtlib-qfbv-aigs-ext_con_032_008_0256-tseitin.cnf.bz2" + )) .unwrap(); let mut solver = rustsat_minisat::core::Minisat::default(); solver.add_cnf(inst.into_cnf().0).unwrap(); diff --git a/tests/maxsat.rs b/tests/maxsat.rs index 93886018..971acc74 100644 --- a/tests/maxsat.rs +++ b/tests/maxsat.rs @@ -4,7 +4,9 @@ use rustsat::{algs::maxsat, encodings::pb, instances::OptInstance}; fn sis_small_gte() { type Alg = maxsat::SolutionImprovingSearch; - let inst: OptInstance = OptInstance::from_dimacs_path("./data/inc-sis-fails.wcnf").unwrap(); + let manifest = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let inst: OptInstance = + OptInstance::from_dimacs_path(format!("{manifest}/data/inc-sis-fails.wcnf")).unwrap(); let sol = inst.solve_maxsat::(); dbg!(&sol); assert!(matches!(sol, Some((_, 8632)))); @@ -14,8 +16,11 @@ fn sis_small_gte() { fn sis_gte() { type Alg = maxsat::SolutionImprovingSearch; - let inst: OptInstance = - OptInstance::from_dimacs_path("./data/auctions_wt-cat_sched_60_70_0003.txt.wcnf").unwrap(); + let manifest = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let inst: OptInstance = OptInstance::from_dimacs_path(format!( + "{manifest}/data/auctions_wt-cat_sched_60_70_0003.txt.wcnf" + )) + .unwrap(); let sol = inst.solve_maxsat::(); dbg!(&sol); assert!(matches!(sol, Some((_, 61169)))); @@ -24,7 +29,9 @@ fn sis_gte() { #[test] fn sis_small_adder() { type Alg = maxsat::SolutionImprovingSearch; - let inst: OptInstance = OptInstance::from_dimacs_path("./data/inc-sis-fails.wcnf").unwrap(); + let manifest = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let inst: OptInstance = + OptInstance::from_dimacs_path(format!("{manifest}/data/inc-sis-fails.wcnf")).unwrap(); let sol = inst.solve_maxsat::(); dbg!(&sol); assert!(matches!(sol, Some((_, 8632)))); @@ -33,8 +40,11 @@ fn sis_small_adder() { #[test] fn sis_adder() { type Alg = maxsat::SolutionImprovingSearch; - let inst: OptInstance = - OptInstance::from_dimacs_path("./data/auctions_wt-cat_sched_60_70_0003.txt.wcnf").unwrap(); + let manifest = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let inst: OptInstance = OptInstance::from_dimacs_path(format!( + "{manifest}/data/auctions_wt-cat_sched_60_70_0003.txt.wcnf" + )) + .unwrap(); let sol = inst.solve_maxsat::(); dbg!(&sol); assert!(matches!(sol, Some((_, 61169)))); diff --git a/tests/pb_encodings.rs b/tests/pb_encodings.rs index 63301ebd..9b324a1c 100644 --- a/tests/pb_encodings.rs +++ b/tests/pb_encodings.rs @@ -522,7 +522,9 @@ mod dpw_inc_prec { #[test] fn incremental_precision_2() { - let inst: OptInstance = OptInstance::from_dimacs_path("./data/inc-sis-fails.wcnf").unwrap(); + let manifest = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let inst: OptInstance = + OptInstance::from_dimacs_path(format!("{manifest}/data/inc-sis-fails.wcnf")).unwrap(); let (constr, obj) = inst.decompose(); let (cnf, mut vm) = constr.into_cnf(); let (hardened, (softs, offset)) = obj.into_soft_lits(&mut vm); @@ -565,7 +567,9 @@ mod dpw_inc_prec { #[test] fn incremental_precision_3() { - let inst: OptInstance = OptInstance::from_dimacs_path("./data/inc-sis-fails.wcnf").unwrap(); + let manifest = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let inst: OptInstance = + OptInstance::from_dimacs_path(format!("{manifest}/data/inc-sis-fails.wcnf")).unwrap(); let (constr, obj) = inst.decompose(); let (cnf, mut vm) = constr.into_cnf(); let (hardened, (softs, offset)) = obj.into_soft_lits(&mut vm); diff --git a/tools/src/encodings/assignment.rs b/tools/src/encodings/assignment.rs index 5f83dc28..aa36e476 100644 --- a/tools/src/encodings/assignment.rs +++ b/tools/src/encodings/assignment.rs @@ -135,7 +135,10 @@ mod parsing { #[test] fn moolib() { - let reader = BufReader::new(File::open("./data/AP_p-3_n-5_ins-1.dat").unwrap()); + let manifest = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let reader = BufReader::new( + File::open(format!("{manifest}/data/AP_p-3_n-5_ins-1.dat")).unwrap(), + ); super::parse_moolib(reader).unwrap(); } } diff --git a/tools/src/encodings/facilitylocation.rs b/tools/src/encodings/facilitylocation.rs index 0bf32abc..3dfe04c7 100644 --- a/tools/src/encodings/facilitylocation.rs +++ b/tools/src/encodings/facilitylocation.rs @@ -124,7 +124,9 @@ mod parsing { #[test] fn voptlib() { - let reader = BufReader::new(File::open("./data/didactic1.txt").unwrap()); + let manifest = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let reader = + BufReader::new(File::open(format!("{manifest}/data/didactic1.txt")).unwrap()); super::parse_voptlib(reader).unwrap(); } } diff --git a/tools/src/encodings/knapsack.rs b/tools/src/encodings/knapsack.rs index 73ccf879..7537bdc7 100644 --- a/tools/src/encodings/knapsack.rs +++ b/tools/src/encodings/knapsack.rs @@ -238,13 +238,18 @@ mod parsing { #[test] fn moolib() { - let reader = BufReader::new(File::open("./data/KP_p-3_n-10_ins-1.dat").unwrap()); + let manifest = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let reader = BufReader::new( + File::open(format!("{manifest}/data/KP_p-3_n-10_ins-1.dat")).unwrap(), + ); super::parse_moolib(reader).unwrap(); } #[test] fn voptlib() { - let reader = BufReader::new(File::open("./data/F5050W01.dat").unwrap()); + let manifest = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let reader = + BufReader::new(File::open(format!("{manifest}/data/F5050W01.dat")).unwrap()); super::parse_voptlib(reader).unwrap(); } } From 1f931e61c21abd7f979c84b19df4feba71dc11d1 Mon Sep 17 00:00:00 2001 From: Christoph Jabs Date: Tue, 19 Aug 2025 13:59:07 +0300 Subject: [PATCH 2/4] chore(codegen): add newlines at end of templates --- codegen/templates/capi-am1-test-basic.c.j2 | 1 + codegen/templates/capi-am1-test-drop.c.j2 | 1 + codegen/templates/capi-card-test-basic.c.j2 | 1 + codegen/templates/capi-card-test-drop.c.j2 | 1 + codegen/templates/capi-card-test-reserve.c.j2 | 1 + codegen/templates/capi-pb-test-basic.c.j2 | 1 + codegen/templates/capi-pb-test-drop.c.j2 | 1 + codegen/templates/capi-pb-test-reserve.c.j2 | 1 + 8 files changed, 8 insertions(+) diff --git a/codegen/templates/capi-am1-test-basic.c.j2 b/codegen/templates/capi-am1-test-basic.c.j2 index 02a425d1..6a3a1ac0 100644 --- a/codegen/templates/capi-am1-test-basic.c.j2 +++ b/codegen/templates/capi-am1-test-basic.c.j2 @@ -25,3 +25,4 @@ int main() { assert(n_clauses == {{ enc.n_clauses }}); return 0; } + diff --git a/codegen/templates/capi-am1-test-drop.c.j2 b/codegen/templates/capi-am1-test-drop.c.j2 index fa0a7e63..a63e827d 100644 --- a/codegen/templates/capi-am1-test-drop.c.j2 +++ b/codegen/templates/capi-am1-test-drop.c.j2 @@ -1,3 +1,4 @@ {% include 'codegen-header.j2' %} {% include 'capi-test-drop.j2' %} + diff --git a/codegen/templates/capi-card-test-basic.c.j2 b/codegen/templates/capi-card-test-basic.c.j2 index d04ccf93..3f67fc34 100644 --- a/codegen/templates/capi-card-test-basic.c.j2 +++ b/codegen/templates/capi-card-test-basic.c.j2 @@ -30,3 +30,4 @@ int main() { assert(n_clauses == {{ enc.n_clauses }}); return 0; } + diff --git a/codegen/templates/capi-card-test-drop.c.j2 b/codegen/templates/capi-card-test-drop.c.j2 index fa0a7e63..a63e827d 100644 --- a/codegen/templates/capi-card-test-drop.c.j2 +++ b/codegen/templates/capi-card-test-drop.c.j2 @@ -1,3 +1,4 @@ {% include 'codegen-header.j2' %} {% include 'capi-test-drop.j2' %} + diff --git a/codegen/templates/capi-card-test-reserve.c.j2 b/codegen/templates/capi-card-test-reserve.c.j2 index bc41ae3e..c39d4185 100644 --- a/codegen/templates/capi-card-test-reserve.c.j2 +++ b/codegen/templates/capi-card-test-reserve.c.j2 @@ -32,3 +32,4 @@ int main() { {{ enc.id }}_drop({{ enc.id }}); return 0; } + diff --git a/codegen/templates/capi-pb-test-basic.c.j2 b/codegen/templates/capi-pb-test-basic.c.j2 index 56bd1ccf..82acb8bd 100644 --- a/codegen/templates/capi-pb-test-basic.c.j2 +++ b/codegen/templates/capi-pb-test-basic.c.j2 @@ -30,3 +30,4 @@ int main() { assert(n_clauses == {{ enc.n_clauses }}); return 0; } + diff --git a/codegen/templates/capi-pb-test-drop.c.j2 b/codegen/templates/capi-pb-test-drop.c.j2 index fa0a7e63..a63e827d 100644 --- a/codegen/templates/capi-pb-test-drop.c.j2 +++ b/codegen/templates/capi-pb-test-drop.c.j2 @@ -1,3 +1,4 @@ {% include 'codegen-header.j2' %} {% include 'capi-test-drop.j2' %} + diff --git a/codegen/templates/capi-pb-test-reserve.c.j2 b/codegen/templates/capi-pb-test-reserve.c.j2 index ecb4452b..8243acdf 100644 --- a/codegen/templates/capi-pb-test-reserve.c.j2 +++ b/codegen/templates/capi-pb-test-reserve.c.j2 @@ -32,3 +32,4 @@ int main() { {{ enc.id }}_drop({{ enc.id }}); return 0; } + From f67835ea3dc6ee6468c92e9b090a09d020732926 Mon Sep 17 00:00:00 2001 From: Christoph Jabs Date: Tue, 19 Aug 2025 13:59:56 +0300 Subject: [PATCH 3/4] chore(codegen): add --check option --- Cargo.lock | 8 ++ codegen/Cargo.toml | 3 + codegen/src/main.rs | 181 +++++++++++++++++++++++++++++++------------- 3 files changed, 141 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 915a2ff2..376a575e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1165,6 +1165,8 @@ dependencies = [ "cbindgen", "minijinja", "serde", + "similar", + "tempfile", ] [[package]] @@ -1343,6 +1345,12 @@ dependencies = [ "libc", ] +[[package]] +name = "similar" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" + [[package]] name = "smallvec" version = "1.15.1" diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 0ce6cd2b..f55f405f 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -1,5 +1,6 @@ [package] name = "rustsat-codegen" +description = "Automated code generation for the RustSAT project." version.workspace = true license.workspace = true edition.workspace = true @@ -16,3 +17,5 @@ minijinja = { version = "2.11.0", default-features = false, features = [ "loader", ] } serde.workspace = true +similar = "2.7.0" +tempfile.workspace = true diff --git a/codegen/src/main.rs b/codegen/src/main.rs index f2dac679..865217c7 100644 --- a/codegen/src/main.rs +++ b/codegen/src/main.rs @@ -1,6 +1,8 @@ use std::io::Write; use minijinja::{context, Environment}; +use similar::{ChangeTag, TextDiff}; +use tempfile::NamedTempFile; fn main() { // Check if current directory has a Cargo.toml with [workspace] @@ -13,6 +15,9 @@ fn main() { ); } + let do_check = std::env::args().any(|arg| arg == "--check"); + let mut has_changes = false; + let templates = template_env(); let am1_encs = [ @@ -53,10 +58,13 @@ fn main() { }, ]; let path = "capi/src/encodings/am1.rs"; - capi_enc_bindings(path, "capi-am1.rs.j2", &am1_encs, &templates) - .expect("failed to write am1 bindings"); - rustfmt(path); - capi_tests("am1", &am1_encs, &templates).expect("failed to write am1 tests"); + let generated = rustfmt(capi_enc_bindings("capi-am1.rs.j2", &am1_encs, &templates)); + if do_check { + has_changes |= diff(path, &generated); + } else { + write!(file(path), "{generated}").unwrap(); + } + has_changes |= capi_tests("am1", &am1_encs, &templates, do_check); let card_encs = [Card { name: "Totalizer", @@ -67,10 +75,13 @@ fn main() { n_clauses: 28, }]; let path = "capi/src/encodings/card.rs"; - capi_enc_bindings(path, "capi-card.rs.j2", &card_encs, &templates) - .expect("failed to write card bindings"); - rustfmt(path); - capi_tests("card", &card_encs, &templates).expect("failed to write card tests"); + let generated = rustfmt(capi_enc_bindings("capi-card.rs.j2", &card_encs, &templates)); + if do_check { + has_changes |= diff(path, &generated); + } else { + write!(file(path), "{generated}").unwrap(); + } + has_changes |= capi_tests("card", &card_encs, &templates, do_check); let pb_encs = [ Pb { @@ -108,12 +119,19 @@ fn main() { }, ]; let path = "capi/src/encodings/pb.rs"; - capi_enc_bindings(path, "capi-pb.rs.j2", &pb_encs, &templates) - .expect("failed to write pb bindings"); - rustfmt(path); - capi_tests("pb", &pb_encs, &templates).expect("failed to write pb tests"); + let generated = rustfmt(capi_enc_bindings("capi-pb.rs.j2", &pb_encs, &templates)); + if do_check { + has_changes |= diff(path, &generated); + } else { + write!(file(path), "{generated}").unwrap(); + } + has_changes |= capi_tests("pb", &pb_encs, &templates, do_check); + + has_changes |= capi_header(do_check); - capi_header(); + if has_changes && do_check { + std::process::exit(1); + } } fn template_env() -> Environment<'static> { @@ -126,53 +144,98 @@ fn file(path: &str) -> impl std::io::Write { std::io::BufWriter::new(std::fs::File::create(path).expect("could not open file")) } -/// Runs `rustfmt` on a generated file -fn rustfmt(path: &str) { - let status = std::process::Command::new("rustfmt") - .arg(path) - .status() - .expect("Failed to execute rustfmt"); +/// Runs `rustfmt` on a generated string +fn rustfmt(generated: String) -> String { + let mut fmt = std::process::Command::new("rustfmt") + .stdin(std::process::Stdio::piped()) + .stdout(std::process::Stdio::piped()) + .spawn() + .expect("Failed to spawn rustfmt"); + + fmt.stdin + .take() + .expect("Failed to get stdin") + .write_all(generated.as_bytes()) + .expect("Failed to write to rustfmt stdin"); - if !status.success() { - eprintln!("rustfmt failed on file {path} with exit code: {status}"); + let formatted_output = fmt.wait_with_output().expect("Failed to wait for rustfmt"); + if !formatted_output.status.success() { + eprintln!("rustfmt failed with exit code: {}", formatted_output.status); + std::process::exit(1); } + + String::from_utf8(formatted_output.stdout).unwrap() } -/// Runs `clang-format` on a generated file -fn clang_format(path: &str) { - let status = std::process::Command::new("clang-format") - .args(["-i", path]) - .status() - .expect("Failed to execute clang-format"); +/// Runs `clang-format` on a generated string +fn clang_format(generated: String) -> String { + let mut fmt = std::process::Command::new("clang-format") + .stdin(std::process::Stdio::piped()) + .stdout(std::process::Stdio::piped()) + .spawn() + .expect("Failed to spawn clang-format"); + + fmt.stdin + .take() + .expect("Failed to get stdin") + .write_all(generated.as_bytes()) + .expect("Failed to write to clang-format stdin"); - if !status.success() { - eprintln!("clang-format failed on file {path} with exit code: {status}"); + let formatted_output = fmt + .wait_with_output() + .expect("Failed to wait for clang-format"); + if !formatted_output.status.success() { + eprintln!( + "clang-format failed with exit code: {}", + formatted_output.status + ); + std::process::exit(1); + } + + String::from_utf8(formatted_output.stdout).unwrap() +} + +fn diff(path: &str, generated: &str) -> bool { + let Ok(old) = std::fs::read(path) else { + eprintln!("Would create {path}"); + return true; + }; + let old = std::str::from_utf8(&old).unwrap(); + if old == generated { + return false; + } + let diff = TextDiff::from_lines(old, generated); + eprintln!("Diff for {path}:"); + for change in diff.iter_all_changes() { + let sign = match change.tag() { + ChangeTag::Delete => "-", + ChangeTag::Insert => "+", + ChangeTag::Equal => " ", + }; + eprint!("{}{}", sign, change); } + true } fn capi_enc_bindings( - path: &str, template: &str, encs: &[E], templates: &Environment<'static>, -) -> std::io::Result<()> { - let mut writer = file(path); +) -> String { let tmpl = templates.get_template(template).expect("missing template"); let ub = encs.iter().any(|enc| enc.ub()); let lb = encs.iter().any(|enc| enc.lb()); - writeln!( - writer, - "{}", - tmpl.render(context!(encodings => encs, ub => ub, lb => lb)) - .expect("missing template context") - ) + tmpl.render(context!(encodings => encs, ub => ub, lb => lb)) + .expect("missing template context") } fn capi_tests( id: &str, encs: &[E], templates: &Environment<'static>, -) -> std::io::Result<()> { + do_check: bool, +) -> bool { + let mut has_changes = false; for entry in std::fs::read_dir("codegen/templates/").expect("failed to iteratre over template") { let entry = entry.unwrap(); @@ -188,20 +251,20 @@ fn capi_tests( continue; } let path = format!("capi/tests/{}-{name}", enc.id()); - let mut writer = file(&path); - writeln!( - writer, - "{}", + let generated = clang_format( tmpl.render(context!(enc => enc)) - .expect("missing template context") - )?; - drop(writer); - clang_format(&path); + .expect("missing template context"), + ); + if do_check { + has_changes |= diff(&path, &generated); + } else { + write!(file(&path), "{generated}").unwrap(); + } } } } } - Ok(()) + has_changes } trait Enc: serde::Serialize { @@ -286,8 +349,17 @@ impl Enc for Pb<'_> { } /// Generates the C-API header -fn capi_header() { - cbindgen::Builder::new() +fn capi_header(do_check: bool) -> bool { + let mut temp_path = None; + let path = if do_check { + let path = NamedTempFile::new().unwrap().into_temp_path(); + std::fs::copy("capi/rustsat.h", &path).unwrap(); + temp_path = Some(path); + temp_path.as_ref().unwrap().to_str().unwrap() + } else { + "capi/rustsat.h" + }; + let changed = cbindgen::Builder::new() .with_config( cbindgen::Config::from_file("capi/cbindgen.toml") .expect("could not read cbindgen.toml"), @@ -305,5 +377,12 @@ fn capi_header() { )) .generate() .expect("Unable to generate bindings") - .write_to_file("capi/rustsat.h"); + .write_to_file(path); + if changed { + let generated = std::fs::read(path).unwrap(); + let generated = std::str::from_utf8(&generated).unwrap(); + diff("capi/rustsat.h", generated); + } + drop(temp_path); + changed } From ef7a4c0dc0af32ad7887e07e97d22971773c36ae Mon Sep 17 00:00:00 2001 From: Christoph Jabs Date: Tue, 12 Aug 2025 18:07:17 +0300 Subject: [PATCH 4/4] chore(ci): switch CI to nix --- .github/actionlint.yaml | 4 + .github/labeler.yml | 2 +- .github/runner/Dockerfile | 107 ------- .github/runner/cache.sh | 75 ----- .github/runner/docker-compose.yml | 14 - .github/runner/start.sh | 34 --- .github/workflows/ci.yml | 134 ++++++--- .github/workflows/pages.yml | 7 +- .github/workflows/release-plz.yml | 38 ++- .github/workflows/semver-checks.yml | 4 +- .gitignore | 3 + flake.lock | 28 +- flake.nix | 449 ++++++++++++++++++++++++---- justfile | 125 +------- pyapi/default.nix | 69 +++++ pyapi/doc.nix | 34 +++ result | 2 +- tools/default.nix | 22 +- 18 files changed, 680 insertions(+), 471 deletions(-) create mode 100644 .github/actionlint.yaml delete mode 100644 .github/runner/Dockerfile delete mode 100644 .github/runner/cache.sh delete mode 100644 .github/runner/docker-compose.yml delete mode 100755 .github/runner/start.sh create mode 100644 pyapi/default.nix create mode 100644 pyapi/doc.nix diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml new file mode 100644 index 00000000..edd07b8a --- /dev/null +++ b/.github/actionlint.yaml @@ -0,0 +1,4 @@ +self-hosted-runner: + labels: + - nix + - nixos diff --git a/.github/labeler.yml b/.github/labeler.yml index 93439c0d..1170565e 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -4,7 +4,7 @@ github_actions: c-api: - changed-files: - any-glob-to-any-file: "capi/**/*" -python-capi: +python-api: - changed-files: - any-glob-to-any-file: "pyapi/**/*" encodings: diff --git a/.github/runner/Dockerfile b/.github/runner/Dockerfile deleted file mode 100644 index c7ae79a3..00000000 --- a/.github/runner/Dockerfile +++ /dev/null @@ -1,107 +0,0 @@ -FROM rust:1.88-bookworm - -ENV CI=true - -# Install Rust components and toolchains -RUN rustup install nightly-2025-07-29 --component clippy,rustfmt,llvm-tools-preview,miri && \ - # MSRVs - rustup install --profile minimal 1.75 && rustup install --profile minimal 1.76 && rustup install --profile minimal 1.77 - -# Install CI dependencies -RUN apt-get update && apt-get install -y \ - # Script dependencies - curl git jq rsync \ - # Rust build dependencies - libssl-dev pkg-config valgrind \ - # VeriPB dependencies - libgmp-dev \ - # C(++) build dependencies - make cmake llvm libclang-dev clang clang-format \ - # Python dependencies - python3 python3-pip pipx python3-dev && \ - # Install cargo-binstall using curl - curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash && \ - # Clean up - remove apt cache - apt-get autoremove -y && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* - -# Clang as default -ENV CC=clang -ENV CXX=clang++ - -ARG GH_VERSION="2.76.2" - -# Install CI tools using cargo-binstall -RUN cargo binstall -y cargo-nextest cargo-hack cargo-rdme cargo-spellcheck cargo-semver-checks cargo-llvm-cov cargo-valgrind just release-plz typos-cli && \ - # Install Python tools with pipx - mkdir /opt/pipx && export PIPX_HOME=/opt/pipx && export PIPX_BIN_DIR=/usr/local/bin && \ - pipx install maturin && \ - # Install VeriPB - pipx install https://gitlab.com/MIAOresearch/software/VeriPB/-/archive/version2/VeriPB-version2.zip && \ - # Install GH - mkdir /tmp/gh && curl -sSL -o /tmp/gh/gh.tar.gz https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_amd64.tar.gz && \ - cd /tmp/gh && tar xvf gh.tar.gz && cp gh_${GH_VERSION}_linux_amd64/bin/gh /usr/local/bin/gh && \ - rm -rf /tmp/gh - -ENV VERIPB_CHECKER=/usr/local/bin/veripb - -ARG KISSAT_VERSION="4.0.3" -ARG CADICAL_VERSION="2.1.3" - -# Install some SAT solver binaries -RUN mkdir /tmp/gimsatul && curl -o /tmp/gimsatul/src.zip -L https://github.com/arminbiere/gimsatul/archive/refs/heads/master.zip && \ - cd /tmp/gimsatul && unzip src.zip && cd gimsatul-master && ./configure && make && \ - mv /tmp/gimsatul/gimsatul-master/gimsatul /usr/local/bin/gimsatul && rm -rf /tmp/gimsatul && \ - mkdir /tmp/kissat && curl -o /tmp/kissat/src.zip -L https://github.com/arminbiere/kissat/archive/refs/tags/rel-${KISSAT_VERSION}.zip && \ - cd /tmp/kissat && unzip src.zip && cd kissat-rel-${KISSAT_VERSION} && ./configure && make && \ - mv /tmp/kissat/kissat-rel-${KISSAT_VERSION}/build/kissat /usr/local/bin/kissat && rm -rf /tmp/kissat && \ - mkdir /tmp/cadical && curl -o /tmp/cadical/src.zip -L https://github.com/arminbiere/cadical/archive/refs/tags/rel-${CADICAL_VERSION}.zip && \ - cd /tmp/cadical && unzip src.zip && cd cadical-rel-${CADICAL_VERSION} && ./configure && make && \ - mv /tmp/cadical/cadical-rel-${CADICAL_VERSION}/build/cadical /usr/local/bin/cadical && rm -rf /tmp/cadical - -# Set environment variables -ENV CARGO_HOME=/runner/.cargo -ENV CARGO_INCREMENTAL=0 -ENV CLICOLOR=1 -ENV ORGANIZATION=chrjabs -ENV REPOSITORY=rustsat - -ARG RUNNER_VERSION="2.326.0" -ENV RUNNER_VERSION=${RUNNER_VERSION} - -# Create a work directory -WORKDIR /runner - -# Setup user for github runner -RUN useradd -m runner && mkdir actions-runner && cd actions-runner && \ - # Install github runner - curl -O -L https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz && \ - tar xzf ./actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz && rm ./actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz && \ - cp -a /runner/actions-runner/. /runner && rm -rf /runner/actions-runner/ && \ - # Install dependencies - mkdir /runner/cache && chown -R runner /runner && /runner/bin/installdependencies.sh && \ - # Clean up - apt-get autoremove -y && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* - -VOLUME /runner/cache -ENV CACHE=/runner/cache - -USER runner - -# Setup Python-API job venv -RUN python3 -m venv /runner/python-api-venv && /runner/python-api-venv/bin/python -m pip install mypy && \ - # Setup pages job venv - python3 -m venv /runner/pages-venv && /runner/pages-venv/bin/python -m pip install pdoc - -ENV PYTHON_API_VENV=/runner/python-api-venv -ENV PAGES_VENV=/runner/pages-venv - -COPY --chmod=0755 start.sh start.sh -COPY --chmod=0755 cache.sh cache.sh - -CMD ["/runner/start.sh"] - -LABEL org.opencontainers.image.source="https://github.com/chrjabs/rustsat" diff --git a/.github/runner/cache.sh b/.github/runner/cache.sh deleted file mode 100644 index 8eb2ef75..00000000 --- a/.github/runner/cache.sh +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -if [ -z "$CACHE" ]; then - CACHE="/runner/cache" -fi - -RUST=false - -# Parse command line -while [[ $# -gt 0 ]]; do - case "$1" in - restore) - COMMAND="restore" - shift - ;; - - save) - COMMAND="save" - shift - ;; - - --key) - KEY="$2" - shift - shift - ;; - - --rust) - RUST=true - shift - ;; - - *) - break - ;; - esac -done - -CACHE_DIR="$CACHE/$KEY.cache/" - -case "$COMMAND" in -"restore") - echo "๐Ÿ—‘๏ธ Deleting existing target directory" - rm -rf "target/" - echo "๐Ÿ”Ž Checking for matching cache" - if [ -d "$CACHE_DIR" ]; then - echo "๐Ÿ”™ Restoring cache from '$CACHE_DIR'" - rsync --archive --stats --human-readable "$CACHE_DIR" . - echo "โœ… Finished restoring cache" - else - echo "โŒ No matching cache found" - fi - ;; - -"save") - if $RUST; then - echo "๐Ÿงน Cleaning up target directory" - cargo metadata --format-version=1 | - jq --raw-output '.packages[] | select(.source==null).name' | - while read -r package; do - cargo clean -p "$package" - done - fi - echo "๐Ÿ’พ Saving cache to '$CACHE_DIR'" - rsync --archive --delete --stats --human-readable --relative "target/" "$@" "$CACHE_DIR" - echo "โœ… Finished saving cache" - ;; - -*) - >&2 echo "Unknown command '$1'" - exit 1 - ;; -esac diff --git a/.github/runner/docker-compose.yml b/.github/runner/docker-compose.yml deleted file mode 100644 index d9a58018..00000000 --- a/.github/runner/docker-compose.yml +++ /dev/null @@ -1,14 +0,0 @@ -services: - gha-runner: - build: - context: . - network: host - deploy: - replicas: 3 - restart: unless-stopped - volumes: - - cache-volume:/runner/cache - environment: - - ACCESS_TOKEN=${RUSTSAT_ACCESS_TOKEN} -volumes: - cache-volume: diff --git a/.github/runner/start.sh b/.github/runner/start.sh deleted file mode 100755 index 6af53d2a..00000000 --- a/.github/runner/start.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env bash - -if [ -n "$REPOSITORY" ]; then - REG_TOKEN=$(curl -sSL \ - -X POST \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer ${ACCESS_TOKEN}" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "https://api.github.com/repos/${ORGANIZATION}/${REPOSITORY}/actions/runners/registration-token" | - jq .token --raw-output) - URL="https://github.com/${ORGANIZATION}/${REPOSITORY}" -else - REG_TOKEN=$(curl -sSL \ - -X POST \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer ${ACCESS_TOKEN}" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "https://api.github.com/orgs/${ORGANIZATION}/actions/runners/registration-token" | - jq .token --raw-output) - URL="https://github.com/${ORGANIZATION}" -fi - -./config.sh --unattended --url "${URL}" --token "${REG_TOKEN}" - -cleanup() { - echo "Removing runner..." - ./config.sh remove --token "${REG_TOKEN}" -} - -trap 'cleanup; exit 130' INT -trap 'cleanup; exit 143' TERM - -./run.sh & -wait $! diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c46e291d..16df6310 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: - msrv-build - code-quality - python-api - - docs + - doc - cadical-each-feature - kissat-each-feature - cadical-valgrind @@ -35,7 +35,7 @@ jobs: echo "๐Ÿ—๏ธ MSRV build: ${{ needs.msrv-build.result }}" echo "โœจ Code Quality: ${{ needs.code-quality.result }}" echo "๐Ÿ Python API: ${{ needs.python-api.result }}" - echo "๐Ÿ“‘ Check documentation: ${{ needs.docs.result }}" + echo "๐Ÿ“‘ Check documentation: ${{ needs.doc.result }}" echo "๐Ÿงช CaDiCaL test each feature: ${{ needs.cadical-each-feature.result }}" echo "๐Ÿงช Kissat test each feature: ${{ needs.kissat-each-feature.result }}" echo "๐Ÿค– CaDiCaL Valgrind: ${{ needs.cadical-valgrind.result }}" @@ -55,7 +55,7 @@ jobs: run: exit 1 - if: ${{ needs.python-api.result != 'success' }} run: exit 1 - - if: ${{ needs.docs.result != 'success' }} + - if: ${{ needs.doc.result != 'success' }} run: exit 1 - if: ${{ needs.cadical-each-feature.result == 'failure' }} run: exit 1 @@ -71,114 +71,168 @@ jobs: run: exit 1 - if: ${{ needs.capi-valgrind.result == 'failure' }} run: exit 1 + dev-deps: + name: ๐Ÿ—๏ธ Dev dependencies + runs-on: [self-hosted, nix] + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + - name: ๐Ÿ—๏ธ Build dev dependencies + run: nix build .#devDeps tests: name: ๐Ÿงช Workspace tests - runs-on: self-hosted + runs-on: [self-hosted, nix] + needs: + - dev-deps steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - name: ๐Ÿงช Run tests - run: just test-ci + run: nix build ".#checks.$(nix eval --impure --expr 'builtins.currentSystem').tests" + - name: ๐Ÿงช Run tests for external solver CaDiCaL + run: nix build ".#checks.$(nix eval --impure --expr 'builtins.currentSystem').externalCadical" + - name: ๐Ÿงช Run tests for external solver Kissat + run: nix build ".#checks.$(nix eval --impure --expr 'builtins.currentSystem').externalKissat" + - name: ๐Ÿงช Run tests for external solver Gimsatul + run: nix build ".#checks.$(nix eval --impure --expr 'builtins.currentSystem').externalGimsatul" msrv-build: name: ๐Ÿ—๏ธ MSRV build - runs-on: self-hosted + runs-on: [self-hosted, nix] steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - - name: ๐Ÿ—๏ธ Build - run: just msrv-ci + - name: ๐Ÿ—๏ธ Check MSRV builds + run: nix build ".#checks.$(nix eval --impure --expr 'builtins.currentSystem').msrv" code-quality: name: โœจ Code Quality - runs-on: self-hosted + runs-on: [self-hosted, nix] + needs: + - dev-deps steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - - name: โœจ Run code quality checks - run: just code-quality-ci + - name: โœจ Check formatting + run: nix build ".#checks.$(nix eval --impure --expr 'builtins.currentSystem').treefmt" + - name: โœจ Check typos + run: nix build ".#checks.$(nix eval --impure --expr 'builtins.currentSystem').typos" + - name: โœจ Check spelling + run: nix build ".#checks.$(nix eval --impure --expr 'builtins.currentSystem').spellcheck" + - name: โœจ Check generated readmes + run: nix build ".#checks.$(nix eval --impure --expr 'builtins.currentSystem').readmes" + - name: โœจ Check generated code + run: nix build ".#checks.$(nix eval --impure --expr 'builtins.currentSystem').codegen" python-api: name: ๐Ÿ Python API - runs-on: self-hosted + runs-on: [self-hosted, nix] steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - - name: โœจ Run Python API checks - run: just python-api-ci - docs: + - uses: actions/checkout@v4 + - name: โœจ Build Python API and check + run: nix build ".#checks.$(nix eval --impure --expr 'builtins.currentSystem').pyapi" + doc: name: ๐Ÿ“‘ Check documentation - runs-on: self-hosted + runs-on: [self-hosted, nix] + needs: + - dev-deps steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - - name: โœจ Run docs checks - run: just docs-ci + - uses: actions/checkout@v4 + - name: โœจ Build doc + run: nix build ".#checks.$(nix eval --impure --expr 'builtins.currentSystem').doc" + - name: ๐Ÿงช Run doc tests + run: nix build ".#checks.$(nix eval --impure --expr 'builtins.currentSystem').docTests" coverage: name: ๐Ÿ“‹ Test coverage - runs-on: self-hosted + runs-on: [self-hosted, nix] + needs: + - tests + - doc steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - - name: โœจ Generate coverage report - run: just coverage-ci + - name: โœจ Collect coverage reports + run: | + nix build .#testCoverage + mkdir -p coverage + cp result/*.lcov coverage + rm result - name: โœจ Publish to Coveralls - uses: coverallsapp/github-action@master - with: - github-token: ${{ secrets.GITHUB_TOKEN }} + run: nix run --inputs-from . nur-packages#coveralls + env: + COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} # TODO: Kani feature-powerset: name: ๐Ÿ”Œ Test feature powerset - runs-on: self-hosted + runs-on: [self-hosted, nix] + needs: + - dev-deps if: contains(github.event.pull_request.labels.*.name, 'heavy-tests') steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - name: โœจ Run feature powerset checks - run: just feature-powerset-ci + run: nix build ".#checks.$(nix eval --impure --expr 'builtins.currentSystem').featurePowerset" cadical-each-feature: name: ๐Ÿงช CaDiCaL test each feature - runs-on: self-hosted + runs-on: [self-hosted, nix] + needs: + - dev-deps if: contains(github.event.pull_request.labels.*.name, 'solvers/cadical') steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - name: ๐Ÿงช Test each feature - run: just cadical-each-feature-ci + run: nix develop ".#ci" --command cargo hack --each-feature --clean-per-run --exclude-features logging nextest run -p rustsat-cadical kissat-each-feature: name: ๐Ÿงช Kissat test each feature - runs-on: self-hosted + runs-on: [self-hosted, nix] + needs: + - dev-deps if: contains(github.event.pull_request.labels.*.name, 'solvers/kissat') steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - name: ๐Ÿงช Test each feature - run: just kissat-each-feature-ci + run: nix develop ".#ci" --command cargo hack --each-feature --clean-per-run nextest run -p rustsat-kissat cadical-valgrind: name: ๐Ÿค– CaDiCaL Valgrind - runs-on: self-hosted + runs-on: [self-hosted, nix] + needs: + - dev-deps if: contains(github.event.pull_request.labels.*.name, 'solvers/cadical') steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - name: ๐Ÿงช Tests through valgrind - run: just cadical-valgrind-ci + run: nix build ".#checks.$(nix eval --impure --expr 'builtins.currentSystem').cadicalValgrind" kissat-valgrind: name: ๐Ÿค– Kissat Valgrind - runs-on: self-hosted + runs-on: [self-hosted, nix] + needs: + - dev-deps if: contains(github.event.pull_request.labels.*.name, 'solvers/kissat') steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - name: ๐Ÿงช Tests through valgrind - run: just kissat-valgrind-ci + run: nix build ".#checks.$(nix eval --impure --expr 'builtins.currentSystem').kissatValgrind" minisat-valgrind: name: ๐Ÿค– Minisat Valgrind - runs-on: self-hosted + runs-on: [self-hosted, nix] + needs: + - dev-deps if: contains(github.event.pull_request.labels.*.name, 'solvers/minisat') steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - name: ๐Ÿงช Tests through valgrind - run: just minisat-valgrind-ci + run: nix build ".#checks.$(nix eval --impure --expr 'builtins.currentSystem').minisatValgrind" glucose-valgrind: name: ๐Ÿค– Glucose Valgrind - runs-on: self-hosted + runs-on: [self-hosted, nix] + needs: + - dev-deps if: contains(github.event.pull_request.labels.*.name, 'solvers/glucose') steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - name: ๐Ÿงช Tests through valgrind - run: just glucose-valgrind-ci + run: nix build ".#checks.$(nix eval --impure --expr 'builtins.currentSystem').glucoseValgrind" capi-valgrind: name: ๐Ÿค– C-API Valgrind - runs-on: self-hosted + runs-on: [self-hosted, nix] + needs: + - dev-deps if: contains(github.event.pull_request.labels.*.name, 'c-api') steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - name: ๐Ÿงช Tests through valgrind - run: just capi-valgrind-ci + run: nix build ".#checks.$(nix eval --impure --expr 'builtins.currentSystem').capiValgrind" diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 99853083..92631afa 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -15,15 +15,16 @@ concurrency: jobs: # Build job build: - runs-on: self-hosted + runs-on: [self-hosted, nix] steps: - name: Checkout uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - name: ๐Ÿ—๏ธ Build pages - run: just pages-ci + run: nix build .#pages - name: โฌ†๏ธ Upload artifact - # Automatically uploads an artifact from the './_site' directory by default uses: actions/upload-pages-artifact@v3 + with: + path: result/ # Deployment job deploy: environment: diff --git a/.github/workflows/release-plz.yml b/.github/workflows/release-plz.yml index f410b4c2..1e6baa3c 100644 --- a/.github/workflows/release-plz.yml +++ b/.github/workflows/release-plz.yml @@ -1,13 +1,13 @@ name: Release-plz on: push: - branches: - - main + branches: ["main"] jobs: # Release unpublished packages + # Taken from https://github.com/release-plz/action/blob/main/action.yml and modified to run with nix binaries release-plz-release: name: Release-plz release - runs-on: self-hosted + runs-on: [self-hosted, nix] if: ${{ github.repository_owner == 'chrjabs' }} permissions: contents: write @@ -30,16 +30,14 @@ jobs: uses: rust-lang/crates-io-auth-action@e919bc7605cde86df457cf5b93c5e103838bd879 # v1.0.1 id: auth - name: Run release-plz - uses: MarcoIeni/release-plz-action@v0.5 - with: - command: release + run: nix develop ".#releasePlz" --command release-plz release --git-token "${GITHUB_TOKEN}" -o json env: GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} # Create a PR with the new versions and changelog, preparing the next release release-plz-pr: name: Release-plz PR - runs-on: self-hosted + runs-on: [self-hosted, nix] if: ${{ github.repository_owner == 'chrjabs' }} permissions: contents: write @@ -61,11 +59,30 @@ jobs: with: fetch-depth: 0 token: ${{ steps.generate-token.outputs.token }} + - name: Configure git user from GitHub token + uses: release-plz/git-config@59144859caf016f8b817a2ac9b051578729173c4 + env: + GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} - name: Run release-plz id: release-plz - uses: MarcoIeni/release-plz-action@v0.5 - with: - command: release-pr + shell: nix develop ".#releasePlz" --command bash -e {0} + run: | + release_pr_output=$(release-plz release-pr\ + --git-token "${GITHUB_TOKEN}"\ + --repo-url "https://github.com/${GITHUB_REPOSITORY}"\ + -o json) + echo "release_pr_output: $release_pr_output" + prs=$(echo $release_pr_output | jq -c .prs) + echo "prs=$prs" >> "$GITHUB_OUTPUT" + prs_length=$(echo "$prs" | jq 'length') + if [ "$prs_length" != "0" ]; then + prs_created=true + first_pr=$(echo $prs | jq -c .[0]) + else + prs_created=false + first_pr="{}" + fi + echo "pr=$first_pr" >> "$GITHUB_OUTPUT" env: GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} - name: Update C-API header @@ -84,4 +101,3 @@ jobs: fi env: GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} - PR: ${{ steps.release-plz.outputs.pr }} diff --git a/.github/workflows/semver-checks.yml b/.github/workflows/semver-checks.yml index e4b5565e..c914dcac 100644 --- a/.github/workflows/semver-checks.yml +++ b/.github/workflows/semver-checks.yml @@ -8,8 +8,8 @@ env: jobs: all: name: ๐Ÿ›ก๏ธ Semver checks - runs-on: self-hosted + runs-on: [self-hosted, nix] steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - name: ๐Ÿ›ก๏ธ Run semver checks - run: just semver-checks-ci + run: nix run nixpkgs#just semver-checks diff --git a/.gitignore b/.gitignore index df41064d..486122d1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ /target +/target-ci + +/result **/*.o **/*.out diff --git a/flake.lock b/flake.lock index 62765089..09323cfa 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,20 @@ { "nodes": { + "crane": { + "locked": { + "lastModified": 1754269165, + "narHash": "sha256-0tcS8FHd4QjbCVoxN9jI+PjHgA4vc/IjkUSp+N3zy0U=", + "owner": "ipetkov", + "repo": "crane", + "rev": "444e81206df3f7d92780680e45858e31d2f07a08", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, "flake-parts": { "inputs": { "nixpkgs-lib": "nixpkgs-lib" @@ -38,11 +53,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1754393734, - "narHash": "sha256-fbnmAwTQkuXHKBlcL5Nq1sMAzd3GFqCOQgEQw6Hy0Ak=", + "lastModified": 1755268003, + "narHash": "sha256-nNaeJjo861wFR0tjHDyCnHs1rbRtrMgxAKMoig9Sj/w=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "a683adc19ff5228af548c6539dbc3440509bfed3", + "rev": "32f313e49e42f715491e1ea7b306a87c16fe0388", "type": "github" }, "original": { @@ -94,11 +109,11 @@ "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1754481534, - "narHash": "sha256-x88YxTTUSXinwskyHxYAD9d5hG2TAnJQv4EuuQm5h3c=", + "lastModified": 1755675547, + "narHash": "sha256-6TDPr39pSOP1XpOINKfw1ZhkpVypObTm0/p/aKNPNSU=", "owner": "chrjabs", "repo": "nur-packages", - "rev": "2e2e884e7b8e5826c2cf8a47c39999183321588e", + "rev": "016af850de85eda39e47376d3901fd64f7874a08", "type": "github" }, "original": { @@ -109,6 +124,7 @@ }, "root": { "inputs": { + "crane": "crane", "flake-parts": "flake-parts", "nixpkgs": "nixpkgs", "nur-packages": "nur-packages", diff --git a/flake.nix b/flake.nix index aecbfa7a..11a6fdc5 100644 --- a/flake.nix +++ b/flake.nix @@ -1,15 +1,6 @@ { description = "Rust library for tools and encodings related to SAT solving library"; - nixConfig = { - extra-substituters = [ - "https://chrjabs.cachix.org" - ]; - extra-trusted-public-keys = [ - "chrjabs.cachix.org-1:hnjWCdXP+IWya+Y+/xTwyfpNtwOlbR0X3/9OqyLoE1o=" - ]; - }; - inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; @@ -18,6 +9,8 @@ rust-overlay.url = "github:oxalica/rust-overlay"; rust-overlay.inputs.nixpkgs.follows = "nixpkgs"; + crane.url = "github:ipetkov/crane"; + nur-packages.url = "github:chrjabs/nur-packages"; nur-packages.inputs.nixpkgs.follows = "nixpkgs"; nur-packages.inputs.rust-overlay.follows = "rust-overlay"; @@ -45,22 +38,275 @@ }: let lib = pkgs.lib; + + libs = with pkgs; [ + openssl + xz + bzip2 + ]; + + stdenv = pkgs.clangStdenv; + + craneLib = (inputs.crane.mkLib pkgs).overrideToolchain (p: p.rust-toolchain); + src = + let + additionalSrcFilter = + path: _type: + builtins.match ".*(data.*|cp?p?|hp?p?|j2|CMakeLists.txt|VERSION|README.md)$" path != null; + allSrc = path: type: (additionalSrcFilter path type) || (craneLib.filterCargoSources path type); + in + lib.cleanSourceWith { + src = ./.; + filter = allSrc; + name = "source"; + }; + commonArgs = { + inherit src; + strictDeps = true; + nativeBuildInputs = with pkgs; [ + llvmPackages.bintools + pkg-config + clang + cmake + ]; + cargoExtraArgs = "--locked --workspace --features=all,internals"; + cargoTestExtraArgs = "--no-run --exclude rustsat-pyapi"; + LIBCLANG_PATH = "${pkgs.libclang.lib}/lib"; + PKG_CONFIG_PATH = "${pkgs.openssl.dev}/lib/pkgconfig"; + LD_LIBRARY_PATH = lib.makeLibraryPath libs; + CARGO_PROFILE = ""; + NEXTEST_PROFILE = "ci"; + }; + cargoArtifacts = craneLib.buildDepsOnly commonArgs; + + crateValgrind = + crate: + craneLib.mkCargoDerivation ( + commonArgs + // { + pnameSuffix = "${crate}-valgrind"; + inherit cargoArtifacts; + nativeBuildInputs = + commonArgs.nativeBuildInputs + ++ (with pkgs; [ + jq + cargo-valgrind + cargo-nextest + ]); + buildPhaseCargoCommand = '' + cargo valgrind nextest run -p ${crate} + ''; + } + ); + + workspaceMsrv = (lib.importTOML ./Cargo.toml).workspace.package.rust-version; + crateMsrvs = { + rustsat = workspaceMsrv; + rustsat-pyapi = workspaceMsrv; + rustsat-tools = (lib.importTOML ./tools/Cargo.toml).package.rust-version; + rustsat-kissat = (lib.importTOML ./kissat/Cargo.toml).package.rust-version; + pigeons = (lib.importTOML ./pigeons/Cargo.toml).package.rust-version; + rustsat-ipasir = workspaceMsrv; + rustsat-batsat = workspaceMsrv; + rustsat-cadical = (lib.importTOML ./cadical/Cargo.toml).package.rust-version; + rustsat-capi = workspaceMsrv; + rustsat-glucose = workspaceMsrv; + rustsat-minisat = workspaceMsrv; + }; + checkMsrv = + crate: + let + craneLib = (inputs.crane.mkLib pkgs).overrideToolchain ( + p: (p.extend (import inputs.rust-overlay)).rust-bin.stable."${crateMsrvs."${crate}"}".minimal + ); + cargoArtifacts = craneLib.buildDepsOnly ( + commonArgs + // { + buildPhaseCargoCommand = "cargo build --workspace"; + checkPhaseCargoCommand = ""; + } + ); + in + craneLib.buildPackage ( + (builtins.removeAttrs commonArgs [ "cargoTestExtraArgs" ]) + // { + inherit cargoArtifacts; + pname = "${crate}-msrv"; + cargoExtraArgs = "--locked -p ${crate}"; + doCheck = false; + } + ); + # Dummy derivation merging multiple MSRV check + msrv = stdenv.mkDerivation { + name = "rustsat-check-msrv"; + nativeBuildInputs = map (crate: checkMsrv crate) (builtins.attrNames crateMsrvs); + doCheck = false; + unpackPhase = "true"; + buildPhase = '' + mkdir -p $out + ''; + }; + docTests = craneLib.mkCargoDerivation ( + commonArgs + // { + pnameSuffix = "-doctests"; + doCheck = true; + nativeBuildInputs = commonArgs.nativeBuildInputs ++ (with pkgs; [ cargo-llvm-cov ]); + inherit cargoArtifacts; + cargoTestExtraArgs = "--exclude rustsat-capi --exclude rustsat-pyapi"; + buildPhaseCargoCommand = "mkdir -p $out"; + checkPhaseCargoCommand = "cargo llvm-cov --doc --workspace --exclude rustsat-ipasir --features=all,internals --lcov --output-path $out/coverage.lcov"; + } + ); + tests = craneLib.cargoNextest ( + commonArgs + // { + inherit cargoArtifacts; + cargoNextestExtraArgs = "--exclude rustsat-pyapi"; + nativeBuildInputs = commonArgs.nativeBuildInputs ++ (with pkgs; [ jq ]); + withLlvmCov = true; + cargoLlvmCovExtraArgs = "--lcov --output-path $out/coverage.lcov"; + } + ); + externalSolverTest = + slv: + (craneLib.cargoNextest ( + commonArgs + // { + inherit cargoArtifacts; + cargoExtraArgs = "--locked --features=all,internals"; + cargoNextestExtraArgs = "-p rustsat --test external_solver -- --ignored"; + withLlvmCov = true; + cargoLlvmCovExtraArgs = "--lcov --output-path $out/coverage.lcov"; + RS_EXT_SOLVER = slv; + } + )); + externalCadical = externalSolverTest (lib.getExe' pkgs.cadical "cadical"); + externalKissat = externalSolverTest (lib.getExe pkgs.kissat); + externalGimsatul = externalSolverTest (lib.getExe pkgs.gimsatul); + doc = craneLib.cargoDoc ( + commonArgs + // { + inherit cargoArtifacts; + cargoDocExtraArgs = "--no-deps -Zunstable-options -Zrustdoc-scrape-examples"; + env.RUSTDOCFLAGS = "--deny warnings"; + } + ); + pyapi = pkgs.python3Packages.callPackage ./pyapi { }; + pyapiDoc = pkgs.callPackage ./pyapi/doc.nix { }; + clippy = craneLib.cargoClippy ( + commonArgs + // { + inherit cargoArtifacts; + cargoClippyExtraArgs = "--all-targets -- --deny warnings"; + } + ); + codegen = craneLib.mkCargoDerivation ( + commonArgs + // { + pnameSuffix = "-codegen-check"; + inherit cargoArtifacts; + buildPhaseCargoCommand = '' + cargo run -p rustsat-codegen -- --check + ''; + } + ); + readmes = craneLib.mkCargoDerivation ( + commonArgs + // { + pnameSuffix = "-readmes"; + inherit cargoArtifacts; + nativeBuildInputs = with pkgs; [ cargo-rdme ]; + buildPhaseCargoCommand = '' + cargo rdme --check + cargo rdme --check --workspace-project pigeons + cargo rdme --check --workspace-project rustsat-batsat + cargo rdme --check --workspace-project rustsat-cadical + cargo rdme --check --workspace-project rustsat-capi + cargo rdme --check --workspace-project rustsat-glucose + cargo rdme --check --workspace-project rustsat-ipasir + cargo rdme --check --workspace-project rustsat-kissat + cargo rdme --check --workspace-project rustsat-minisat + cargo rdme --check --workspace-project rustsat-pyapi + cargo rdme --check --workspace-project rustsat-tools + ''; + } + ); + spellcheck = stdenv.mkDerivation { + name = "cargo-spellcheck-check"; + nativeBuildInputs = with pkgs; [ cargo-spellcheck ]; + doCheck = true; + unpackPhase = "true"; + buildPhase = '' + mkdir -p $out + ''; + checkPhase = '' + mkdir -p .cache + HOME=$PWD cargo-spellcheck --code 1 check + ''; + }; + typosCheck = stdenv.mkDerivation { + name = "typos-check"; + nativeBuildInputs = with pkgs; [ typos ]; + doCheck = true; + unpackPhase = "true"; + buildPhase = '' + mkdir -p $out + ''; + checkPhase = '' + typos + ''; + }; + featurePowerset = craneLib.mkCargoDerivation ( + commonArgs + // { + pname-suffix = "-feature-powerset"; + inherit cargoArtifacts; + nativeBuildInputs = + commonArgs.nativeBuildInputs + ++ (with pkgs; [ + cargo-hack + cargo-nextest + ]); + buildPhaseCargoCommand = '' + cargo hack --feature-powerset --clean-per-run --depth 2 --exclude-features bench nextest run -p rustsat + ''; + } + ); + capiValgrind = craneLib.mkCargoDerivation ( + commonArgs + // { + pnameSuffix = "-capi-valgrind"; + inherit cargoArtifacts; + nativeBuildInputs = + commonArgs.nativeBuildInputs + ++ (with pkgs; [ + jq + valgrind + cargo-nextest + ]); + # libtestmimic calling threads is leaking memory + # C-API tests are only actually build when _executing_ them, so we can't use `--no-run` + buildPhaseCargoCommand = '' + cargo nextest run -p rustsat-capi + for test in capi/tests/*.c; do + valgrind ''${CARGO_TARGET_DIR:-target}/tmp/"$(basename -s .c "$test")" + done + ''; + } + ); in { _module.args.pkgs = import inputs.nixpkgs { inherit system; overlays = let - toolchain-overlay = _: super: { - rust-toolchain = super.symlinkJoin { - name = "rust-toolchain"; - paths = [ - ((pkgs.extend (import inputs.rust-overlay)).rust-bin.fromRustupToolchainFile ./rust-toolchain.toml) - ]; - buildInputs = [ super.makeWrapper ]; - postBuild = '' - wrapProgram $out/bin/cargo --set LIBCLANG_PATH ${super.libclang.lib}/lib - ''; + rustPkgs = pkgs.extend (import inputs.rust-overlay); + toolchain-overlay = final: _super: { + rust-toolchain = rustPkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml; + rust-toolchain-platform = rustPkgs.makeRustPlatform { + cargo = final.rust-toolchain; + rustc = final.rust-toolchain; }; }; in @@ -70,48 +316,139 @@ ]; }; - packages.tools = pkgs.callPackage ./tools { }; + packages = { + tools = pkgs.callPackage ./tools { }; + inherit doc; + inherit pyapi; + inherit pyapiDoc; + devDeps = cargoArtifacts; + testCoverage = stdenv.mkDerivation { + name = "rustsat-test-coverage"; + unpackPhase = "true"; + buildPhase = '' + mkdir -p $out + cp ${tests}/coverage.lcov $out/tests.lcov + cp ${externalCadical}/coverage.lcov $out/external-cadical.lcov + cp ${externalKissat}/coverage.lcov $out/external-kissat.lcov + cp ${externalGimsatul}/coverage.lcov $out/external-gimsatul.lcov + cp ${docTests}/coverage.lcov $out/doc-tests.lcov + for cov in $out/*.lcov; do + substituteInPlace $cov --replace-fail '/build/source/' './' + done + ''; + }; + pages = stdenv.mkDerivation { + name = "rustsat-pages"; + unpackPhase = "true"; + buildPhase = '' + mkdir -p $out + cp -r ${doc}/share/doc $out/main + cp -r ${pyapiDoc}/share/doc $out/pyapi + ''; + }; + }; - devShells.default = + devShells = let - libs = with pkgs; [ - openssl - xz - bzip2 - ]; + mkBaseShell = + { nativeBuildInputs, ... }@args: + pkgs.mkShell.override { inherit stdenv; } ( + { + nativeBuildInputs = + (with pkgs; [ + just + jq + llvmPackages.bintools + pkg-config + clang + cmake + ]) + ++ nativeBuildInputs; + buildInputs = libs; + LIBCLANG_PATH = "${pkgs.libclang.lib}/lib"; + LD_LIBRARY_PATH = lib.makeLibraryPath libs; + PKG_CONFIG_PATH = "${pkgs.openssl.dev}/lib/pkgconfig"; + } + // (builtins.removeAttrs args [ "nativeBuildInputs" ]) + ); in - pkgs.mkShell.override { stdenv = pkgs.clangStdenv; } rec { - nativeBuildInputs = with pkgs; [ - llvmPackages.bintools - pkg-config - clang - cmake - rust-toolchain - cargo-rdme - cargo-nextest - cargo-semver-checks - cargo-hack - cargo-spellcheck - cargo-llvm-cov - cargo-valgrind - valgrind - just - release-plz - jq - maturin - kani - veripb - typos - rust-cbindgen - config.treefmt.build.wrapper - ]; - buildInputs = libs; - LIBCLANG_PATH = "${pkgs.libclang.lib}/lib"; - LD_LIBRARY_PATH = lib.makeLibraryPath libs; - PKG_CONFIG_PATH = "${pkgs.openssl.dev}/lib/pkgconfig/"; - VERIPB_CHECKER = lib.getExe pkgs.veripb; + { + default = mkBaseShell { + nativeBuildInputs = with pkgs; [ + rust-toolchain + cargo-rdme + cargo-nextest + cargo-hack + cargo-spellcheck + cargo-llvm-cov + cargo-valgrind + valgrind + release-plz + maturin + kani + veripb + typos + rust-cbindgen + config.treefmt.build.wrapper + ]; + VERIPB_CHECKER = lib.getExe pkgs.veripb; + RS_EXT_SOLVER = lib.getExe' pkgs.cadical "cadical"; + }; + semverChecks = mkBaseShell { + nativeBuildInputs = with pkgs; [ + # use the stable toolchain here for compatibility with semver-checks + cargo + cargo-semver-checks + ]; + }; + releasePlz = mkBaseShell { + nativeBuildInputs = with pkgs; [ + # use the stable toolchain here for compatibility with semver-checks + cargo + cargo-semver-checks + release-plz + ]; + }; + ci = mkBaseShell { + nativeBuildInputs = with pkgs; [ + rust-toolchain + cargo-hack + cargo-nextest + craneLib.inheritCargoArtifactsHook + ]; + inherit cargoArtifacts; + shellHook = '' + rm -rf "$CARGO_TARGET_DIR" + inheritCargoArtifacts + ''; + CARGO_TARGET_DIR = "./target-ci/"; + }; }; + checks = { + inherit + tests + externalCadical + externalKissat + externalGimsatul + doc + docTests + pyapi + clippy + codegen + readmes + spellcheck + featurePowerset + msrv + capiValgrind + ; + typos = typosCheck; + cadicalValgrind = crateValgrind "rustsat-cadical"; + kissatValgrind = crateValgrind "rustsat-kissat"; + minisatValgrind = crateValgrind "rustsat-minisat"; + glucoseValgrind = crateValgrind "rustsat-glucose"; + }; + treefmt = { settings.global = { on-unmatched = "error"; diff --git a/justfile b/justfile index 4ff23ecd..e7748dda 100644 --- a/justfile +++ b/justfile @@ -1,25 +1,16 @@ -spellcheck: - cargo spellcheck --code 1 +spellcheck *args: + cargo-spellcheck --code 1 {{ args }} typos test *args: cargo nextest run --workspace --exclude rustsat-pyapi --features=all,internals {{ args }} -test-ci: (ci-cache "restore --key ci-tests") && (ci-cache "save --rust --key ci-tests") - #!/usr/bin/env -S bash -euo pipefail - source .env - cmd_group "just test --profile ci" - cmd_group "RS_EXT_SOLVER=$(which cadical) cargo nextest run --profile ci -p rustsat --test external_solver --verbose -- --ignored" - cmd_group "RS_EXT_SOLVER=$(which kissat) cargo nextest run --profile ci -p rustsat --test external_solver --verbose -- --ignored" - cmd_group "RS_EXT_SOLVER=$(which gimsatul) cargo nextest run --profile ci -p rustsat --test external_solver --verbose -- --ignored" - -msrv-ci: (ci-cache "restore --key ci-msrv-build") && (ci-cache "save --rust --key ci-msrv-build") - #!/usr/bin/env -S bash -euo pipefail - source .env - cmd_group "cargo hack build --rust-version --workspace --features=all,internals --ignore-unknown-features" +test-external-solver *args: + echo "RS_EXT_SOLVER=$RS_EXT_SOLVER" + cargo nextest run {{ args }} -p rustsat --test external_solver --verbose -- --ignored doc-tests *args: - cargo test --workspace --features=all,internals --doc {{ args }} + cargo test --workspace --exclude rustsat-capi --exclude rustsat-pyapi --features=all,internals --doc {{ args }} clippy *args: cargo clippy --workspace --all-targets --target-dir target/clippy --features=all,internals {{ args }} -- -Dwarnings @@ -27,9 +18,6 @@ clippy *args: gen *args: cargo run -p rustsat-codegen -- {{ args }} -gen-check *args: - cargo run -p rustsat-codegen -- {{ args }} && test -z "$(git status --porcelain)" - readmes *args: cargo rdme {{ args }} cargo rdme --workspace-project pigeons {{ args }} @@ -43,80 +31,24 @@ readmes *args: cargo rdme --workspace-project rustsat-pyapi {{ args }} cargo rdme --workspace-project rustsat-tools {{ args }} -code-quality-ci: (ci-cache "restore --key ci-code-quality") && (ci-cache "save --rust --key ci-code-quality") - #!/usr/bin/env -S bash -euo pipefail - source .env - cmd_group "cargo fmt --all --check" - cmd_group "just spellcheck" - cmd_group "just gen-check" - cmd_group "just readmes --check" - cmd_group "just clippy" - pyapi cmd *args: maturin {{ cmd }} -m pyapi/Cargo.toml {{ args }} pyapi-build-install: (pyapi "build") pip install --no-index --find-links target/wheels/ rustsat -python-api-ci: (ci-cache "restore --key ci-python-api") && (ci-cache "save --rust --key ci-python-api") - #!/usr/bin/env -S bash -euo pipefail - source .env - source "$PYTHON_API_VENV/bin/activate" - cmd_group "just pyapi-build-install" - cmd_group "python pyapi/examples/pyapi-dpw.py" - cmd_group "stubtest --mypy-config-file pyapi/pyproject.toml --allowlist pyapi/stubtest-allowlist.txt rustsat" - cmd_group "pip uninstall -y rustsat" - docs *args: cargo doc -Zunstable-options -Zrustdoc-scrape-examples --workspace --features=all,internals {{ args }} -docs-ci: (ci-cache "restore --key ci-docs") && (ci-cache "save --rust --key ci-docs") - #!/usr/bin/env -S bash -euo pipefail - source .env - export RUSTDOCFLAGS="-Dwarnings --cfg docsrs" - cmd_group "just docs --no-deps" - cmd_group "cargo test --doc --workspace --features=all,internals" - test-each-feature *args: cargo hack nextest run --each-feature {{ args }} feature-powerset *args: cargo hack nextest run -p rustsat --feature-powerset --depth 2 --exclude-features bench {{ args }} -feature-powerset-ci: (ci-cache "restore --key ci-feature-powerset") && (ci-cache "save --rust --key ci-feature-powerset") - #!/usr/bin/env -S bash -euo pipefail - source .env - cmd_group "just feature-powerset --profile ci --exclude-features bench" - -cadical-each-feature-ci: (ci-cache "restore --key ci-cadical-each-feature") && (ci-cache "save --rust --key ci-cadical-each-feature") - #!/usr/bin/env -S bash -euo pipefail - source .env - cmd_group "just test-each-feature --profile ci -p rustsat-cadical --exclude-features logging" - -kissat-each-feature-ci: (ci-cache "restore --key ci-kissat-each-feature") && (ci-cache "save --rust --key ci-kissat-each-feature") - #!/usr/bin/env -S bash -euo pipefail - source .env - cmd_group "just test-each-feature --profile ci -p rustsat-kissat" - semver-checks: - cargo semver-checks --workspace --exclude rustsat-cadical - cargo semver-checks -p rustsat-cadical --default-features - -semver-checks-ci: (ci-cache "restore --key ci-semver-checks") && (ci-cache "save --rust --key ci-semver-checks") - #!/usr/bin/env -S bash -euo pipefail - source .env - cmd_group "just semver-checks" - -pages-ci: - #!/usr/bin/env -S bash -euo pipefail - source .env - mkdir -p _site/ - cmd_group "just docs --no-deps" - mv target/doc/ _site/main/ - source "$PAGES_VENV/bin/activate" - cmd_group "just pyapi-build-install" - cmd_group "pdoc -o _site/pyapi/ --no-show-source rustsat" - cmd_group "pip uninstall -y rustsat" + nix develop '.#semverChecks' --command cargo semver-checks --workspace --exclude rustsat-cadical + nix develop '.#semverChecks' --command cargo semver-checks -p rustsat-cadical --default-features kani: cargo kani @@ -152,27 +84,11 @@ subtree tree cmd ref: exit 1 esac -ci-cache *args: - #!/usr/bin/env -S bash -euo pipefail - source .env - if [[ -x /runner/cache.sh ]]; then - cmd_group "/runner/cache.sh {{ args }}" - fi - coverage *args: cargo llvm-cov --no-report nextest --workspace --exclude rustsat-pyapi --features=all,internals cargo llvm-cov --no-report --doc --workspace --exclude rustsat-ipasir --features=all,internals cargo llvm-cov report --doctests --html {{ args }} -coverage-ci: - #!/usr/bin/env -S bash -euo pipefail - source .env - cmd_group "cargo llvm-cov --no-report nextest --workspace --exclude rustsat-pyapi --features=all,internals" - cmd_group "cargo llvm-cov --no-report --doc --workspace --exclude rustsat-ipasir --features=all,internals" - cmd_group "RS_EXT_SOLVER=$(which cadical) cargo llvm-cov --no-report nextest -p rustsat --test external_solver --verbose -- --ignored" - mkdir coverage - cmd_group "cargo llvm-cov report --doctests --lcov --output-path coverage/lcov.info" - valgrind *args: cargo valgrind nextest run {{ args }} @@ -186,31 +102,6 @@ capi-valgrind: all-valgrind *args: (valgrind "--workspace --exclude rustsat-pyapi --exclude rustsat-capi --features=all,internals" args) capi-valgrind -cadical-valgrind-ci: (ci-cache "restore --key ci-cadical-valgrind") && (ci-cache "save --rust --key ci-cadical-valgrind") - #!/usr/bin/env -S bash -euo pipefail - source .env - cmd_group "just valgrind --profile ci -p rustsat-cadical" - -kissat-valgrind-ci: (ci-cache "restore --key ci-kissat-valgrind") && (ci-cache "save --rust --key ci-kissat-valgrind") - #!/usr/bin/env -S bash -euo pipefail - source .env - cmd_group "just valgrind --profile ci -p rustsat-kissat" - -minisat-valgrind-ci: (ci-cache "restore --key ci-minisat-valgrind") && (ci-cache "save --rust --key ci-minisat-valgrind") - #!/usr/bin/env -S bash -euo pipefail - source .env - cmd_group "just valgrind --profile ci -p rustsat-minisat" - -glucose-valgrind-ci: (ci-cache "restore --key ci-glucose-valgrind") && (ci-cache "save --rust --key ci-glucose-valgrind") - #!/usr/bin/env -S bash -euo pipefail - source .env - cmd_group "just valgrind --profile ci -p rustsat-glucose" - -capi-valgrind-ci: (ci-cache "restore --key ci-capi-valgrind") && (ci-cache "save --rust --key ci-capi-valgrind") - #!/usr/bin/env -S bash -euo pipefail - source .env - cmd_group "just capi-valgrind" - miri *args: MIRIFLAGS=-Zmiri-disable-isolation cargo miri nextest run {{ args }} diff --git a/pyapi/default.nix b/pyapi/default.nix new file mode 100644 index 00000000..dded8b42 --- /dev/null +++ b/pyapi/default.nix @@ -0,0 +1,69 @@ +{ + lib, + buildPythonPackage, + pkg-config, + rust-toolchain-platform, + openssl, + mypy, +}: +let + manifest = (lib.importTOML ./Cargo.toml).package; + workspace-manifest = (lib.importTOML ../Cargo.toml).workspace.package; + libs = [ + openssl + ]; +in +buildPythonPackage { + pname = manifest.name; + version = workspace-manifest.version; + + src = + let + filter = + path: type: + type == "directory" + || ( + builtins.match ".*(src/(lib|main).rs|Cargo.lock|-source/(examples/.*rs|src/.*rs)|pyapi/(python/.*|README.md|build.rs|stubtest-allowlist.txt|src/.*rs|examples/.*py)|toml)$" path + != null + ); + in + lib.cleanSourceWith { + src = ../.; + inherit filter; + name = "source"; + }; + buildAndTestSubdir = "pyapi"; + pyproject = true; + cargoDeps = rust-toolchain-platform.importCargoLock { lockFile = ../Cargo.lock; }; + + nativeBuildInputs = [ + pkg-config + rust-toolchain-platform.cargoSetupHook + rust-toolchain-platform.maturinBuildHook + ]; + buildInputs = libs; + + nativeCheckInputs = [ + mypy + ]; + + checkPhase = '' + runHook preCheck + + python pyapi/examples/pyapi-dpw.py + stubtest --mypy-config-file pyapi/pyproject.toml --allowlist pyapi/stubtest-allowlist.txt rustsat + + runHook postCheck + ''; + + pythonImportsCheck = [ + "rustsat" + "rustsat.encodings" + "rustsat.encodings.am1" + "rustsat.encodings.card" + "rustsat.encodings.pb" + ]; + + LD_LIBRARY_PATH = lib.makeLibraryPath libs; + PKG_CONFIG_PATH = "${openssl.dev}/lib/pkgconfig"; +} diff --git a/pyapi/doc.nix b/pyapi/doc.nix new file mode 100644 index 00000000..a6e42203 --- /dev/null +++ b/pyapi/doc.nix @@ -0,0 +1,34 @@ +{ + lib, + stdenv, + python3, + python3Packages, +}: +let + manifest = (lib.importTOML ./Cargo.toml).package; + workspace-manifest = (lib.importTOML ../Cargo.toml).workspace.package; +in +stdenv.mkDerivation { + pname = "${manifest.name}-docs"; + version = workspace-manifest.version; + + nativeBuildInputs = [ + (python3.withPackages ( + pp: with pp; [ + pdoc + (python3Packages.callPackage ./. { }) + ] + )) + ]; + + unpackPhase = "true"; + + buildPhase = '' + runHook preBuild + + mkdir -p $out/share/doc + pdoc -o $out/share/doc --no-show-source rustsat + + runHook postBuild + ''; +} diff --git a/result b/result index f54bf1b7..22d995a3 120000 --- a/result +++ b/result @@ -1 +1 @@ -/nix/store/zp3k3fs6prz3n619aj695im18ficngmn-rustsat-test-coverage \ No newline at end of file +/nix/store/jm2l2g37kc63cvhws7qfs47g3smdrpwp-rustsat-test-coverage \ No newline at end of file diff --git a/tools/default.nix b/tools/default.nix index 5ac0bf77..7b85e5cc 100644 --- a/tools/default.nix +++ b/tools/default.nix @@ -1,6 +1,6 @@ { lib, - rustPlatform, + rust-toolchain-platform, openssl, pkg-config, cmake, @@ -14,11 +14,25 @@ in assert lib.assertMsg ( withCadical && !withMinisat || !withCadical && withMinisat ) "either withCadical or withMinisat, but not both must be set"; -rustPlatform.buildRustPackage { +rust-toolchain-platform.buildRustPackage { pname = manifest.name; version = workspace-manifest.version; - src = ../.; + src = + let + filter = + path: type: + type == "directory" + || ( + builtins.match ".*(src/(lib|main).rs|Cargo.lock|-source/(data/.*|examples/.*rs|src/.*rs)|tools/(src/.*rs|data/.*)|(minisat|cadical)/(build.rs|src/.*rs|cpp(src|-extension)/.*(cp?p?|hp?p?|CMakeLists.txt|VERSION))|toml)$" path + != null + ); + in + lib.cleanSourceWith { + src = ../.; + inherit filter; + name = "source"; + }; buildAndTestSubdir = "tools"; cargoLock.lockFile = ../Cargo.lock; @@ -28,7 +42,7 @@ rustPlatform.buildRustPackage { buildInputs = [ openssl - rustPlatform.bindgenHook + rust-toolchain-platform.bindgenHook ]; nativeBuildInputs = [ pkg-config