#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 1998-2026 Stephane Galland <galland@arakhne.org>
#
# This program is free library; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation; either version 3 of the
# License, or any later version.
#
# This library is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; see the file COPYING.  If not,
# write to the Free Software Foundation, Inc., 59 Temple Place - Suite
# 330, Boston, MA 02111-1307, USA.

import inspect
import logging
import os
import re
import sys
import textwrap
from collections import deque
from typing import Any

from autolatex2.config.configobj import Config
from autolatex2.make.filedescription import FileDescription
from autolatex2.make.make_enums import TeXTools, TeXCompiler, BibCompiler, IndexCompiler, GlossaryCompiler
from autolatex2.tex.biber import BiberErrorParser
from autolatex2.tex.bibtex import BibTeXErrorParser
from autolatex2.tex.citationanalyzer import AuxiliaryCitationAnalyzer
from autolatex2.tex.citationanalyzer import BiblatexCitationAnalyzer
from autolatex2.tex.dependencyanalyzer import DependencyAnalyzer
import autolatex2.tex.utils as texutils
from autolatex2.tex.glossaryanalyzer import GlossaryAnalyzer
from autolatex2.tex.indexanalyzer import IndexAnalyzer
from autolatex2.tex.utils import TeXWarnings, FileType
from autolatex2.translator.translatorrepository import TranslatorRepository
from autolatex2.translator.translatorrunner import TranslatorRunner
import autolatex2.utils.extlogging as extlogging
from autolatex2.utils.runner import Runner, ScriptOutput
from autolatex2.utils import extprint
import autolatex2.utils.utilfunctions as genutils
from autolatex2.utils.i18n import T

