Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions ci_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,13 @@
"windows": false
}
},
"libtommath": {
"build_options": [
"libtommath:tests=true",
"libtommath:buildtype=release",
"libtommath:default_library=both"
]
},
"libupnp": {
"_comment": "Requires some special pthread library",
"build_on": {
Expand Down
8 changes: 8 additions & 0 deletions releases.json
Original file line number Diff line number Diff line change
Expand Up @@ -2459,6 +2459,14 @@
"1.17-1"
]
},
"libtommath": {
"dependency_names": [
"libtommath"
],
"versions": [
"1.3.0-1"
]
},
"libunibreak": {
"dependency_names": [
"libunibreak"
Expand Down
9 changes: 9 additions & 0 deletions subprojects/libtommath.wrap
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[wrap-file]
directory = libtommath-1.3.0
source_url = https://github.com/libtom/libtommath/releases/download/v1.3.0/ltm-1.3.0.tar.xz
source_filename = ltm-1.3.0.tar.xz
source_hash = 296272d93435991308eb73607600c034b558807a07e829e751142e65ccfa9d08
patch_directory = libtommath

[provide]
dependency_names = libtommath
110 changes: 110 additions & 0 deletions subprojects/packagefiles/libtommath/doc/build-manual.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#!/usr/bin/env python3
"""Build the libtommath PDF manual from LaTeX sources

This script is called by meson with the following environment variables set:
LATEX, PDFLATEX, MAKEINDEX

Only requires standard Python library - no external tools beyond LaTeX itself.
"""

import os
import sys
import shutil
import subprocess
import re
from datetime import datetime, timezone


def main():
if len(sys.argv) != 4:
print("Usage: build-manual.py INPUT OUTDIR OUTPUT", file=sys.stderr)
sys.exit(1)

input_file = sys.argv[1]
outdir = sys.argv[2]
output_file = sys.argv[3]

# Get tools from environment
latex = os.environ.get('LATEX', 'latex')
pdflatex = os.environ.get('PDFLATEX', 'pdflatex')
makeindex = os.environ.get('MAKEINDEX', 'makeindex')

# Convert paths to absolute before changing directories
input_file = os.path.abspath(input_file)
output_file = os.path.abspath(output_file)

# Change to output directory
os.chdir(outdir)

# Copy input to bn.tex
shutil.copy2(input_file, 'bn.tex')

# Create backup with same timestamp
shutil.copy2('bn.tex', 'bn.bak')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like there's a lot of unnecessary code to move files around. Why not just write the deterministic header directly into bn.tex (or into tempfile.NamedTemporaryFile()), copy input_file into it afterward, and set its timestamp from input_file?

Copy link
Author

@cyanogilvie cyanogilvie Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re this and the other review comments on this script: this is a python port of upstream's doc build rules:

docs: manual

#LTM user manual
mandvi: bn.tex
    cp bn.tex bn.bak
    touch --reference=bn.tex bn.bak
    (printf "%s" "\def\fixedpdfdate{"; date +'D:%Y%m%d%H%M%S%:z' -d @$$(stat --format=%Y bn.tex) | sed "s/:\([0-9][0-9]\)$$/'\1'}/g") > bn-deterministic.tex
    printf "%s\n" "\pdfinfo{" >> bn-deterministic.tex
    printf "%s\n" "  /CreationDate (\fixedpdfdate)" >> bn-deterministic.tex
    printf "%s\n}\n" "  /ModDate (\fixedpdfdate)" >> bn-deterministic.tex
    cat bn.tex >> bn-deterministic.tex
    mv bn-deterministic.tex bn.tex
    touch --reference=bn.bak bn.tex
    echo "hello" > bn.ind
    latex bn ${silent_stdout}
    latex bn ${silent_stdout}
    makeindex bn
    latex bn ${silent_stdout}

#LTM user manual [pdf]
manual: mandvi
    pdflatex bn >/dev/null
    sed -b -i 's,^/ID \[.*\]$$,/ID [<0> <0>],g' bn.pdf
    mv bn.bak bn.tex
    rm -f bn.aux bn.dvi bn.log bn.idx bn.lof bn.out bn.toc

Which seem to contain a lot of voodoo in pursuit of deterministic builds, but I honestly don't understand the intent of each bit well enough to reason about which side effects are being relied upon (and the commit history didn't shed any light on it), so I instead aimed to replicate the process and side-effects as exactly as possible.

My python is pretty rusty (I last did any substantial work in python over 5 years ago, and even then not much), so I used AI for the initial port and then checked it as best I can, which probably hasn't helped matters.

For my use case I don't care about deterministic builds (especially for an optional documentation target). That seems like a core focus of other build systems like bazel, and I've seen it mentioned for meson but I don't know what the official stance is towards deterministic builds here. It's curious that libtommath seemed to work so hard on making this output deterministic 23 years go, long before I'd heard anything about deterministic builds.


# Get modification time of bn.tex and format for PDF
mtime = os.stat('bn.tex').st_mtime
dt = datetime.fromtimestamp(mtime, tz=timezone.utc)

# Format as PDF date string: D:YYYYMMDDHHmmSS+HH'mm'
# For UTC, timezone offset is +00'00'
date_str = dt.strftime("D:%Y%m%d%H%M%S+00'00'")

# Write deterministic PDF header
with open('bn-deterministic.tex', 'w') as f:
f.write(f"\\def\\fixedpdfdate{{{date_str}}}\n")
f.write("\\pdfinfo{\n")
f.write(" /CreationDate (\\fixedpdfdate)\n")
f.write(" /ModDate (\\fixedpdfdate)\n")
f.write("}\n")

# Append original content
with open('bn.tex', 'r') as orig:
f.write(orig.read())

# Replace bn.tex with deterministic version
shutil.move('bn-deterministic.tex', 'bn.tex')

# Restore original timestamp
shutil.copystat('bn.bak', 'bn.tex')

# Build the manual
with open('bn.ind', 'w') as f:
f.write('hello\n')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's happening here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no idea. It dates back to a commit in 2003 with the message added libtommath-0.17 which I take to mean that this libtommath repo was extracted from some other project that imported libtommath from elsewhere (libtomcrypt?), or that back then libtommath development didn't use VC, so wasn't able to trace the intent.


subprocess.run([latex, 'bn'], stdout=subprocess.DEVNULL, check=True)
subprocess.run([latex, 'bn'], stdout=subprocess.DEVNULL, check=True)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this intentionally doubled?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only insofar as it replicates what upstream's makefile does. My best guess is that there is some build stability hysteresis issue that produces different results if the output artifacts exist already

subprocess.run([makeindex, 'bn'], check=True)
subprocess.run([latex, 'bn'], stdout=subprocess.DEVNULL, check=True)
subprocess.run([pdflatex, 'bn'], stdout=subprocess.DEVNULL, check=True)

# Make PDF ID deterministic using Python
with open('bn.pdf', 'rb') as f:
pdf_data = f.read()

# Replace /ID [<...> <...>] with /ID [<0> <0>]
pdf_data = re.sub(rb'^/ID \[.*\]$', rb'/ID [<0> <0>]', pdf_data, flags=re.MULTILINE)

with open('bn.pdf', 'wb') as f:
f.write(pdf_data)

# Rename to desired output name
shutil.move('bn.pdf', output_file)

# Cleanup
shutil.move('bn.bak', 'bn.tex')

cleanup_files = [
'bn.aux', 'bn.dvi', 'bn.log', 'bn.idx',
'bn.lof', 'bn.out', 'bn.toc', 'bn.ilg',
'bn.ind', 'bn.tex'
]
for f in cleanup_files:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are all written to the build directory. Is it important to clean them up?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would certainly leave that to the normal build dir handling, but since upstream's rules explicitly cleaned up these files separately from its clean target (which includes a superset of them), I interpreted it as meaning that their presence during subsequent builds would affect the determinism so I cargo culted it.

try:
os.remove(f)
except FileNotFoundError:
pass


if __name__ == '__main__':
main()
219 changes: 219 additions & 0 deletions subprojects/packagefiles/libtommath/libtommath.def
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
LIBRARY tommath-1
EXPORTS
fast_mp_invmod
fast_mp_montgomery_reduce
fast_s_mp_mul_digs
fast_s_mp_mul_high_digs
fast_s_mp_sqr
mp_2expt
mp_abs
mp_add
mp_add_d
mp_addmod
mp_and
mp_balance_mul
mp_clamp
mp_clear
mp_clear_multi
mp_cmp
mp_cmp_d
mp_cmp_mag
mp_cnt_lsb
mp_complement
mp_copy
mp_count_bits
mp_decr
mp_div
mp_div_2
mp_div_2d
mp_div_3
mp_div_d
mp_dr_is_modulus
mp_dr_reduce
mp_dr_setup
mp_error_to_string
mp_exch
mp_export
mp_expt_d
mp_expt_d_ex
mp_exptmod
mp_exptmod_fast
mp_expt_n
mp_expt_u32
mp_exteuclid
mp_fread
mp_from_sbin
mp_from_ubin
mp_fwrite
mp_gcd
mp_get_bit
mp_get_double
mp_get_i32
mp_get_i64
mp_get_int
mp_get_l
mp_get_ll
mp_get_long
mp_get_long_long
mp_get_mag_u32
mp_get_mag_u64
mp_get_mag_ul
mp_get_mag_ull
mp_grow
mp_import
mp_incr
mp_init
mp_init_copy
mp_init_i32
mp_init_i64
mp_init_l
mp_init_ll
mp_init_multi
mp_init_set
mp_init_set_int
mp_init_size
mp_init_u32
mp_init_u64
mp_init_ul
mp_init_ull
mp_invmod
mp_invmod_slow
mp_iseven
mp_isodd
mp_is_square
mp_jacobi
mp_karatsuba_mul
mp_karatsuba_sqr
mp_kronecker
mp_lcm
mp_log_n
mp_log_u32
mp_lshd
mp_mod
mp_mod_2d
mp_mod_d
mp_montgomery_calc_normalization
mp_montgomery_reduce
mp_montgomery_setup
mp_mul
mp_mul_2
mp_mul_2d
mp_mul_d
mp_mulmod
mp_neg
mp_n_root
mp_n_root_ex
mp_or
mp_pack
mp_pack_count
mp_prime_fermat
mp_prime_frobenius_underwood
mp_prime_is_divisible
mp_prime_is_prime
mp_prime_miller_rabin
mp_prime_next_prime
mp_prime_rabin_miller_trials
mp_prime_rand
mp_prime_random_ex
mp_prime_strong_lucas_selfridge
mp_radix_size
mp_rand
mp_rand_digit
mp_rand_source
mp_read_radix
mp_read_signed_bin
mp_read_unsigned_bin
mp_reduce
mp_reduce_2k
mp_reduce_2k_l
mp_reduce_2k_setup
mp_reduce_2k_setup_l
mp_reduce_is_2k
mp_reduce_is_2k_l
mp_reduce_setup
mp_root_n
mp_root_u32
mp_rshd
mp_sbin_size
mp_set
mp_set_double
mp_set_i32
mp_set_i64
mp_set_int
mp_set_l
mp_set_ll
mp_set_long
mp_set_long_long
mp_set_u32
mp_set_u64
mp_set_ul
mp_set_ull
mp_shrink
mp_signed_bin_size
mp_signed_rsh
mp_sqr
mp_sqrmod
mp_sqrt
mp_sqrtmod_prime
mp_sub
mp_sub_d
mp_submod
mp_tc_and
mp_tc_div_2d
mp_tc_or
mp_tc_xor
mp_toom_mul
mp_toom_sqr
mp_to_radix
mp_toradix
mp_toradix_n
mp_to_sbin
mp_to_signed_bin
mp_to_signed_bin_n
mp_to_ubin
mp_to_unsigned_bin
mp_to_unsigned_bin_n
mp_ubin_size
mp_unpack
mp_unsigned_bin_size
mp_xor
mp_zero
s_mp_add
s_mp_balance_mul
s_mp_div_3
s_mp_exptmod
s_mp_exptmod_fast
s_mp_get_bit
s_mp_invmod_fast
s_mp_invmod_slow
s_mp_karatsuba_mul
s_mp_karatsuba_sqr
s_mp_log
s_mp_log_2expt
s_mp_log_d
s_mp_montgomery_reduce_fast
s_mp_mul_digs
s_mp_mul_digs_fast
s_mp_mul_high_digs
s_mp_mul_high_digs_fast
s_mp_prime_is_divisible
s_mp_prime_random_ex
s_mp_rand_jenkins
s_mp_rand_jenkins_init
s_mp_rand_platform
s_mp_reverse
s_mp_sqr
s_mp_sqr_fast
s_mp_sub
s_mp_toom_mul
s_mp_toom_sqr
KARATSUBA_MUL_CUTOFF DATA
KARATSUBA_SQR_CUTOFF DATA
TOOM_MUL_CUTOFF DATA
TOOM_SQR_CUTOFF DATA
ltm_prime_tab DATA
mp_s_rmap DATA
mp_s_rmap_reverse DATA
mp_s_rmap_reverse_sz DATA
s_mp_prime_tab DATA
s_mp_rand_source DATA
Loading
Loading