diff --git a/CLAUDE.md b/CLAUDE.md index 5214efd..ee5c865 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -26,15 +26,15 @@ cargo clippy # lint cargo fmt --check # check formatting ``` -Shell-based equivalence and integration tests live in `tests/`: -- `tests/equivalence-e2e.sh` — end-to-end comparison against original MaxBin2 -- `tests/pipeline-trace.sh` — recursive pipeline equivalence (Rust vs C++) -- `tests/cli-equivalence.sh` / `tests/cli-integration.sh` — CLI behavior -- `tests/bench-genecaller.sh` — FragGeneScan vs FragGeneScanRs benchmark +Shell-based equivalence and integration tests live in `crates/maxbin-rs/tests/`: +- `crates/maxbin-rs/tests/equivalence-e2e.sh` — end-to-end comparison against original MaxBin2 +- `crates/maxbin-rs/tests/pipeline-trace.sh` — recursive pipeline equivalence (Rust vs C++) +- `crates/maxbin-rs/tests/cli-equivalence.sh` / `crates/maxbin-rs/tests/cli-integration.sh` — CLI behavior +- `crates/maxbin-rs/tests/bench-genecaller.sh` — FragGeneScan vs FragGeneScanRs benchmark ## Architecture -### CLI (clap derive, `src/cli.rs`) +### CLI (clap derive, `crates/maxbin-rs/src/cli.rs`) Uses standard double-dash flags (`--contig`, `--abund`, etc.). Single-dash compatibility was dropped in v0.2.0. Subcommands expose individual pipeline stages: @@ -45,7 +45,7 @@ Uses standard double-dash flags (`--contig`, `--abund`, etc.). Single-dash compa - `maxbin-rs cpp-em` — run the original C++ EM via FFI (equivalence testing only) - `maxbin-rs sam-to-abund` — compute abundance from a SAM file -### Key source files +### Key source files (in `crates/maxbin-rs/src/`) - `pipeline.rs` — orchestration for each subcommand - `emanager.rs` — EM algorithm core (E-step, M-step, convergence) diff --git a/Cargo.toml b/Cargo.toml index faeed79..71838e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,26 +1,10 @@ -[package] -name = "maxbin-rs" -version = "0.4.0" -edition = "2024" +[workspace] +resolver = "3" +members = ["crates/maxbin-rs"] -[dependencies] -clap = { version = "4.6.1", features = ["derive"] } -flate2 = "1.1.9" -frag_gene_scan_rs = "1.1.0" -rayon = "1.10" -serde = { version = "1", features = ["derive"] } -serde_json = "1" -seq_io = "0.3" -tempfile = "3" - -[build-dependencies] -cc = "1.2.60" - -[dev-dependencies] -proptest = "1.11.0" - -# Optimized test profile for benchmarks. +# Profiles are only honored at the workspace root, so the benchmark profile +# that used to live in the crate manifest moves here. # Usage: cargo nextest run --cargo-profile bench bench_components -- --ignored [profile.bench] inherits = "release" -debug = true # keep symbols for profiling +debug = true # keep symbols for profiling diff --git a/crates/maxbin-rs/Cargo.toml b/crates/maxbin-rs/Cargo.toml new file mode 100644 index 0000000..8e8fb32 --- /dev/null +++ b/crates/maxbin-rs/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "maxbin-rs" +version = "0.4.0" +edition = "2024" + +[dependencies] +clap = { version = "4.6.1", features = ["derive"] } +flate2 = "1.1.9" +frag_gene_scan_rs = "1.1.0" +rayon = "1.10" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +seq_io = "0.3" +tempfile = "3" + +[build-dependencies] +cc = "1.2.60" + +[dev-dependencies] +proptest = "1.11.0" diff --git a/build.rs b/crates/maxbin-rs/build.rs similarity index 97% rename from build.rs rename to crates/maxbin-rs/build.rs index d56245c..0e26227 100644 --- a/build.rs +++ b/crates/maxbin-rs/build.rs @@ -50,7 +50,7 @@ fn main() { let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); // Rebuild if patch or FFI wrappers change. - println!("cargo:rerun-if-changed=nix/maxbin2-cpp-ffi.patch"); + println!("cargo:rerun-if-changed=vendor/maxbin2-cpp-ffi.patch"); println!("cargo:rerun-if-changed=vendor/ffi"); println!("cargo:rerun-if-env-changed=MAXBIN2_SRC_TARBALL"); @@ -108,7 +108,7 @@ fn main() { fs::remove_dir(&src_subdir).unwrap(); // Step 3: Apply the patch. - let patch_path = manifest_dir.join("nix/maxbin2-cpp-ffi.patch"); + let patch_path = manifest_dir.join("vendor/maxbin2-cpp-ffi.patch"); // The patch has paths like a/src/EManager.cpp, so we need to adjust. // We'll apply with -p2 to strip "a/src/" down to the filename. let patch_output = Command::new("patch") diff --git a/src/abundance.rs b/crates/maxbin-rs/src/abundance.rs similarity index 100% rename from src/abundance.rs rename to crates/maxbin-rs/src/abundance.rs diff --git a/src/cli.rs b/crates/maxbin-rs/src/cli.rs similarity index 100% rename from src/cli.rs rename to crates/maxbin-rs/src/cli.rs diff --git a/src/distance.rs b/crates/maxbin-rs/src/distance.rs similarity index 100% rename from src/distance.rs rename to crates/maxbin-rs/src/distance.rs diff --git a/src/emanager.rs b/crates/maxbin-rs/src/emanager.rs similarity index 100% rename from src/emanager.rs rename to crates/maxbin-rs/src/emanager.rs diff --git a/src/external.rs b/crates/maxbin-rs/src/external.rs similarity index 100% rename from src/external.rs rename to crates/maxbin-rs/src/external.rs diff --git a/src/fasta.rs b/crates/maxbin-rs/src/fasta.rs similarity index 100% rename from src/fasta.rs rename to crates/maxbin-rs/src/fasta.rs diff --git a/src/kmer_map.rs b/crates/maxbin-rs/src/kmer_map.rs similarity index 100% rename from src/kmer_map.rs rename to crates/maxbin-rs/src/kmer_map.rs diff --git a/src/lib.rs b/crates/maxbin-rs/src/lib.rs similarity index 100% rename from src/lib.rs rename to crates/maxbin-rs/src/lib.rs diff --git a/src/main.rs b/crates/maxbin-rs/src/main.rs similarity index 100% rename from src/main.rs rename to crates/maxbin-rs/src/main.rs diff --git a/src/normal_distribution.rs b/crates/maxbin-rs/src/normal_distribution.rs similarity index 100% rename from src/normal_distribution.rs rename to crates/maxbin-rs/src/normal_distribution.rs diff --git a/src/original_ffi.rs b/crates/maxbin-rs/src/original_ffi.rs similarity index 100% rename from src/original_ffi.rs rename to crates/maxbin-rs/src/original_ffi.rs diff --git a/src/paths.rs b/crates/maxbin-rs/src/paths.rs similarity index 100% rename from src/paths.rs rename to crates/maxbin-rs/src/paths.rs diff --git a/src/pipeline.rs b/crates/maxbin-rs/src/pipeline.rs similarity index 100% rename from src/pipeline.rs rename to crates/maxbin-rs/src/pipeline.rs diff --git a/src/profiler.rs b/crates/maxbin-rs/src/profiler.rs similarity index 100% rename from src/profiler.rs rename to crates/maxbin-rs/src/profiler.rs diff --git a/src/quicksort.rs b/crates/maxbin-rs/src/quicksort.rs similarity index 100% rename from src/quicksort.rs rename to crates/maxbin-rs/src/quicksort.rs diff --git a/tests/bench-genecaller.sh b/crates/maxbin-rs/tests/bench-genecaller.sh similarity index 100% rename from tests/bench-genecaller.sh rename to crates/maxbin-rs/tests/bench-genecaller.sh diff --git a/tests/bench-pipeline.sh b/crates/maxbin-rs/tests/bench-pipeline.sh similarity index 100% rename from tests/bench-pipeline.sh rename to crates/maxbin-rs/tests/bench-pipeline.sh diff --git a/tests/bench_components.rs b/crates/maxbin-rs/tests/bench_components.rs similarity index 100% rename from tests/bench_components.rs rename to crates/maxbin-rs/tests/bench_components.rs diff --git a/tests/cli-equivalence.sh b/crates/maxbin-rs/tests/cli-equivalence.sh similarity index 100% rename from tests/cli-equivalence.sh rename to crates/maxbin-rs/tests/cli-equivalence.sh diff --git a/tests/cli-integration.sh b/crates/maxbin-rs/tests/cli-integration.sh similarity index 100% rename from tests/cli-integration.sh rename to crates/maxbin-rs/tests/cli-integration.sh diff --git a/tests/cli_list_files.rs b/crates/maxbin-rs/tests/cli_list_files.rs similarity index 100% rename from tests/cli_list_files.rs rename to crates/maxbin-rs/tests/cli_list_files.rs diff --git a/tests/emanager_equivalence.rs b/crates/maxbin-rs/tests/emanager_equivalence.rs similarity index 100% rename from tests/emanager_equivalence.rs rename to crates/maxbin-rs/tests/emanager_equivalence.rs diff --git a/tests/equivalence-e2e.sh b/crates/maxbin-rs/tests/equivalence-e2e.sh similarity index 100% rename from tests/equivalence-e2e.sh rename to crates/maxbin-rs/tests/equivalence-e2e.sh diff --git a/tests/fixtures/divergent-em/abund b/crates/maxbin-rs/tests/fixtures/divergent-em/abund similarity index 100% rename from tests/fixtures/divergent-em/abund rename to crates/maxbin-rs/tests/fixtures/divergent-em/abund diff --git a/tests/fixtures/divergent-em/contigs.fa b/crates/maxbin-rs/tests/fixtures/divergent-em/contigs.fa similarity index 100% rename from tests/fixtures/divergent-em/contigs.fa rename to crates/maxbin-rs/tests/fixtures/divergent-em/contigs.fa diff --git a/tests/fixtures/divergent-em/seed b/crates/maxbin-rs/tests/fixtures/divergent-em/seed similarity index 100% rename from tests/fixtures/divergent-em/seed rename to crates/maxbin-rs/tests/fixtures/divergent-em/seed diff --git a/tests/pipeline-stages.sh b/crates/maxbin-rs/tests/pipeline-stages.sh similarity index 100% rename from tests/pipeline-stages.sh rename to crates/maxbin-rs/tests/pipeline-stages.sh diff --git a/tests/pipeline-trace.sh b/crates/maxbin-rs/tests/pipeline-trace.sh similarity index 100% rename from tests/pipeline-trace.sh rename to crates/maxbin-rs/tests/pipeline-trace.sh diff --git a/tests/proptest_abundance.rs b/crates/maxbin-rs/tests/proptest_abundance.rs similarity index 100% rename from tests/proptest_abundance.rs rename to crates/maxbin-rs/tests/proptest_abundance.rs diff --git a/tests/proptest_distance.rs b/crates/maxbin-rs/tests/proptest_distance.rs similarity index 100% rename from tests/proptest_distance.rs rename to crates/maxbin-rs/tests/proptest_distance.rs diff --git a/tests/proptest_emanager.rs b/crates/maxbin-rs/tests/proptest_emanager.rs similarity index 100% rename from tests/proptest_emanager.rs rename to crates/maxbin-rs/tests/proptest_emanager.rs diff --git a/tests/proptest_fasta.rs b/crates/maxbin-rs/tests/proptest_fasta.rs similarity index 100% rename from tests/proptest_fasta.rs rename to crates/maxbin-rs/tests/proptest_fasta.rs diff --git a/tests/proptest_kmermap.rs b/crates/maxbin-rs/tests/proptest_kmermap.rs similarity index 100% rename from tests/proptest_kmermap.rs rename to crates/maxbin-rs/tests/proptest_kmermap.rs diff --git a/tests/proptest_normal_distribution.rs b/crates/maxbin-rs/tests/proptest_normal_distribution.rs similarity index 100% rename from tests/proptest_normal_distribution.rs rename to crates/maxbin-rs/tests/proptest_normal_distribution.rs diff --git a/tests/proptest_profiler.rs b/crates/maxbin-rs/tests/proptest_profiler.rs similarity index 100% rename from tests/proptest_profiler.rs rename to crates/maxbin-rs/tests/proptest_profiler.rs diff --git a/tests/proptest_quicksort.rs b/crates/maxbin-rs/tests/proptest_quicksort.rs similarity index 100% rename from tests/proptest_quicksort.rs rename to crates/maxbin-rs/tests/proptest_quicksort.rs diff --git a/tests/sam_to_abund.rs b/crates/maxbin-rs/tests/sam_to_abund.rs similarity index 100% rename from tests/sam_to_abund.rs rename to crates/maxbin-rs/tests/sam_to_abund.rs diff --git a/vendor/ffi/abundance_ffi.cpp b/crates/maxbin-rs/vendor/ffi/abundance_ffi.cpp similarity index 100% rename from vendor/ffi/abundance_ffi.cpp rename to crates/maxbin-rs/vendor/ffi/abundance_ffi.cpp diff --git a/vendor/ffi/distance_ffi.cpp b/crates/maxbin-rs/vendor/ffi/distance_ffi.cpp similarity index 100% rename from vendor/ffi/distance_ffi.cpp rename to crates/maxbin-rs/vendor/ffi/distance_ffi.cpp diff --git a/vendor/ffi/emanager_ffi.cpp b/crates/maxbin-rs/vendor/ffi/emanager_ffi.cpp similarity index 100% rename from vendor/ffi/emanager_ffi.cpp rename to crates/maxbin-rs/vendor/ffi/emanager_ffi.cpp diff --git a/vendor/ffi/fasta_ffi.cpp b/crates/maxbin-rs/vendor/ffi/fasta_ffi.cpp similarity index 100% rename from vendor/ffi/fasta_ffi.cpp rename to crates/maxbin-rs/vendor/ffi/fasta_ffi.cpp diff --git a/vendor/ffi/kmermap_ffi.cpp b/crates/maxbin-rs/vendor/ffi/kmermap_ffi.cpp similarity index 100% rename from vendor/ffi/kmermap_ffi.cpp rename to crates/maxbin-rs/vendor/ffi/kmermap_ffi.cpp diff --git a/vendor/ffi/normaldist_ffi.cpp b/crates/maxbin-rs/vendor/ffi/normaldist_ffi.cpp similarity index 100% rename from vendor/ffi/normaldist_ffi.cpp rename to crates/maxbin-rs/vendor/ffi/normaldist_ffi.cpp diff --git a/vendor/ffi/profiler_ffi.cpp b/crates/maxbin-rs/vendor/ffi/profiler_ffi.cpp similarity index 100% rename from vendor/ffi/profiler_ffi.cpp rename to crates/maxbin-rs/vendor/ffi/profiler_ffi.cpp diff --git a/vendor/ffi/quicksort_ffi.cpp b/crates/maxbin-rs/vendor/ffi/quicksort_ffi.cpp similarity index 100% rename from vendor/ffi/quicksort_ffi.cpp rename to crates/maxbin-rs/vendor/ffi/quicksort_ffi.cpp diff --git a/nix/maxbin2-cpp-ffi.patch b/crates/maxbin-rs/vendor/maxbin2-cpp-ffi.patch similarity index 100% rename from nix/maxbin2-cpp-ffi.patch rename to crates/maxbin-rs/vendor/maxbin2-cpp-ffi.patch diff --git a/flake.nix b/flake.nix index a994eaf..9124d19 100644 --- a/flake.nix +++ b/flake.nix @@ -536,7 +536,7 @@ '' export MAXBIN_RS_DETERMINISTIC=1 export HOME=$(mktemp -d) - bash ${./tests/pipeline-trace.sh} \ + bash ${./crates/maxbin-rs/tests/pipeline-trace.sh} \ "${intermediates.cami-small}/contigs.fa" \ "${intermediates.cami-small}/abund" \ "${intermediates.cami-small}/hmmout" diff --git a/nix/tests.nix b/nix/tests.nix index f44faf3..8c1cd97 100644 --- a/nix/tests.nix +++ b/nix/tests.nix @@ -2,7 +2,7 @@ # # Each entry here creates a small shell script that: # 1. Sets environment variables pointing to Nix-cached test data -# 2. Runs the actual test script from tests/ +# 2. Runs the actual test script from crates/maxbin-rs/tests/ # # This indirection exists because the test scripts are plain bash (readable # without knowing Nix), but they need paths to data in the Nix store — @@ -31,7 +31,7 @@ let # Helper: create a sandboxed CLI integration test for a given dataset. - # Runs tests/cli-integration.sh with the binary on PATH and test data + # Runs crates/maxbin-rs/tests/cli-integration.sh with the binary on PATH and test data # available. Tests flag parsing, subcommands, error cases, and output # validation end-to-end. mkCliTest = @@ -55,7 +55,7 @@ let export MAXBIN_RS_DETERMINISTIC=1 export HOME=$(mktemp -d) - bash ${../tests/cli-integration.sh} + bash ${../crates/maxbin-rs/tests/cli-integration.sh} touch $out ''; @@ -83,7 +83,7 @@ let export MAXBIN_RS_DETERMINISTIC=1 export HOME=$(mktemp -d) - bash ${../tests/cli-equivalence.sh} + bash ${../crates/maxbin-rs/tests/cli-equivalence.sh} touch $out ''; @@ -114,7 +114,7 @@ let maxbin-rs ]; text = '' - bash ${../tests/pipeline-trace.sh} \ + bash ${../crates/maxbin-rs/tests/pipeline-trace.sh} \ "${contigs}" \ "${intermediates'}/abund" \ "${intermediates'}/hmmout" @@ -443,7 +443,7 @@ in export CONTIGS="${datasets.bfragilis.contigs}" export TRAIN_DIR="${fraggenescan-rs}/share/FragGeneScanRs/train" export PATH="${fraggenescan}/libexec/FragGeneScan:$PATH" - bash ${../tests/bench-genecaller.sh} + bash ${../crates/maxbin-rs/tests/bench-genecaller.sh} ''; }; @@ -456,7 +456,7 @@ in export CONTIGS="${datasets.capes-s7.contigs}" export TRAIN_DIR="${fraggenescan-rs}/share/FragGeneScanRs/train" export PATH="${fraggenescan}/libexec/FragGeneScan:$PATH" - bash ${../tests/bench-genecaller.sh} + bash ${../crates/maxbin-rs/tests/bench-genecaller.sh} ''; }; @@ -469,7 +469,7 @@ in text = '' export CONTIGS="${datasets.bfragilis.contigs}" export ABUND="${intermediates.bfragilis}/abund" - bash ${../tests/bench-pipeline.sh} + bash ${../crates/maxbin-rs/tests/bench-pipeline.sh} ''; }; @@ -479,7 +479,7 @@ in text = '' export CONTIGS="${datasets.capes-s7.contigs}" export ABUND="${intermediates.capes}/abund" - bash ${../tests/bench-pipeline.sh} + bash ${../crates/maxbin-rs/tests/bench-pipeline.sh} ''; }; @@ -490,7 +490,7 @@ in text = '' export CONTIGS="${datasets.capes-s7.contigs}" export READS="${datasets.capes-s7.reads1} ${datasets.capes-s7.reads2}" - bash ${../tests/bench-pipeline.sh} + bash ${../crates/maxbin-rs/tests/bench-pipeline.sh} ''; };