class AutoLaTeXMaker(Runner):
	"""
	The maker for the program.
	"""

	__EXTENDED_WARNING_CODE : str = textwrap.dedent("""\
		%*************************************************************
		% CODE ADDED BY AUTOLATEX TO CHANGE THE OUPUT OF THE WARNINGS
		%*************************************************************
		\\makeatletter
		\\newcount\\autolatex@@@lineno
		\\newcount\\autolatex@@@lineno@delta
		\\xdef\\autolatex@@@mainfile@real{::::REALFILENAME::::}
		\\def\\autolatex@@@mainfile{autolatex_autogenerated.tex}
		\\xdef\\autolatex@@@filename@stack{{\\autolatex@@@mainfile}{\\autolatex@@@mainfile}}
		\\global\\let\\autolatex@@@currentfile\\autolatex@@@mainfile
		\\def\\autolatex@@@filename@stack@push#1{%
			\\xdef\\autolatex@@@filename@stack{{#1}\\autolatex@@@filename@stack}%
		}
		\\def\\autolatex@@@filename@stack@pop@split#1#2\\@nil{%
			\\gdef\\autolatex@@@currentfile{#1}%
			\\gdef\\autolatex@@@filename@stack{#2}%
		}
		\\def\\autolatex@@@filename@stack@pop{%
			\\expandafter\\autolatex@@@filename@stack@pop@split\\autolatex@@@filename@stack\\@nil}
		\\def\\autolatex@@@update@filename{%
			\\ifx\\autolatex@@@mainfile\\autolatex@@@currentfile%
				\\edef\\autolatex@@@warning@filename{\\autolatex@@@mainfile@real}%
				\\global\\autolatex@@@lineno@delta=::::AUTOLATEXHEADERSIZE::::\\relax%
			\\else%
				\\edef\\autolatex@@@warning@filename{\\autolatex@@@currentfile}%
				\\global\\autolatex@@@lineno@delta=0\\relax%
			\\fi%
			{\\filename@parse{\\autolatex@@@warning@filename}\\global\\let\\autolatex@@@filename@ext\\filename@ext}%
			\\xdef\\autolatex@@@generic@warning@beginmessage{!!!![BeginWarning]\\autolatex@@@warning@filename:\\ifx\\autolatex@@@filename@ext\\relax.tex\\fi:}%
			\\xdef\\autolatex@@@generic@warning@endmessage{!!!![EndWarning]\\autolatex@@@warning@filename}%
		}
		\\def\\autolatex@@@openfile#1{%
			\\expandafter\\autolatex@@@filename@stack@push{\\autolatex@@@currentfile}%
			\\xdef\\autolatex@@@currentfile{#1}%
			\\autolatex@@@update@filename%
		}
		\\def\\autolatex@@@closefile{%
			\\autolatex@@@filename@stack@pop%
			\\autolatex@@@update@filename%
		}
		\\let\\autolatex@@@InputIfFileExists\\InputIfFileExists
		\\long\\def\\InputIfFileExists#1#2#3{%
			\\autolatex@@@openfile{#1}%
			\\autolatex@@@InputIfFileExists{#1}{#2}{#3}%
			\\autolatex@@@closefile%
		}
		\\let\\autolatex@@@input\\@input
		\\long\\def\\@input#1{%
			\\autolatex@@@openfile{#1}%
			\\autolatex@@@input{#1}%
			\\autolatex@@@closefile%
		}
		\\global\\DeclareRobustCommand{\\GenericWarning}[2]{%
			\\global\\autolatex@@@lineno\\inputlineno\\relax%
			\\global\\advance\\autolatex@@@lineno\\autolatex@@@lineno@delta\\relax%
			\\begingroup
			\\def\\MessageBreak{^^J#1}%
			\\set@display@protect
			\\immediate\\write\\@unused{^^J\\autolatex@@@generic@warning@beginmessage\\the\\autolatex@@@lineno: #2\\on@line.^^J\\autolatex@@@generic@warning@endmessage^^J}%
			\\endgroup
		}
		\\autolatex@@@update@filename
		\\makeatother
		%*************************************************************
		""")

	__COMMAND_DEFINITIONS : dict[int,dict[str,str|list[str]|None]] = {
		TeXTools.pdflatex.value: {
			'cmd': 'pdflatex',
			'flags': ['-halt-on-error', '-interaction', 'batchmode', '-file-line-error'],
			'to_dvi': ['-output-format=dvi'],
			'to_ps': None,
			'to_pdf': ['-output-format=pdf'],
			'synctex': '-synctex=1',
			'jobname': '-jobname',
			'output_dir':  '-output-directory', 
			'ewarnings': __EXTENDED_WARNING_CODE,
			'utf8': [],
		},
		TeXTools.latex.value: {
			'cmd': 'latex',
			'flags': ['-halt-on-error', '-interaction', 'batchmode', '-file-line-error'],
			'to_dvi': ['-output-format=dvi'],
			'to_ps': None,
			'to_pdf': ['-output-format=pdf'],
			'synctex': '-synctex=1',
			'jobname': '-jobname',
			'output_dir':  '-output-directory', 
			'ewarnings': __EXTENDED_WARNING_CODE,
			'utf8': [],
		},
		TeXTools.xelatex.value: {
			'cmd': 'xelatex',
			'flags': ['-halt-on-error', '-interaction', 'batchmode', '-file-line-error'],
			'to_dvi': ['-no-pdf'],
			'to_ps': None,
			'to_pdf': [],
			'synctex': '-synctex=1',
			'jobname': '-jobname',
			'output_dir':  '-output-directory', 
			'ewarnings': __EXTENDED_WARNING_CODE,
			'utf8': [],
		},
		TeXTools.lualatex.value: {
			'cmd': 'luatex',
			'flags': ['-halt-on-error', '-interaction', 'batchmode', '-file-line-error'],
			'to_dvi': ['-output-format=dvi'],
			'to_ps': None,
			'to_pdf': ['-output-format=pdf'],
			'synctex': '-synctex=1',
			'jobname': '-jobname',
			'output_dir':  '-output-directory', 
			'ewarnings': __EXTENDED_WARNING_CODE,
			'utf8': [],
		},
		TeXTools.bibtex.value: {
			'cmd': 'bibtex',
			'flags': [],
			'utf8': [],
		},
		TeXTools.biber.value: {
			'cmd': 'biber',
			'flags': [],
			'utf8': [],
		},
		TeXTools.makeindex.value: {
			'cmd': 'makeindex',
			'flags': [],
			'index_style_flag': '-s',
			'utf8': [],
		},
		TeXTools.texindy.value: {
			'cmd': 'texindy',
			'flags': [],
			'index_style_flag': '',
			'utf8': ['-C', 'utf8'], 
		},
		TeXTools.makeglossaries.value: {
			'cmd': 'makeglossaries',
			'flags': [],
			'glossary_style_flag': '-s',
			'utf8': [],
		},
		TeXTools.dvips.value: {
			'cmd': 'dvips',
			'flags': [],
			'output':'-o', 
			'utf8': [],
		},
	}

	def __init__(self, translator_runner : TranslatorRunner):
		"""
		Construct the make of translators.
		:param translator_runner: The runner of translators.
		:type translator_runner: TranslatorRunner
		"""
		self.__root_files : set[str] = set()
		self.__files : dict[str,FileDescription] = dict()
		self.__stamps : dict[str,dict[str,str]] = dict()
		self.__reset_stamps()
		self.translator_runner : TranslatorRunner = translator_runner
		if self.translator_runner is None:
			self.__configuration : Config = Config()
		else:
			self.__configuration : Config = translator_runner.configuration

		# Initialization of the compiler definitions and the command-line options are
		# differed to the "__internal_register_commands" method factory
		self.__instance_compiler_definition : dict[str,str|list[str]|None] | None = None

		# Initialization of the callback definitions and the command-line options are
		# differed to the "__internal_register_building_callbacks" method factory
		self.__build_callback_functions : dict[str,Any] | None = None

		# Initialization of the callback definitions are
		# differed to the "__internal_register_needrebuild_callbacks" method factory
		self.__needrebuild_callback_functions : dict[str,Any] | None = None

		# Initialize fields by resetting them
		self.reset()

	@staticmethod
	def create(configuration : Config) -> 'AutoLaTeXMaker':
		"""
		Static factory method for creating an instance of AutoLaTeXMaker with the "standard" building method.
		:param configuration: the configuration to use.
		:param configuration: Config
		:return: the instance of the maker
		:rtype: AutoaTeXMaker
		"""
		# Create the translator repository
		repository = TranslatorRepository(configuration)
		# Create the runner of translators
		runner = TranslatorRunner(repository)
		# Create the general maker
		maker = AutoLaTeXMaker(runner)
		# Set the maker from the configuration
		ddir = configuration.document_directory
		document_file = configuration.document_filename
		if ddir:
			fn = os.path.join(ddir, document_file)
		else:
			fn = document_file
		maker.add_root_file(fn)
		return maker

	def reset_commands(self):
		"""
		Reset the external tool commands and rebuild them from the current configuration.
		"""
		self.__instance_compiler_definition = None

	# noinspection PyTypeChecker
	def __internal_register_commands(self):
		"""
		Build the different commands according to the current configuration. This method should not be called from outside the class.
		It is based on the method factory design pattern.
		"""
		if self.__instance_compiler_definition is None:
			encoding = sys.getdefaultencoding()
			is_utf8_system = encoding.lower() == 'utf-8'

			compiler_num = -1
			compiler = self.__configuration.generation.latex_compiler
			if compiler is None:
				compiler_num = TeXTools.pdflatex.value if self.__configuration.generation.pdf_mode else TeXTools.latex.value
				compiler = AutoLaTeXMaker.__COMMAND_DEFINITIONS[compiler_num]['cmd']
			elif TeXCompiler[compiler]:
				compiler_definition = TeXCompiler[compiler]
				if compiler_definition is not None:
					compiler_num = TeXCompiler[compiler].value
					

			if compiler_num in AutoLaTeXMaker.__COMMAND_DEFINITIONS:
				self.__instance_compiler_definition = AutoLaTeXMaker.__COMMAND_DEFINITIONS[compiler_num].copy()
			else:
				self.__instance_compiler_definition = None
			if not self.__instance_compiler_definition:
				raise Exception(T("Cannot find a definition of the command line for the LaTeX compiler '%s'") % compiler)

			out_type = 'pdf' if self.__configuration.generation.pdf_mode else 'ps'

			# LaTeX
			self.__latex_cli : list[str] = list()
			if self.__configuration.generation.latex_cli:
				self.__latex_cli.extend(self.__configuration.generation.latex_cli)
			else:
				self.__latex_cli.append(self.__instance_compiler_definition['cmd'])
				self.__latex_cli.extend(self.__instance_compiler_definition['flags'])
				if is_utf8_system and 'utf8' in self.__instance_compiler_definition and self.__instance_compiler_definition['utf8']:
					self.__latex_cli.extend(self.__instance_compiler_definition['utf8'])
				if ('to_%s' % out_type) not in self.__instance_compiler_definition:
					raise Exception(T("No command definition for '%s/%s'") % (compiler, out_type))
				# Support of SyncTeX
				if self.__configuration.generation.synctex and self.__instance_compiler_definition['synctex']:
					if isinstance(self.__instance_compiler_definition['synctex'], list):
						self.__latex_cli.extend(self.__instance_compiler_definition['synctex'])
					else:
						self.__latex_cli.append(self.__instance_compiler_definition['synctex'])

				target = self.__instance_compiler_definition['to_%s' % out_type]
				if target:
					if isinstance(target, list):
						self.__latex_cli.extend(target)
					else:
						self.__latex_cli.append(target)
				elif out_type == 'ps':
					if isinstance(self.__instance_compiler_definition['to_dvi'], list):
						self.__latex_cli.extend(self.__instance_compiler_definition['to_dvi'])
					else:
						self.__latex_cli.append(self.__instance_compiler_definition['to_dvi'])
				else:
					raise Exception(T('Invalid maker state: cannot find the command line to compile TeX files.'))

			if self.__configuration.generation.latex_flags:
				self.__latex_cli.extend(self.__configuration.generation.latex_flags)

			# BibTeX
			self.__bibtex_cli = list()
			if self.__configuration.generation.bibtex_cli:
				self.__bibtex_cli.extend(self.__configuration.generation.bibtex_cli)
			else:
				cmd = AutoLaTeXMaker.__COMMAND_DEFINITIONS[BibCompiler.bibtex.value]
				if not cmd:
					raise Exception(T("No command definition for 'bibtex'"))
				self.__bibtex_cli.append(cmd['cmd'])
				self.__bibtex_cli.extend(cmd['flags'])
				if is_utf8_system and 'utf8' in cmd and cmd['utf8']:
					self.__bibtex_cli.extend(cmd['utf8'])

			if self.__configuration.generation.bibtex_flags:
				self.__bibtex_cli.extend(self.__configuration.generation.bibtex_flags)

			# Biber
			self.__biber_cli = list()
			if self.__configuration.generation.biber_cli:
				self.__biber_cli.extend(self.__configuration.generation.biber_cli)
			else:
				cmd = AutoLaTeXMaker.__COMMAND_DEFINITIONS[BibCompiler.biber.value]
				if not cmd:
					raise Exception(T("No command definition for 'biber'"))
				self.__biber_cli.append(cmd['cmd'])
				self.__biber_cli.extend(cmd['flags'])
				if is_utf8_system and 'utf8' in cmd and cmd['utf8']:
					self.__biber_cli.extend(cmd['utf8'])

			if self.__configuration.generation.biber_flags:
				self.__biber_cli.extend(self.__configuration.generation.biber_flags)

			# MakeIndex
			self.__makeindex_cli = list()
			if self.__configuration.generation.makeindex_cli:
				self.__makeindex_cli.extend(self.__configuration.generation.makeindex_cli)
			else:
				cmd = AutoLaTeXMaker.__COMMAND_DEFINITIONS[IndexCompiler.makeindex.value]
				if not cmd:
					raise Exception(T("No command definition for 'makeindex'"))
				self.__makeindex_cli.append(cmd['cmd'])
				self.__makeindex_cli.extend(cmd['flags'])
				if is_utf8_system and 'utf8' in cmd and cmd['utf8']:
					self.__makeindex_cli.extend(cmd['utf8'])

			if self.__configuration.generation.makeindex_flags:
				self.__makeindex_cli.extend(self.__configuration.generation.makeindex_flags)

			# texindy
			self.__texindy_cli = list()
			if self.__configuration.generation.texindy_cli:
				self.__texindy_cli.extend(self.__configuration.generation.texindy_cli)
			else:
				cmd = AutoLaTeXMaker.__COMMAND_DEFINITIONS[IndexCompiler.texindy.value]
				if not cmd:
					raise Exception(T("No command definition for 'texindy'"))
				self.__texindy_cli.append(cmd['cmd'])
				self.__texindy_cli.extend(cmd['flags'])
				if is_utf8_system and 'utf8' in cmd and cmd['utf8']:
					self.__texindy_cli.extend(cmd['utf8'])

			if self.__configuration.generation.texindy_flags:
				self.__texindy_cli.extend(self.__configuration.generation.texindy_flags)

			# MakeGlossaries
			self.__makeglossaries_cli = list()
			if self.__configuration.generation.makeglossary_cli:
				self.__makeglossaries_cli.extend(self.__configuration.generation.makeglossary_cli)
			else:
				cmd = AutoLaTeXMaker.__COMMAND_DEFINITIONS[GlossaryCompiler.makeglossaries.value]
				if not cmd:
					raise Exception(T("No command definition for 'makeglossaries'"))
				self.__makeglossaries_cli.append(cmd['cmd'])
				self.__makeglossaries_cli.extend(cmd['flags'])
				if is_utf8_system and 'utf8' in cmd and cmd['utf8']:
					self.__makeglossaries_cli.extend(cmd['utf8'])

			if self.__configuration.generation.makeglossary_flags:
				self.__makeglossaries_cli.extend(self.__configuration.generation.makeglossary_flags)

			# dvips
			self.__dvips_cli = list()
			if self.__configuration.generation.dvips_cli:
				self.__dvips_cli.extend(self.__configuration.generation.dvips_cli)
			else:
				cmd = AutoLaTeXMaker.__COMMAND_DEFINITIONS[TeXTools.dvips.value]
				if not cmd:
					raise Exception(T("No command definition for 'dvips'"))
				self.__dvips_cli.append(cmd['cmd'])
				self.__dvips_cli.extend(cmd['flags'])
				if is_utf8_system and 'utf8' in cmd and cmd['utf8']:
					self.__dvips_cli.extend(cmd['utf8'])

			if self.__configuration.generation.dvips_flags:
				self.__dvips_cli.extend(self.__configuration.generation.dvips_flags)

			# Support of extended warnings
			if self.__configuration.generation.extended_warnings and 'ewarnings' in self.__instance_compiler_definition and self.__instance_compiler_definition['ewarnings']:
				code = str(self.__instance_compiler_definition['ewarnings']).strip()
				s = str(-(code.count('\n') + 1))
				code = code.replace('::::AUTOLATEXHEADERSIZE::::', s)
				self.__latex_warning_code = code
				self.__is_extended_warning_enable = True
			else:
				self.__latex_warning_code = ''
				self.__is_extended_warning_enable = False

	def reset(self):
		"""
		Reset the maker.
		"""
		self.__root_files = set()
		self.__reset_warnings()
		self.__reset_process_data()

	def __reset_process_data(self):
		"""
		Reset the processing data.
		"""
		self.__files = dict()
		self.__stamps = dict()

	def __reset_warnings(self):
		"""
		Reset the lists of warnings.
		"""
		self.__standards_warnings : set[TeXWarnings] = set()
		self.__detailled_warnings = list()

	@property
	def compiler_definition(self) -> dict[str,Any]:
		"""
		The definition of the LaTeX compiler that must be used by this maker.
		:rtype: dict[str,Any]
		"""
		self.__internal_register_commands()
		assert self.__instance_compiler_definition is not None
		return self.__instance_compiler_definition

	@property
	def extended_warnings_enabled(self) -> bool:
		"""
		Replies if the extended warnings are supported by the TeX compiler.
		:rtype: bool
		"""
		self.__internal_register_commands()
		return self.__is_extended_warning_enable

	@property
	def extended_warnings_code(self) -> str:
		"""
		Replies the TeX code that permits to output the extended warnings.
		:rtype: str
		"""
		self.__internal_register_commands()
		return self.__latex_warning_code

	@property
	def latex_cli(self) -> list[str]:
		"""
		The command-line that is used for running the LaTeX tool.
		:rtype: list[str]
		"""
		self.__internal_register_commands()
		return self.__latex_cli

	@property
	def bibtex_cli(self) -> list[str]:
		"""
		The command-line that is used for running the BibTeX tool.
		:rtype: list[str]
		"""
		self.__internal_register_commands()
		return self.__bibtex_cli

	@property
	def biber_cli(self) -> list[str]:
		"""
		The command-line that is used for running the Biber tool.
		:rtype: list[str]
		"""
		self.__internal_register_commands()
		return self.__biber_cli

	@property
	def makeindex_cli(self) -> list[str]:
		"""
		The command-line that is used for running the makeindex tool.
		:rtype: list[str]
		"""
		self.__internal_register_commands()
		return self.__makeindex_cli

	@property
	def texindy_cli(self) -> list[str]:
		"""
		The command-line that is used for running the texindy tool.
		:rtype: list[str]
		"""
		self.__internal_register_commands()
		return self.__texindy_cli

	@property
	def makeglossaries_cli(self) -> list[str]:
		"""
		The command-line that is used for running the makeglossaries tool.
		:rtype: list[str]
		"""
		self.__internal_register_commands()
		return self.__makeglossaries_cli

	@property
	def dvips_cli(self) -> list[str]:
		"""
		The command-line that is used for running the dvips tool.
		:rtype: list[str]
		"""
		self.__internal_register_commands()
		return self.__dvips_cli

	@property
	def root_files(self) -> set[str]:
		"""
		The root files that are involved within the lastest compilation process.
		:rtype: set[str]
		"""
		return self.__root_files

	def add_root_file(self, filename : str):
		"""
		Add root file.
		:param filename: The name of the root file.
		:type filename: str
		"""
		self.__root_files.add(filename)

	@property
	def files(self) -> dict[str,FileDescription]:
		"""
		The files that are involved within the lastest compilation process.
		:rtype: dict[str,FileDescription]
		"""
		return self.__files

	@property
	def standard_warnings(self) -> set[TeXWarnings]:
		"""
		The standard LaTeX warnings that are discovered during the lastest compilation process.
		:rtype: set[str]
		"""
		return self.__standards_warnings

	@property
	def extended_warnings(self) -> list[dict[str,str|int]]:
		"""
		The extended warnings that are discovered during the lastest compilation process.
		:rtype: list[dict[str,str|int]]
		"""
		return self.__detailled_warnings

	def __extract_info_from_tex_log_file(self, log_file : str, loop : bool) -> bool:
		"""
		Parse the TeX log in order to extract warnings and replies if another TeX compilation is needed.
		:param log_file: The filename of the log file that is used for detecting the compilation loop.
		:type log_file: str
		:param loop: Indicates if the compilation loop is enabled.
		:type loop: bool
		:return: True if another compilation is needed; Otherwise returns False
		:rtype: bool
		"""
		logging.debug(T("Reading log file: %s") % os.path.basename(log_file))
		if os.path.exists(log_file):
			with open(log_file, 'r') as f:
				content = f.read()
			rerun = texutils.extract_tex_warning_from_line(content, self.__standards_warnings)
			if rerun and loop:
				return True
			if self.__is_extended_warning_enable:
				warns = re.findall(re.escape('!!!![BeginWarning]')+'(.*?)'+ re.escape('!!!![EndWarning]'), content, re.S)
				for warn in warns:
					m = re.search(r'^(.*?):([^:]*):([0-9]+):\s*(.*?)\s*$', warn, re.S)
					if m is not None:
						warn_details = dict()
						warn_details['filename'] = m.group(1)
						warn_details['extension'] = m.group(2)
						warn_details['lineno'] = int(m.group(3))
						warn_details['message'] = m.group(4)
						self.__detailled_warnings.append(warn_details)
		return False

	def run_latex(self, filename : str, loop : bool = False, extra_run_support : bool = False) -> int:
		"""
		Launch the LaTeX tool and return the number of times the
		tool was launched.
		:param filename: The name TeX file to compile.
		:type filename: str
		:param loop: Indicates if this function may loop on the LaTeX compilation when it is requested by the LaTeX tool. Default value: False.
		:type loop: bool
		:param extra_run_support: Indicates if this function may apply an additional run of LaTeX tool because a tool used in TeX file does
		not provide accurate log message, and needs to have an extra LaTeX run to solves the problem. This is the case of Multibib for example.
		This argument is considered only if the argument "loop" is True; Otherwise, it is ignored.
		Default is False.
		:type extra_run_support: bool
		:return: The number of times the latex tool was run.
		:rtype: int
		"""
		self.__internal_register_commands()
		if filename in self.__files:
			mfn = self.__files[filename].main_filename
			if mfn is not None and mfn != '':
				filename = mfn
		log_file = genutils.basename2(filename, *FileType.tex_extensions()) + '.log'
		nb_runs = 0
		# This is a do-while implementation
		while True:
			logging.info(T('LATEX: %s') % os.path.basename(filename))
			self.__reset_warnings()
			if os.path.isfile(log_file):
				os.remove(log_file)
			command_output = None
			continue_to_compile = False
			if self.__is_extended_warning_enable:
				with open(filename, "r") as f:
					content = f.readlines()
				autofile = texutils.create_extended_tex_filename(filename)
				with open(autofile, "w") as f:
					code = self.__latex_warning_code.replace('::::REALFILENAME::::', filename)
					f.write(code)
					f.write("\n")
					f.write(''.join(content))
					f.write("\n")
				try:
					cmd = self.__latex_cli.copy()
					assert self.__instance_compiler_definition is not None
					if 'jobname' in self.__instance_compiler_definition and self.__instance_compiler_definition['jobname']:
						cmd.append(str(self.__instance_compiler_definition['jobname']))
						cmd.append(genutils.basename(filename, *FileType.tex_extensions()))
					if 'output_dir' in self.__instance_compiler_definition and self.__instance_compiler_definition['output_dir'] is not None and self.__instance_compiler_definition['output_dir']:
						cmd.append(str(self.__instance_compiler_definition['output_dir']))
						cmd.append(os.path.dirname(filename))
					else:
						logging.warning(T('LATEX: no command-line option provided for changing the output directory'))
					cmd.append(autofile)
					cmd = Runner.normalize_command(*cmd)
					nb_runs += 1
					logging.debug(T('Running: %s') % repr(cmd))
					command_output = self.__run_cmd(*cmd)
					logging.debug(T('Run finished'))
				finally:
					genutils.unlink(autofile)
			else:
				cmd = self.__latex_cli.copy()
				cmd.append(os.path.relpath(filename))
				cmd = Runner.normalize_command(*cmd)
				nb_runs += 1
				logging.debug(T('Running: %s') % repr(cmd))
				command_output = self.__run_cmd(*cmd)
				logging.debug(T('Run finished'))

			if command_output is not None and command_output.return_code != 0:
				logging.debug(T("LATEX: Error when processing %s") % os.path.basename(filename))

				# Parse the log to extract the blocks of messages
				fatal_error, log_blocks = texutils.parse_tex_log_file(log_file)

				# Display the message
				if fatal_error:

					# Test if the message is an emergency stop
					if re.search(r'^.*?:[0-9]+:\s*emergency\s+stop\.', fatal_error, re.I | re.M):
						for block in log_blocks:
							m = re.search(r'^\s*!\s*(.*?)\s*$', block, re.S | re.M)
							if m:
								fatal_error += "\n" + m.group(1)

					fatal_error = re.sub(r'^latex_autogenerated.tex:', os.path.basename(filename) + ':', fatal_error)
					logging.debug(T("LATEX: The first error found in the log file is:"))
					extlogging.multiline_error(fatal_error)
					logging.debug(T("LATEX: End of error log."))
				else:
					logging.error(T("LATEX: Unable to extract the error from the log. Please read the log file."))

				raise Exception(T("LaTeX compiler fail. See log file for details"))

			else:
				continue_to_compile = self.__extract_info_from_tex_log_file(log_file, loop)
				logging.debug(T('Detection of rebuild: %s') % (str(continue_to_compile)))

			# Stoping condition for the do-while loop
			if not continue_to_compile:
				# Special case of Multibib that may not output the "Re-run" warning message when it has missed citations.
				if loop and extra_run_support and (TeXWarnings.undefined_reference in self.standard_warnings or TeXWarnings.undefined_citation in self.standard_warnings):
					# Disable the Multibib support because it is needed to compile only once for solving
					# the missed citations from Multibib
					extra_run_support = False
					continue
				# Stop the do-while loop
				break


		return nb_runs

	# noinspection PyMethodMayBeStatic
	def __run_cmd(self, *cmd) -> ScriptOutput:
		"""
		Run the given command and show up the standard output if it is in debug mode.
		:param cmd: The command to run.
		:type cmd: str array
		:return: An output containing the standard output, the
				 error output, and the exception, the return code.
		:rtype: ScriptOutput
		"""
		if logging.getLogger().isEnabledFor(logging.DEBUG):
			exit_code = 0
			sex = None
			sout = ''
			serr = ''
			try:
				exit_code = Runner.run_command_without_redirect(*cmd)
			except Exception as ex:
				sex = ex
			return ScriptOutput(standard_output=sout, error_output=serr, exception=sex, return_code=exit_code)
		else:
			return Runner.run_command(*cmd)

	# noinspection PyMethodMayBeStatic,PyBroadException
	def __select_aux_file(self, filename : str) -> bool:
		try:
			with open(filename, 'r') as f:
				line = f.readline()
				expr = re.compile(r'\\(?:abx@aux@cite|citation|bibcite)', re.S)
				while line:
					if expr.search(line):
						return True
					line = f.readline()
		except:
			pass
		return False

	def detect_aux_files_with_biliography(self, filename : str, check_aux_content : bool = True) -> list[str]:
		"""
		Explore the document folder and subfolders for finding auxiliary files that contains bibliographical citations.
		:param filename: The name TeX file to compile.
		:type filename: str
		:param check_aux_content: Indicates if this function has to read the auxiliary files to determine if a citation is inside.
		Then, if it is the case, the auxiliary file is passed to the bibliography tool; Otherwise it is ignored. Default is: True.
		:type check_aux_content: bool
		:return: The list of auxiliary files
		:rtype: list[str]
		"""
		if check_aux_content:
			aux_file_list = texutils.find_aux_files(filename, self.__select_aux_file)
		else:
			aux_file_list = texutils.find_aux_files(filename)
		if not aux_file_list:
			aux_file = genutils.basename2(filename, *FileType.tex_extensions()) + '.aux'
			aux_file_list.append(aux_file)
		return aux_file_list

	def run_bibtex(self, filename : str, check_aux_content : bool = True) -> dict[str,Any] | None:
		"""
		Launch the BibTeX tool (BibTeX, Biber, etc.) once time and replies a dictionary that describes any error.
		The returned dictionary has the keys: filename, lineno and message.
		This function also supports the document with zro, one or more bibliography sections, such a those
		introduced by the LaTeX package 'bibunits'.
		:param filename: The name of the auxiliary file or the root TeX file to use as input for the bibliography tool.
		:type filename: str
		:param check_aux_content: Indicates if this function has to read the auxiliary files to determine if a citation is inside.
		Then, if it is the case, the auxiliary file is passed to the bibliography tool; Otherwise it is ignored. Default is: True.
		:type check_aux_content: bool
		:return: the error result, or None if there is no error.
		:rtype: dict[str,Any] | None
		"""
		self.__internal_register_commands()
		self.__reset_warnings()
		if FileType.aux.is_file(filename):
			# The input filename is a auxiliary file, that is the standard type of file for BibTeX.
			aux_file_list = [ filename ]
		else:
			if filename in self.__files:
				mfn = self.__files[filename].main_filename
				if mfn is not None and mfn != '':
					filename = mfn
			aux_file_list = self.detect_aux_files_with_biliography(filename, check_aux_content)
		for aux_file in aux_file_list:
			if self.__configuration.generation.is_biber:
				# Remove the file extension because Biber does not support it properly from the CLI
				aux_file = genutils.basename2(aux_file, *FileType.aux.extensions())
				logging.info(T('BIBER: %s') % os.path.basename(aux_file))
				cmd = self.__biber_cli.copy()
			else:
				logging.info(T('BIBTEX: %s') % os.path.basename(aux_file))
				cmd = self.__bibtex_cli.copy()
			cmd.append(os.path.relpath(aux_file))
			cmd = Runner.normalize_command(*cmd)
			if self.__configuration.generation.is_biber:
				logging.debug(T('BIBER: Command line is: %s') % ' '.join(cmd))
			else:
				logging.debug(T('BIBTEX: Command line is: %s') % ' '.join(cmd))
			command_output = Runner.run_command(*cmd)
			if command_output.return_code != 0:
				if self.__configuration.generation.is_biber:
					logging.debug(T('BIBER: error when processing %s') % os.path.basename(aux_file))
				else:
					logging.debug(T('BIBTEX: error when processing %s') % os.path.basename(aux_file))
				log = command_output.standard_output
				if not log:
					log = command_output.error_output
				if log:
					if self.__configuration.generation.is_biber:
						log_parser = BiberErrorParser()
					else:
						log_parser = BibTeXErrorParser()
					current_error = log_parser.parse_log(aux_file, log)
					if current_error:
						return current_error
				current_error = {'filename': aux_file, 'lineno': 0, 'message': command_output.standard_output + "\n" + command_output.error_output}
				return current_error
		return None

	def run_makeindex(self, filename : str) -> ScriptOutput | None:
		"""
		Launch the MakeIndex tool once time.
		The success status if the run of MakeIndex is replied.
		:param filename: The filename of the index file to compile.
		:type filename: str
		:return: None on success; Otherwise a tuple with the exit code and the standard and error outputs from the Makeindex tool.
		:rtype: tuple[int,str,str] | None
		"""
		self.__internal_register_commands()
		idx_ext = FileType.idx.extension()
		idx_file = genutils.basename2(filename, *FileType.index_extensions()) + idx_ext
		logging.info(T('MAKEINDEX: %s') % os.path.basename(idx_file))
		self.__reset_warnings()
		if self.__configuration.generation.is_xindy_index:
			cmd = self.__texindy_cli.copy()
			cmd_def = AutoLaTeXMaker.__COMMAND_DEFINITIONS[IndexCompiler.texindy.value]
		else:
			cmd = self.__makeindex_cli.copy()
			cmd_def = AutoLaTeXMaker.__COMMAND_DEFINITIONS[IndexCompiler.makeindex.value]
		if cmd_def and 'index_style_flag' in cmd_def and cmd_def['index_style_flag']:
			ist_file = self.__configuration.generation.makeindex_style_filename
			if ist_file:
				cmd.append(cmd_def['index_style_flag'])
				cmd.append(os.path.relpath(ist_file))
		cmd.append(os.path.relpath(idx_file))
		cmd = Runner.normalize_command(*cmd)
		command_output = Runner.run_command(*cmd)
		if command_output.return_code != 0:
			logging.error(T("%s\n%s") % (command_output.standard_output, command_output.error_output))
			return command_output
		return None

	def run_makeglossaries(self, filename : str) -> bool:
		"""
		Launch the MakeGlossaries tool once time.
		The success status if the run of MakeGlossaries is replied.
		:param filename: The filename of the TeX file to compile.
		:type filename: str
		:return: True to continue the process. False to stop.
		:rtype: bool
		"""
		self.__internal_register_commands()
		tex_wo_ext = genutils.basename2(filename, *FileType.tex_extensions())
		gls_file = tex_wo_ext + FileType.glo.extension()
		logging.info(T('MAKEGLOSSARIES: %s') % (os.path.basename(gls_file)))
		self.__reset_warnings()
		cmd = self.__makeglossaries_cli.copy()
		ist_file = self.__configuration.generation.makeindex_style_filename
		if ist_file:
			cmd_def = AutoLaTeXMaker.__COMMAND_DEFINITIONS[GlossaryCompiler.makeglossaries.value]
			if not cmd_def:
				raise Exception(T("No command definition for 'makeglossaries'"))
			cmd.append(cmd_def['glossary_style_flag'])
			cmd.append(os.path.relpath(ist_file))
		cmd.append(os.path.relpath(tex_wo_ext))
		cmd = Runner.normalize_command(*cmd)
		command_output = Runner.run_command(*cmd)
		return command_output.return_code == 0

	def run_dvips(self, filename : str) -> dict[str,Any] | None:
		"""
		Launch the tool for converting a DVI file to a Postscript-based file. Replies the description of the current error.
		:param filename: The name dvi file to convert.
		:type filename: str
		:return: None on success; Otherwise a dict with the exit code and the standard and error outputs from the dvips tool.
		:rtype: dict[str,Any] | None
		"""
		self.__internal_register_commands()
		logging.info(T('DVIPS: %s') % os.path.basename(filename))
		if filename in self.__files:
			mfn = self.__files[filename].main_filename
			if mfn is not None and mfn != '':
				filename = mfn
		output = genutils.basename2(filename, '.dvi') + '.ps'
		cmd_def = AutoLaTeXMaker.__COMMAND_DEFINITIONS[TeXTools.dvips.value]
		self.__reset_warnings()
		cmd = self.__dvips_cli.copy()
		cmd.append(cmd_def['output'])
		cmd.append(os.path.relpath(output))
		cmd.append(os.path.relpath(filename))
		cmd = Runner.normalize_command(*cmd)
		command_output = Runner.run_command(*cmd)
		if command_output.return_code != 0:
			logging.debug(T('DVIPS: error when processing %s') % (os.path.basename(filename)))
			current_error = {'filename': filename, 'lineno': 0, 'message': command_output.standard_output + "\n" + command_output.error_output}
			return current_error
		return None

	def __create_file_description(self, output_file : str, output_type : FileType,
								  input_filename : str, main_filename : str | None,
								  use_xindy: bool = False,
								  use_biber: bool = False, use_multibib: bool = False,
								  use_bibunits: bool = False) -> bool:
		"""
		Create an entry into the list of files involved into the execution process.
		:param output_file: The name of the output file.
		:type output_file: str
		:param output_type: The type of the file, 'pdf' or 'ps'.
		:type output_type: str
		:param input_filename: The name of the input file.
		:type input_filename: str
		:param main_filename: The name of the main file associated to this file in the process. If it is None, the
		current file is the main file itself.
		:type main_filename: str | None
		:param use_xindy: Indicates if Xindy must be used for building the index. Default is False.
		:type use_xindy: bool
		:param use_biber: Indicates if Biber must be used for building the bibliography. Default is False.
		:type use_biber: bool
		:param use_multibib: Indicates if Multibib must be used for building the bibliography. Default is False.
		:type use_multibib: bool
		:param use_bibunits: Indicates if Bibunits must be used for building the bibliography. Default is False.
		:type use_bibunits: bool
		:return: True if the list of known dependencies has been changed
		:rtype: bool
		"""
		if output_file not in self.__files:
			desc = FileDescription(output_filename = output_file, file_type = output_type,
								   input_filename = input_filename, main_filename = main_filename)
			self.__files[output_file] = desc
			self.__files[output_file].use_xindy = use_xindy
			self.__files[output_file].use_biber = use_biber
			self.__files[output_file].use_multibib = use_multibib
			self.__files[output_file].use_bibunits = use_bibunits
			return True
		return False

	# noinspection DuplicatedCode
	def __compute_tex_dependencies(self, tex_root_filename : str, root_dir : str, pdf_filename : str) -> bool:
		"""
		Build the dependency tree for the given TeX file. Replies if the dependencies have been changed.
		:param tex_root_filename: The root TeX filename.
		:type tex_root_filename: str
		:param root_dir: The name of the root directory.
		:type root_dir: str
		:param pdf_filename: The name of the PDF file to generate.
		:type pdf_filename: str
		:return: True if the dependency tree has changed.
		:rtype: bool
		"""
		tex_files : deque[str] = deque()
		tex_files.append(tex_root_filename)
		changed = False
		while tex_files:
			tex_file = tex_files.popleft()
			assert tex_file is not None
			if os.path.isfile(tex_file):
				logging.debug(T("Computing dependencies for %s") % tex_file)
				analyzer = DependencyAnalyzer(tex_file, root_dir, tex_root_filename, self.__configuration.generation.include_extra_macros)
				analyzer.run()
				chg = self.__create_file_description(tex_file, FileType.tex, tex_file,
													 main_filename=None if tex_file == tex_root_filename else tex_root_filename,
													 use_xindy=analyzer.is_xindy_index,
													 use_biber=analyzer.is_biber,
													 use_bibunits=analyzer.is_bibunits,
													 use_multibib=analyzer.is_multibib)
				changed = changed or chg
				all_dep_types = analyzer.get_dependency_types()
				# Treat the pure TeX files
				for dep_type in all_dep_types.intersection(FileType.tex_types()):
						deps = analyzer.get_dependencies_for_type(dep_type)
						for dep in deps:
							self.__create_file_description(dep.file_name, dep_type, dep.file_name,
														   None if dep_type != FileType.tex or dep == tex_root_filename
														   else tex_root_filename,
														   use_xindy=analyzer.is_xindy_index,
														   use_biber=analyzer.is_biber,
														   use_bibunits=analyzer.is_bibunits,
														   use_multibib=analyzer.is_multibib)
							self.__files[tex_file].dependencies.add(dep.file_name)
							changed = True
							if dep_type == FileType.tex:
								tex_files.append(dep.file_name)
				# Treat the bibliography files that are referred from the TeX code
				all_bbl_files = set()
				bibliography_dep_types = all_dep_types.intersection(FileType.bibliography_types())
				for dep_type in bibliography_dep_types:
						deps = analyzer.get_dependencies_for_type(dep_type)
						if dep_type == FileType.bib:
							for description in deps:
								bib_file = description.file_name
								self.__create_file_description(bib_file, FileType.bib, bib_file,
								                               main_filename=tex_root_filename,
								                               use_xindy=analyzer.is_xindy_index,
								                               use_biber=analyzer.is_biber,
								                               use_bibunits=analyzer.is_bibunits,
								                               use_multibib=analyzer.is_multibib)
								dep_bbl_files = description.output_files
								if not dep_bbl_files:
									# The name of the BBL file is from the basename of the AUX file,
									# that is based on the basename of the TeX file.
									bbl_file = genutils.basename2(tex_root_filename, *FileType.tex_extensions()) + '.bbl'
									dep_bbl_files = [bbl_file]
								for bbl_file in dep_bbl_files:
									bbl_file = genutils.abs_path(genutils.ensure_filename_extension(bbl_file, '.bbl'),
																 os.path.dirname(bib_file))
									aux_file = genutils.basename2(bbl_file, *FileType.bbl.extensions()) + '.aux'
									self.__create_file_description(bbl_file, FileType.bbl, aux_file,
																   main_filename=tex_root_filename,
																   use_xindy=analyzer.is_xindy_index,
																   use_biber=analyzer.is_biber,
																   use_bibunits=analyzer.is_bibunits,
																   use_multibib=analyzer.is_multibib)
									self.__files[pdf_filename].dependencies.add(bbl_file)
									self.__files[bbl_file].dependencies.add(bib_file)
									self.__files[bbl_file].dependencies.add(tex_file)
									self.__files[bbl_file].use_xindy = analyzer.is_xindy_index
									self.__files[bbl_file].use_biber = analyzer.is_biber
									self.__files[bbl_file].use_multibib = analyzer.is_multibib
									self.__files[bbl_file].use_bibunits = analyzer.is_bibunits
									all_bbl_files.add(bbl_file)
									changed = True
				for dep_type in bibliography_dep_types:
					deps = analyzer.get_dependencies_for_type(dep_type)
					if dep_type != FileType.bib:
						for description in deps:
							chg = self.__create_file_description(description.filename,
																 description.file_type,
																 tex_root_filename,
																 main_filename=tex_root_filename,
																 use_xindy=analyzer.is_xindy_index,
																 use_biber=analyzer.is_biber,
																 use_bibunits=analyzer.is_bibunits,
																 use_multibib=analyzer.is_multibib)
							changed = changed or chg
							for bbl_file in all_bbl_files:
								self.__files[bbl_file].dependencies.add(description.file_name)
								changed = True

				# Treat the index files that  are referred from the TeX code
				if analyzer.is_makeindex:
					idx_file = genutils.basename2(tex_root_filename, *FileType.tex_extensions()) + '.idx'
					self.__create_file_description(idx_file, FileType.idx, tex_root_filename,
												   main_filename=tex_root_filename,
												   use_xindy=analyzer.is_xindy_index,
												   use_biber=analyzer.is_biber,
												   use_bibunits=analyzer.is_bibunits,
												   use_multibib=analyzer.is_multibib)
					self.__files[idx_file].use_xindy = analyzer.is_xindy_index
					self.__files[idx_file].dependencies.add(tex_file)
					ind_file = genutils.basename2(idx_file, '.idx') + '.ind'
					self.__create_file_description(ind_file, FileType.ind, idx_file,
												   main_filename=tex_root_filename,
												   use_xindy=analyzer.is_xindy_index,
												   use_biber=analyzer.is_biber,
												   use_bibunits=analyzer.is_bibunits,
												   use_multibib=analyzer.is_multibib)
					self.__files[ind_file].use_xindy = analyzer.is_xindy_index
					self.__files[ind_file].dependencies.add(idx_file)
					self.__files[pdf_filename].dependencies.add(ind_file)
					changed = True

				# Treat the glossaries files that  are referred from the TeX code
				if analyzer.is_glossary:
					glo_file = genutils.basename2(tex_root_filename, *FileType.tex_extensions()) + '.glo'
					self.__create_file_description( glo_file, FileType.glo, tex_root_filename,
													main_filename=tex_root_filename,
													use_xindy=analyzer.is_xindy_index,
													use_biber=analyzer.is_biber,
													use_bibunits=analyzer.is_bibunits,
													use_multibib=analyzer.is_multibib)
					self.__files[glo_file].dependencies.add(tex_file)
					gls_file = genutils.basename2(glo_file, '.glo') + '.gls'
					self.__create_file_description(gls_file, FileType.gls, tex_root_filename,
												   main_filename=tex_root_filename,
												   use_xindy=analyzer.is_xindy_index,
												   use_biber=analyzer.is_biber,
												   use_bibunits=analyzer.is_bibunits,
												   use_multibib=analyzer.is_multibib)
					self.__files[gls_file].dependencies.add(glo_file)
					self.__files[pdf_filename].dependencies.add(gls_file)
					changed = True
		return changed

	def __compute_aux_dependencies(self, tex_root_filename : str, root_dir : str, pdf_filename : str) -> bool:
		"""
		Build the dependency tree for the given Aux file. Replies if the dependencies have been changed.
		The references in the auxiliary file is related to specific bibliography systems, e.g., multibib.
		:param tex_root_filename: The TeX root filename.
		:type tex_root_filename: str
		:param root_dir: The name of the root directory.
		:type root_dir: str
		:param pdf_filename: The PDF root filename.
		:type pdf_filename: str
		:rtype: bool
		"""
		onlyfiles = [os.path.join(root_dir, f) for f in os.listdir(root_dir) if f.lower().endswith('.aux') and os.path.isfile(os.path.join(root_dir, f))]
		changed = False
		for aux_file in onlyfiles:
			analyzer = AuxiliaryCitationAnalyzer(aux_file)
			analyzer.run()
			styles = analyzer.styles
			databases = analyzer.databases
			if styles:
				for style in styles:
					bst_file = os.path.abspath(style + '.bst')
					if os.path.isfile(bst_file):
						chg = self.__create_file_description(bst_file, FileType.bst, tex_root_filename,
															 main_filename=tex_root_filename)
						changed = changed or chg
						for db in databases:
							bib_file = os.path.abspath(db)
							if os.path.isfile(bib_file):
								self.__create_file_description(bib_file, FileType.bib, db,
															   main_filename=tex_root_filename)
								bbl_file = os.path.abspath(genutils.basename2(bib_file, '.bib') + '.bbl')
								self.__create_file_description(bbl_file, FileType.bbl, tex_root_filename,
															   main_filename=tex_root_filename)
								changed = changed or chg
								self.__files[bbl_file].dependencies.add(bst_file)
								self.__files[bbl_file].dependencies.add(bib_file)
								self.__files[pdf_filename].dependencies.add(bbl_file)
								changed = True
			if databases:
				for db in databases:
					bib_file = os.path.abspath(db)
					if os.path.isfile(bib_file):
						self.__create_file_description(bib_file, FileType.bib, tex_root_filename,
													   main_filename=tex_root_filename)
						bbl_file = genutils.basename2(bib_file, '.bib') + '.bbl'
						self.__create_file_description(bbl_file, FileType.bbl, tex_root_filename,
													   main_filename=tex_root_filename)
						self.__files[bbl_file].dependencies.add(bib_file)
						self.__files[pdf_filename].dependencies.add(bbl_file)
						changed = True
		return changed

	def compute_dependencies(self, tex_filename : str, read_aux_file : bool = True) -> tuple[str,dict[str,FileDescription]]:
		"""
		Build the dependency tree for the given TeX file.
		:param tex_filename: The TeX filename.
		:type tex_filename: str
		:param read_aux_file: Indicates if the auxiliary files must be read too. Default is True.
		:type read_aux_file: bool
		:return: The tuple with the root dependency file and the description of a file.
		:rtype: tuple[str,dict[str,FileDescription]]
		"""
		root_dir = os.path.dirname(tex_filename)
		out_type = 'pdf' if self.__configuration.generation.pdf_mode else 'ps'
		# Add dependency for the final PDF file
		out_file = genutils.basename2(tex_filename, *FileType.tex_extensions()) + '.' + out_type
		self.__create_file_description(out_file, FileType[out_type], tex_filename, tex_filename)
		self.__files[out_file].dependencies.add(tex_filename)
		# TeX files
		self.__compute_tex_dependencies(tex_filename, root_dir, out_file)
		if tex_filename in self.__files:
			in_file = self.__files[tex_filename]
			self.__files[out_file].use_xindy = in_file.use_xindy
			self.__files[out_file].use_multibib = in_file.use_multibib
			self.__files[out_file].use_bibunits = in_file.use_bibunits
			self.__files[out_file].use_biber = in_file.use_biber
		# Aux files
		if read_aux_file:
			self.__compute_aux_dependencies(tex_filename, root_dir, out_file)
		return out_file, self.__files

	def __build_internal_execution_list(self, builds : list[FileDescription], parent_change_date : float|None, filename : str,
										force_changes : bool, main_tex_filename : str, known_answers : dict[str,bool]) -> bool:
		"""
		Build the list of files that needs to be generated in the best order. For each file, a building function named "_build_<ext>" is defined and must be invoked.
		:param builds: List of builds to fill up.
		:type builds: list[FileDescription]
		:param parent_change_date: The change date of the parent file that depends on the filename. It could be None.
		:type parent_change_date: float|None
		:param filename: The name of the file to consider.
		:type filename: str
		:param force_changes: Indicates all the files should be considered as changed. Default value is: False.
		:type force_changes: bool
		:param main_tex_filename: Name of the main TeX file.
		:type main_tex_filename: str
		:param known_answers: Dictionary of already computed answers.
		:type known_answers: dict[str,bool]
		:return: True if the given filename has been added to the build.
		:rtype: bool
		"""
		added = False
		if filename in self.__files:
			# Check the memory for known answers
			if filename in known_answers:
				return known_answers[filename]
			description = self.__files[filename]
			if description:
				child_changed = False
				dependencies = description.dependencies
				if dependencies:
					for dependency in dependencies:
						change = self.__build_internal_execution_list(builds=builds,
																	  parent_change_date=description.change,
																	  filename=dependency,
																	  force_changes=force_changes,
																	  main_tex_filename=main_tex_filename,
																	  known_answers=known_answers)
						if change:
							child_changed = True
				known_answers[filename] = (child_changed or force_changes
										   or self.__need_rebuild(parent_change_date, filename, description, main_tex_filename, known_answers))
				if known_answers[filename]:
					method_name = self.__internal_build_callback_funcname(description.file_type)
					if method_name in self.__internal_register_building_callbacks():
						added = True
						builds.append(description)
		return added

	def build_internal_execution_list(self, root_file : str, root_pdf_file : str, dependencies : dict, force_changes : bool = False) -> list[FileDescription]:
		"""
		Build the list of files that needs to be generated in the best order. For each file, a building function named "_build_<ext>" is defined and must be invoked.
		This function uses the lasted change date of each file to determine if a build is needed, except if force_changes is True.
		If force_changes is True, all the files are supported to be built.
		This function must be invoked after a call to compute_dependencies().
		:param root_file: The LaTeX file to compile.
		:type root_file: str
		:param root_pdf_file: The root PDF file to generate.
		:type root_pdf_file: str
		:param dependencies: The tree of the dependencies for the root file.
		:type dependencies: dict
		:param force_changes: Indicates all the files should be considered as changed. Default value is: False.
		:type force_changes: bool
		:return: the list of files to be built.
		:rtype: list[FileDescription]
		"""
		builds = list()
		known_answers = dict()
		root_change = self.__files[root_pdf_file].change
		self.__build_internal_execution_list(builds=builds,
											 parent_change_date=root_change,
											 filename=root_pdf_file,
											 force_changes=force_changes,
											 main_tex_filename=root_file,
											 known_answers=known_answers)
		return builds

	def run_translators(self, force_generation : bool = False, detect_conflicts : bool = True) -> dict[str,str]:
		"""
		Run the image translators. Replies the list of images that is detected.
		The replied dict associates each source image (keys) to the generated image's filename (values) or None if no file was generated by the call to this function.
		:param force_generation: Indicates if the image generation is forced to be run on all the images (True) or if only the changed source images are considered for the image generation (False). Default is: False.
		:type force_generation: bool
		:param detect_conflicts: Indicates if the conflicts in translator loading is run. Default is True.
		:type detect_conflicts: bool
		:return: the dictionary that maps the source image's filename to the generated image's filename.
		:rtype: dict[str,str]
		"""
		self.translator_runner.sync(detect_conflicts = detect_conflicts)
		images = self.translator_runner.get_source_images()
		generated_images = dict()
		for img in images:
			generated_image = self.translator_runner.generate_image(in_file = img, only_more_recent = not force_generation)
			generated_images[img] = generated_image
		return generated_images

	def __reset_stamps(self):
		self.__stamps = dict()
		self.__stamps['bib'] = dict()
		self.__stamps['idx'] = dict()
		self.__stamps['gls'] = dict()

	def read_build_stamps(self, folder : str, basename : str = ".autolatex_stamp") -> dict[str,dict[str,str]]:
		"""
		Read the build stamps. There stamps are used to memorize the building dates of each element (LaTeX, BibTeX, etc.).
		Fill up the "__stamps" fields and reply them.
		:param folder: name of the folder in which the stamps file is located.
		:type folder: str
		:param basename: name of the temporary file. Default is: ".autolatex_stamp".
		:type basename: str
		:return: the "__stamps" field.
		:rtype: dict[str,dict[str,str]]
		"""
		stamp_file = os.path.join(folder, basename)
		self.__reset_stamps()
		if os.access(stamp_file, os.R_OK):
			with open(stamp_file, 'r') as sf:
				line = sf.readline()
				while line:
					m = re.match(r'^BIB\(([^)]+?)\):(.+)$', line)
					if m:
						k = m.group(1)
						n = m.group(2)
						self.__stamps['bib'][n] = k
					else:
						m = re.match(r'^IDX\(([^)]+?)\):(.+)$', line)
						if m:
							k = m.group(1)
							n = m.group(2)
							self.__stamps['idx'][n] = k
						else:
							m = re.match(r'^GLS\(([^)]+?)\):(.+)$', line)
							if m:
								k = m.group(1)
								n = m.group(2)
								self.__stamps['gls'][n] = k
					line = sf.readline()
		return self.__stamps

	def write_build_stamps(self, folder : str, basename : str = ".autolatex_stamp", stamps : dict[str,dict[str,str]] = None):
		"""
		Write the build stamps. There stamps are used to memorize the building dates of each element (LaTeX, BibTeX, etc.).
		:param folder: name of the folder in which the stamps file is located.
		:type folder: str
		:param basename: name of the temporary file. Default is: ".autolatex_stamp".
		:type basename: str
		:param stamps: Stamps to write
		:type stamps: dict[str,dict[str,str]]
		"""
		stamp_file = os.path.join(folder, basename)
		if stamps is None:
			s = self.__stamps
		else:
			s = stamps
		with open(stamp_file, 'w') as sf:
			if s:
				if 'bib' in s and s['bib']:
					for (k, n) in s['bib'].items():
						sf.write("BIB(")
						sf.write(str(n))
						sf.write("):")
						sf.write(str(k))
						sf.write("\n")
				if 'idx' in s and s['idx']:
					for (k, n) in s['idx'].items():
						sf.write("IDX(")
						sf.write(str(n))
						sf.write("):")
						sf.write(str(k))
						sf.write("\n")
				if 'gls' in s and s['gls']:
					for (k, n) in s['gls'].items():
						sf.write("GLS(")
						sf.write(str(n))
						sf.write("):")
						sf.write(str(k))
						sf.write("\n")

	def __launch_file_build(self, root_file : str, input_file : FileDescription) -> bool:
		"""
		Launch the building process for generating the output file for the given input file.
		This function launch one of the callback building functions, named "__build_callback_<ext>" if it exists.
		This function is a utility function
		:param root_file: Name of the root file (tex document).
		:type root_file: str
		:param input_file: Description of the input file.
		:type input_file: FileDescription
		:return: The continuation status, i.e. True if the build could continue.
		:rtype: bool
		"""
		if input_file:
			file_type = input_file.file_type
			if file_type:
				method_name = self.__internal_build_callback_funcname(file_type)
				if method_name:
					cb = self.__internal_register_building_callbacks()
					if method_name in cb:
						func = cb[method_name]
						continuation = func(root_file, input_file)
						if not continuation:
							return False
		return True

	# noinspection PyBroadException
	def build(self) -> bool:
		"""
		Launch the building process (latex*, BibTeX, Makeindex, Makeglossaries).
		Caution: this function does not generate the images (See run_translators function).
		Caution: this function may invoke multiple times the latex tool.
		:return: True to continue process. False to stop the process.
		"""
		self.__reset_process_data()
		for root_file in self.__root_files:
			root_dir = os.path.dirname(root_file)

			# Read building stamps
			self.read_build_stamps(root_dir)

			# Launch one LaTeX compilation to be sure that every files that are expected are generated
			try:
				self.run_latex(filename=root_file, loop=False)
			except BaseException as ex:
				logging.debug(str(ex))
				return False

			# Compute the dependencies of the files
			root_dep_file, dependencies = self.compute_dependencies(root_file)
			extlogging.multiline_debug(T("Dependency List = %s") % repr(dependencies))

			# Construct the build list and launch the required builds
			builds = self.build_internal_execution_list(root_file, root_dep_file, dependencies)
			if logging.getLogger().isEnabledFor(logging.DEBUG):
				if builds:
					logging.debug(T("Build list:"))
					idx = 1
					for b in builds:
						logging.debug(T("%d) %s") % (idx, b.output_filename))
						idx = idx + 1
				else:
					logging.debug(T("Empty build list"))

			# Build the files
			if builds:
				for file in builds:
					continuation = self.__launch_file_build(root_file, file)
					if not continuation:
						return False

			# Output the warnings from the last TeX builds
			if self.extended_warnings:
				for w in self.extended_warnings:
					logging.warning(T("%s:%d: %s") % (w['filename'], w['lineno'], w['message']))
				self.__reset_warnings()

			# Write building stamps
			self.write_build_stamps(root_dir)

			# Generate the Postscript-based file when requested
			if not self.__configuration.generation.pdf_mode:
				basename = genutils.basename2(root_file, *FileType.tex_extensions())
				dvi_file = basename + '.dvi'
				dvi_date = genutils.get_file_last_change(dvi_file)
				if dvi_date is not None:
					ps_file = genutils.basename2(basename, '.dvi') + '.ps'
					ps_date = genutils.get_file_last_change(ps_file)
					if ps_date  is None or dvi_date >= ps_date:
						self.run_dvips(dvi_file)

			# Compute the log filename
			main_tex_file = self.__files[root_file].main_filename or root_file
			log_file = genutils.basename2(main_tex_file, *FileType.tex_extensions()) + '.log'

			# Detect warnings if not already done
			if not self.standard_warnings:
				self.__extract_info_from_tex_log_file(log_file, False)

			# Output the last LaTeX warning indicators.
			if logging.getLogger().isEnabledFor(logging.WARNING):
				if texutils.TeXWarnings.multiple_definition in self.standard_warnings:
					s = T("LaTeX Warning: There were multiply-defined labels.")
					logging.warning(s)
					if self.extended_warnings_enabled:
						extprint.eprint("!!" + log_file + ":W1: " + s + "\n")
				if texutils.TeXWarnings.undefined_reference in self.standard_warnings:
					s = T("LaTeX Warning: There were undefined references.")
					logging.warning(s)
					if self.extended_warnings_enabled:
						extprint.eprint("!!" + log_file + ":W2: " + s + "\n")
				if texutils.TeXWarnings.undefined_citation in self.standard_warnings:
					s = T("LaTeX Warning: There were undefined citations.")
					logging.warning(s)
					if self.extended_warnings_enabled:
						extprint.eprint("!!" + log_file + ":W3: " + s + "\n")
				if texutils.TeXWarnings.other_warning in self.standard_warnings:
					bn = os.path.basename(log_file)
					logging.warning((T("LaTeX Warning: Please look inside %s for the other the warning messages.") % bn) + "\n")
		return True

	#--------------------------------------------------------------------------------------------
	# CALLBACKS FOR DETERMINING IF A REBUILD IS NEEDED
	#--------------------------------------------------------------------------------------------

	# noinspection PyMethodMayBeStatic
	def __callback_needrebuild_function_selector(self, x : Any) -> bool:
		return inspect.ismethod(x) and x.__name__.startswith('__needrebuild_callback_')

	def __internal_register_needrebuild_callbacks(self) -> dict[str,Any]:
		"""
		Register the different callbacks according to the current configuration. This method should not be called
		from outside the class.
		It is based on the method factory design pattern.
		:return: The callback functions.
		:rtype: dict[str,Any]
		"""
		if self.__needrebuild_callback_functions is None:
			defined_callbacks = inspect.getmembers(self, predicate=self.__callback_needrebuild_function_selector)
			self.__needrebuild_callback_functions = dict(defined_callbacks)
		assert self.__needrebuild_callback_functions is not None
		return self.__needrebuild_callback_functions

	def __internal_needrebuild_callback_funcname(self, file_type : FileType) -> str:
		"""
		Build the internal name of a need-building callback function.
		:param file_type: The type of file concerned by the callback.
		:type file_type: str
		:return: the name of the callback function.
		:rtype: str
		"""
		return '_' + self.__class__.__name__ + '__needrebuild_callback_' + file_type.name

	@staticmethod
	def __is_file_changed(in_change : float | None, out_change : float | None) -> bool:
		"""
		Test if the change date of an input file is greater than the change date of an output file.
		This function supports None as change date if this date is unknown.
		:param in_change: The change date of the input file.
		:type in_change: float | None
		:param out_change: The change date of the output file.
		:type out_change: float | None
		:return: True if the change date of the input file is more recent than those of the output file.
		:rtype: bool
		"""
		if in_change is None:
			return False
		return out_change is None or in_change > out_change

	# noinspection PyMethodMayBeStatic
	def __need_rebuild(self, parent_change_date : float | None, filename : str, description : FileDescription,
	                   main_tex_filename : str, known_answers : dict[str,bool]) -> bool:
		"""
		Test if a rebuild is needed for the given files.
		:param parent_change_date: Time stamp of the parent file. It could be None.
		:type parent_change_date: float | None
		:param filename: Name of the file to test.
		:type filename: str
		:param description: Description of the file to test.
		:type description: FileDescription
		:param main_tex_filename: Name of the main TeX file.
		:type main_tex_filename: str
		:param known_answers: Dictionary of already computed answers.
		:type known_answers: dict[str,bool]
		:return: True if the file needs to be rebuilt
		:rtype: bool
		"""
		# Special case, the files have not dates nor are existing
		if parent_change_date is None or description.change is None or not os.path.isfile(filename):
			return True
		# Check the memory for known answers
		if filename in known_answers:
			return known_answers[filename]
		# Delegate to the callback function
		method_name = self.__internal_needrebuild_callback_funcname(description.file_type)
		if method_name:
			cb = self.__internal_register_needrebuild_callbacks()
			if method_name in cb:
				func = cb[method_name]
				return func(parent_change_date, filename, description, main_tex_filename)
		return AutoLaTeXMaker.__is_file_changed(description.change, parent_change_date)

	# noinspection PyUnusedLocal
	def __needrebuild_callback_pdf(self, root_change_date : float, filename : str, description : FileDescription, main_tex_filename : str) -> bool:
		"""
		Test if the PDF file must be rebuilt.
		:param root_change_date: Time stamp of the root file. It could be None.
		:type root_change_date: int
		:param filename: Name of the file to test.
		:type filename: str
		:param description: Description of the file to test.
		:type description: FileDescription
		:param main_tex_filename: Name of the main TeX file.
		:type main_tex_filename: str
		:return: True if the file needs to be rebuilt
		:rtype: bool
		"""
		log_file = genutils.basename2(main_tex_filename, *FileType.tex_extensions()) + '.log'
		if log_file is not None and os.path.isfile(log_file):
			need_rebuild = self.__extract_info_from_tex_log_file(log_file, True)
			if need_rebuild:
				return True
		return AutoLaTeXMaker.__is_file_changed(root_change_date, description.change)

	# noinspection PyUnusedLocal
	def __needrebuild_callback_bbl(self, root_change_date : float, filename : str, description : FileDescription, main_tex_filename : str) -> bool:
		"""
		Test if the BBL file must be rebuilt from the TeX file and the BibTeX file.
		:param root_change_date: Time stamp of the root file. It could be None.
		:type root_change_date: int
		:param filename: Name of the file to test.
		:type filename: str
		:param description: Description of the file to test.
		:type description: FileDescription
		:param main_tex_filename: Name of the main TeX file.
		:type main_tex_filename: str
		:return: True if the file needs to be rebuilt
		:rtype: bool
		"""
		if description.use_biber:
			# Parse the BCF file to detect the citations
			bcf_file = genutils.basename2(filename, '.bbl') + '.bcf'
			bcf_analyzer = BiblatexCitationAnalyzer(bcf_file)
			current_md5: str = bcf_analyzer.md5 or ''
			old_md5 = self.__stamps['bib'][bcf_file] or ''
			if current_md5 != old_md5:
				self.__stamps['bib'][bcf_file] = current_md5
				return True
		else:
			# Parse the AUX file to detect the citations
			aux_file = genutils.basename2(filename, '.bbl') + '.aux'
			aux_analyzer = AuxiliaryCitationAnalyzer(aux_file)
			current_md5 = aux_analyzer.md5 or ''
			old_md5 = ''
			if 'bib' in self.__stamps and aux_file in self.__stamps['bib']:
				old_md5 = self.__stamps['bib'][aux_file] or ''
			if current_md5 != old_md5:
				if 'bib' not in self.__stamps:
					self.__stamps['bib'] = dict()
				self.__stamps['bib'][aux_file] = current_md5
				return True
		return False

	# noinspection PyUnusedLocal,DuplicatedCode
	def __needrebuild_callback_ind(self, root_change_date : float, filename : str, description : FileDescription, main_tex_filename : str) -> bool:
		"""
		Test if the IND file must be rebuilt from the TeX file and the IDX file.
		:param root_change_date: Time stamp of the root file. It could be None.
		:type root_change_date: int
		:param filename: Name of the file to test.
		:type filename: str
		:param description: Description of the file to test.
		:type description: FileDescription
		:param main_tex_filename: Name of the main TeX file.
		:type main_tex_filename: str
		:return: True if the file needs to be rebuilt
		:rtype: bool
		"""
		# Parse the IDX file to detect the index definitions
		idx_file = genutils.basename2(filename, '.idx', '.ind') + '.idx'
		idx_analyzer = IndexAnalyzer(idx_file)
		current_md5 = idx_analyzer.md5 or ''
		if 'idx' not in self.__stamps:
			self.__stamps['idx'] = dict()
		if idx_file in self.__stamps['idx']:
			old_md5 = self.__stamps['idx'][idx_file]
		else:
			old_md5 = ''
		if current_md5 != old_md5:
			self.__stamps['idx'][idx_file] = current_md5
			return True
		return False

	# noinspection PyUnusedLocal,DuplicatedCode
	def __needrebuild_callback_idx(self, root_change_date : float, filename : str, description : FileDescription, main_tex_filename : str) -> bool:
		"""
		Test if the IDX file must be rebuilt from the TeX file.
		:param root_change_date: Time stamp of the root file. It could be None.
		:type root_change_date: int
		:param filename: Name of the file to test.
		:type filename: str
		:param description: Description of the file to test.
		:type description: FileDescription
		:param main_tex_filename: Name of the main TeX file.
		:type main_tex_filename: str
		:return: True if the file needs to be rebuilt
		:rtype: bool
		"""
		if description.use_xindy:
			# Parse the IDX file to detect the index definitions
			idx_file = genutils.basename2(filename, '.idx', '.ind') + '.idx'
			idx_analyzer = IndexAnalyzer(idx_file)
			current_md5 = idx_analyzer.md5 or ''
			if not 'idx' in self.__stamps:
				self.__stamps['idx'] = dict()
			if idx_file in self.__stamps['idx']:
				old_md5 = self.__stamps['idx'][idx_file]
			else:
				old_md5 = ''
			if current_md5 != old_md5:
				self.__stamps['idx'][idx_file] = current_md5
				return True
		return False

	# noinspection PyUnusedLocal,DuplicatedCode
	def __needrebuild_callback_gls(self, root_change_date : float, filename : str, description : FileDescription, main_tex_filename : str) -> bool:
		"""
		Test if the GLS file must be rebuilt from the TeX file and the GLO file.
		:param root_change_date: Time stamp of the root file. It could be None.
		:type root_change_date: int
		:param filename: Name of the file to test.
		:type filename: str
		:param description: Description of the file to test.
		:type description: FileDescription
		:param main_tex_filename: Name of the main TeX file.
		:type main_tex_filename: str
		:return: True if the file needs to be rebuilt
		:rtype: bool
		"""
		# Parse the GLO file to detect the index definitions
		gls_analyzer = GlossaryAnalyzer(filename)
		current_md5 = gls_analyzer.md5 or ''
		if 'gls' not in self.__stamps:
			self.__stamps['gls'] = dict()
		if filename in self.__stamps['gls']:
			old_md5 = self.__stamps['gls'][filename]
		else:
			old_md5 = ''
		if current_md5 != old_md5:
			self.__stamps['gls'][filename] = current_md5
			return True
		return False

	#--------------------------------------------------------------------------------------------
	# CALLBACKS FOR BUILDING DOCUMENT COMPONENTS
	#--------------------------------------------------------------------------------------------

	# noinspection PyMethodMayBeStatic
	def __callback_building_function_selector(self, x : Any) -> bool:
		return inspect.ismethod(x) and x.__name__.startswith('__build_callback_')

	def __internal_register_building_callbacks(self) -> dict[str,Any]:
		"""
		Register the different callbacks according to the current configuration. This method should not be called from outside the class.
		It is based on the method factory design pattern.
		:return: The callback functions.
		:rtype: dict[str,Any]
		"""
		if self.__build_callback_functions is None:
			defined_callbacks = inspect.getmembers(self, predicate=self.__callback_building_function_selector)
			self.__build_callback_functions = dict(defined_callbacks)
		assert self.__build_callback_functions is not None
		return self.__build_callback_functions

	def __internal_build_callback_funcname(self, file_type : FileType) -> str:
		"""
		Build the internal name of a building callback function.
		:param file_type: The type of file concerned by the callback.
		:type file_type: str
		:return: the name of the callback function.
		:rtype: str
		"""
		return '_' + self.__class__.__name__ + '__build_callback_' + file_type.name

	# noinspection PyUnusedLocal
	def __build_callback_bbl(self, root_file : str, input_file : FileDescription) -> bool:
		"""
		Generate BBL (BibTeX) file. This function runs run_bibtex().
		:param root_file: Name of the root file (tex document).
		:type root_file: str
		:param input_file: Description of the input TeX file.
		:type input_file: FileDescription
		:return: The continuation status, i.e. True if the build could continue.
		:rtype: bool
		"""
		if self.__configuration.generation.is_biblio_enable:
			# Ensure Biber configuration
			self.__configuration.generation.is_biber = input_file.use_biber
			error = self.run_bibtex(input_file.input_filename)
			if error:
				extlogging.multiline_error(error['message'])
				return False
		return True

	# noinspection PyBroadException,PyUnusedLocal,DuplicatedCode
	def __build_callback_ind(self, root_file : str, input_file : FileDescription) -> bool | None:
		"""
		Generate IND (index) file. This function invokes run_latex() (without loop support) if xindyis used or
		if the idx file is less recent than the input TeX file. Then, the function calls run_makeindex().
		:param root_file: Name of the root file (tex document).
		:type root_file: str
		:param input_file: Description of the input TeX file.
		:type input_file: FileDescription
		:return: The continuation status, i.e. True if the build could continue.
		:rtype: bool
		"""
		if self.__configuration.generation.is_index_enable:
			# Ensure texindy configuration
			self.__configuration.generation.is_xindy_index = input_file.use_xindy

			is_run_latex = False
			if self.__configuration.generation.is_xindy_index:
				# Special case: TeX should be launched before running texindy
				is_run_latex = True
			else:
				tex_wo_ext = genutils.basename2(input_file.input_filename, *FileType.tex_extensions())
				idx_file = tex_wo_ext + FileType.idx.extension()
				input_change = genutils.get_file_last_change(input_file.input_filename)
				idx_change = genutils.get_file_last_change(idx_file)
				if AutoLaTeXMaker.__is_file_changed(input_change, idx_change):
					is_run_latex = True
			if is_run_latex:
				try:
					self.run_latex(filename=input_file.input_filename, loop=False)
				except:
					return False
			result = self.run_makeindex(input_file.input_filename)
			if result is not None:
				exit_code, sout, serr = result
				if exit_code != 0:
					message = (sout or '') + (serr or '')
					extlogging.multiline_error(T("Error when running the indexing tool: %s") % message)
					return False
		return True

	# noinspection PyUnusedLocal
	def __build_callback_gls(self, root_file : str, input_file : FileDescription) -> bool:
		"""
		Generate GLS (glossary) file. This function calls run_makeglossaries()
		This function invokes run_latex() (without loop support) before run_makeglossaries() if
		the GLO file is less recent than the input file.
		:param root_file: Name of the root file (tex document).
		:type root_file: str
		:param input_file: Description of the input TeX file.
		:type input_file: FileDescription
		:return: The continuation status, i.e. True if the build could continue.
		:rtype: bool
		"""
		if self.__configuration.generation.is_glossary_enable:
			tex_wo_ext = genutils.basename2(input_file.input_filename, *FileType.tex_extensions())
			glo_file = tex_wo_ext + FileType.glo.extension()
			input_change = genutils.get_file_last_change(input_file.input_filename)
			glo_change = genutils.get_file_last_change(glo_file)
			if AutoLaTeXMaker.__is_file_changed(input_change, glo_change) and not self.run_latex(input_file.input_filename, loop=False):
				return False
			return self.run_makeglossaries(input_file.input_filename)
		return True

	# noinspection PyBroadException,PyUnusedLocal
	def __build_callback_pdf(self, root_file : str, input_file : FileDescription) -> bool:
		"""
		Generate root PDF file. This function invokes run_latex() with loop support enabled.
		:param root_file: Name of the root file (tex document).
		:type root_file: str
		:param input_file: Description of the input TeX file.
		:type input_file: FileDescription
		:return: The continuation status, i.e. True if the build could continue.
		:rtype: bool
		"""
		try:
			self.run_latex(filename=input_file.input_filename,
						   loop=True,
						   extra_run_support=input_file.use_multibib)
		except:
			return False
		return True

