# autolatex - Make.pm
# Copyright (C) 2013-2016 Stephane Galland <
[email protected]>
#
# 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<use AutoLaTeX::Make::Make;>.
=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<configuration> 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<file> is the name of the PDF or the TeX file to compile.
=item * C<enableLoop> (optional boolean) indicates if this function may loop on the LaTeX compilation when it is requested by the LaTeX tool.
=item * C<buffering_warnings> (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 = <LOGFILE>) && !$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 = <LOGFILE>)) {
$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<progress> (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<progress> (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<progress> (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<progress> (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 = <FILE>) {
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, "<autolatex_exec_stdout.log") or printErr("autolatex_exec_stdout.log: $!");
while (my $line = <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, "<autolatex_exec_stdout.log") or printErr("autolatex_exec_stdout.log: $!");
my %currentError = ();
my $previousline = '';
while (my $line = <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<Returns:> 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<Returns:> 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<Returns:> 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<Returns:> 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 <
http://www.arakhne.org/autolatex/> or send email to the author at L<
[email protected]>.
=head1 LICENSE
S<GNU Public License (GPL)>
=head1 COPYRIGHT
S<Copyright (c) 2013-2016 Stéphane Galland E<lt>
[email protected]<gt>>
=head1 SEE ALSO
L<autolatex-dev>