arXiv and Zenodo
The main goal of this workflow is to automate submission tasks and make the resulting packages reproducible, that is, something that compiles in another system (e.g., arXiv) or that has identical checksums for identical content (Zenodo).
arXiv
--dry-run runs the inventory and the checks and writes nothing, so it is safe in CI, and it
exits with 1 if anything is wrong.
$ research-helpers arxiv --dry-run
paper.tex 125.3 KB
paper.bbl 341.2 KB
tables/annotation-effort-efficiency.tex 1.2 KB
tables/consistency.tex 1.1 KB
...
figures/pos-distribution.png 83.3 KB
18 files, 1.21 MB
bbl format 3.3 (TeX Live 2025)
ready to upload: select xelatex and TeX Live 2025
(dry run, nothing written)
Omit --dry-run to write to the submission directory. Include --tar to pack it, and --preview
to compile it with the engine arXiv will use, so the output can be verified.
manifest() keys every file by the path it takes inside the
submission, relative to the manuscript’s directory. So a table installed at tex/tables/scores.tex
and read as \input{tables/scores} is uploaded as tables/scores.tex and resolves identically on
arXiv.
Testing with --check
check() returns every reason why the submission would be rejected
or would fail to build:
A filename arXiv will not take. Only [A-Za-z0-9_+,=.-] are permitted.
A missing or stale .bbl. arXiv does not run biber, so an uploaded .bbl is the
bibliography.
microtype font expansion under XeTeX. expansion=true is a hard error under xelatex and
xetex, which have no font expansion.
Reproducible archives
research_helpers.archive writes tarballs that are byte-identical across runs and
machines. A tarball by default records mtimes, uids, gids, and the compressing machine’s filename
in the gzip header, and uses the order the filesystem returned. Two archives of identical content
thus get different checksums.
write_archive() pins each of those: mtime to a fixed epoch, uid
and gid to zero. It also normalizes modes, sorts members, sets GNU_FORMAT, and empties the gzip
header’s filename field.
from research_helpers.archive import archive_directory, digest, write_checksums
archive = archive_directory('build/deposit.tar.gz', 'build/deposit')
write_checksums([archive], 'build/SHA256SUMS')
digest() always gives the same answer for the same content.