# autolatex - Make.pm # Copyright (C) 2013-2016 Stephane Galland # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. =pod =head1 NAME Make.pm - Make-like Tools =head1 DESCRIPTION Provides tools that are close to the Make tool. To use this library, type C. =head1 GETTING STARTED =head2 Initialization To create a Make tool, say something like this: use AutoLaTeX::Make::Make; my $make = AutoLaTeX::Make::Make->new($configuration) ; ...or something similar. Acceptable parameters to the constructor are: =over =item * C is an associative array that contains all the configuration of AutoLaTeX. =back =head1 METHOD DESCRIPTIONS This section contains only the methods in TeXParser.pm itself. =over =cut package AutoLaTeX::Make::Make; our @ISA = qw( Exporter ); our @EXPORT = qw( ); our @EXPORT_OK = qw(); require 5.014; use strict; use utf8; use vars qw(@ISA @EXPORT @EXPORT_OK $VERSION); use Exporter; use Class::Struct; use File::Basename; use File::Spec; use Carp; use AutoLaTeX::Core::Util; use AutoLaTeX::Core::IntUtils; use AutoLaTeX::Core::Config; use AutoLaTeX::Core::OS; use AutoLaTeX::Core::Progress; use AutoLaTeX::TeX::BibCitationAnalyzer; use AutoLaTeX::TeX::TeXDependencyAnalyzer; use AutoLaTeX::TeX::IndexAnalyzer; use AutoLaTeX::TeX::GlossaryAnalyzer; our $VERSION = '34.0'; my $EXTENDED_WARNING_CODE = <<'ENDOFTEX'; %************************************************************* % 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 %************************************************************* ENDOFTEX my %COMMAND_DEFINITIONS = ( 'pdflatex' => { 'cmd' => 'pdflatex', 'flags' => ['-halt-on-error', '-interaction', 'batchmode', '-file-line-error'], 'to_dvi' => ['-output-format=dvi'], 'to_ps' => undef, 'to_pdf' => ['-output-format=pdf'], 'synctex' => '-synctex=1', 'jobname' => '-jobname', 'ewarnings' => $EXTENDED_WARNING_CODE, }, 'latex' => { 'cmd' => 'latex', 'flags' => ['-halt-on-error', '-interaction', 'batchmode', '-file-line-error'], 'to_dvi' => ['-output-format=dvi'], 'to_ps' => undef, 'to_pdf' => ['-output-format=pdf'], 'synctex' => '-synctex=1', 'jobname' => '-jobname', 'ewarnings' => $EXTENDED_WARNING_CODE, }, 'xelatex' => { 'cmd' => 'xelatex', 'flags' => ['-halt-on-error', '-interaction', 'batchmode', '-file-line-error'], 'to_dvi' => ['-no-pdf'], 'to_ps' => undef, 'to_pdf' => [], 'synctex' => '-synctex=1', 'jobname' => '-jobname', 'ewarnings' => $EXTENDED_WARNING_CODE, }, 'lualatex' => { 'cmd' => 'lualatex', 'flags' => ['-halt-on-error', '-interaction', 'batchmode', '-file-line-error'], 'to_dvi' => ['-output-format=dvi'], 'to_ps' => undef, 'to_pdf' => ['-output-format=pdf'], 'synctex' => '-synctex=1', 'jobname' => '-jobname', 'ewarnings' => $EXTENDED_WARNING_CODE, }, 'bibtex' => { 'cmd' => 'bibtex', 'flags' => [], }, 'biber' => { 'cmd' => 'biber', 'flags' => [], }, 'makeindex' => { 'cmd' => 'makeindex', 'flags' => [], }, 'makeglossaries' => { 'cmd' => 'makeglossaries', 'flags' => [], }, 'dvi2ps' => { 'cmd' => 'dvips', 'flags' => [], }, ); struct( Entry => [ 'file' => '$', 'go_up' => '$', 'rebuild' => '$', 'parent' => '$', ] ); sub newEntry($$) { my $e = Entry->new; @$e = ($_[0],0,0,$_[1]); return $e; } #------------------------------------------------------ # # Constructor # #------------------------------------------------------ sub new(\%) : method { my $proto = shift; my $class = ref($proto) || $proto; my $parent = ref($proto) && $proto ; my $self ; if ( $parent ) { %{$self} = %{$parent} ; } else { $self = { 'configuration' => $_[0], 'files' => {}, 'rootFiles' => [], 'is_extended_warning_enable' => 0, 'is_biblio_enable' => 1, 'is_makeindex_enable' => 1, 'is_makeglossaries_enable' => 1, 'warning_level' => 1, 'generation_type' => 'pdf', 'latex_cmd' => [], 'bibtex_cmd' => [], 'biber_cmd' => [], 'makeindex_cmd' => [], 'makeglossaries_cmd' => [], 'dvi2ps_cmd' => [], }; } bless( $self, $class ); # # Build the different commands according to the current configuration # $self->{'type'} = $_[0]->{'generation.generation type'} || 'pdf'; my $compiler = $_[0]->{'generation.tex compiler'} || 'pdflatex'; $self->{'compiler_definition'} = $COMMAND_DEFINITIONS{"$compiler"}; my $def = $self->{'compiler_definition'}; confess("No command definition for '$compiler'") unless ($def); # LaTeX if ($_[0]->{'generation.latex_cmd'}) { push @{$self->{'latex_cmd'}}, $_[0]->{'generation.latex_cmd'}; } else { push @{$self->{'latex_cmd'}}, $def->{'cmd'}, @{$def->{'flags'}}; confess("No command definition for '$compiler/".$self->{'type'}."'") unless (exists $def->{'to_'.$self->{'type'}}); # Support of SyncTeX if (cfgBoolean($_[0]->{'generation.synctex'}) && $def->{'synctex'}) { push @{$self->{'latex_cmd'}}, $def->{'synctex'}; } my $target = $def->{'to_'.$self->{'type'}}; if (defined($target)) { push @{$self->{'latex_cmd'}}, @{$target}; } elsif ($self->{'type'} eq 'ps') { push @{$self->{'latex_cmd'}}, @{$def->{'to_dvi'}}; } else { confess('invalided Maker state: cannot find the command line to compile TeX files.'); } } if ($_[0]->{'generation.latex_flags'}) { my @params = split(/\s+/, ($_[0]->{'generation.latex_flags'})); push @{$self->{'latex_cmd'}}, @params; } # Change the warning level if (defined($_[0]->{'__private__'}{'CLI.warning level'})) { $self->{'warning_level'} = int($_[0]->{'__private__'}{'CLI.warning level'}); } # Support of extended warnings if (($_[0]->{'__private__'}{'CLI.is extended tex warnings'} || ($self->{'warning_level'}>1)) && $def->{'ewarnings'}) { my $code = $def->{'ewarnings'} || ''; $code =~ s/^\s+//gm; $code =~ s/\s+$//gm; my $s = - countLinesIn($code); $code =~ s/\Q::::AUTOLATEXHEADERSIZE::::\E/$s/sg; $self->{'latex_warning_code'} = $code; $self->{'is_extended_warning_enable'} = 1; } # BibTeX if ($_[0]->{'generation.bibtex_cmd'}) { push @{$self->{'bibtex_cmd'}}, $_[0]->{'generation.bibtex_cmd'}; } else { $def = $COMMAND_DEFINITIONS{'bibtex'}; confess("No command definition for 'bibtex'") unless ($def); push @{$self->{'bibtex_cmd'}}, $def->{'cmd'}, @{$def->{'flags'}}; } if ($_[0]->{'generation.bibtex_flags'}) { my @params = split(/\s+/, ($_[0]->{'generation.bibtex_flags'})); push @{$self->{'bibtex_cmd'}}, @params; } # Biber if ($_[0]->{'generation.biber_cmd'}) { push @{$self->{'biber_cmd'}}, $_[0]->{'generation.biber_cmd'}; } else { $def = $COMMAND_DEFINITIONS{'biber'}; confess("No command definition for 'biber'") unless ($def); push @{$self->{'biber_cmd'}}, $def->{'cmd'}, @{$def->{'flags'}}; } if ($_[0]->{'generation.biber_flags'}) { my @params = split(/\s+/, ($_[0]->{'generation.biber_flags'})); push @{$self->{'biber_cmd'}}, @params; } # MakeIndex if ($_[0]->{'generation.makeindex_cmd'}) { push @{$self->{'makeindex_cmd'}}, $_[0]->{'generation.makeindex_cmd'}; } else { $def = $COMMAND_DEFINITIONS{'makeindex'}; confess("No command definition for 'makeindex'") unless ($def); push @{$self->{'makeindex_cmd'}}, $def->{'cmd'}, @{$def->{'flags'}}; } if ($_[0]->{'generation.makeindex_flags'}) { my @params = split(/\s+/, ($_[0]->{'generation.makeindex_flags'})); push @{$self->{'makeindex_cmd'}}, @params; } # MakeGlossaries if ($_[0]->{'generation.makeglossaries_cmd'}) { push @{$self->{'makeglossaries_cmd'}}, $_[0]->{'generation.makeglossaries_cmd'}; } else { $def = $COMMAND_DEFINITIONS{'makeglossaries'}; confess("No command definition for 'makeglossaries'") unless ($def); push @{$self->{'makeglossaries_cmd'}}, $def->{'cmd'}, @{$def->{'flags'}}; } if ($_[0]->{'generation.makeglossaries_flags'}) { my @params = split(/\s+/, ($_[0]->{'generation.makeglossaries_flags'})); push @{$self->{'makeglossaries_cmd'}}, @params; } # dvi2ps if ($_[0]->{'generation.dvi2ps_cmd'}) { push @{$self->{'dvi2ps_cmd'}}, $_[0]->{'generation.dvi2ps_cmd'}; } else { $def = $COMMAND_DEFINITIONS{'dvi2ps'}; confess("No command definition for 'dvi2ps'") unless ($def); push @{$self->{'dvi2ps_cmd'}}, $def->{'cmd'}, @{$def->{'flags'}}; } if ($_[0]->{'generation.dvi2ps_flags'}) { my @params = split(/\s+/, ($_[0]->{'generation.dvi2ps_flags'})); push @{$self->{'dvi2ps_cmd'}}, @params; } return $self; } =pod =item * makeRelativePath($) Make the path relative to the current directory. =cut sub makeRelativePath($) : method { my $self = shift; my $relativePath = File::Spec->abs2rel($_[0]); return $relativePath; } =pod =item * reset() Reset this make tool. =cut sub reset() : method { my $self = shift; $self->{'files'} = {}; $self->{'rootFiles'} = []; return undef; } =pod =item * addTeXFile($) Add the given TeX file into the building process. Takes 1 arg: =over =item * file (string) is the name of the TeX file to read. =back =cut sub addTeXFile($) : method { my $self = shift; my $rootfile = shift; $rootfile = File::Spec->rel2abs($rootfile); my $rootbasename = basename($rootfile, '.tex'); my $roottemplate = File::Spec->catfile(dirname($rootfile), "$rootbasename"); my $pdfFile = "$roottemplate.pdf"; $self->{'files'}{$pdfFile} = { 'type' => 'pdf', 'dependencies' => { $rootfile => undef }, 'change' => lastFileChange($pdfFile), 'mainFile' => $rootfile, }; push @{$self->{'rootFiles'}}, "$pdfFile"; return undef; } sub _computeDependenciesForRootFile($) : method { my $self = shift; my $pdfFile = shift; my $rootfile = $self->{'files'}{$pdfFile}{'mainFile'}; my $rootdir = dirname($rootfile); my $rootbasename = basename($rootfile, '.tex'); my $roottemplate = File::Spec->catfile(dirname($rootfile), "$rootbasename"); my @files = ( $rootfile ); while (@files) { my $file = shift @files; printDbgFor(2, formatText(_T("Parsing '{}'"), $file)); if (-f "$file" ) { printDbgIndent(); printDbgFor(3, formatText(_T("Adding file '{}'"), removePathPrefix($rootdir,$file))); $self->{'files'}{$file} = { 'type' => 'tex', 'dependencies' => {}, 'change' => lastFileChange($file), }; my %deps = getDependenciesOfTeX($file,$rootdir); if (%deps) { my $dir = dirname($file); # # INCLUDED FILES # foreach my $cat ('tex', 'sty', 'cls') { if ($deps{$cat}) { foreach my $dpath (@{$deps{$cat}}) { if (!File::Spec->file_name_is_absolute($dpath)) { $dpath = File::Spec->catfile($dir, $dpath); } if ($dpath !~ /\.$cat/) { $dpath .= ".$cat"; } printDbgFor(3, formatText(_T("Adding file '{}'"), removePathPrefix($rootdir,$dpath))); $self->{'files'}{$dpath} = { 'type' => $cat, 'dependencies' => {}, 'change' => lastFileChange($dpath), }; $self->{'files'}{$pdfFile}{'dependencies'}{$dpath} = undef; if ($cat eq 'tex') { push @files, $dpath; } } } } # # BIBLIOGRAPHY CALLED FROM THE TEX # if ($deps{'biblio'}) { while (my ($bibdb,$bibdt) = each(%{$deps{'biblio'}})) { my $dir = dirname($file); if ($rootdir ne $dir) { $bibdb = $rootbasename; } my $bblfile = File::Spec->catfile("$rootdir", "$bibdb.bbl"); printDbgFor(3, formatText(_T("Adding file '{}'"), removePathPrefix($rootdir,$bblfile))); $self->{'files'}{"$bblfile"} = { 'type' => 'bbl', 'dependencies' => {}, 'change' => lastFileChange("$bblfile"), 'use_biber' => $deps{'biber'}, }; $self->{'files'}{$pdfFile}{'dependencies'}{$bblfile} = undef; foreach my $cat ('bib', 'bst', 'bbc', 'cbx') { if ($bibdt->{$cat}) { foreach my $dpath (@{$bibdt->{$cat}}) { if (!File::Spec->file_name_is_absolute($dpath)) { $dpath = File::Spec->catfile("$rootdir", $dpath); } if ($dpath !~ /\.$cat/) { $dpath .= ".$cat"; } printDbgFor(3, formatText(_T("Adding file '{}'"), removePathPrefix($rootdir,$dpath))); $self->{'files'}{$dpath} = { 'type' => $cat, 'dependencies' => {}, 'change' => lastFileChange($dpath), }; $self->{'files'}{"$bblfile"}{'dependencies'}{$dpath} = undef; } } } } } # # INDEX # if ($deps{'idx'}) { for my $idxdep (@{$deps{'idx'}}) { my $idxbasefilename; if ($idxdep) { $idxbasefilename = "$idxdep"; } else { $idxbasefilename = "$roottemplate"; } my $idxfile = "$idxbasefilename.idx"; printDbgFor(3, formatText(_T("Adding file '{}'"), removePathPrefix($rootdir,$idxfile))); $self->{'files'}{"$idxfile"} = { 'type' => 'idx', 'dependencies' => {}, 'change' => lastFileChange("$idxfile"), }; my $indfile = "$idxbasefilename.ind"; printDbgFor(3, formatText(_T("Adding file '{}'"), removePathPrefix($rootdir,$indfile))); $self->{'files'}{"$indfile"} = { 'type' => 'ind', 'dependencies' => { $idxfile => undef }, 'change' => lastFileChange("$indfile"), }; $self->{'files'}{$pdfFile}{'dependencies'}{$indfile} = undef; } } # # GLOSSARIES # if ($deps{'glo'}) { my $glofile = "$roottemplate.glo"; printDbgFor(3, formatText(_T("Adding file '{}'"), removePathPrefix($rootdir,$glofile))); $self->{'files'}{"$glofile"} = { 'type' => 'glo', 'dependencies' => {}, 'change' => lastFileChange("$glofile"), }; my $glsfile = "$roottemplate.gls"; printDbgFor(3, formatText(_T("Adding file '{}'"), removePathPrefix($rootdir,$glsfile))); $self->{'files'}{"$glsfile"} = { 'type' => 'gls', 'dependencies' => { $glofile => undef }, 'change' => lastFileChange("$glsfile"), }; $self->{'files'}{$pdfFile}{'dependencies'}{$glsfile} = undef; } } printDbgUnindent(); } } printDbgFor(2, formatText(_T("Parsing auxiliary files"))); printDbgIndent(); # # BIBLIOGRAPHY FROM INSIDE AUXILIARY FILES (MULTIBIB...) # local *DIR; opendir(*DIR, "$rootdir") or printErr("$rootdir: $!"); while (my $dir = readdir(*DIR)) { if ((!isIgnorableDirectory($dir)) && $dir =~ /^(.+?)\.aux$/) { my $bibdb = "$1"; if ($bibdb ne "$rootbasename") { my $auxfile = File::Spec->catfile("$rootdir", "$dir"); my %data = getAuxBibliographyData("$auxfile"); if ($data{'databases'} || $data{'styles'}) { my $bblfile = File::Spec->catfile("$rootdir", "$bibdb.bbl"); printDbgFor(3, formatText(_T("Adding file '{}'"), removePathPrefix($rootdir,$bblfile))); $self->{'files'}{"$bblfile"} = { 'type' => 'bbl', 'dependencies' => {}, 'change' => lastFileChange("$bblfile"), }; $self->{'files'}{$pdfFile}{'dependencies'}{$bblfile} = undef; if ($data{'styles'}) { foreach my $style (@{$data{'styles'}}) { my $bstfile = File::Spec->catfile("$rootdir", "$style.bst"); if (-r "$bstfile") { printDbgFor(3, formatText(_T("Adding file '{}'"), removePathPrefix($rootdir,$bstfile))); $self->{'files'}{"$bstfile"} = { 'type' => 'bst', 'dependencies' => {}, 'change' => lastFileChange("$bstfile"), }; $self->{'files'}{$bblfile}{'dependencies'}{$bstfile} = undef; } } } if ($data{'databases'}) { foreach my $db (@{$data{'databases'}}) { my $bibfile = File::Spec->catfile("$rootdir", "$db.bib"); if (-r "$bibfile") { printDbgFor(3, formatText(_T("Adding file '{}'"), removePathPrefix($rootdir,$bibfile))); $self->{'files'}{"$bibfile"} = { 'type' => 'bib', 'dependencies' => {}, 'change' => lastFileChange("$bibfile"), }; $self->{'files'}{$bblfile}{'dependencies'}{$bibfile} = undef; } } } } } } } closedir(*DIR); printDbgUnindent(); return undef; } =pod =item * runLaTeX() Launch pdfLaTeX once time. =over 4 =item * C is the name of the PDF or the TeX file to compile. =item * C (optional boolean) indicates if this function may loop on the LaTeX compilation when it is requested by the LaTeX tool. =item * C (optional boolean) indicates if the warnings are buffered or not. =back =cut sub runLaTeX($;$$) : method { my $self = shift; my $file = shift; my $linenumber = 0; my $enableLoop = shift; my $buffering_warnings = shift; if ($self->{'files'}{$file}{'mainFile'}) { $file = $self->{'files'}{$file}{'mainFile'}; } my $logFile = File::Spec->catfile(dirname($file), basename($file, '.tex').'.log'); my $minNumberOfLaunchs = $self->{'configuration'}{'generation.post compilation runs'} || 1; my $numberOfRuns = 0; my $continueToCompile; do { printDbg(formatText(_T('{}: {}'), 'PDFLATEX', basename($file))); $continueToCompile = 0; $self->{'buffered_warnings'} = []; $self->{'warnings'} = {}; unlink($logFile); my $exitcode; if ($self->{'is_extended_warning_enable'}) { local *OUTFILE; open(*OUTFILE, ">autolatex_autogenerated.tex") or printErr("autolatex_ewarnings.tex: $!"); my $code = $self->{'latex_warning_code'}; $code =~ s/\Q::::REALFILENAME::::\E/$file/sg; print OUTFILE $code."\n"; print OUTFILE readFileLines($file); close(*OUTFILE); $exitcode = runCommandSilently( @{$self->{'latex_cmd'}}, $self->{'compiler_definition'}{'jobname'}, basename($file, '.tex'), 'autolatex_autogenerated.tex'); unlink('autolatex_autogenerated.tex') if ($exitcode==0); } else { $exitcode = runCommandSilently(@{$self->{'latex_cmd'}}, $self->makeRelativePath($file)); } local *LOGFILE; if ($exitcode!=0) { printDbg(formatText(_T("{}: Error when processing {}"), 'PDFLATEX', basename($file))); # Parse the log to extract the blocks of messages my $line; my $fatal_error = undef; my @log_blocks = (); my $current_log_block = ''; my $re_fatal_error = "\Q==>\E\\s*f\\s*a\\s*t\\s*a\\s*l\\s+e\\s*r\\s*r\\s*o\\s*r"; open(*LOGFILE, "< $logFile") or printErr("$logFile: $!"); while (*LOGFILE && ($line = ) && !$fatal_error) { my $is_empty_line = (!$line || $line =~/^\s*$/); if ($is_empty_line) { # Empty line => break the block if ($current_log_block) { if ($current_log_block =~ /^(.+):([0-9]+):/m) { if ($current_log_block =~ /^\!\s*$re_fatal_error/si) { $fatal_error = "\Q!\E[^!]"; } elsif ($current_log_block =~ /^(.+:[0-9]+:)\s*$re_fatal_error/si) { $fatal_error = $1; } else { push @log_blocks, $current_log_block; } } elsif ($current_log_block =~ /^\!/m) { push @log_blocks, $current_log_block; } } $current_log_block = ''; } else { $current_log_block .= $line; } } close(*LOGFILE); if ($current_log_block) { if ($current_log_block =~ /^.+:[0-9]+:/m) { if ($current_log_block =~ /^\!\s*$re_fatal_error/si) { $fatal_error = "\Q!\E[^!]"; } elsif ($current_log_block =~ /^(.+:[0-9]+:)\s*$re_fatal_error/si) { $fatal_error = $1; } else { push @log_blocks, $current_log_block; } } elsif ($current_log_block =~ /^\!/m) { if ($current_log_block =~ /^\!\s*$re_fatal_error/si) { $fatal_error = "\Q!\E[^!]"; } else { push @log_blocks, $current_log_block; } } } # Search the fatal error inside the blocks my $extracted_message = ''; if ($fatal_error) { # Parse the fatal error block to extract the filename # where the error occured if ($fatal_error =~ /^(.+?)\:([0-9]+)\:$/s) { my ($candidate, $post) = ($1,$2); my @candidates = split(/[\n\r]+/, $candidate); $candidate = pop @candidates; my $candidate_pattern = "\Q$candidate\E"; while ($candidate && @candidates && ! -f "$candidate") { my $l = pop @candidates; $candidate_pattern = "\Q$l\E[\n\r]+$candidate_pattern"; $candidate = $l.$candidate; } if ($candidate && -e "$candidate") { $linenumber = int($post); # Search the error message in the log. $candidate_pattern .= "\Q:$post:\E"; # Filtering the 'autogenerated' file if ($self->{'is_extended_warning_enable'} && basename($candidate) eq 'autolatex_autogenerated.tex') { my $code = $self->{'latex_warning_code'}; $candidate = $file; $linenumber -= countLinesIn($code); } my $i = 0; while (!$extracted_message && $i<@log_blocks) { my $block = $log_blocks[$i]; if ($block =~ /$candidate_pattern(.*)$/s) { my $message_text = $1 || ''; $extracted_message = trim($candidate.':'.$linenumber.':'.$message_text); } $i++; } if ($extracted_message) { if (int($post)!=$linenumber) { $extracted_message =~ s/^\Ql.$post\E/l.$linenumber/gm; } # Do not cut the words with carriage returns $extracted_message =~ s/([a-z])[\n\r\f]([a-z])/$1$2/sgi; $extracted_message =~ s/([a-z]) [\n\r\f]([a-z])/$1 $2/sgi; $extracted_message =~ s/([a-z])[\n\r\f] ([a-z])/$1 $2/sgi; } } } else { # Search the error message in the log. my $candidate_pattern .= "$fatal_error"; my $i = 0; while (!$extracted_message && $i<@log_blocks) { my $block = $log_blocks[$i]; if ($block =~ /(?:^|\n|\r)$candidate_pattern\s*(.*)$/s) { my $message = $1; $linenumber = 0; if ($message =~ /line\s+([0-9]+)/i) { $linenumber = int($1); } $extracted_message = trim("$file:$linenumber: $message"); } $i++; } } } # Display the message if ($extracted_message) { # Test if the message is an emergency stop if ($extracted_message =~ /^.*?:[0-9]+:\s*emergency\s+stop\./i) { foreach my $block (@log_blocks) { if ($block =~ /^\s*!\s*(.*?)\s*$/s) { my $errmsg = "$1"; $extracted_message .= "\n$errmsg"; } } } printDbg(formatText(_T("{}: The first error found in the log file is:"), 'PDFLATEX')); print STDERR "$extracted_message\n"; printDbg(formatText(_T("{}: End of error log."), 'PDFLATEX')); } else { print STDERR (formatText(_T("{}: Unable to extract the error from the log. Please read the log file."), 'PDFLATEX'))."\n"; } exit(255); } elsif ($enableLoop) { $numberOfRuns ++; if ($numberOfRuns < $minNumberOfLaunchs) { # Force a new run of the LaTeX tool. printDbg(formatText(_T('{}: Forcing a new launch to reach {} on {}'), 'PDFLATEX', ($numberOfRuns + 1), $minNumberOfLaunchs)); $continueToCompile = 1; } else { ($continueToCompile,$enableLoop) = $self->_testLaTeXWarningInFile( $logFile,$continueToCompile,$enableLoop); } } } while ($continueToCompile); if (!$buffering_warnings && $self->{'buffered_warnings'}) { foreach my $w (@{$self->{'buffered_warnings'}}) { print STDERR "$w"; } $self->{'buffered_warnings'} = []; } return 0; } sub _printWarning($$$$) : method { my $self = shift; my $filename = shift; my $extension = shift; my $line = shift; my $message = shift; if ($message =~ /^\s*latex\s+warning\s*\:\s*(.*)$/i) { $message = "$1"; } if (!$self->{'buffered_warnings'}) { $self->{'buffered_warnings'} = []; } push @{$self->{'buffered_warnings'}}, "$filename:$line:warning: $message\n"; } sub _testLaTeXWarningInFile($$$) : method { my $self = shift; my $logFile = shift; my $continueToCompile = shift; my $enableLoop = shift; my $line; my $warning = 0; open(*LOGFILE, "< $logFile") or printErr("$logFile: $!"); my $lastline = ''; my $current_log_block = ''; while (!$continueToCompile && ($line = )) { $lastline .= $line; if ($lastline =~ /\.\s*$/) { if ($self->_testLaTeXWarningOn($lastline)) { $continueToCompile = $enableLoop; } $lastline = ''; } # Parse and output the detailled warning messages if ($self->{'warning_level'}>1) { if ($warning) { if ($line =~ /^\Q!!!![EndWarning]\E/) { if ($current_log_block =~ /^(.*?):([^:]*):([0-9]+):\s*(.*?)\s*$/) { my ($filename, $extension, $line, $message) = ($1, $2, $3, $4); $self->_printWarning($filename, $extension, $line, $message); } $warning = 0; $current_log_block = ''; } else { my $l = $line; if ($l !~ /\.\n+$/) { $l =~ s/\s+//; } $current_log_block .= $l; } } elsif ($line =~ /^\Q!!!![BeginWarning]\E(.*)$/) { my $l = "$1"; if ($l !~ /\.\n+$/) { $l =~ s/\s+//; } $current_log_block = $l; $warning = 1; } } } if ($lastline =~ /\.\s*$/ && $self->_testLaTeXWarningOn($lastline)) { $continueToCompile = $enableLoop; } close(*LOGFILE); # Output the detailled wanring message that was not already output if ($warning && $current_log_block) { if ($current_log_block =~ /^(.*?):([^:]*):([0-9]+):\s*(.*?)\s*$/) { my ($filename, $extension, $line, $message) = ($1, $2, $3, $4); $self->_printWarning($filename, $extension, $line, $message); } } $self->{'warnings'}{'done'} = 1; return ($continueToCompile,$enableLoop); } sub _testLaTeXWarningOn($) : method { my $self = shift; my $line = shift; my $oline = "$line"; $line =~ s/[\n\r\t \f]+//g; if ($line =~ /Warning.*re\-?run\s/i) { return 1; } elsif ($line =~ /Warning:Therewereundefinedreferences/i) { $self->{'warnings'}{'undefined_reference'} = 1; } elsif ($line =~ /Warning:Citation.+undefined/i) { $self->{'warnings'}{'undefined_citation'} = 1; } elsif ($line =~ /Warning:Thereweremultiply\-definedlabels/i) { $self->{'warnings'}{'multiple_definition'} = 1; } elsif ($line =~ /(?:\s|^)Warning/im) { $self->{'warnings'}{'other_warning'} = 1; } return 0; } =pod =item * build($) Build all the root files. =over 4 =item B (optional) is the progress indicator to use. =back =cut sub build(;$) : method { my $self = shift; my $progress = shift; my $progValue; if ($progress) { my $numberOfRootFiles = @{$self->{'rootFiles'}}; $progress->setMax($numberOfRootFiles*100); $progValue = 0; } foreach my $rootFile (@{$self->{'rootFiles'}}) { my $sprogress = undef; if ($progress) { $progress->setComment(formatText(_T("Generating {}"), basename($rootFile))); $sprogress = $progress->subProgress(100); $sprogress->setMax(1000); } # Read building stamps $self->_readBuildStamps($rootFile); $sprogress->setValue(10) if ($sprogress); # Launch at least one LaTeX compilation $self->runLaTeX($rootFile,0,1); $sprogress->setValue(210) if ($sprogress); # Compute the dependencies of the file $self->_computeDependenciesForRootFile($rootFile); $sprogress->setValue(260) if ($sprogress); # Construct the build list and launch the required builds my @builds = $self->_buildExecutionList("$rootFile"); $sprogress->setValue(310) if ($sprogress); # Build the files if (@builds) { my $sprogStep = 600 / @builds; foreach my $file (@builds) { if ($sprogress) { $sprogress->setComment(formatText(_T("Compiling {}"), basename($file))); } $self->_build($rootFile, $file); $sprogress->increment($sprogStep) if ($sprogress); } } # Output the warnings from the last TeX builds if ($self->{'buffered_warnings'}) { foreach my $w (@{$self->{'buffered_warnings'}}) { print STDERR "$w"; } $self->{'buffered_warnings'} = []; } $sprogress->setValue(910) if ($sprogress); # Write building stamps $self->_writeBuildStamps($rootFile); # Generate the Postscript file when requested if (($self->{'configuration'}{'generation.generation type'}||'pdf') eq 'ps') { my $dirname = dirname($rootFile); my $basename = basename($rootFile, '.pdf', '.ps', '.dvi', '.xdv'); my $dviFile = File::Spec->catfile($dirname, $basename.'.dvi'); my $dviDate = lastFileChange("$dviFile"); if (defined($dviDate)) { my $psFile = File::Spec->catfile($dirname, $basename.'.ps'); my $psDate = lastFileChange("$psFile"); if (!$psDate || ($dviDate>=$psDate)) { if ($sprogress) { $sprogress->setComment(formatText(_T("Generating {}"), basename($psFile))); } printDbg(formatText(_T('{}: {}'), 'DVI2PS', basename($dviFile))); runCommandOrFail(@{$self->{'dvi2ps_cmd'}}, $self->makeRelativePath($dviFile)); } } } if ($sprogress) { $sprogress->setComment(formatText(_T("Analyzing logs for {}"), basename($rootFile))); } # Compute the log filename my $texFile = $self->{'files'}{$rootFile}{'mainFile'}; my $logFile = File::Spec->catfile(dirname($texFile), basename($texFile, '.tex').'.log'); # Detect warnings if not already done if (!%{$self->{'warnings'}}) { $self->_testLaTeXWarningInFile($logFile, 0, 0); } # Output the last LaTeX warning indicators. if ($self->{'warning_level'}>0) { if ($self->{'warnings'}{'multiple_definition'}) { my $s = _T("LaTeX Warning: There were multiply-defined labels.\n"); if ($self->{'is_extended_warning_enable'}) { print STDERR "!!$logFile:W1: $s"; } else { print STDERR "$s"; } } if ($self->{'warnings'}{'undefined_reference'}) { my $s = _T("LaTeX Warning: There were undefined references.\n"); if ($self->{'is_extended_warning_enable'}) { print STDERR "!!$logFile:W2: $s"; } else { print STDERR "$s"; } } if ($self->{'warnings'}{'undefined_citation'}) { my $s = _T("LaTeX Warning: There were undefined citations.\n"); if ($self->{'is_extended_warning_enable'}) { print STDERR "!!$logFile:W3: $s"; } else { print STDERR "$s"; } } if ($self->{'warnings'}{'other_warning'}) { my $texFile = $rootFile; if ($self->{'files'}{$rootFile}{'mainFile'}) { $texFile = $self->{'files'}{$rootFile}{'mainFile'}; } print STDERR formatText(_T("LaTeX Warning: Please look inside {} for the other the warning messages.\n"), basename($logFile)); } } if ($progress) { $progValue += 100; $progress->setValue($progValue); } } $progress->stop() if ($progress); return undef; } =pod =item * buildBiblio($) Launch the Biblio only. =over 4 =item B (optional) is the progress indicator to use. =back =cut sub buildBiblio(;$) : method { my $self = shift; my $progress = shift; my $progValue; if ($progress) { my $numberOfRootFiles = @{$self->{'rootFiles'}}; $progress->setMax($numberOfRootFiles*100); $progValue = 0; } foreach my $rootFile (@{$self->{'rootFiles'}}) { my $sprogress = undef; if ($progress) { $sprogress = $progress->subProgress(100); $sprogress->setMax(1000); } # Read building stamps $self->_readBuildStamps($rootFile); $sprogress->setValue(10) if ($sprogress); # Compute the dependencies of the file $self->_computeDependenciesForRootFile($rootFile); $sprogress->setValue(60) if ($sprogress); # Construct the build list and launch the required builds my @builds = $self->_buildExecutionList("$rootFile",1); $sprogress->setValue(110) if ($sprogress); if (@builds) { foreach my $file (@builds) { if (exists $self->{'files'}{$file}) { my $type = $self->{'files'}{$file}{'type'}; if ($type eq 'bbl') { my $func = $self->can('__build_'.lc($type)); if ($func) { $func->($self, $rootFile, $file, $self->{'files'}{$file}); } } } } } else { printDbgFor(2, formatText(_T('{} is up-to-date.'), basename($rootFile))); } $sprogress->setValue(990) if ($sprogress); # Write building stamps $self->_writeBuildStamps($rootFile); if ($progress) { $progValue += 100; $progress->setValue($progValue); } } $progress->stop() if ($progress); return undef; } =pod =item * buildMakeGlossaries($) Launch the MakeGlossaries only. =over 4 =item B (optional) is the progress indicator to use. =back =cut sub buildMakeGlossaries(;$) : method { my $self = shift; my $progress = shift; my $progValue; if ($progress) { my $numberOfRootFiles = @{$self->{'rootFiles'}}; $progress->setMax($numberOfRootFiles*100); $progValue = 0; } foreach my $rootFile (@{$self->{'rootFiles'}}) { my $sprogress = undef; if ($progress) { $sprogress = $progress->subProgress(100); $sprogress->setMax(1000); } # Read building stamps $self->_readBuildStamps($rootFile); $sprogress->setValue(10) if ($sprogress); # Compute the dependencies of the file $self->_computeDependenciesForRootFile($rootFile); $sprogress->setValue(60) if ($sprogress); # Construct the build list and launch the required builds my @builds = $self->_buildExecutionList("$rootFile",1); $sprogress->setValue(110) if ($sprogress); if (@builds) { foreach my $file (@builds) { if (exists $self->{'files'}{$file}) { my $type = $self->{'files'}{$file}{'type'}; if ($type eq 'gls') { my $func = $self->can('__build_'.lc($type)); if ($func) { $func->($self, $rootFile, $file, $self->{'files'}{$file}); return undef; } } } } } else { printDbgFor(2, formatText(_T('{} is up-to-date.'), basename($rootFile))); } $sprogress->setValue(990) if ($sprogress); # Write building stamps $self->_writeBuildStamps($rootFile); if ($progress) { $progValue += 100; $progress->setValue($progValue); } } $progress->stop() if ($progress); return undef; } =pod =item * buildMakeIndex($) Launch the MakeIndex only. =over 4 =item B (optional) is the progress indicator to use. =back =cut sub buildMakeIndex(;$) : method { my $self = shift; my $progress = shift; my $progValue; if ($progress) { my $numberOfRootFiles = @{$self->{'rootFiles'}}; $progress->setMax($numberOfRootFiles*100); $progValue = 0; } foreach my $rootFile (@{$self->{'rootFiles'}}) { my $sprogress = undef; if ($progress) { $sprogress = $progress->subProgress(100); $sprogress->setMax(1000); } # Read building stamps $self->_readBuildStamps($rootFile); $sprogress->setValue(10) if ($sprogress); # Compute the dependencies of the file $self->_computeDependenciesForRootFile($rootFile); $sprogress->setValue(60) if ($sprogress); # Construct the build list and launch the required builds my @builds = $self->_buildExecutionList("$rootFile",1); $sprogress->setValue(110) if ($sprogress); if (@builds) { foreach my $file (@builds) { if (exists $self->{'files'}{$file}) { my $type = $self->{'files'}{$file}{'type'}; if ($type eq 'ind') { my $func = $self->can('__build_'.lc($type)); if ($func) { $func->($self, $rootFile, $file, $self->{'files'}{$file}); return undef; } } } } } else { printDbgFor(2, formatText(_T('{} is up-to-date.'), basename($rootFile))); } $sprogress->setValue(990) if ($sprogress); # Write building stamps $self->_writeBuildStamps($rootFile); if ($progress) { $progValue += 100; $progress->setValue($progValue); } } $progress->stop() if ($progress); return undef; } # Read the building stamps. # This function puts the stamps in $self->{'stamps'}. # Parameter: # $_[0] = path to the root TeX file. # Result: nothing sub _readBuildStamps($) : method { my $self = shift; my $rootFile = shift; my $stampFile = File::Spec->catfile(dirname($rootFile), '.autolatex_stamp'); if (exists $self->{'stamps'}) { delete $self->{'stamps'}; } if (-r "$stampFile") { local *FILE; open(*FILE, "< $stampFile") or printErr("$stampFile: $!"); while (my $line = ) { if ($line =~ /^BIB\(([^)]+?)\)\:(.+)$/) { my ($k,$n) = ($1,$2); $self->{'stamps'}{'bib'}{$n} = $k; } if ($line =~ /^IDX\(([^)]+?)\)\:(.+)$/) { my ($k,$n) = ($1,$2); $self->{'stamps'}{'idx'}{$n} = $k; } if ($line =~ /^GLS\(([^)]+?)\)\:(.+)$/) { my ($k,$n) = ($1,$2); $self->{'stamps'}{'gls'}{$n} = $k; } } close(*FILE); } } # Write the building stamps. # This function gets the stamps from $self->{'stamps'}. # Parameter: # $_[0] = path to the root TeX file. # Result: nothing sub _writeBuildStamps($) : method { my $self = shift; my $rootFile = shift; my $stampFile = File::Spec->catfile(dirname($rootFile), '.autolatex_stamp'); local *FILE; open(*FILE, "> $stampFile") or printErr("$stampFile: $!"); if ($self->{'stamps'}{'bib'}) { while (my ($k,$v) = each(%{$self->{'stamps'}{'bib'}})) { print FILE "BIB($v):$k\n"; } } if ($self->{'stamps'}{'idx'}) { while (my ($k,$v) = each(%{$self->{'stamps'}{'idx'}})) { print FILE "IDX($v):$k\n"; } } if ($self->{'stamps'}{'gls'}) { while (my ($k,$v) = each(%{$self->{'stamps'}{'gls'}})) { print FILE "GLS($v):$k\n"; } } close(*FILE); } # Static function that is testing if the timestamp a is # more recent than the timestamp b. # Parameters: # $_[0] = a. # $_[1] = b. # Result: true if a is more recent than b, or not defined; # false otherwise. sub _a_more_recent_than_b($$) { my $a = shift; my $b = shift; return (!defined($a) || (defined($b) && $a>$b)); } # Test if the specified file is needing to be rebuild. # Parameters: # $_[0] = timestamp of the root file. # $_[1] = filename of the file to test. # $_[2] = parent element of the file, of type Entry. # $_[3] = is the description of the file to test. # Result: true if the file is needing to be rebuild, # false if the file is up-to-date. sub _need_rebuild($$$$) : method { my $self = shift; my $rootchange = shift; my $filename = shift; my $parent = shift; my $file = shift; if (!defined($file->{'change'}) || (!-f "$filename")) { return 1; } if ($filename =~ /(\.[^.]+)$/) { my $ext = $1; if ($ext eq '.bbl') { if ($file->{'use_biber'}) { # Parse the BCF file to detect the citations my $bcfFile = File::Spec->catfile(dirname($filename), basename($filename, '.bbl').'.bcf'); my $currentMd5 = makeBcfBibliographyCitationMd5($bcfFile) || ''; my $oldMd5 = $self->{'stamps'}{'bib'}{$bcfFile} || ''; if ($currentMd5 ne $oldMd5) { $self->{'stamps'}{'bib'}{$bcfFile} = $currentMd5; return 1; } } else { # Parse the AUX file to detect the citations my $auxFile = File::Spec->catfile(dirname($filename), basename($filename, '.bbl').'.aux'); my $currentMd5 = makeAuxBibliographyCitationMd5($auxFile) || ''; my $oldMd5 = $self->{'stamps'}{'bib'}{$auxFile} || ''; if ($currentMd5 ne $oldMd5) { $self->{'stamps'}{'bib'}{$auxFile} = $currentMd5; return 1; } } return 0; } elsif ($ext eq '.ind') { # Parse the IDX file to detect the index definitions my $idxFile = File::Spec->catfile(dirname($filename), basename($filename, '.ind').'.idx'); my $currentMd5 = makeIdxIndexDefinitionMd5($idxFile) || ''; my $oldMd5 = $self->{'stamps'}{'idx'}{$idxFile} || ''; if ($currentMd5 ne $oldMd5) { $self->{'stamps'}{'idx'}{$idxFile} = $currentMd5; return 1; } return 0; } elsif ($ext eq '.gls') { # Parse the GLS file to detect the index definitions my $glsFile = File::Spec->catfile(dirname($filename), basename($filename, '.pdf').'.gls'); my $currentMd5 = makeGlsIndexDefinitionMd5($glsFile) || ''; my $oldMd5 = $self->{'stamps'}{'gls'}{$glsFile} || ''; if ($currentMd5 ne $oldMd5) { $self->{'stamps'}{'gls'}{$glsFile} = $currentMd5; return 1; } return 0; } } return _a_more_recent_than_b( $file->{'change'}, $rootchange ); } # Build the list of the files to be build. # Parameters: # $_[0] = name of the root file that should be build. # $_[1] = boolean value that permits to force to consider all the files has changed. # Result: the build list. sub _buildExecutionList($;$) : method { my $self = shift; my $rootfile = shift; my $forceChange = shift; my @builds = (); # Go through the dependency tree with en iterative algorithm my $rootchange = $self->{'files'}{$rootfile}{'change'}; my $element = newEntry($rootfile,undef) ; my $child; my @iterator = ( $element ); while (@iterator) { $element = pop @iterator; my $deps = $self->{'files'}{$element->file}{'dependencies'}; if ($element->go_up || !%$deps) { if ( $forceChange || $element->rebuild || $self->_need_rebuild( $rootchange, $element->file, $element->parent, $self->{'files'}{$element->file})) { if ($element->parent) { $element->parent->rebuild(1); } if ($self->can('__build_'.lc($self->{'files'}{$element->file}{'type'}))) { push @builds, $element->file; } } } else { push @iterator, $element; foreach my $dep (keys %$deps) { $child = newEntry($dep,$element); push @iterator, $child; } $element->go_up(1); } } return @builds; } # Run the building process. # Parameters: # $_[0] = name of the root file that should be build. # $_[1] = name of the file to build (the root file or one of its dependencies). # Result: nothing. sub _build($$) : method { my $self = shift; my $rootFile = shift; my $file = shift; if (exists $self->{'files'}{$file}) { my $type = $self->{'files'}{$file}{'type'}; if ($type) { my $func = $self->can('__build_'.lc($type)); if ($func) { $func->($self, $rootFile, $file, $self->{'files'}{$file}); return undef; } } } # Default building behavior: do nothing return undef; } sub __find_file_with_basename($) { my $self = shift; my $basename = shift; if (%{$self->{'files'}}) { foreach my $k (keys %{$self->{'files'}}) { my $bn = basename($k); if ($bn eq $basename) { return File::Spec->abs2rel($k, $self->{'configuration'}{'__private__'}{'input.project directory'}); } } } return $basename; } # Callback to build a BBL file. # Parameters: # $_[0] = name of the root file that should be build. # $_[1] = name of the file to build (the root file or one of its dependencies). # $_[2] = description of the file to build. # Result: nothing. sub __build_bbl($$$) : method { my $self = shift; my $rootFile = shift; my $file = shift; my $filedesc = shift; if ($self->{'is_biblio_enable'}) { my $basename = basename($file,'.bbl'); if ($filedesc->{'use_biber'}) { #################################### # BIBER #################################### printDbg(formatText(_T('{}: {}'), 'BIBER', basename($basename))); my $retcode = runCommandRedirectToInternalLogs( @{$self->{'biber_cmd'}}, "$basename"); # Output the log from the bibliography tool if ($retcode!=0) { printDbg(formatText(_T("{}: Error when processing {}"), 'BIBER', $basename)); local *INFILE; open(*INFILE, ") { if ($line =~ /^\s*ERROR\s*\-\s*.*subsystem:\s*(.+?),\s*line\s+([0-9]+),\s*(.*?)\s*$/i) { my ($filename, $linenumber, $message) = ($1, $2, $3); if ($filename =~ /^(.+)_[0-9]+\.[a-zA-Z0-9_-]+$/) { $filename = $1; } $filename = $self->__find_file_with_basename(basename($filename)); print STDERR "$filename:$linenumber: $message\n"; } elsif ($line =~ /^\s*ERROR\s*\-\s*(.+?)\s*$/i) { my $message = $1; print STDERR "$message\n"; } } close(*INFILE); exit(255); } else { unlink("autolatex_exec_stdout.log"); unlink("autolatex_exec_stderr.log"); } } else { #################################### # BIBTEX #################################### my $auxFile = File::Spec->catfile(dirname($file),"$basename.aux"); printDbg(formatText(_T('{}: {}'), 'BIBTEX', basename($auxFile))); my $retcode = runCommandRedirectToInternalLogs( @{$self->{'bibtex_cmd'}}, $self->makeRelativePath("$auxFile")); # Output the log from the bibliography tool if ($retcode!=0) { printDbg(formatText(_T("{}: Error when processing {}"), 'BIBTEX', basename($auxFile))); local *INFILE; open(*INFILE, ") { if (%currentError) { if ($line =~ /^\s*:\s*(.*?)\s*$/) { $currentError{'message'} .= " $1"; } else { print STDERR $currentError{'filename'}.':'.$currentError{'lineno'}.': '.$currentError{'message'}."\n"; %currentError = (); } } elsif ($line =~ /^\s*(.*?)\s*\-\-\-line\s+([0-9]+)\s+of\s+file\s+(.*?)\s*$/i) { my ($message, $linenumber, $filename) = ($1, $2, $3); if (!$message) { $message = $previousline; $message =~ s/^\s+//s; $message =~ s/\s+$//s; } %currentError = ( 'filename' => $filename, 'lineno' => $linenumber, 'message' => $message, ); $previousline = ''; } else { $previousline = $line; %currentError = (); } } close(*INFILE); if (%currentError) { print STDERR $currentError{'filename'}.':'. $currentError{'lineno'}.': '.$currentError{'message'}."\n"; } exit(255); } else { unlink("autolatex_exec_stdout.log"); unlink("autolatex_exec_stderr.log"); } } } } # Callback to build a IND file. # Parameters: # $_[0] = name of the root file that should be build. # $_[1] = name of the file to build (the root file or one of its dependencies). # $_[2] = description of the file to build. # Result: nothing. sub __build_ind($$$) : method { my $self = shift; my $rootFile = shift; my $file = shift; my $filedesc = shift; if ($self->{'is_makeindex_enable'}) { my $basename = basename($file,'.ind'); my $idxFile = File::Spec->catfile(dirname($file),"$basename.idx"); if (-f "$idxFile") { printDbg(formatText(_T('{}: {}'), 'MAKEINDEX', basename($idxFile))); my @styleArgs = (); my $istFile = $self->{'configuration'}{'__private__'}{'output.ist file'}; if ($istFile && -f "$istFile") { printDbgFor(2, formatText(_T('Style file: {}'), $istFile)); push @styleArgs, '-s', "$istFile"; } runCommandOrFail(@{$self->{'makeindex_cmd'}}, @styleArgs, $self->makeRelativePath("$idxFile")); } } } # Callback to build a GLS file. # Parameters: # $_[0] = name of the root file that should be build. # $_[1] = name of the file to build (the root file or one of its dependencies). # $_[2] = description of the file to build. # Result: nothing. sub __build_gls($$$) : method { my $self = shift; my $rootFile = shift; my $file = shift; my $filedesc = shift; if ($self->{'is_makeglossaries_enable'}) { my $filename = File::Spec->catfile(dirname($rootFile), basename($rootFile,'.pdf')); $filename = $self->makeRelativePath("$filename"); printDbg(formatText(_T('{}: {}'), 'MAKEGLOSSARIES', basename($rootFile))); runCommandOrFail(@{$self->{'makeglossaries_cmd'}}, "$filename"); } } # Callback to build a PDF file. # Parameters: # $_[0] = name of the root file that should be build. # $_[1] = name of the file to build (the root file or one of its dependencies). # $_[2] = description of the file to build. # Result: nothing. sub __build_pdf($$$) : method { my $self = shift; my $rootFile = shift; my $file = shift; my $filedesc = shift; my $runs = 2; my $majorFailure = 0; do { $runs--; $self->runLaTeX($file,1,1); $majorFailure = (exists $self->{'warnings'}{'multiple_definition'}) || (exists $self->{'warnings'}{'undefined_reference'}) || (exists $self->{'warnings'}{'undefined_citation'}); } while ($majorFailure && $runs>0); } =pod =item * enableBiblio Enable or disable the call to bibtex/biber. If this function has a parameter, the flag is changed. =over =item * isEnable (optional boolean) =back I the value of the enabling flag. =cut sub enableBiblio : method { my $self = shift; if (@_) { $self->{'is_biblio_enable'} = $_[0]; } return $self->{'is_biblio_enable'}; } =pod =item * enableMakeIndex Enable or disable the call to makeindex. If this function has a parameter, the flag is changed. =over =item * isEnable (optional boolean) =back I the vlaue of the enabling flag. =cut sub enableMakeIndex : method { my $self = shift; if (@_) { $self->{'is_makeindex_enable'} = $_[0]; } return $self->{'is_makeindex_enable'}; } =pod =item * enableMakeGlossaries Enable or disable the call to makeglossaries. If this function has a parameter, the flag is changed. =over =item * isEnable (optional boolean) =back I the vlaue of the enabling flag. =cut sub enableMakeGlossaries : method { my $self = shift; if (@_) { $self->{'is_makeglossaries_enable'} = $_[0]; } return $self->{'is_makeglossaries_enable'}; } =pod =item * generationType Get or change the type of generation. If this function has a parameter, the type is changed. =over =item * type (optional string) C<"pdf"> to use pdflatex, C<"dvi"> to use latex, C<"ps"> to use latex and dvips, C<"pspdf"> to use latex, dvips and ps2pdf. =back I the generation type. =cut sub generationType : method { my $self = shift; if (@_) { my $type = $_[0] || 'pdf'; if ($type ne 'dvi' && $type ne 'ps' && $type ne 'pdf') { $type = 'pdf'; } $self->{'generation_type'} = $type; } return $self->{'generation_type'}; } 1; __END__ =back =head1 BUG REPORT AND FEEDBACK To report bug, provide feedback, suggest new features, etc. visit the AutoLaTeX Project management page at or send email to the author at L. =head1 LICENSE S =head1 COPYRIGHT Sgalland@arakhne.orgE> =head1 SEE ALSO L