Imported from brendanf/fastqindexr (
AGENTS.md). Install upstream withnpx skills add brendanf/fastqindexr. Copyright stays with the author.
Agent / developer handoff: fastqindexr
This note is for future work in this repository (humans and coding agents). It captures intent, structure, and tooling quirks so you can stay aligned with how the project is meant to evolve.
What this package is
- R package (MIT) that indexes gzip-compressed FASTA or FASTQ and extracts records by position through an Rcpp layer.
- The heavy lifting comes from a vendored subset of FastqIndEx (MIT), adapted for in-memory use inside R—not a runtime dependency on upstream binaries.
- Not affiliated with the FastqIndEx maintainers; attribute upstream in
inst/LICENSE.noteand preserve copyright headers in vendored files.
Design philosophy (do not fight these without an explicit product decision)
- Vendor upstream shape, adapt behavior. C++ under
src/fastqindex_core/keeps the same directory layout and filenames as FastqIndEx’ssrc/tree (common/,process/base/,process/index/,process/extract/). When refreshing from upstream, diff against that structure; avoid flattening or renaming “for convenience.” - In-memory index, no
.fqion disk in the core path. The R workflow builds and uses index data in process; the adapted core notes this where upstream assumed file-based index I/O. - R-facing semantics: record IDs in R are 1-based; the C++ side uses 0-based offsets where appropriate (
extract_sequencessubtracts 1 before calling the bridge). - Multiple files = one logical stream for ID purposes:
create_index(c(f1, f2, ...))assigns global record IDs in order;file_record_offsetsin the index list defines which global IDs belong to which file. - Output order = input order:
extract_sequences(index, ids = c(3, 1, 3, ...))returns rows in that order, including duplicates. - Performance idea: extraction batches requests into dense regions (see
kDenseGapinsrc/fastqindexr.cpp) to reduce seek/decompression work, then reorders to the caller’s ID order. - FASTA records: logical records begin at header lines (
>). Sequence may span multiple physical lines in plain or gzip FASTA; gzip extraction uses line-granular reads plus assembly insrc/fastqindexr.cpp(vendored core stays line-oriented). - Fail-fast: avoid using fallbacks to produce correct output even when optimized routines fail, as this can hide implementation problems in the optimized path. Instead, fail cleanly with a descriptive error.
- Single vendored tree only: keep one FastqIndEx-derived source tree under
src/fastqindex_core/. For new features, merge required upstream files into this tree (and reconcile same-name files there) instead of creating parallel vendored trees likesrc/*_ioorsrc/*_core2. - In-memory extraction for in-memory outputs: implementation of methods which extract sequences and produce R objects should avoid unnecessary disk access. Temporary files in particular should not be used.
- Low memory overhead: Extraction methods typically produce outputs as either R objects or files on disk. Large intermediate memory allocations of C++ objects which are not visible to R, or of intermediate R objects, should be avoided.
Code layout (where to look)
| Area | Role |
|---|---|
R/create_index.R, R/extract_sequences.R |
Public API, validation, S3 print for the index object |
R/imports.R |
useDynLib + importFrom(Rcpp, evalCpp) for NAMESPACE |
src/fastqindexr.cpp |
Rcpp exports, index registry, dense-region planning, parsing helpers |
src/fastqindex_core_bridge.cpp |
Unity build: #include of the vendored .cpp files once (avoids duplicate symbols) |
src/fastqindex_core/** |
Vendored FastqIndEx-style sources; fastqindex_core namespace isolates symbols |
src/Makevars |
PKG_LIBS = -lz (zlib); Catch runner TUs compiled with -fno-lto |
inst/LICENSE.note |
Upstream reference commit + list of vendored files + high-level change notes |
src/fastqindex_core/README.md |
Shorter note on layout and the bridge; keep in sync when the file set changes |
tests/testthat/ |
Correctness tests; tests/benchmarks/ for optional Biostrings comparisons |
tests/benchmarks/results/ |
Small committed benchmark snapshots (*.txt) for regression diffing (see below) |
Internal helpers validate_input_files / validate_index are @noRd: not part of the public API.
C++ and Rcpp workflow
- After changing
[[Rcpp::export]]signatures insrc/fastqindexr.cpp, runRcpp::compileAttributes()orpkgbuild::compile_dll()soR/RcppExports.Randsrc/RcppExports.cppstay in sync. Rf_mkCharLen()/Rf_mkChar()results must bePROTECTed (or held inRcpp::Shield<SEXP>) until they are attached to an R object. A later allocation can GC an unprotected CHARSXP;SET_STRING_ELT()then errors with a recycled type such assymbolorinteger. This is not visible to ASan.- Do not compile the vendored
.cppfiles as separate translation units in addition to the bridge, or you will get duplicate symbol errors. The bridge is the single inclusion point. - Vendored headers that use quoted includes (e.g. same-directory
#include "CommonStructsAndConstants.h") rely on the unity include order from the bridge; keep includes coherent. - Catch C++ tests (
testthat::use_catch()): Same registration pattern as the sibling package optimotu (../optimotu):src/test-runner.cppprovidesrun_testthat_testsfrom<testthat.h>. Rcpp's generatedR_init_fastqindexrregisters only[[Rcpp::export]]entry points and setsR_useDynamicSymbols(dll, FALSE), sorun_testthat_testsmust be added by hand at the end ofsrc/RcppExports.cpp— copy the block from optimotu'ssrc/RcppExports.cpp: a forward declarationRcppExport SEXP run_testthat_tests(SEXP);immediately beforestatic const R_CallMethodDef CallEntries[], and a row{"run_testthat_tests", (DL_FUNC) &run_testthat_tests, 1},in that table (before the{NULL, NULL, 0}sentinel). AfterRcpp::compileAttributes(), re-append those lines if the file was regenerated. Catchcontext/test_thatblocks may live insrc/test-fastqindexr.cppor alongside other.cppsources, as in optimotu.
Documentation (Roxygen)
DESCRIPTIONsetsRoxygen: list(markdown = TRUE): write markdown-style Roxygen inR/*.R(backticks,**bold**,[fun()]links).- Regenerate with
roxygen2::roxygenize()(ordevtools::document()).NAMESPACEis roxygen-generated;R/imports.RholdsuseDynLib/ Rcpp imports. roxygen2is in Suggests. For markdown processing, a workingcommonmarkinstall is expected in practice; without it,roxygenize()can fail or emit awkward Rd. If documentation looks wrong, check that dependency chain (historicallystringi/vctrsin renv can also break loading if install was interrupted—see below).
R environment: renv
.Rprofilesourcesrenv/activate.R: the project uses renv for a project library.- Agents may see
renv::status()“out of sync” ifrenv.lockand installed packages differ—normal during dependency edits; runrenv::snapshot()when the maintainer wants the lockfile updated. - Broken or half-installed packages under the renv cache (missing
DESCRIPTIONin a package directory) can break unrelated loads (e.g.roxygen2failing becausestringiis broken). Fix by reinstalling the broken package or cleaning the bad directory—not a problem with this repo’s R code per se.
Continuous integration
.github/workflows/r-cmd-check.yaml: r-libcheck-standardmatrix (macos-latest/windows-latest/ Ubuntu devel+release+oldrel-1) with--no-manualand CRAN-like args..github/workflows/test-coverage.yaml: tests +covr::codecov(); repository must have Codecov (or similar) set up for badges to be meaningful.- Pushes/PRs to
mainormaster.
README
- User-facing narrative lives in
README.Rmd;README.mdis generated—edit the.Rmdand knit if you change the public story.
What to avoid in routine changes
- Reformatting or “cleaning up” vendored files beyond what a clear bug or upstream sync requires.
- Drive-by refactors unrelated to the task, or new markdown files unless the task asks for them.
- Renaming
src/fastqindex_coretree paths to match personal taste—keep upstream alignment. - Creating a second parallel vendored source tree for new upstream subsets; extend the existing
src/fastqindex_core/tree instead.
Quick verification commands (from the package root)
With renv active and dev dependencies available:
pkgbuild::compile_dll() # or devtools::load_all()
testthat::test_local()
roxygen2::roxygenize()
For a full sanity check: R CMD build . then R CMD check on the tarball.
Benchmark snapshots (tests/benchmarks/)
Optional scripts under tests/benchmarks/ load the package via pkgload::load_all() (from the package root) and should be run after a clean rebuild so timings are not skewed by stale objects or accidental debug-only compiler flags.
Clean rebuild before benchmarking
- From the package root, remove stale compilation artifacts, for example:
pkgbuild::clean_dll()in R, and/orrm -f src/*.o src/fastqindexr.so(exact names depend on platform).
- Recompile with the same
PKG_CFLAGS/PKG_CXXFLAGSyou intend to compare (seefile.path(R.home("etc"), "Makeconf")for defaults). For apples-to-apples regressions, avoid unintentionally enabling heavy debug flags (for example-g,-fno-omit-frame-pointer) in local~/.R/Makevarsunless you mean to measure that configuration.
Recording results
Each script sources tests/benchmarks/benchmark_sink.R and writes a stable summary to tests/benchmarks/results/<script_basename>.txt (git SHA and R.version.string prefix + tee’d console lines). Commit those files when you intentionally refresh the baseline; use git diff on tests/benchmarks/results/ to spot performance regressions after code changes.
Some scripts require Biostrings (see script headers).
If you add or remove vendored files, update inst/LICENSE.note and src/fastqindex_core/README.md, and consider refreshing the upstream commit reference in inst/LICENSE.note to match the revision you actually compared or imported.