From 77b92c498c284122cdfbb93ba3e6395618f8933e Mon Sep 17 00:00:00 2001 From: kprussing Date: Sat, 15 Nov 2025 09:54:43 -0500 Subject: [PATCH 01/12] Add preliminary test showing failure This test (should) generate two identical LaTeX input files: main.tex and main.extra.tex. The should compile to the same content, but SCons strips accidentally strips the '.extra' from the second preventing it from finding the bibliography file. The test needs to be checked on a non-Windows system because Miktek does not support the ~ in the shortened path name to the temp directory. --- test/TEX/bibtex-extra-extension.py | 81 ++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 test/TEX/bibtex-extra-extension.py diff --git a/test/TEX/bibtex-extra-extension.py b/test/TEX/bibtex-extra-extension.py new file mode 100644 index 0000000000..d89ce98578 --- /dev/null +++ b/test/TEX/bibtex-extra-extension.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +""" +Verify that running LaTeX with a file named main.extra.tex pulls the +right bibliography file. +""" + +import TestSCons + +test = TestSCons.TestSCons() + +pdflatex = test.where_is('pdflatex') + +if not pdflatex: + test.skip_test("Could not find 'pdflatex'; skipping test(s).\n") + +mains = ["main.tex", "main.extra.tex"] + +test.write(["SConstruct"], f"""\ +import os +env = Environment(ENV={{'PATH': os.environ['PATH']}}, tools=['pdftex', 'tex']) +env.PDF({mains}) +""") + +body_content = r"""\documentclass{article} +\begin{document} +SCons \cite{scons}! +\bibliography{references} +\bibliographystyle{plain} +\end{document} +""" + +for _ in mains: + test.write(_, body_content) + +test.write("references.bib", r""" +@misc{scons, + title = {SCons}, + url = {https://scons.org}, + language = {en-US}, + urldate = {2025-08-15}, + author = {{SCons} {Foundation}}, + year = {2025}, +} +""") + +test.run() + +pdfs = [test.read(_[:-3] + "pdf") for _ in mains] +test.fail_test(pdfs[0] != pdfs[1]) +test.pass_test() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: From 283ca61fbc928e19a261de79abb8baa3a5ca8b77 Mon Sep 17 00:00:00 2001 From: kprussing Date: Sat, 15 Nov 2025 09:57:32 -0500 Subject: [PATCH 02/12] Pass the correct files to the Actions The second argument to the action should be the aux file not the bibliography file. Also, we have to add the bib extension to the target because it will be stripped by the command string via TARGET.filebase. --- SCons/Tool/tex.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/SCons/Tool/tex.py b/SCons/Tool/tex.py index 54b21fbe84..2473404679 100644 --- a/SCons/Tool/tex.py +++ b/SCons/Tool/tex.py @@ -340,8 +340,9 @@ def check_content_hash(filenode, suffix) -> bool: if content.find("bibdata") != -1: if Verbose: print("Need to run bibtex on ",auxfilename) - bibfile = env.fs.File(SCons.Util.splitext(target_aux)[0]) - result = BibTeXAction(bibfile, bibfile, env) + auxfile = env.fs.File(target_aux) + bibfile = env.fs.File(SCons.Util.splitext(target_aux)[0] + ".bib") + result = BibTeXAction(bibfile, auxfile, env) if result != 0: check_file_error_message(env['BIBTEX'], 'blg') check_content_hash(suffix_nodes[".bbl"], ".bbl") @@ -364,8 +365,9 @@ def check_content_hash(filenode, suffix) -> bool: if content.find("bibdata") != -1: if Verbose: print("Need to run biber on ",bcffilename) - bibfile = env.fs.File(SCons.Util.splitext(target_bcf)[0]) - result = BiberAction(bibfile, bibfile, env) + auxfile = env.fs.File(target_aux) + bibfile = env.fs.File(SCons.Util.splitext(target_aux)[0] + ".bib") + result = BiberAction(bibfile, auxfile, env) if result != 0: check_file_error_message(env['BIBER'], 'blg') check_content_hash(suffix_nodes[".bbl"], ".bbl") From fc0acf982eed61a3f9c58578af01f2b9e48417ff Mon Sep 17 00:00:00 2001 From: "Keith F. Prussing" Date: Thu, 20 Nov 2025 14:27:27 -0500 Subject: [PATCH 03/12] Compare the normalized text The normalized text in the PDF should be the same. That ensures we have the proper output from the stripping. --- test/TEX/bibtex-extra-extension.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/TEX/bibtex-extra-extension.py b/test/TEX/bibtex-extra-extension.py index d89ce98578..98e75e427e 100644 --- a/test/TEX/bibtex-extra-extension.py +++ b/test/TEX/bibtex-extra-extension.py @@ -70,7 +70,7 @@ test.run() -pdfs = [test.read(_[:-3] + "pdf") for _ in mains] +pdfs = [test.normalize_pdf(test.read(_[:-3] + "pdf")) for _ in mains] test.fail_test(pdfs[0] != pdfs[1]) test.pass_test() From 5918bc13dcae99d0339ccf483c670ce28a849fcf Mon Sep 17 00:00:00 2001 From: "Keith F. Prussing" Date: Thu, 20 Nov 2025 14:30:16 -0500 Subject: [PATCH 04/12] Do not pull in the environment Testing on non-Windows finds pdflatex on the PATH so pulling in the os.envrion is not necessary. --- test/TEX/bibtex-extra-extension.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/TEX/bibtex-extra-extension.py b/test/TEX/bibtex-extra-extension.py index 98e75e427e..22b357facb 100644 --- a/test/TEX/bibtex-extra-extension.py +++ b/test/TEX/bibtex-extra-extension.py @@ -42,7 +42,7 @@ test.write(["SConstruct"], f"""\ import os -env = Environment(ENV={{'PATH': os.environ['PATH']}}, tools=['pdftex', 'tex']) +env = Environment(tools=['pdftex', 'tex']) env.PDF({mains}) """) From 2869dade18cc5db04a330c51d36c70e7255ecd30 Mon Sep 17 00:00:00 2001 From: "Keith F. Prussing" Date: Thu, 20 Nov 2025 15:12:49 -0500 Subject: [PATCH 05/12] Update changes and release notes --- CHANGES.txt | 3 +++ RELEASE.txt | 1 + 2 files changed, 4 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 0d4c4dad8f..cbcd3f5da7 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -16,6 +16,9 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER - Whatever John Doe did. + From Keith F Prussing: + - Fix multiple extension stripping for BibTeX and Biber processing + RELEASE 4.10.1 - Sun, 16 Nov 2025 10:51:57 -0700 diff --git a/RELEASE.txt b/RELEASE.txt index 9047a7a9a8..81941fb056 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -33,6 +33,7 @@ FIXES ----- - List fixes of outright bugs +- Fix multiple extension stripping for BibTeX and Biber processing IMPROVEMENTS ------------ From 5448845b5f659face3edc4cb59b6913c3b4c4000 Mon Sep 17 00:00:00 2001 From: "Keith F. Prussing" Date: Thu, 20 Nov 2025 15:31:48 -0500 Subject: [PATCH 06/12] Use the correct biber extension --- SCons/Tool/tex.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SCons/Tool/tex.py b/SCons/Tool/tex.py index 383481e7e5..952c8f6fc2 100644 --- a/SCons/Tool/tex.py +++ b/SCons/Tool/tex.py @@ -366,7 +366,7 @@ def check_content_hash(filenode, suffix) -> bool: if Verbose: print("Need to run biber on ",bcffilename) auxfile = env.fs.File(target_aux) - bibfile = env.fs.File(SCons.Util.splitext(target_aux)[0] + ".bib") + bibfile = env.fs.File(SCons.Util.splitext(target_aux)[0] + ".bbl") result = BiberAction(bibfile, auxfile, env) if result != 0: check_file_error_message(env['BIBER'], 'blg') From 020f82909da1122ddc9f4805e7ebc203f603e347 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Thu, 20 Nov 2025 15:55:52 -0800 Subject: [PATCH 07/12] [ci skip] switch test to use current test template file header --- test/TEX/bibtex-extra-extension.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/TEX/bibtex-extra-extension.py b/test/TEX/bibtex-extra-extension.py index 22b357facb..d2b1231b20 100644 --- a/test/TEX/bibtex-extra-extension.py +++ b/test/TEX/bibtex-extra-extension.py @@ -1,6 +1,8 @@ #!/usr/bin/env python # -# __COPYRIGHT__ +# MIT License +# +# Copyright The SCons Foundation # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -20,9 +22,6 @@ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# - -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" """ Verify that running LaTeX with a file named main.extra.tex pulls the From bc60d2573e4ba336dce55d9f107518d0624ee558 Mon Sep 17 00:00:00 2001 From: "Keith F. Prussing" Date: Tue, 14 Jul 2026 15:37:42 -0400 Subject: [PATCH 08/12] Use the file instead of the filebase Per request, using the SOURCE.file instead of SOURCE.filebase when processing bibliographies. --- SCons/Tool/tex.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SCons/Tool/tex.py b/SCons/Tool/tex.py index 3b130b3682..2137e4ba7d 100644 --- a/SCons/Tool/tex.py +++ b/SCons/Tool/tex.py @@ -960,11 +960,11 @@ def generate_common(env) -> None: env['BIBTEX'] = 'bibtex' env['BIBTEXFLAGS'] = SCons.Util.CLVar('') - env['BIBTEXCOM'] = CDCOM + '${TARGET.dir} && $BIBTEX $BIBTEXFLAGS ${SOURCE.filebase}' + env['BIBTEXCOM'] = CDCOM + '${TARGET.dir} && $BIBTEX $BIBTEXFLAGS ${SOURCE.file}' env['BIBER'] = 'biber' env['BIBERFLAGS'] = SCons.Util.CLVar('') - env['BIBERCOM'] = CDCOM + '${TARGET.dir} && $BIBER $BIBERFLAGS ${SOURCE.filebase}' + env['BIBERCOM'] = CDCOM + '${TARGET.dir} && $BIBER $BIBERFLAGS ${SOURCE.file}' env['MAKEINDEX'] = 'makeindex' env['MAKEINDEXFLAGS'] = SCons.Util.CLVar('') From 7f6df2463da2d9be922ee96587d2ed4f531918d2 Mon Sep 17 00:00:00 2001 From: "Keith F. Prussing" Date: Wed, 15 Jul 2026 08:14:39 -0400 Subject: [PATCH 09/12] Manual revert of 283ca61fbc928e19a261de79abb8baa3a5ca8b77 Returning the Action logic to the original state since the COMSTR update handles splitting the extension. --- SCons/Tool/tex.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/SCons/Tool/tex.py b/SCons/Tool/tex.py index 2137e4ba7d..9701dcc587 100644 --- a/SCons/Tool/tex.py +++ b/SCons/Tool/tex.py @@ -339,9 +339,8 @@ def check_content_hash(filenode, suffix) -> bool: if 'bibdata' in content: if Verbose: print("Need to run bibtex on ",auxfilename) - auxfile = env.fs.File(target_aux) - bibfile = env.fs.File(SCons.Util.splitext(target_aux)[0] + ".bib") - result = BibTeXAction(bibfile, auxfile, env) + bibfile = env.fs.File(SCons.Util.splitext(target_aux)[0]) + result = BibTeXAction(bibfile, bibfile, env) if result != 0: check_file_error_message(env['BIBTEX'], 'blg') check_content_hash(suffix_nodes[".bbl"], ".bbl") @@ -364,9 +363,8 @@ def check_content_hash(filenode, suffix) -> bool: if 'bibdata' in content: if Verbose: print("Need to run biber on ",bcffilename) - auxfile = env.fs.File(target_aux) - bibfile = env.fs.File(SCons.Util.splitext(target_aux)[0] + ".bbl") - result = BiberAction(bibfile, auxfile, env) + bibfile = env.fs.File(SCons.Util.splitext(target_aux)[0]) + result = BiberAction(bibfile, bibfile, env) if result != 0: check_file_error_message(env['BIBER'], 'blg') check_content_hash(suffix_nodes[".bbl"], ".bbl") From a3681f9026e62f1932e9a19926535e2e7b6782b5 Mon Sep 17 00:00:00 2001 From: "Keith F. Prussing" Date: Wed, 15 Jul 2026 08:16:13 -0400 Subject: [PATCH 10/12] Correct file name in revert The original target was the bcf not aux. --- SCons/Tool/tex.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SCons/Tool/tex.py b/SCons/Tool/tex.py index 9701dcc587..1a1bd23835 100644 --- a/SCons/Tool/tex.py +++ b/SCons/Tool/tex.py @@ -363,7 +363,7 @@ def check_content_hash(filenode, suffix) -> bool: if 'bibdata' in content: if Verbose: print("Need to run biber on ",bcffilename) - bibfile = env.fs.File(SCons.Util.splitext(target_aux)[0]) + bibfile = env.fs.File(SCons.Util.splitext(target_bcf)[0]) result = BiberAction(bibfile, bibfile, env) if result != 0: check_file_error_message(env['BIBER'], 'blg') From 71b8c68b1cc123570a2c4c0d26c9cf0b22d540f7 Mon Sep 17 00:00:00 2001 From: "Keith F. Prussing" Date: Thu, 16 Jul 2026 11:02:00 -0400 Subject: [PATCH 11/12] Add E2E LaTeX tests with body in subdirectory Some authors place the body of their document in a subdirectory with the same stem as the main file. When this is the structure, SCons tries to make a File node when processing the bibliography which raises a TypeError because it finds a directory. --- test/TEX/biber-with-subdir.py | 70 ++++++++++++++++++++++++++++++++++ test/TEX/bibtex-with-subdir.py | 69 +++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 test/TEX/biber-with-subdir.py create mode 100644 test/TEX/bibtex-with-subdir.py diff --git a/test/TEX/biber-with-subdir.py b/test/TEX/biber-with-subdir.py new file mode 100644 index 0000000000..0f8b4b8844 --- /dev/null +++ b/test/TEX/biber-with-subdir.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python +# +# MIT License +# +# Copyright The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +""" +Verify running LaTeX with a bibliography and a subdirectory named after +the main document does not fail to locate the bibliography file +""" + +import TestSCons + +test = TestSCons.TestSCons() + +pdflatex = test.where_is('pdflatex') + +if not pdflatex: + test.skip_test("Could not find 'pdflatex'; skipping test(s).\n") + +test.write(["SConstruct"], """\ +env = Environment(tools=['pdftex', 'tex']) +env.PDF('paper.tex') +""") + +test.write(["paper.tex"], r"""\documentclass{article} +\usepackage{biblatex} +\addbibresource{paper.bib} +\begin{document} +\input{paper/body.tex} +\printbibliography +\end{document} +""") + +test.write("paper.bib", r""" +@misc{scons, + title = {SCons}, + url = {https://scons.org}, + language = {en-US}, + urldate = {2025-08-15}, + author = {{SCons} {Foundation}}, + year = {2025}, +} +""") + +test.subdir("paper") + +test.write(["paper", "body.tex"], r"SCons \cite{scons}!") + +test.run() +test.pass_test() diff --git a/test/TEX/bibtex-with-subdir.py b/test/TEX/bibtex-with-subdir.py new file mode 100644 index 0000000000..a34e90d495 --- /dev/null +++ b/test/TEX/bibtex-with-subdir.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python +# +# MIT License +# +# Copyright The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +""" +Verify running LaTeX with a bibliography and a subdirectory named after +the main document does not fail to locate the bibliography file +""" + +import TestSCons + +test = TestSCons.TestSCons() + +pdflatex = test.where_is('pdflatex') + +if not pdflatex: + test.skip_test("Could not find 'pdflatex'; skipping test(s).\n") + +test.write(["SConstruct"], """\ +env = Environment(tools=['pdftex', 'tex']) +env.PDF('paper.tex') +""") + +test.write(["paper.tex"], r"""\documentclass{article} +\begin{document} +\input{paper/body.tex} +\bibliography{paper} +\bibliographystyle{plain} +\end{document} +""") + +test.write("paper.bib", r""" +@misc{scons, + title = {SCons}, + url = {https://scons.org}, + language = {en-US}, + urldate = {2025-08-15}, + author = {{SCons} {Foundation}}, + year = {2025}, +} +""") + +test.subdir("paper") + +test.write(["paper", "body.tex"], r"SCons \cite{scons}!") + +test.run() +test.pass_test() From 2463684a90f7d72d09c2df30b084c4180519cded Mon Sep 17 00:00:00 2001 From: "Keith F. Prussing" Date: Thu, 16 Jul 2026 11:04:40 -0400 Subject: [PATCH 12/12] Ensure the extension is added to the bibliography file Establish the correct file extensions for bibtex and biber and pass the correct auxiliary file (.aux and .bcf respectively) as the source to the action. --- SCons/Tool/tex.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/SCons/Tool/tex.py b/SCons/Tool/tex.py index 1a1bd23835..eefdd70819 100644 --- a/SCons/Tool/tex.py +++ b/SCons/Tool/tex.py @@ -339,8 +339,9 @@ def check_content_hash(filenode, suffix) -> bool: if 'bibdata' in content: if Verbose: print("Need to run bibtex on ",auxfilename) - bibfile = env.fs.File(SCons.Util.splitext(target_aux)[0]) - result = BibTeXAction(bibfile, bibfile, env) + auxfile = env.fs.File(target_aux) + bibfile = env.fs.File(SCons.Util.splitext(target_aux)[0] + ".bib") + result = BibTeXAction(bibfile, auxfile, env) if result != 0: check_file_error_message(env['BIBTEX'], 'blg') check_content_hash(suffix_nodes[".bbl"], ".bbl") @@ -363,8 +364,9 @@ def check_content_hash(filenode, suffix) -> bool: if 'bibdata' in content: if Verbose: print("Need to run biber on ",bcffilename) - bibfile = env.fs.File(SCons.Util.splitext(target_bcf)[0]) - result = BiberAction(bibfile, bibfile, env) + bcffile = env.fs.File(target_bcf) + bibfile = env.fs.File(SCons.Util.splitext(target_bcf)[0] + ".bbl") + result = BiberAction(bibfile, bcffile, env) if result != 0: check_file_error_message(env['BIBER'], 'blg') check_content_hash(suffix_nodes[".bbl"], ".bbl")