eval '(exit $?0)' && eval 'exec perl -x -S $0 ${1+"$@"}' && eval 'exec perl -x -S $0 $argv:q' if 0; #!/usr/bin/perl -w #!/opt/local/bin/perl -w #!/usr/local/bin/perl -w #================= TO DO ================ # # 1. Check file of bug reports and requests # The above code allows this script to be run under UNIX/LINUX without # the need to adjust the path to the perl program in a "shebang" line. # (The location of perl changes between different installations, and # may even be different when several computers running different # flavors of UNIX/LINUX share a copy of latex or other scripts.) The # script is started under the default command interpreter sh, and the # evals in the first two lines restart the script under perl, and work # under various flavors of sh. The -x switch tells perl to start the # script at the first #! line containing "perl". The "if 0;" on the # 3rd line converts the first two lines into a valid perl statement # that does nothing. # To do: # Rationalize again handling of include files. # Perhaps use kpsewhich to do searches. # (How do I avoid getting slowed down too much?) # Better parsing of log file for includes. # Do I handle the recursive dependence of bbl and aux files sensibly. # Perhaps some appropriate touching of the .aux and .bbl files would help? # Document the assumptions at each stage of processing algorithm. # Option to restart previewer automatically, if it dies under -pvc # Test for already running previewer gets wrong answer if another # process has the viewed file in its command line $version_num = '3.07a'; $version_details = 'latexmk, John Collins, 2 June 2004'; use File::Copy; use File::Basename; use FileHandle; use File::Find; ## Copyright John Collins 1998-2004 ## (username collins at node phys.psu.edu) ## (and thanks to David Coppit (username david at node coppit.org) ## for suggestions) ## Copyright Evan McLean ## (modifications up to version 2) ## Copyright 1992 by David J. Musliner and The University of Michigan. ## (original version) ## ## 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; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ## ## ## For Win95 et al.: Remaining items on list. ## 1. See specific flags ## 6. Need to check out all calls to system ## 8. Now check that I do the bibtex and index stuff correctly. ## 9. Convert routines to have arguments. ## 12. Need to have default filetypes for graphics includes. ## To make that rational, the find_file routine, etc must be modified, ## otherwise there will be much duplication of code. ## ## ## NEW FEATURES, since v. 2.0: ## 1. Correct algorithm for deciding how many times to run latex: ## based on whether the aux file changes between runs ## 2. Continuous preview works, and can be of ps file or dvi file ## 3. pdf creation by pdflatex possible ## 4. Defaults for commands are OS dependent. ## 5. Parsing of log file instead of source file is used to ## obtain dependencies, by default. ## 6. Meta-commands in source file ## ## %#{}raw ## %#{}begin.raw ## Start section of raw LaTeX: treat as %#{latex}begin.ignore. ## ## %#{}end.raw ## End section of raw LaTeX: treat as %#{latex}end.ignore. ## ## %#{latexmk}ignore ## %#{latexmk}begin.ignore ## Start section that is to be ignored in scanning dependencies. ## %#{latexmk}end.ignore ## End section that is to be ignored in scanning dependencies. ## ## %#{latexmk}include.file FILE ## %#{latexmk}input.file FILE ## Add FILE to list of include files. (FILE is separated from ## the keyword "include.file" or "input.file" by white space, ## and is terminated by end-of-line or whitespace.) ## ## ## ## Modified ## ## ## 2 Jun 2004, John Collins ## Correct bug in parsing of log files from pdflatex ## 17 May 2004, John Collins ## 16 May 2004, John Collins ## Correct bug in using $print_type eq 'pdf' ## Command line switches for print_type ## 14 May 2004, John Collins ## 11 May 2004, John Collins ## Correct parse_log bug ## 3 May 2004, John Collins ## Correct bug ## 2 May 2004, John Collins ## Redo handling of errors in -pvc mode: $force_mode off. ## Deal with dying in caller of &make_files (etc) ## 1 May 2004, John Collins ## Go mode applies to custom dependencies ## Rerun custom dependency and latex when previously non-found ## files are found. ## 30 Apr 2004, John Collins ## Fix bug in make_dependents ## Add messages about up-to-date files when doing nothing ## Silence more informational messages in silent mode. ## In list of dependent files generated from .log file ## excluded those whose extenstions are in @generated_exts. ## This removes generated files from the dependency list. ## 29 Apr 2004, John Collins ## 28 Apr 2004, John Collins ## Rationalize default file-list to be in @default_files, etc ## Change behavior about lack of command line files: ## Files specified on command-line but none found: error ## No files specified on command-line: ## Try @default_files (with wildcards) ## If still none: error ## Also apply sort and uniq to globbed list, to eliminate duplicates ## 27 Apr 2004, John Collins ## Correct parsing of filenames in log file and in aux file ## 26 Apr 2004, John Collins ## Correct calls to open, to be OK under perl 5.005 (on SUN) ## 25 Apr 2004, John Collins ## Cleanup of comments, some error messages, etc ## Change terminology 'include' to 'input' where appropriate ## Switches to turn off modes: -F-, -g-, -i-, -I-, -pv-, -pvc- ## -pv and -pvc override each other. (Easier than making them ## incompatible, and particularly necessary when one is ## specified in a startup file.) ## Die if incorrect option specified on command line. ## Turn off bibtex mode if no .bib files specified in .aux ## file. That would be the situation if a .bbl file was ## input by an input command, so that the user is not ## expecting to use bibtex. ## Add a condition for rerun to include a change in the list ## of input files. ## In force_mode, don't die when files are not found; they may ## be in a search path where I don't look. (But I have only ## done this for the case that I parse the log file.) ## When files for viewing, converting (dvi to ps, etc), etc are ## not found, give a warning rather than trying to continue. ## Revise parsing in .log file to allow filenames with spaces. ## Use arrays for all lists of filenames. ## 5 Apr 2004, John Collins ## If latexmk exits because of an error with latex, bibtex ## or makeindex, then give a non-zero exit code. ## 4 Apr 2004, John Collins ## All options can be introduced by -- or - ## Attempt to treat cygwin correctly ## Complete the list of commands printed with -commands ## Correct commands for postscript mode written to .dep file ## 3 Apr 2004, John Collins ## V. 3.07 started ## 2 Mar 2004, John Collins ## Obfuscate e-mail addresses so that they aren't picked up by ## worms and spammers ## 17 Jun 2003, John Collins ## For pdf viewing set $viewer_update_method to $pdf_update_method ## Previously it was set wrongly to $ps_update_method ## 10 Dec 2002, John Collins ## Recursive search of BIBINPUTS and TEXINPUTS ## Thanks to Nevin Kapur ## 5 Nov 2002, John Collins ## Reorganize handling of filename specification, so that ## filenames can be specified in an rc file. E.g., ## @file_list = ("paper"); ## 26 Oct 2002, John Collins ## In -pvc mode keep going (i.e., -f switch), but have and -f- ## option to turn it off. ## 25 Oct 2002, John Collins ## Correct bug in splitting of command line ## 24 Oct 2002, John Collins ## pdfdvi option ## For -pvc take PID of viewer from fork. ## 22 Oct 2002, John Collins ## Change pscmd on linux ## 12--15 Oct 2002, John Collins ## Improve error messages. ## Add "_switch" to names of configuration variables giving switches ## (to avoid confusion with variables specifying commands). ## E.g., latex_silent_switch ## -P pdf switch on dvips, to ensure correct type 1 font generation ## (otherwise pdf files have type 3 fonts, and look bad on screen). ## Correct running of ps filter. ## It now only runs when ps file is updated. ## Some clean-ups of code. ## Allow preview of multiple files ## Option to generate pdf file by ps2pdf (switch -pdfps) ## Prevent detached previewer from responding to ctrl/C given to ## parent latemk process. ## Under MS Windows, wildcards allowed in file names. ## 14 Apr 2002, John Collins ## Ver. 3.05 ## 24 Nov 2001, John Collins ## Ver. 3.04. Released on CTAN ## 11 Jun 2001, John Collins ## Ver. 3.00. Misc cleanup. ## ## 1998-2001, John Collins. Many improvements and fixes. ## ## Modified by Evan McLean (no longer available for support) ## Original script (RCS version 2.3) called "go" written by David J. Musliner ## ## 2.0 - Final release, no enhancements. LatexMk is no longer supported ## by the author. ## 1.9 - Fixed bug that was introduced in 1.8 with path name fix. ## - Fixed buglet in man page. ## 1.8 - Add not about announcement mailling list above. ## - Added texput.dvi and texput.aux to files deleted with -c and/or ## the -C options. ## - Added landscape mode (-l option and a bunch of RC variables). ## - Added sensing of "\epsfig{file=...}" forms in dependency generation. ## - Fixed path names when specified tex file is not in the current ## directory. ## - Fixed combined use of -pvc and -s options. ## - Fixed a bunch of speling errors in the source. :-) ## - Fixed bugs in xdvi patches in contrib directory. ## 1.7 - Fixed -pvc continuous viewing to reattach to pre-existing ## process correctly. ## - Added $pscmd to allow changing process grepping for different ## systems. ## 1.6 - Fixed buglet in help message ## - Fixed bugs in detection of input and include files. ## 1.5 - Removed test message I accidentally left in version 1.4 ## - Made dvips use -o option instead of stdout redirection as some ## people had problems with dvips not going to stdout by default. ## - Fixed bug in input and include file detection ## - Fixed dependency resolution process so it detects new .toc file ## and makeindex files properly. ## - Added dvi and postscript filtering options -dF and -pF. ## - Added -v version commmand. ## 1.4 - Fixed bug in -pvc option. ## - Made "-F" option include non-existant file in the dependency list. ## (RC variable: $force_include_mode) ## - Added .lot and .lof files to clean up list of extensions. ## - Added file "texput.log" to list of files to clean for -c. ## - LatexMk now handles file names in a similar fashion to latex. ## The ".tex" extension is no longer enforced. ## - Added $texfile_search RC variable to look for default files. ## - Fixed \input and \include so they add ".tex" extension if necessary. ## - Allow intermixing of file names and options. ## - Added "-d" and banner options (-bm, -bs, and -bi). ## (RC variables: $banner, $banner_message, $banner_scale, ## $banner_intensity, $tmpdir) ## - Fixed "-r" option to detect an command line syntax errors better. ## 1.3 - Added "-F" option, patch supplied by Patrick van der Smagt. ## 1.2 - Added "-C" option. ## - Added $clean_ext and $clean_full_ext variables for RC files. ## - Added custom dependency generation capabilities. ## - Added command line and variable to specify custom RC file. ## - Added reading of rc file in current directly. ## 1.1 - Fixed bug where Dependency file generation header is printed ## rependatively. ## - Fixed bug where TEXINPUTS path is searched for file that was ## specified with absolute an pathname. ## 1.0 - Ripped from script by David J. Musliner (RCS version 2.3) called "go" ## - Fixed a couple of file naming bugs ## e.g. when calling latex, left the ".tex" extension off the end ## of the file name which could do some interesting things ## with some file names. ## - Redirected output of dvips. My version of dvips was a filter. ## - Cleaned up the rc file mumbo jumbo and created a dependency file ## instead. Include dependencies are always searched for if a ## dependency file doesn't exist. The -i option regenerates the ## dependency file. ## Getting rid of the rc file stuff also gave the advantage of ## not being restricted to one tex file per directory. ## - Can specify multiple files on the command line or no files ## on the command line. ## - Removed lpr options stuff. I would guess that generally, ## you always use the same options in which case they can ## be set up from an rc file with the $lpr variable. ## - Removed the dviselect stuff. If I ever get time (or money :-) ) ## I might put it back in if I find myself needing it or people ## express interest in it. ## - Made it possible to view dvi or postscript file automatically ## depending on if -ps option selected. ## - Made specification of dvi file viewer seperate for -pv and -pvc ## options. ##----------------------------------------------------------------------- #Line length in log file that indicates wrapping. # This number EXCLUDES line-end characters, and is one-based $log_wrap = 79; ######################################################################### ## Default document processing programs, and related settings, ## These are mostly the same on all systems. ## Most of these variables represents the external command needed to ## perform a certain action. Some represent switches. ## Commands to invoke latex, pdflatex $latex = 'latex'; $pdflatex = 'pdflatex'; ## Switch(es) to make them silent: $latex_silent_switch = '-interaction=batchmode'; $pdflatex_silent_switch = '-interaction=batchmode'; ## Command to invoke bibtex $bibtex = 'bibtex'; # Switch(es) to make bibtex silent: $bibtex_silent_switch = '-terse'; ## Command to invoke makeindex $makeindex = 'makeindex'; ## Command to convert dvi file to pdf file directly: $dvipdf = 'dvipdf'; ## Command to convert dvi file to ps file: $dvips = 'dvips'; ## Command to convert dvi file to ps file in landscape format: $dvips_landscape = 'dvips -tlandscape'; # Switch(es) to get dvips to make ps file suitable for conversion to good pdf: # (If this is not used, ps file and hence pdf file contains bitmap fonts # (type 3), which look horrible under acroread. An appropriate switch # ensures type 1 fonts are generated. You can put this switch in the # dvips command if you prefer.) $dvips_pdf_switch = '-P pdf'; # Switch(es) to make dvips silent: $dvips_silent_switch = '-q'; ## Command to convert ps file to pdf file: $ps2pdf = 'ps2pdf'; ##Printing: $print_type = 'ps'; # When printing, print the postscript file. # Possible values: 'dvi', 'ps', 'pdf', 'none' ## Which treatment of default extensions and filenames with ## multiple extensions is used, for given filename on ## tex/latex's command line? See sub find_basename for the ## possibilities. ## Current tex's treat extensions like UNIX teTeX: $extension_treatment = 'unix'; ######################################################################### # System-dependent overrides: if ( $^O eq "MSWin32" ) { # Pure MSWindows configuration ## Configuration parameters: $tmpdir = "$ENV{TEMP}"; ## List of possibilities for the system-wide initialization file. ## The first one found (if any) is used. @rc_system_files = ( 'C:/latexmk/LatexMk' ); $search_path_separator = ';'; # Separator of elements in search_path # For both fptex and miktex, the following makes error messages explicit: $latex_silent_switch = '-interaction=batchmode -c-style-errors'; $pdflatex_silent_switch = '-interaction=batchmode -c-style-errors'; # For a pdf-file, "start x.pdf" starts the pdf viewer associated with # pdf files, so no program name is needed: $pdf_previewer = 'start'; $ps_previewer = 'start'; $ps_previewer_landscape = "$ps_previewer"; $dvi_previewer = 'start'; $dvi_previewer_landscape = "$dvi_previewer"; # Viewer update methods: # 0 => auto update: viewer watches file (e.g., gv) # 1 => manual update: user must do something: e.g., click on window. # (e.g., ghostview, MSWIN previewers, acroread under UNIX) # 2 => send SIGUSR1 signal (xdvi) # 3 => viewer can't update, because it locks the file and the file # cannot be updated. (acroread under MSWIN) $dvi_update_method = 1; $ps_update_method = 1; $pdf_update_method = 3; # acroread locks the pdf file $lpr = 'NONE lpr'; # Use NONE as flag that I am not implementing this $lpr_dvi = 'NONE lpr_dvi';# Use NONE as flag that I am not implementing this $lpr_pdf = 'NONE lpr_pdf';# Use NONE as flag that I am not implementing this # The $pscmd below holds a command to list running processes. It # is used to find the process ID of the viewer looking at the # current output file. The output of the command must include the # process number and the command line of the processes, since the # relevant process is identified by the name of file to be viewed. # Its use is not essential. $pscmd = 'NONE pscmd'; # Use NONE as flag that I am not implementing this $pid_position = -1; # offset of PID in output of pscmd. # Negative means I cannot use ps } elsif ( $^O eq "cygwin" ) { # The problem is a mixed MSWin32 and UNIX environment. # Perl decides the OS is cygwin in two situations: # 1. When latexmk is run from a cygwin shell under a cygwin # environment. Perl behaves in a UNIX way. This is OK, since # the user is presumably expecting UNIXy behavior. # 2. When CYGWIN exectuables are in the path, but latexmk is run # from a native NT shell. Presumably the user is expecting NT # behavior. But perl behaves more UNIXy. This causes some # clashes. # The issues to handle are: # 1. Perl sees both MSWin32 and cygwin filenames. This is # normally only an advantage. # 2. Perl uses a UNIX shell in the system command # This is a nasty problem: under native NT, there is a # start command that knows about NT file associations, so that # we can do, e.g., (under native NT) system("start file.pdf"); # But this won't work when perl has decided the OS is cygwin, # even if it is invoked from a native NT command line. An # NT command processor must be used to deal with this. # 3. External executables can be native NT (which only know # NT-style file names) or cygwin executables (which normally # know both cygwin UNIX-style file names and NT file names, # but not always; some do not know about drive names, for # example). # Cygwin executables for tex and latex may only know cygwin # filenames. # 4. The BIBINPUTS and TEXINPUTS environment variables may be # UNIX-style or MSWin-style depending on whether native NT or # cygwin executables are used. They are therefore parsed # differently. Here is the clash: # a. If a user is running under an NT shell, is using a # native NT installation of tex (e.g., fptex or miktex), # but has the cygwin executables in the path, then perl # detects the OS as cygwin, but the user needs NT # behavior from latexmk. # b. If a user is running under an UNIX shell in a cygwin # environment, and is using the cygwin installation of # tex, then perl detects the OS as cygwin, and the user # needs UNIX behavior from latexmk. # Latexmk has no way of detecting the difference. The two # situations may even arise for the same user on the same # computer simply by changing the order of directories in the # path environment variable ## Configuration parameters: We'll assume native NT executables. ## The user should override if they are not. # This may fail: perl converts MSWin temp directory name to cygwin # format. Names containing this string cannot be handled by native # NT executables. $tmpdir = "$ENV{TEMP}"; ## List of possibilities for the system-wide initialization file. ## The first one found (if any) is used. ## We can stay with MSWin files here, since perl understands them, @rc_system_files = ( 'C:/latexmk/LatexMk' ); $search_path_separator = ';'; # Separator of elements in search_path # This is tricky. The search_path_separator depends on the kind # of executable: native NT v. cygwin. # So the user will have to override this. # For both fptex and miktex, the following makes error messages explicit: $latex_silent_switch = '-interaction=batchmode -c-style-errors'; $pdflatex_silent_switch = '-interaction=batchmode -c-style-errors'; # We will assume that files can be viewed by native NT programs. # Then we must fix the start command/directive, so that the # NT-native start command of a cmd.exe is used. # For a pdf-file, "start x.pdf" starts the pdf viewer associated with # pdf files, so no program name is needed: $start_NT = "cmd /c start"; $pdf_previewer = "$start_NT"; $ps_previewer = "$start_NT"; $ps_previewer_landscape = "$ps_previewer"; $dvi_previewer = "$start_NT"; $dvi_previewer_landscape = "$dvi_previewer"; # Viewer update methods: # 0 => auto update: viewer watches file (e.g., gv) # 1 => manual update: user must do something: e.g., click on window. # (e.g., ghostview, MSWIN previewers, acroread under UNIX) # 2 => send SIGUSR1 signal (xdvi) # 3 => viewer can't update, because it locks the file and the file # cannot be updated. (acroread under MSWIN) $dvi_update_method = 1; $ps_update_method = 1; $pdf_update_method = 3; # acroread locks the pdf file $lpr = 'NONE lpr'; # Use NONE as flag that I am not implementing this $lpr_dvi = 'NONE lpr_dvi';# Use NONE as flag that I am not implementing this $lpr_pdf = 'NONE lpr_pdf';# Use NONE as flag that I am not implementing this # The $pscmd below holds a command to list running processes. It # is used to find the process ID of the viewer looking at the # current output file. The output of the command must include the # process number and the command line of the processes, since the # relevant process is identified by the name of file to be viewed. # Its use is not essential. # When the OS is detected as cygwin, there are two possibilities: # a. Latexmk was run from an NT prompt, but cygwin is in the # path. Then the cygwin ps command will not see commands # started from latexmk. So we cannot use it. # b. Latexmk was started within a cygwin environment. Then # the ps command works as we need. # Only the user, not latemk knows which, so we default to not # using the ps command. The user can override this in a # configuration file. $pscmd = 'NONE pscmd'; # Use NONE as flag that I am not implementing this $pid_position = -1; # offset of PID in output of pscmd. # Negative means I cannot use ps } else { # Assume anything else is UNIX or clone ## Configuration parameters: $tmpdir = '/tmp'; ## List of possibilities for the system-wide initialization file. ## The first one found (if any) is used. ## Normally on a UNIX it will be in a subdirectory of /opt/local/share or ## /usr/local/share, depending on the local conventions. ## /usr/local/lib/latexmk/LatexMk is put in the list for ## compatibility with older versions of latexmk. @rc_system_files = ( '/opt/local/share/latexmk/LatexMk', '/usr/local/share/latexmk/LatexMk', '/usr/local/lib/latexmk/LatexMk' ); $search_path_separator = ':'; # Separator of elements in search_path ## default document processing programs. $pdf_previewer = 'start acroread'; ## The following are corrects for the SUNs at phys.psu.edu: #$ps_previewer = 'start ghostview'; #$ps_previewer_landscape = 'start ghostview -swap'; $ps_previewer = 'start gv -watch'; $ps_previewer_landscape = 'start gv -swap -watch'; $dvi_previewer = 'start xdvi'; $dvi_previewer_landscape = 'start xdvi -paper usr'; # Viewer update methods: # 0 => auto update: viewer watches file (e.g., gv) # 1 => manual update: user must do something: e.g., click on window. # (e.g., ghostview, MSWIN previewers, acroread under UNIX) # 2 => send SIGUSR1 signal (xdvi) # 3 => viewer can't update, because it locks the file and the file # cannot be updated. (acroread under MSWIN) $dvi_update_method = 2; # xdvi responds to SIGUSR1 to update $ps_update_method = 0; # gv -watch watches the ps file $pdf_update_method = 1; # acroread under unix needs manual update $lpr = 'lpr'; # Assume lpr command prints postscript files correctly $lpr_dvi = 'NONE lpr_dvi';# Use NONE as flag that I am not implementing this $lpr_pdf = 'NONE lpr_pdf';# Use NONE as flag that I am not implementing this # The $pscmd below holds a command to list running processes. It # is used to find the process ID of the viewer looking at the # current output file. The output of the command must include the # process number and the command line of the processes, since the # relevant process is identified by the name of file to be viewed. # Uses: # 1. In preview_continuous mode, to save running a previewer # when one is already running on the relevant file. # 2. With xdvi in preview_continuous mode, xdvi must be # signalled to make it read a new dvi file. # # The following works on Solaris, LINUX, HP-UX, IRIX # Use -f to get full listing, including command line arguments. # Use -u $ENV{CMD} to get all processes started by current user (not just # those associated with current terminal), but none of other users' # processes. $pscmd = "ps -f -u $ENV{USER}"; $pid_position = 1; # offset of PID in output of pscmd; first item is 0. if ( $^O eq "linux" ) { # Ps on Redhat (at least v. 7.2) appears to truncate its output # at 80 cols, so that a long command string is truncated. # Fix this with the --width option. This option works under # other versions of linux even if not necessary (at least # for SUSE 7.2). # However the option is not available under other UNIX-type # systems, e.g., Solaris 8. $pscmd = "ps --width 200 -f -u $ENV{USER}"; } } ## default parameters $max_repeat = 5; # Maximum times I repeat latex. Normally # 3 would be sufficient: 1st run generates aux file, # 2nd run picks up aux file, and maybe toc, lof which # contain out-of-date information, e.g., wrong page # references in toc, lof and index, and unresolved # references in the middle of lines. But the # formatting is more-or-less correct. On the 3rd # run, the page refs etc in toc, lof, etc are about # correct, but some slight formatting changes may # occur, which mess up page numbers in the toc and lof, # Hence a 4th run is conceivably necessary. # At least one document class (JHEP.cls) works # in such a way that a 4th run is needed. # We allow an extra run for safety for a # maximum of 5. Needing further runs is # usually an indication of a problem; further # runs may not resolve the problem, and # instead could cause an infinite loop. $clean_ext = ""; # space separated extensions of files that are # to be deleted when doing cleanup, beyond # standard set $clean_full_ext = ""; # space separated extensions of files that are # to be deleted when doing cleanup_full, beyond # standard set and those in $clean_ext @cus_dep_list = (); # Custom dependency list @default_files = ( '*.tex' ); # Array of LaTeX files to process when # no files are specified on the command line. # Wildcards allowed # Best used for project specific files. $texfile_search = ""; # Specification for extra files to search for # when no files are specified on the command line # and the @file_list variable is empty. # Space separated, and wildcards allowed. # These files are IN ADDITION to *.tex in current # directory. # This variable is obsolete, and only in here for # backward compatibility. ## default flag settings. $silent = 0; # silence latex's messages? $bibtex_mode = 0; # is there a bibliography needing bibtexing? $index_mode = 0; # is there an index needing makeindex run? $landscape_mode = 0; # default to portrait mode # The following contains a list of extensions for files that may be read in # during a LaTeX run but that are generated in the previous run. They should be # excluded from the dependents, since NORMALLY they are not true source files. # This list can be overridden in a configuration file if it causes problems. # The extensions "aux" and "bbl" are always excluded from the dependents, # because they get special treatment. @generated_exts = ( 'lof', 'lot', 'toc'); # Which kinds of file do I have requests to make? # If no requests at all are made, then I will make dvi file # If particular requests are made then other files may also have to be # made. E.g., ps file requires a dvi file $dvi_mode = 0; # No dvi file requested $postscript_mode = 0; # No postscript file requested $pdf_mode = 0; # No pdf file requested to be made by pdflatex # Possible values: # 0 don't create pdf file # 1 to create pdf file by pdflatex # 2 to create pdf file by ps2pdf # 3 to create pdf file by dvipdf $view = 'default'; # Default preview is of highest of dvi, ps, pdf $sleep_time = 2; # time to sleep b/w checks for file changes in -pvc mode $banner = 0; # Non-zero if we have a banner to insert $banner_scale = 220; # Original default scale $banner_intensity = 0.95; # Darkness of the banner message $banner_message = 'DRAFT'; # Original default message $cleanup_mode = 0; # No cleanup of nonessential files. # $cleanup_mode = 0: no cleanup # $cleanup_mode = 1: full cleanup # $cleanup_mode = 2: cleanup except for dvi and ps # $cleanup_mode = 3: cleanup except for dep and aux $diagnostics = 0; $dvi_filter = ''; # DVI filter command $ps_filter = ''; # Postscript filter command $includes_from_log = 1; # =1 to work on log file to find dependencies $force_mode = 0; # =1 to force processing past errors $force_include_mode = 0;# =1 to ignore non-existent files when making # dependency files. $go_mode = 0; # =1 to force processing regardless of time-stamps # =2 full clean-up first $preview_mode = 0; $preview_continuous_mode = 0; $printout_mode = 0; # Don't print the file @bib_files = (); # State variables initialized here: $updated = 0; # Flags when something has been remade # Used to allow convenient user message in -pvc mode # Used for some results of parsing log file: $reference_changed = 0; $bad_reference = 0; $bad_citation = 0; # Get search paths for includes. $TEXINPUTS = $ENV{'TEXINPUTS'}; if (!$TEXINPUTS) { $TEXINPUTS = '.'; } $BIBINPUTS = $ENV{'BIBINPUTS'}; if (!$BIBINPUTS) { $BIBINPUTS = '.'; } @psfigsearchpath = ('.'); # Convert search paths to arrays: # If any of the paths end in '//' then recursively search the # directory. After these operations, @BIBINPUTS and @TEXINPUTS should # have all the directories that need to be searched @TEXINPUTS = find_dirs1 ($TEXINPUTS); @BIBINPUTS = find_dirs1 ($BIBINPUTS); #show_array ("TEXINPUTS", @TEXINPUTS); show_array ("BIBINPUTS", @BIBINPUTS); die; ## Read rc files: # Read first system rc file that is found: SYSTEM_RC_FILE: foreach $rc_file ( @rc_system_files ) { # print "===Testing for system rc file \"$rc_file\" ...\n"; if ( -e $rc_file ) { # print "===Reading system rc file \"$rc_file\" ...\n"; # Read the system rc file do "$rc_file"; last SYSTEM_RC_FILE; } } # Read user rc file. $rc_file = "$ENV{'HOME'}/.latexmkrc"; if ( -e $rc_file ) { # Read the user rc file do "$rc_file"; } # Read rc file in current directory. $rc_file = "latexmkrc"; if ( -e $rc_file ) { # Read the user rc file do "$rc_file"; } ## Process command line args. @command_line_file_list = (); $bad_options = 0; while ($_ = $ARGV[0]) { # Make -- and - equivalent at beginning of option: s/^--/-/; shift; if (/^-c$/) { $cleanup_mode = 2; } elsif (/^-commands$/) { &print_commands; exit; } elsif (/^-C$/) { $cleanup_mode = 1; } elsif (/^-c1$/) { $cleanup_mode = 3; } elsif (/^-d$/) { $banner = 1; } elsif (/^-dvi$/) { $dvi_mode = 1; } elsif (/^-dvi-$/) { $dvi_mode = 0; } elsif (/^-f$/) { $force_mode = 1; } elsif (/^-f-$/) { $force_mode = 0; } elsif (/^-F$/) { $force_include_mode = 1; } elsif (/^-F-$/) { $force_include_mode = 0; } elsif (/^-g$/) { $go_mode = 1; } elsif (/^-g-$/) { $go_mode = 0; } elsif ( /^-h$/ || /^-help$/ ) { &print_help; } elsif (/^-il$/) { $includes_from_log = 1; } elsif (/^-it$/) { $includes_from_log = 0; } elsif (/^-i$/) { $generate_and_save_includes = 1; } elsif (/^-i-$/) { $generate_and_save_includes = 0; } elsif (/^-I$/) { $force_generate_and_save_includes = 1; } elsif (/^-I-$/) { $force_generate_and_save_includes = 0; } elsif (/^-diagnostics/) { $diagnostics = 1; } elsif (/^-l$/) { $landscape_mode = 1; } elsif (/^-l-$/) { $landscape_mode = 0; } elsif (/^-p$/) { $printout_mode = 1; $preview_continuous_mode = 0; # to avoid conflicts $preview_mode = 0; } elsif (/^-p-$/) { $printout_mode = 0; } elsif (/^-pdfdvi$/){ $pdf_mode = 3; } elsif (/^-pdfps$/) { $pdf_mode = 2; } elsif (/^-pdf$/) { $pdf_mode = 1; } elsif (/^-pdf-$/) { $pdf_mode = 0; } elsif (/^-print=(.*)$/) { $value = $1; if ( $value =~ /^dvi$|^ps$|^pdf$/ ) { $print_type = $value; $printout_mode = 1; } else { &die_help("Latexmk: unknown print type '$value' in option '$_'"); } } elsif (/^-ps$/) { $postscript_mode = 1; } elsif (/^-ps-$/) { $postscript_mode = 0; } elsif (/^-pv$/) { $preview_mode = 1; $preview_continuous_mode = 0; # to avoid conflicts $printout_mode = 0; } elsif (/^-pv-$/) { $preview_mode = 0; } elsif (/^-pvc$/) { $preview_continuous_mode = 1; $force_mode = 0; # So that errors do not cause loops $preview_mode = 0; # to avoid conflicts $printout_mode = 0; } elsif (/^-pvc-$/) { $preview_continuous_mode = 0; } elsif (/^-silent$/ || /^-quiet$/ ){ $silent = 1; } elsif (/^-v$/ || /^-version$/) { print "\n$version_details. Version $version_num\n"; exit; } elsif (/^-verbose$/) { $silent = 0; } elsif (/^-view=default$/) { $view = "default";} elsif (/^-view=dvi$/) { $view = "dvi";} elsif (/^-view=ps$/) { $view = "ps";} elsif (/^-view=pdf$/) { $view = "pdf"; } elsif (/^-r$/) { if ( $ARGV[0] eq '' ) { &die_help( "Latexmk: No RC file specified after -r switch"); } if ( -e $ARGV[0] ) { do "$ARGV[0]"; } else { die "Latexmk: RC file [$ARGV[0]] does not exist\n"; } shift; } elsif (/^-bm$/) { if ( $ARGV[0] eq '' ) { &die_help( "Latexmk: No message specified after -bm switch"); } $banner = 1; $banner_message = $ARGV[0]; shift; } elsif (/^-bi$/) { if ( $ARGV[0] eq '' ) { &die_help( "Latexmk: No intensity specified after -bi switch"); } $banner_intensity = $ARGV[0]; shift; } elsif (/^-bs$/) { if ( $ARGV[0] eq '' ) { &die_help( "Latexmk: No scale specified after -bs switch"); } $banner_scale = $ARGV[0]; shift; } elsif (/^-dF$/) { if ( $ARGV[0] eq '' ) { &die_help( "Latexmk: No dvi filter specified after -dF switch"); } $dvi_filter = $ARGV[0]; shift; } elsif (/^-pF$/) { if ( $ARGV[0] eq '' ) { &die_help( "Latexmk: No ps filter specified after -pF switch"); } $ps_filter = $ARGV[0]; shift; } elsif (/^-/) { warn "Latexmk: $_ bad option\n"; $bad_options++; } else { push @command_line_file_list, $_ ; } } if ( $bad_options > 0 ) { &die_help( "Bad options specified" ); } warn "This is $version_details, version: $version_num.\n", "**** Report bugs etc to John Collins . ****\n" unless $silent; # For backward compatibility, convert $texfile_search to @default_files # Since $texfile_search is initialized to "", a nonzero value indicates # that an initialization file has set it. if ( $texfile_search ne "" ) { @default_files = split / /, "*.tex $texfile_search"; } #Glob the filenames command line if the script was not invoked under a # UNIX-like environment. # Cases: (1) MS/MSwin native Glob # (OS detected as MSWin32) # (2) MS/MSwin cygwin Glob [because we do not know whether # the cmd interpreter is UNIXy (and does glob) or is # native MS-Win (and does not glob).] # (OS detected as cygwin) # (3) UNIX Don't glob (cmd interpreter does it) # (Currently, I assume this is everything else) if ( ($^O eq "MSWin32") || ($^O eq "cygwin") ) { @file_list = glob_list(@command_line_file_list); } else { @file_list = uniq( sort(@command_line_file_list) ); } # Check we haven't selected mutually exclusive modes. # Note that -c overides all other options, but doesn't cause # an error if they are selected. if (($printout_mode && ( $preview_mode || $preview_continuous_mode )) || ( $preview_mode && $preview_continuous_mode )) { # Each of the options -p, -pv, -pvc turns the other off. # So the only reason to arrive here is an incorrect inititalization # file, or a bug. &die_help( "Latexmk: Conflicting options (print, preview, preview_continuous) selected"); } if ( @command_line_file_list ) { # At least one file specified on command line (before possible globbing). if ( !@file_list ) { &die_help( "Latexmk: Wildcards in file names didn't match any files"); } } else { # No files specified on command line, try and find some @file_list = glob_list(@default_files); if ( !@file_list ) { &die_help( "Latexmk: No file name specified, and I couldn't find any"); } } $num_files = $#file_list + 1; $num_specified = $#command_line_file_list + 1; # If selected a preview-continuous mode, make sure exactly one filename was specified if ($preview_continuous_mode && ($num_files != 1) ) { if ($num_specified > 0) { &die_help( "Latexmk: Need to specify exactly one filename for ". "preview-continuous mode\n". " but $num_specified were specified" ); } elsif ($num_specified == 1) { &die_help( "Latexmk: Need to specify exactly one filename for ". "preview-continuous mode\n". " but wildcarding produced $num_files files" ); } else { &die_help( "Latexmk: Need to specify exactly one filename for ". "preview-continuous mode.\n". " Since none were specified on the command line, I looked for \n". " files in '@default_files'.\n". " But I found $num_files files, not 1." ); } } # If landscape mode, change dvips processor, and the previewers: if ( $landscape_mode ) { $dvips = $dvips_landscape; $dvi_previewer = $dvi_previewer_landscape; $ps_previewer = $ps_previewer_landscape; } if ( $silent ) { $latex .= " $latex_silent_switch"; $pdflatex .= " $pdflatex_silent_switch"; $bibtex .= " $bibtex_silent_switch"; $dvips .= " $dvips_silent_switch"; } # Which files do we need to make? $need_dvi = $need_ps = $need_pdf = 0; # Which kind of file do we preview? if ( $view eq "default" ) { # If default viewer requested, use "highest" of dvi, ps and pdf # that was requested by user. # No explicit request means view dvi. $view = "dvi"; if ( $postscript_mode ) { $view = "ps"; } if ( $pdf_mode ) { $view = "pdf"; } } # Now check all the requirements that force us to make files. $need_dvi = $dvi_mode; $need_ps = $postscript_mode; $need_pdf = $pdf_mode; if ( $preview_continuous_mode || $preview_mode ) { if ( $view eq "dvi" ) {$need_dvi = 1; } if ( $view eq "ps" ) {$need_ps = 1; } if ( $view eq "pdf" ) { # Which of the two ways to make pdf files do we use? if ( $need_pdf ) { # We already have been told how to make pdf files } else { # Use pdflatex route: $need_pdf = 1; } } } # What implicit requests are made? if ( length($dvi_filter) != 0 ) {$need_dvi = 1; } if ( length($ps_filter) != 0 ) {$need_ps = 1; } if ( $banner ) { $need_ps = 1; } # printout => need ps if ( $printout_mode ) { ## May be wrong if print from other kinds of file if ( $print_type eq 'dvi' ) { $need_dvi = 1; } elsif ( $print_type eq 'none' ) { # Nothing } elsif ( $print_type eq 'pdf' ) { # Will need a pdf file, but there are several methods to make it # Respect a previous request, otherwise get pdf by pdflatex if ( $need_pdf == 0 ) { $need_pdf = 1; } } elsif ( $print_type eq 'ps' ) { $need_ps = 1; } else { die "Latexmk: incorrect value \"$print_type\" for type of file to print\n". "Allowed values are \"dvi\", \"pdf\", \"ps\", \"none\"\n" } } # pdf file by ps2pdf => ps file needed. if ( $need_pdf == 3 ) { $need_dvi = 1; } if ( $need_pdf == 2 ) { $need_ps = 1; } # postscript file => dvi file needed. if ( $need_ps ) { $need_dvi = 1; } # If no files requested, default to dvi: if ( ! ($need_dvi || $need_ps || $need_pdf) ) { $need_dvi = 1; } if ( $need_pdf == 2 ) { # We generate pdf from ps. Make sure we have the correct kind of ps. $dvips .= " $dvips_pdf_switch"; } # Which conversions do we need to make? $tex_to_dvi = $dvi_to_ps = $ps_to_pdf = $dvi_to_pdf = $tex_to_pdf = 0; if ($need_dvi) { $tex_to_dvi = 1; } if ($need_ps) { $dvi_to_ps = 1; } if ($need_pdf == 1) { $tex_to_pdf = 1; } if ($need_pdf == 2) { $ps_to_pdf = 1; } if ($need_pdf == 3) { $dvi_to_pdf = 1; } # Make convenient forms for lookup. # Extensions always have period. # Convert @generated_exts to a hash for ease of look up, with exts preceeded # by a '.' %generated_exts = (); foreach (@generated_exts) { $generated_exts{".$_"} = 1; } $quell_uptodate_msgs = $silent; # Whether to quell informational messages when files are uptodate # Will turn off in -pvc mode # Process for each file. # The value of $bibtex_mode set in an initialization file may get # overridden, during file processing, so save it: $save_bibtex_mode = $bibtex_mode; FILE: foreach $filename ( @file_list ) { $failure = 0; # Set nonzero to indicate failure at some point of # a make. Use value as exit code if I exit. $failure_msg = ''; # Indicate reason for failure $bibtex_mode = $save_bibtex_mode; ## remove extension from filename if was given. if ( &find_basename($filename, $root_filename, $texfile_name) ) { if ( $force_mode ) { warn "Latexmk: Could not find file [$texfile_name]\n"; } else { &exit_msg1( "Could not find file [$texfile_name]", 1); } } if ($cleanup_mode > 0) { ## Do clean if necessary &cleanup_basic; if ( $cleanup_mode != 2 ) { &cleanup_dvi_ps_pdf; } if ( $cleanup_mode != 3 ) { &cleanup_aux_dep; } next FILE; } if ($go_mode == 2) { warn "Latexmk: Removing all generated files\n" unless $silent; &cleanup_dvi_ps_pdf; &cleanup_aux_dep; } #Default aux file list: @aux_files = ("$root_filename.aux"); ## Make file. ## ## Find includes. @includes = (); # Input files %includes_missing = (); # Possible input files, status problematic: # filename => [wherefrom, fullname] # wherefrom 0: exact name known. # 1: name for non-existent file, from logfile, possibly # bad parse, possibly it's been deleted. # 2: not necessarily fullname, from logfile, probably OK # (graphics file, typically). # File not found by latexmk. # 3: possibly incomplete (pathless, possibly extension-less) # name from error message in .log file # 4: non-found file from .tex file: either non-existent file # or I didn't find it. $read_depend = 0; # True to read depend file, false to generate it. $dep_file = "$root_filename.dep"; ## Figure out if we read the dependency file or generate a new one. if ( ! $force_generate_and_save_includes ) { if ( $generate_and_save_includes ) { if ( -e $dep_file ) { # Compare timestamp of dependency file and root tex file. $dep_mtime = &get_mtime("$dep_file"); $tex_mtime = &get_mtime("$texfile_name"); if ( $tex_mtime < $dep_mtime ) { $read_depend = 1; } } } elsif ( -e $dep_file ) # If dependency file already exists. { $read_depend = 1; } } if ( $includes_from_log ) { &parse_log; if ( $force_generate_and_save_includes || ($generate_and_save_includes && $read_depend == 0 ) ) { &update_depend_file; } } elsif ( $read_depend ) { # Read the dependency file open(dep_file) || die "Latexmk: Couldn't open dependency file [$root_filename.dep]\n"; while() { eval; } close(dep_file); } else { # Generate dependency file. &scan_for_includes("$texfile_name"); &update_depend_file; } # warn "====@includes===\n"; #************************************************************ # Ensure bbl file up-to-date. # Also remake the bbl file if there is a bad citation, or if we # use go_mode (which says to remake everything) # The call to &make_bbl will also remake the bbl file if it is # out-of-date with respect to the bib files # But ignore the return code from make_bbl, since the bbl file depends # on the aux file, which may in fact be out of date if the tex file has # changed, and we are about to re-latex it. if ($bibtex_mode) { &make_bbl($bad_citation || $go_mode); if ( ($failure > 0) && !$force_mode && ! $preview_continuous_mode) { goto END_FIRST; } } # Similarly for ind file. This is simpler because it only depends # on the idx file. if ($index_mode) { &make_ind($go_mode); if ( ($failure > 0) && !$force_mode && ! $preview_continuous_mode) { goto END_FIRST; } } $dest_condition_ignore = 0; #According to this setting # &make_latex_dvi_pdf will or will not examine whether destination # files exist when deciding to make latex &make_files($go_mode); END_FIRST: if ( ($failure > 0) && !$force_mode && ! $preview_continuous_mode) { &exit_msg1( $failure_msg, $failure ); } if ( $preview_continuous_mode ) { &make_preview_continous; } elsif ( $preview_mode ) { &make_preview; if ( ($failure > 0) && !$force_mode ) { &exit_msg1( $failure_msg, $failure ); } } elsif ( $printout_mode) { &make_printout; if ( ($failure > 0) && !$force_mode ) { &exit_msg1( $failure_msg, $failure ); } } } # end FILES #************************************************************ #### Subroutines #************************************************************ #************************************************************ #### Highest level sub make_files { my $do_build = $_[0]; my $new_files = &find_new_files; my $new_deps = &make_dependents($do_build); if ($failure > 0) {return;} if ( ($new_files > 0) || ($new_deps > 0) ) { $do_build = 1; } if ($need_dvi) { &make_latex_dvi_pdf($do_build, 'dvi'); if ($failure > 0) {return;} } if ( $need_ps ) { &make_postscript; if ($failure > 0) {return;} } if ( $need_pdf == 1 ) { &make_latex_dvi_pdf($do_build, 'pdf'); if ($failure > 0) {return;} } if ( $need_pdf == 2 ) { &make_pdf2; if ($failure > 0) {return;} } if ( $need_pdf == 3 ) { &make_pdf3; if ($failure > 0) {return;} } } #************************************************************ sub make_latex_dvi_pdf { my $do_build = $_[0]; my $dest_type = $_[1]; my $dest; my $processor; if ( $dest_type eq 'dvi' ) { $dest = "$root_filename.dvi"; $processor = $latex; } elsif ( $dest_type eq 'pdf' ) { $dest = "$root_filename.pdf"; $processor = $pdflatex; } else { die("Latexmk: BUG: undefined destination type $dest_type in make_latex_dvi_pdf\n"); } ## get initial last modified times. my $tex_mtime = &get_latest_mtime(@includes); my $dest_mtime= &get_mtime("$dest"); my $aux_mtime = &get_mtime("$root_filename.aux"); my $bib_mtime = &get_latest_mtime(@bib_files); my $bbl_mtime = &get_mtime("$root_filename.bbl"); my $ilg_mtime = &get_mtime("$root_filename.ilg"); my $ind_mtime = &get_mtime("$root_filename.ind"); ## - if no destination file (dvi or pdf), ## or .aux older than tex file or bib file or anything they input, ## then run latex. #&list_conditions ( # $do_build, # !(-e "$root_filename.aux"), # ($aux_mtime < $tex_mtime), # !(-e "$dest"), # ( (-e "$root_filename.bbl") && ($aux_mtime < $bbl_mtime) ), # ($dest_mtime < $tex_mtime), # ( (-e "$root_filename.ilg") && ($aux_mtime < $ilg_mtime) ), # ( (-e "$root_filename.ind") && ($aux_mtime < $ind_mtime) ), # ( $includes_from_log && ! -e "$root_filename.log" ) #); my $dest_bad = !(-e "$dest") || ($dest_mtime < $tex_mtime) ; if ($dest_condition_ignore) {$dest_bad = 0;} if ( $do_build || !(-e "$root_filename.aux") || ($aux_mtime < $tex_mtime) || $dest_bad || ( (-e "$root_filename.bbl") && ($aux_mtime < $bbl_mtime) ) || ( (-e "$root_filename.ilg") && ($aux_mtime < $ilg_mtime) ) || ( (-e "$root_filename.ind") && ($aux_mtime < $ind_mtime) ) || ( $includes_from_log && ! -e "$root_filename.log" ) ) { &build_latex_dvi_pdf($processor); if ($failure > 0) {return;} if ( $dest_type eq 'dvi') { &make_dvi_filtered; if ($failure > 0) {return;} } } else { warn "Latexmk: File '$dest' is up to date\n" if !$quell_uptodate_msgs; } } sub list_conditions { my $on = 0; foreach (@_) { if ($_) {$on = 1;} } if (!$on) {return;} print "On conditions: "; for ($i = 1; $i <= $#_+1; $i++) { if ($_[$i-1]) {print "$i ";} } print "===\n"; } #************************************************************ sub build_latex_dvi_pdf { # Argument: 0 = processor (e.g., 'latex' or 'pdflatex') # # I don't need to know whether I run latex or pdflatex! # # Repeat running latex as many times as needed to resolve cross # references, also running bibtex and makeindex as necessary. The # algorithm for determining whether latex needs to be run again is # whether certain generated files have changed since the previous # run. A limit (contained in $max_repeat) is applied on the # number of runs, to avoid infinite loops in pathological cases or # in the case of a bug. $max_repeat should be at least 4, to # allow for the maximum number of repeats under normal # circumstances, which is 3, plus one for unusual cases of page # breaks moving around. # # The criterion for needing a rerun is that one or both of the # .aux file and the .idx file has changed. To prove this: observe # that reruns are necessary because information that needs to be # read in from a file does not correspond to the current # conditions. The relevant files are: the .aux file, and possibly # one or more of: the index files, the table of contents file # (.toc), the list of figures file (.lof), the list of tables file # (.lot). The information read in is: cross reference # definitions, page references, tables of contents, figures and # tables. Note that the list of figures (for example) may itself # contain undefined references or incorrect page references. If # there is any incorrectness, changed information will be written # to the corresponding file on the current run, and therefore one # or more of the auxiliary files will have changed. # # In fact the lines in the .toc, .lof and .lot files correspond to # entries in the .aux file, so it is not necessary to check the # .toc, .lof and .lot files (if any). However WHETHER these files # are input by latex is not determined by the aux file, but does # affect the output. It is possible for the tex file to change # the state of one of the .toc, .lof, or .lot files between begin # required and not required. The same could happen for other # input files. # # For example, initially suppose no TOC is required, and that all # generated files are up-to-date. The .toc file is either not # present or is out-of-date. Then change the source file so # that a TOC is requested. Run latex; it uses wrong TOC # information, but the .aux file might not change. # # Two possibilities: (a) check .toc, .lof, .lot (possibly etc!) # files for changes; (b) check for a change in the list of input # files. We'll choose the second: since it requires less file # checking and is more general. It applies in all situations # where the extra auxiliary input files (e.g., .toc) correspond in # contents to the state of the .aux file, but only if these files # are used. # # Therefore a correct algorithm is to require a rerun if either of # the following is true: # # 1. The .aux file has changed from the previous run. # 2. The .idx file has changed from the previous run. # 3. The set of input files has changed from the previous run. # # Of course, if a previous version of one of these files did not # exist (as on a first run), then that implies a rerun is # necessary. And if a .aux file or a .idx file is not generated, # there is no point in testing it against a previous version. # # Assume on entry that the .ind and .bbl files are up-to-date with # respect to the already existing .idx and .aux files. This will # be the case if latexmk was used to make them. my $aux_file; my $processor = $_[0]; my $count_latex = 0; my $repeat = 0; do { # Arrival here means that a new run of latex/pdflatex is requested # Assume that the .ind file (if any) corresponds to the .idx file # from the previous run # and that the .bbl file (if any) corresponds to the .aux file from # the previous run. $repeat = 0; # Assume no repeat necessary. $count_latex++; # Count the number of passes through latex warn "------------\nRun number $count_latex of ", "$processor [$texfile_name]\n------------\n"; foreach $aux_file (@aux_files) { if ( -e $aux_file ) { warn "Saving old .aux file \"$aux_file\"\n" unless $silent; copy_file_and_time ( "$aux_file", "$aux_file.bak"); } } if ( (! -e "$root_filename.aux") && (! -e "$root_filename.aux.bak") ) { # Arrive here if neither of the .aux and the .aux.bak files exists # for the base file. # I make minimal .aux.bak file, containing a single line "\relax " # This is the same as the aux file generated for a latex file # which does not need any cross references, etc. Generating this # .aux.bak file will save a pass through latex on simple files. local $aux_bak_file = ">$root_filename.aux.bak"; open(aux_bak_file) || die "Cannot write file $aux_bak_file\n"; print aux_bak_file "\\relax \n"; close(aux_bak_file); } if ( (-e "$root_filename.aux") && $bibtex_mode) { # We have assumed that the bbl file is up-to-date # with respect to the previous aux file. However # it may be out-of-date with respect to the bib file(s). # So run bibtex in this case my $bibtex_return_code = &make_bbl(0); if ( ($bibtex_return_code eq 2) & !$force_mode ) { $failure = 1; $failure_msg = 'Bibtex reported an error'; return; } } if ( -e "$root_filename.idx" ) { warn "Saving old .idx file\n" unless $silent; copy_file_and_time ( "$root_filename.idx", "$root_filename.idx.bak"); } my @includes_previous = @includes; my ($pid, $return) = &Run("$processor $texfile_name"); $updated = 1; # Flag that some dependent file has been remade if ($return) { if (!$force_mode) { $failure = $return; $failure_msg = 'Latex encountered an error'; &aux_restore; return; } elsif ($silent) { # User may not have seen error warn "====Latex encountered an error: see .log file====\n"; } } $return = &parse_log; my $aux_changed = 0; my $idx_changed = 0; if ( $return == 0 ) { $failure = 1; $failure_msg = 'Latex failed to generate a log file'; &aux_restore; return; } if ( $includes_from_log ) { my @aux_files_previous = @aux_files; if ( @aux_files ne @aux_files_previous ){ $aux_changed = 1; warn "List of .aux files has changed. ", "I must run latex again\n" unless $silent; } } if ( !$aux_changed ) { # Look for changes in the individual aux files. foreach $aux_file (@aux_files) { if ( -e "$aux_file" ) { if ( &diff ("$aux_file", "$aux_file.bak") ) { warn ".aux file \"$aux_file\" changed. ", "I must run latex again\n" unless $silent; $aux_changed = 1; last; } else { warn "File \"$aux_file\" has not changed, ", "so it is valid\n" unless $silent; } } } } if ( (!-e "$root_filename.aux") && (-e "$root_filename.aux.bak") ) { warn "No aux file was generated, so I don't need .aux.bak file\n" unless $silent; unlink ("$root_filename.aux.bak"); } if ( (-e "$root_filename.aux") && $aux_changed && $bibtex_mode) { # Note running bibtex only makes sense if the aux file exists. # If any aux file has changed, then the citations may # have changed and we must run bibtex. # The argument to &make_bbl forces this. # &make_bbl also checks whether the bbl file is # out-of-date with respect to the bib files. my $bibtex_return_code = &make_bbl($aux_changed); if ( ($bibtex_return_code eq 2) && !$force_mode ) { $failure = 1; $failure_msg = 'Bibtex reported an error'; return; } } if ( -e "$root_filename.idx" ) { if ( &diff ("$root_filename.idx", "$root_filename.idx.bak") ) { warn "The .idx file changed. I must run latex again\n" unless $silent; # idx file exists and has changed # implies idx file written # implies indexing being used $index_mode = 1; $idx_changed = 1; } else { warn "The .idx file has not changed, so it is valid\n" unless $silent; } if ($index_mode) { my $makeindex_return_code = &make_ind($idx_changed); if ( ($makeindex_return_code eq 2) && !$force_mode ) { $failure = 1; $failure_msg = 'Makeindex encountered an error'; return; } } } else { if ($index_mode) { warn "No .idx file was generated, but index_mode is set; ", "I will unset it" unless $silent; $index_mode = 0; } if ( -e "$root_filename.idx.bak") { warn "No idx file was generated. ", "So I delete unneeded .idx.bak file\n" unless $silent; unlink ("$root_filename.idx.bak"); } } if ( @includes ne @includes_previous ) { warn "The set of input files changed. I must run latex again\n" unless $silent; } if ( $aux_changed || $idx_changed ||( @includes ne @includes_previous ) ) { $repeat = 1; } if ( $count_latex ge $max_repeat ) { # Avoid infinite loop by having a maximum repeat count # Getting here represents some kind of weird error. if ($repeat ) { warn "Maximum runs of latex reached ", "without correctly resolving cross references\n"; } $repeat = 0; } } until ($repeat == 0); if ($bibtex_mode) { my $retcode = &check_for_bibtex_warnings; if ($retcode == 1) { print "See $root_filename.blg for details\n"; } elsif ($retcode == 2) { return; } } if (!$force_mode && $bad_reference) { $failure = 1; $failure_msg = 'Latex could not resolve all references'; return; } if (!$force_mode && $bad_citation) { $failure = 1; $failure_msg = 'Latex could not resolve all citations or labels'; return; } } #************************************************************ # Finds the basename of the root file # Arguments: # 1 - Filename to breakdown # 2 - Where to place base file # 3 - Where to place tex file # Returns non-zero if tex file does not exist # # The rules for determining this depend on the implementation of TeX. # The variable $extension_treatment determines which rules are used. sub find_basename { local($given_name, $base_name, $ext, $path, $tex_name); $given_name = $_[0]; if ( "$extension_treatment" eq "miktex_old" ) { # Miktex v. 1.20d: # 1. If the filename has an extension, then use it. # 2. Else append ".tex". # 3. The basename is obtained from the filename by # removing the path component, and the extension, if it # exists. If a filename has a multiple extension, then # all parts of the extension are removed. # 4. The names of generated files (log, aux) are obtained by # appending .log, .aux, etc to the basename. Note that # these are all in the CURRENT directory, and the drive/path # part of the originally given filename is ignored. # # Thus when the given filename is "\tmp\a.b.c", the tex # filename is the same, and the basename is "a". ($base_name, $path, $ext) = fileparse ($given_name, '\..*'); if ( "$ext" eq "") { $tex_name = "$given_name.tex"; } else { $tex_name = $given_name; } $_[1] = $base_name; $_[2] = $tex_name; } elsif ( "$extension_treatment" eq "unix" ) { # unix (at least web2c 7.3.1) => # 1. If filename.tex exists, use it, # 2. else if filename exists, use it. # 3. The base filename is obtained by deleting the path # component and, if an extension exists, the last # component of the extension, even if the extension is # null. (A name ending in "." has a null extension.) # 4. The names of generated files (log, aux) are obtained by # appending .log, .aux, etc to the basename. Note that # these are all in the CURRENT directory, and the drive/path # part of the originally given filename is ignored. # # Thus when the given filename is "/tmp/a.b.c", there are two # cases: # a. /tmp/a.b.c.tex exists. Then this is the tex file, # and the basename is "a.b.c". # b. /tmp/a.b.c.tex does not exist. Then the tex file is # "/tmp/a.b.c", and the basename is "a.b". if ( -e "$given_name.tex" ) { $tex_name = "$given_name.tex"; } else { $tex_name = "$given_name"; } ($base_name, $path, $ext) = fileparse ($tex_name, '\.[^\.]*'); $_[1] = $base_name; $_[2] = $tex_name; } else { die "Latexmk: Incorrect configuration gives \$extension_treatment=", "\"$extension_treatment\"\n"; } if ($diagnostics) { print "Given=\"$given_name\", tex=\"$tex_name\", base=\"$base_name\"\n"; } return ! -e $tex_name; } #************************************************************ sub make_bbl { # If necessary, make bbl file. Assume bibtex mode on. # Force run if first argument is non-zero. # Return 0 if nothing made, # 1 if bbl file made, # 2 if bibtex reported an error # 3 if .blg file couldn't be opened # 4 if there was another error my $bib_mtime = &get_latest_mtime(@bib_files); my $bbl_mtime = &get_mtime("$root_filename.bbl"); ## if no .bbl or .bib changed since last bibtex run, run bibtex. if ( !-e "$root_filename.aux" ) { # bibtex reads aux file, so if there is no aux file, there is # nothing to do return 0; } if (($_[0] != 0) || !(-e "$root_filename.bbl") || ($bbl_mtime < $bib_mtime) ) { warn "------------\nRunning $bibtex [$root_filename]", "\n------------\n" unless $silent; my ($pid, $return) = &Run("$bibtex $root_filename"); $updated = 1; $bbl_mtime = &get_mtime("$root_filename.bbl"); if ( $return != 0 ) { return 2; } $return = &check_for_bibtex_errors; if ( $return == 0 ) { return 1;} elsif ( $return == 1 ) { return 2;} elsif ( $return == 2 ) { return 3;} else { return 4; } } else { return 0; } } #************************************************************ sub make_ind { # If necessary, make ind file. Assume makeindex mode on. # Force run if first argument is non-zero. # Return 0 if nothing made, # 1 if ind file made, # 2 if makeindex reported an error if ( !-e "$root_filename.idx" ) { # makeindex reads idx file, so if there is no idx file, there is # nothing to do return 0; } if ( ($_[0] != 0) || !(-e "$root_filename.ind") ) { warn "------------\nRunning $makeindex [$root_filename]", "\n------------\n" unless $silent; my ($pid, $return) = &Run("$makeindex $root_filename"); $updated = 1; if ($return) { $failure = 1; $failure_msg = 'Problem with makeindex'; return 2; } else { return 1; } } else { return 0; } } #************************************************************ sub find_new_files { my @new_includes = (); MISSING_FILE: foreach my $missing (sort keys %includes_missing) { my ($base, $path, $ext) = fileparse ($missing, '\.[^\.]*'); if ( -e "$missing.tex" ) { push @new_includes, "$missing.tex"; # It's probably best to try all possibilities, since # we don't know which one applies. So go on to next case. # next MISSING_FILE; } if ( -e $missing ) { push @new_includes, $missing; # next MISSING_FILE; } if ( $ext ne "" ) { foreach my $dep (@cus_dep_list){ my ($fromext,$toext) = split(' ',$dep); if ( ( "$ext" eq ".$toext" ) && ( -e "$path$base.$fromext" ) ) { # Source file for the missing file exists # So we have a real include file, and it will be made # next time by &make_dependents push @new_includes, $missing ; # next MISSING_FILE; } # no point testing the $toext if the file doesn't exist. } } else { # $_ doesn't exist, $_.tex doesn't exist, # and $_ doesn't have an extension foreach my $dep (@cus_dep_list){ my ($fromext,$toext) = split(' ',$dep); if ( -e "$path$base.$fromext" ) { # Source file for the missing file exists # So we have a real include file, and it will be made # next time by &make_dependents push @new_includes, "$path$base.$toext" ; # next MISSING_FILE; } if ( -e "$path$base.$toext" ) { # We've found the extensionfor the missing file, # and the file exists push @new_includes, "$path$base.$toext" ; # next MISSING_FILE; } } } } # end MISSING_FILES @new_includes = uniq( sort(@new_includes) ); @includes = uniq( sort(@includes, @new_includes) ); my $found = $#new_includes + 1; if ( $diagnostics && ( $found > 0 ) ) { warn "Detected previously missing files:\n"; foreach (@new_includes) { warn " '$_'\n"; } } return $found; } #************************************************************ sub make_dependents { # Usage: make_dependents(build) # First argument = 1 => rebuild unconditionally # 0 => rebuild only if dest is out-of-date # Return 0 if nothing made, 1 if something made my $build = shift; my $makes = 0; # Count of makes done FILE: foreach my $file (@includes) { my ($base_name, $path, $toext) = fileparse ($file, '\.[^\.]*'); $base_name = $path.$base_name; if ( $toext eq "" ) {next FILE;} $toext =~ s/^\.//; DEP: foreach my $dep ( @cus_dep_list ) { my ($fromext,$proptoext,$must,$func_name) = split(' ',$dep); if ( $toext eq $proptoext ) { # Found match of rule if ( -e "$base_name.$fromext" ) { # From file exists, now check if it is newer if (( ! (-e "$base_name.$toext" )) || $build || ( &get_mtime("$base_name.$toext") < &get_mtime("$base_name.$fromext") ) ) { warn "------------\nRunning $func_name [$base_name]\n------------\n" unless $silent; my $return = &$func_name($base_name); $updated = 1; if ( !$force_mode && $return ) { $failure = $return; $failure_msg = "$func_name encountered an error"; last FILE; } else { $makes++; } } } else { # Source file does not exist # Perhaps the rule is not to be applied. if ( !$force_mode && ( $must != 0 )) { $failure = 1; $failure_msg = "File '$base_name.$fromext' does not exist ". "to build '$base_name.$toext'"; last FILE; } } # } # End of Rule found } # End DEP } # End FILE return ($makes>0 ? 1 : 0); } # End sub make_dependents #************************************************************ sub make_dvi_filtered { my $dvi_file = "$root_filename.dvi"; return if ( length($dvi_filter) == 0 ); if ( ! -e $dvi_file ) { warn "Dvi file \"$dvi_file\" has not been made, so I cannot filter it\n"; return; } warn "------------\nRunning $dvi_filter [$root_filename]\n------------\n" unless $silent; &Run("$dvi_filter < $dvi_file > $root_filename.dviF"); $updated = 1; } #************************************************************ sub make_pdf2 { my $ps_file; my $pdf_file; if ( length($ps_filter) == 0 ) {$ps_file = "$root_filename.ps";} else {$ps_file = "$root_filename.psF";} $pdf_file = "$root_filename.pdf"; my $ps_mtime = &get_mtime("$ps_file"); my $pdf_mtime = &get_mtime("$pdf_file"); if ( ! -e $ps_file ) { warn "Postscript file \"$ps_file\" has not been made\n"; return; } if ((! -e "$pdf_file") ||( $pdf_mtime < $ps_mtime ) ) { warn "------------\nRunning $ps2pdf [$root_filename]\n------------\n" unless $silent; &Run("$ps2pdf $ps_file $pdf_file"); $updated = 1; } else { warn "Latexmk: File '$pdf_file' is up to date\n" if !$quell_uptodate_msgs; } } #************************************************************ sub make_pdf3 { my $dvi_file; my $pdf_file; if ( length($dvi_filter) == 0 ) {$dvi_file = "$root_filename.dvi";} else {$dvi_file = "$root_filename.dviF";} $pdf_file = "$root_filename.pdf"; my $dvi_mtime = &get_mtime("$dvi_file"); my $pdf_mtime = &get_mtime("$pdf_file"); if ( ! -e $dvi_file ) { warn "Dvi file \"$dvi_file\" has not been made, so I cannot convert it to pdf\n"; return; } if ((! -e "$pdf_file") ||( $pdf_mtime < $dvi_mtime ) ) { warn "------------\nRunning $dvipdf [$root_filename]\n------------\n" unless $silent; &Run("$dvipdf $dvi_file $pdf_file"); $updated = 1; } else { warn "Latexmk: File '$pdf_file' is up to date\n" if !$quell_uptodate_msgs; } } #************************************************************ sub make_printout { my $ext = ''; # extension of file to print my $command = ''; # command to print it if ( $print_type eq 'dvi' ) { if ( length($dvi_filter) == 0 ) { $ext = '.dvi'; } else { $ext = '.dviF'; } $command = $lpr_dvi; } elsif ( $print_type eq 'pdf' ) { $ext = '.pdf'; $command = $lpr_pdf; } elsif ( $print_type eq 'ps' ) { if ( length($ps_filter) == 0 ) { $ext = '.ps'; } else { $ext = '.psF'; } $command = $lpr; } elsif ( $print_type eq 'none' ) { warn "------------\nPrinting is configured off\n------------\n"; return; } else { die "Latexmk: incorrect value \"$print_type\" for type of file to print\n". "Allowed values are \"dvi\", \"pdf\", \"ps\", \"none\"\n" } my $file = $root_filename.$ext; if ( ! -e $file ) { warn "File \"$file\" has not been made, so I cannot print it\n"; return; } warn "------------\nPrinting using $command $file\n------------\n" unless $silent; &Run("$command $file"); } #************************************************************ sub make_postscript { my $tmpfile; my $header; my $dvi_file; # Figure out the dvi file name if ( length($dvi_filter) == 0 ) { $dvi_file = "$root_filename.dvi"; } else { $dvi_file = "$root_filename.dviF"; } if ( ! -e $dvi_file ) { warn "Dvi file \"$dvi_file\" has not been made, so I cannot convert it to postscript\n"; return; } # Do banner stuff if ( $banner ) { ## Make temp banner file # local(*INFILE,*OUTFILE,$count); local(*OUTFILE,$count); $tmpfile = "$tmpdir/latexmk.$$"; $count = 0; while ( -e $tmpfile ) { $count = $count + 1; $tmpfile = "$tmpdir/latexmk.$$.$count"; } if ( ! open(OUTFILE, ">$tmpfile") ) { die "Latexmk: Could not open temporary file [$tmpfile]\n"; } print OUTFILE "userdict begin /bop-hook{gsave 200 30 translate\n"; print OUTFILE "65 rotate /Times-Roman findfont $banner_scale scalefont setfont\n"; print OUTFILE "0 0 moveto $banner_intensity setgray ($banner_message) show grestore}def end\n"; close(OUTFILE); $header = "-h $tmpfile"; } else { $header = ''; } my $ps_mtime = &get_mtime("$root_filename.ps"); my $dvi_mtime = &get_mtime("$dvi_file"); if ((! -e "$root_filename.ps") ||( $ps_mtime < $dvi_mtime ) ) { warn "------------\nRunning $dvips [$root_filename]\n------------\n" unless $silent; &Run("$dvips $header $dvi_file -o $root_filename.ps"); ## Do we have postscript filtering? if ( length($ps_filter) != 0 ) { warn "------------\nRunning $ps_filter [$root_filename]\n------------\n" unless $silent; &Run("$ps_filter < $root_filename.ps > $root_filename.psF"); } $updated = 1; } else { warn "Latexmk: File '$root_filename.ps' is up to date\n" if !$quell_uptodate_msgs; } if ( $banner ) { unlink("$tmpfile"); } } #************************************************************ # run appropriate previewer. sub make_preview { my $ext; my $viewer; if ( $view eq 'dvi' ) { $viewer = $dvi_previewer; $ext = '.dvi'; if ( length($dvi_filter) != 0 ) { $ext = '.dviF'; } } elsif ( $view eq 'ps' ) { $viewer = $ps_previewer; $ext = '.ps'; if ( length($ps_filter) != 0 ) { $ext = '.psF'; } } elsif ( $view eq 'pdf' ) { $viewer = $pdf_previewer; $ext = '.pdf'; } else { die "Latexmk: BUG: No preview method defined\n"; } my $view_file = "$root_filename$ext"; if ( ! -e $view_file ) { warn "File \"$view_file\" has not been made, so I cannot view it\n"; return; } warn "------------\nStarting previewer: $viewer $view_file\n------------\n" unless $silent; my ($pid, $return) = &Run ("$viewer $view_file"); if ($return){ warn "Latexmk: Could not start previewer [$viewer $view_file]"; } # exit; } #************************************************************ sub make_preview_continous { # How do we persuade viewer to update. Default is to do nothing. my $viewer_update_method = 0; # Extension of file: my $ext; $quell_uptodate_msgs = 1; if ( $view eq 'dvi' ) { $viewer = $dvi_previewer; $viewer_update_method = $dvi_update_method; $ext = '.dvi'; if ( length($dvi_filter) != 0 ) { $ext = '.dviF'; } } elsif ( $view eq 'ps' ) { $viewer = $ps_previewer; $viewer_update_method = $ps_update_method; $ext = '.ps'; if ( length($ps_filter) != 0 ) { $ext = '.psF'; } } elsif ( $view eq 'pdf' ) { $viewer_update_method = $pdf_update_method; $viewer = $pdf_previewer; $ext = '.pdf'; } else { die "Latexmk: BUG: No preview method defined\n"; } # Viewer information: my $viewer_running = 0; # No viewer running yet my $view_file = "$root_filename$ext"; my $viewer_process = 0; # default: no viewer process number known $dest_condition_ignore = 1; #make_latex_dvi-pdf will ignore destination #in make rule. If the destination file does not #exists, it is because of an error. # Loop forever, rebuilding .dvi and .ps as necessary. my $first_time = 1; while ( 1 ) { # Wait to run a viewer until we know we have the view file # If there are errors in the initial run, the if ( (!$viewer_running) && (-e $view_file) ) { # note, we only launch a previewer if one isn't already running... # But I only know how to do this under UNIX $viewer_process = &find_process_id( $view_file ); if ( $viewer_process ) { warn "Previewer is already running\n" if $first_time && !$silent; } else { warn "I have not found a previewer that is already running. \n", "So I will start it: $viewer $view_file\n------------\n" unless $silent; my $retcode; ($viewer_process, $retcode) = &Run ("start $viewer $root_filename$ext"); if ( $retcode != 0 ) { if ($force_mode) { warn "I could not run previewer\n"; } else { &exit_msg1( "I could not run previewer", $retcode); } } else { $viewer_running = 1; } # end analyze result of trying to run viewer } # end if $viewer_process } # end start viewer if ( $first_time || $updated ) { print "\n=== Watching for updated files. Use ctrl/C to stop ...\n"; $first_time = 0; } $updated = 0; $failure = 0; sleep($sleep_time); &make_files; if ( ($failure <= 0) && ($viewer_process != 0) && $updated && ($viewer_update_method == 2) ) { if ( $diagnostics ) { print "I am signalling viewer, process ID $viewer_process\n"; } kill 'USR1', $viewer_process; } } #end infinite_loop } #end sub make_preview_continuous #************************************************************ # cleanup_basic # - erases basic set of generated files, exits w/ no other processing. # (all but aux, dep, dvi, pdf, and ps), # and also texput.log, and files with extensions in $clean_ext sub cleanup_basic { # Basic set: unlink("$root_filename.aux.bak"); unlink("$root_filename.bbl"); unlink("$root_filename.blg"); unlink("$root_filename.log"); unlink("$root_filename.ind"); unlink("$root_filename.idx"); unlink("$root_filename.idx.bak"); unlink("$root_filename.ilg"); unlink("$root_filename.toc"); unlink("$root_filename.toc.bak"); unlink("$root_filename.lof"); unlink("$root_filename.lot"); unlink("texput.log"); # Do any other file extensions specified foreach $ext (split(' ',$clean_ext)) { unlink("$root_filename.$ext"); } } #************************************************************ # cleanup_dvi_ps_pdf # - erases generated dvi, ps, and pdf files (and others specified in # $cleanup_full_ext), # and also texput.dvi, and files with extensions in $clean_full_ext sub cleanup_dvi_ps_pdf { unlink("$root_filename.dvi"); unlink("$root_filename.pdf"); unlink("$root_filename.ps"); unlink("$root_filename.dviF"); unlink("$root_filename.psF"); unlink("texput.dvi"); # Do any other file extensions specified foreach $ext (split(' ',$clean_full_ext)) { unlink("$root_filename.$ext"); } } #************************************************************ # cleanup_aux_dep # - erases generated aux and dep files, and also texput.aux sub cleanup_aux_dep { unlink("$root_filename.aux"); unlink("$root_filename.dep"); unlink("texput.aux"); # .aux files are also made for \include'd files foreach my $include (@includes) { $include =~ s/\.[^\.]*$/.aux/; unlink($include); } } #************************************************************ sub aux_restore { warn "Latexmk: restoring last $root_filename.aux file\n"; # But don't copy the time from the aux.bak file # So the aux file will look up-to-date copy_file_keep_time( "$root_filename.aux.bak", "$root_filename.aux" ); } #************************************************************ sub exit_msg1 { # exit_msg1( error_message, retcode [, action]) # 1. display error message # 2. if action set, then restore aux file # 3. exit with retcode warn "\n------------\n"; warn "Latexmk: $_[0].\n"; warn "-- Use the -f option to force complete processing.\n"; if ($_[2]) { &aux_restore; } my $retcode = $_[1]; if ($retcode >= 256) { # Retcode is the kind returned by system from an external command # which is 256 * command's_retcode $retcode /= 256; } exit $retcode; } #************************************************************ sub die_help # Die giving diagnostic from arguments and how to get help. { die "\n@_\nUse\n latexmk -help\nto get usage information\n"; } #************************************************************ sub print_help { print "Latexmk $version_num: Automatic LaTeX document generation routine\n\n", "Usage: latexmk [latexmk_options] [filename ...]\n\n", " Latexmk_options:\n", " -bm - Print message across the page when converting to postscript\n", " -bi - Set contrast or intensity of banner\n", " -bs - Set scale for banner\n", " -commands - list commands used by latexmk for processing files\n", " -c - clean up (remove) all nonessential files, except\n", " dvi, ps and pdf files\n", " -C - clean up (remove) all nonessential files\n", " including aux, dep, dvi, postscript and pdf files\n", " -c1 - clean up (remove) all nonessential files,\n", " including dvi, pdf and ps files, but excluding aux and dep files \n", " -d - Print `DRAFT' across the page when converting to postscript\n", " -dF - Filter to apply to dvi file\n", " -dvi - generate dvi\n", " -dvi- - turn off required dvi\n", " -f - force continued processing past errors\n", " -f- - turn off forced continuing processing past errors\n", " -F - Ignore non-existent files when making dependencies\n", " -F- - Turn off -F\n", " -g - process regardless of file timestamps\n", " -g- - Turn off -g\n", " -h - print help\n", " -help - print help\n", " -i - rescan for input if dependency file older than tex file\n", " -i- - Turn off -i\n", " -il - make list of input files by parsing log file\n", " -it - make list of input files by parsing tex file\n", " -I - force rescan for input files\n", " -I- - Turn off -I\n", " -l - force landscape mode\n", " -l- - turn off -l\n", " -pdf - generate pdf by pdflatex\n", " -pdfdvi - generate pdf by dvipdf\n", " -pdfps - generate pdf by ps2pdf\n", " -pdf- - turn off pdf\n", " -ps - generate postscript\n", " -ps- - turn off postscript\n", " -pF - Filter to apply to postscript file\n", " -p - print document after generating postscript.\n", " (Can also print .dvi or .pdf files -- see documentation)\n", " -print=dvi - print dvi file\n", " -print=ps - print ps file\n", " -print=pdf - print pdf file\n", " -pv - preview document. (Side effect turn off continuous preview)\n", " -pv- - turn off preview mode\n", " -pvc - preview document and continuously update. (This also turns\n", " on force mode, so errors do not cause latexmk to stop.)\n", " (Side effect: turn off ordinary preview mode.)\n", " -pvc- - turn off -pvc\n", " -r - Read custom RC file\n", " -silent - silence progress messages from called programs\n", " -v - display program version\n", " -verbose - display usual progress messages from called programs\n", " -version - display program version\n", " -view=default - viewer is default (dvi, ps, pdf)\n", " -view=dvi - viewer is for dvi\n", " -view=ps - viewer is for ps\n", " -view=pdf - viewer is for pdf\n", " filename = the root filename of LaTeX document\n", "\n", "-p, -pv and -pvc are mutually exclusive\n", "-h, -c and -C overides all other options.\n", "-pv and -pvc require one and only one filename specified\n", "All options can be introduced by '-' or '--'. (E.g., --help or -help.)\n", "Contents of RC file specified by -r overrides options specified\n", " before the -r option on the command line\n"; exit; } #************************************************************ sub print_commands { warn "Commands used by latexmk:\n", " To run latex, I use \"$latex\"\n", " To run pdflatex, I use \"$pdflatex\"\n", " To run bibtex, I use \"$bibtex\"\n", " To run makeindex, I use \"$makeindex\"\n", " To make a ps file from a dvi file, I use \"$dvips\"\n", " To make a ps file from a dvi file with landscape format, ", "I use \"$dvips_landscape\"\n", " To make a pdf file from a dvi file, I use \"$dvipdf\"\n", " To make a pdf file from a ps file, I use \"$ps2pdf\"\n", " To view a pdf file, I use \"$pdf_previewer\"\n", " To view a ps file, I use \"$ps_previewer\"\n", " To view a ps file in landscape format, ", "I use \"$ps_previewer_landscape\"\n", " To view a dvi file, I use \"$dvi_previewer\"\n", " To view a dvi file in landscape format, ", "I use \"$dvi_previewer_landscape\"\n", " To print a ps file, I use \"$lpr\"\n", " To print a dvi file, I use \"$lpr_dvi\"\n", " To print a pdf file, I use \"$lpr_pdf\"\n", " To find running processes, I use \"$pscmd\", ", "and the process number is at position $pid_position\n"; warn "Notes:\n", " Command starting with \"start\" is run detached\n", " Command that is just \"start\" without any other command, is\n", " used under MS-Windows to run the command the operating system\n", " has associated with the relevant file.\n", " Command starting with \"NONE\" is not used at all\n"; } #************************************************************ #### Tex-related utilities # check for citation which bibtex didnt find. sub check_for_bibtex_errors # return 0: OK, 1: bibtex error, 2: could not open .blg file. { my $log_name = "$root_filename.blg"; my $log_file = 0; my $retcode = open( $log_file, "<$log_name" ); if ( $retcode == 0) { if ( !$force_mode ) { $failure = 1; $failure_msg = "Could not open bibtex log file for error check"; } warn "Could not open bibtex log file for error check\n"; return 2; } $retcode = 0; while (<$log_file>) { # if (/Warning--/) { return 1; } if (/error message/) { $retcode = 1; last; } } close $log_file; return $retcode; } #************************************************************ # check for bibtex warnings sub check_for_bibtex_warnings # return 0: OK, 1: bibtex warnings, 2: could not open .blg file. { my $log_name = "$root_filename.blg"; my $log_file = 0; my $retcode = open( $log_file, "<$log_name" ); if ( $retcode == 0 ) { $failure = 1; $failure_msg = "Could not open bibtex log file for warning check"; warn "Latexmk: $failure_msg\n"; return 2; } my $have_warning = 0; while (<$log_file>) { if (/Warning--/) { print "Bibtex warning: $_"; $have_warning = 1} if (/error message/) { print "Bibtex error: $_"; $have_warning = 1} } close $log_file; return $have_warning; } #************************************************************ # - looks recursively for included & inputted and psfig'd files and puts # them into @includes. # - note only primitive comment removal: cannot deal with escaped %s, but then, # when would they occur in any line important to latexmk?? sub scan_for_includes { my $texfile_name = $_[0]; warn "-----Scanning [$texfile_name] for input files etc ... -----\n"; &scan_for_includes_($texfile_name); ## put root tex file into list of includes. push @includes, $texfile_name; } sub scan_for_includes_ { local(*FILE,$orig_filename); ##JCC local($ignoremode,$line); $ignoremode = 0; $line = 0; if (!open(FILE,$_[0])) { warn "Latexmk: could not open input file [$_[0]]\n"; return; } LINE: while() { $line = $line + 1; if ( /^\s*(%#.*)\s*$/ ) { $_ = $1; ##warn "======Metacommand \"$_\"\n"; if ( /%#{}end.raw/ || /%#{latexmk}end.ignore/ ) { $ignoremode = 0; warn " Ignore mode off, at line $line in file $_[0].\n"; } elsif ( $ignoremode == 1 ) { # In ignore mode only end.raw, etc metacommands are recognized. next LINE; } elsif ( /%#{}raw/ || /%#{}begin.raw/ || /%#{latexmk}ignore/ || /%#{latexmk}begin.ignore/ ) { $ignoremode = 1; warn " Ignore mode on, at line $line in file $_[0].\n"; } elsif ( /%#{latexmk}include.file[\040\011]+([^\040\011\n]*)/ || /%#{latexmk}input.file[\040\011]+([^\040\011\n]*)/ ) { push @includes, $1; warn " Adding input file \"$1\" at line $line in file $_[0].\n"; } else { # Unrecognized metacommands are, by specification, to be ignored. warn "Unrec. \"$_\"\n"; } next LINE; } if ( $ignoremode == 1 ) { ##warn "Skipping a line:\n $_"; next LINE; } ($_) = split('%',$_); # primitive comment removal if (/\\def/ || /\\newcommand/ || /\\renewcommand/ || /\\providecommand/) { ##JCC Ignore definitions: warn "Ignoring definition:\n $_"; } elsif (/\\include[{\s]+([^\001\040\011}]*)[\s}]/) { $full_filename = $1; $orig_filename = $full_filename; $full_filename = &find_file_ext($full_filename, 'tex', \@TEXINPUTS); if ($full_filename) { push @includes, $full_filename; if ( -e $full_filename ) { warn " Found input file [$full_filename]\n"; &scan_for_includes_($full_filename); } else { if ( $orig_filename =~ /^\// ) { warn "Latexmk: In \\include, ", "could not find file [$orig_filename]\n"; } else { warn "Latexmk: In \\include, ", "could not find file [$orig_filename] in path [@TEXINPUTS]\n"; warn " assuming in current directory ($full_filename)\n"; } } } else { if ( ! $force_include_mode ) { if ( $orig_filename =~ /^\// ) { die "Latexmk: In \\include, ", "could not find file [$orig_filename]\n"; } else { die "Latexmk: In \\include, ", "could not find file [$orig_filename] in path [@TEXINPUTS]\n"; } } } } elsif (/\\input[{\s]+([^\001\040\011}]*)[\s}]/) { $full_filename = $1; $orig_filename = $full_filename; $full_filename = &find_file_ext($full_filename, 'tex', \@TEXINPUTS); if ($full_filename) { push @includes, $full_filename; # warn "added '$full_filename'\n"; if ( -e $full_filename ) { warn " Found input for file [$full_filename]\n"; &scan_for_includes_($full_filename); } else { if ( $orig_filename =~ /^\// ) { warn "Latexmk: In \\input, could not find file [$orig_filename]\n"; } else { warn "Latexmk: In \\input, ", "could not find file [$orig_filename] in path [@TEXINPUTS]\n"; warn " assuming in current directory ($full_filename)\n"; } } } else { if ( ! $force_include_mode ) { if ( $orig_filename =~ /^\// ) { die "Latexmk: In \\input, could not find file [$orig_filename]\n"; } else { die "Latexmk: In \\input, ", "could not find file [$orig_filename] in path [@TEXINPUTS]\n"; } } } } elsif (/\\blackandwhite{([^\001\040\011}]*)}/ || /\\colorslides{([^\001}]*)}/) { ############ $slide_mode = 1; $full_filename = &find_file_ext($1, 'tex', \@TEXINPUTS); if ($full_filename) { push @includes, $full_filename; if ( -e $full_filename ) { warn " Found slide input for file [$full_filename]\n"; &scan_for_includes_($full_filename); } } } elsif (/\\psfig{file=([^,}]+)/ || /\\psfig{figure=([^,}]+)/) { $orig_filename = $1; $full_filename = &find_file($1, \@psfigsearchpath); if ($full_filename) { push @includes, $full_filename; if ( -e $full_filename ) { warn " Found psfig for file [$full_filename]\n"; } } } elsif ( /\\epsfbox{([^}]+)}/ || /\\epsfbox\[[^\]]*\]{([^}]+)}/ || /\\epsffile{([^}]+)}/ || /\\epsffile\[[^\]]*\]{([^}]+)}/ || /\\epsfig{file=([^,}]+)/ || /\\epsfig{figure=([^,}]+)/ ) { $orig_filename = $1; $full_filename = &find_file($1, \@psfigsearchpath); if ($full_filename) { push @includes, $full_filename; if ( -e $full_filename ) { warn " Found epsf for file [$full_filename]\n"; } } } elsif ( /\\includegraphics{([^}]+)}/ || /\\includegraphics\[[^\]]*\]{([^}]+)}/ ) { $orig_filename = $1; $full_filename = &find_file_ext($1,'eps', \@psfigsearchpath); if ($full_filename) { push @includes, $full_filename; if ( -e $full_filename ) { warn " Found epsf for file [$full_filename]\n"; } } else { warn "Latexmk: For \\includegraphics, ", "could not find file [$orig_filename]\n", " in path [@psfigsearchpath]\n"; if ( ! $force_include_mode ) {die "\n";} } } elsif (/\\documentstyle[^\000]+landscape/) { warn " Detected landscape mode\n"; $landscape_mode = 1; } elsif (/\\bibliography{([^}]+)}/) { @bib_files = split /,/, $1; &find_file_list1( \@bib_files, \@bib_files, '.bib', \@BIBINPUTS ); warn " Found bibliography files [@bib_files]\n" unless $silent; $bibtex_mode = 1; } elsif (/\\psfigsearchpath{([^}]+)}/) { @psfigsearchpath = &split_search_path(':', '', $1); } elsif (/\\graphicspath{([^}]+)}/) { @psfigsearchpath = &split_search_path(':', '', $1); } elsif (/\\makeindex/) { $index_mode = 1; warn " Detected index mode\n"; } } } #************************************************** sub parse_log { # Scan log file for: include files, bibtex mode, # reference_changed, bad_reference, bad_citation # In bibtex mode, scan aux file for bib files # Return value: 1 if success, 0 if no log file. # Set global variables: # @includes to list of included files that exist or that appear to be # genuine files (as opposed to incorrectly parsed names). # %includes_missing to list of files that latex appeared to search for # and didn't find, i.e., from error messages # @aux_files to list of .aux files. # Leave these unchanged if there is no log file. # $reference_changed, $bad_reference, $bad_citation my $log_name = "$root_filename.log"; my $log_file = 0; if ( ! open( $log_file, "<$log_name" ) ) { return 0; } my $line_number = 0; my $graphic_line = 0; my @bbl_files = (); my @ignored_input_files = (); my @existent = (); my @include_list = ($texfile_name); my @include_graphics_list = (); my %includes_from_errors = (); @includes = (); %includes_missing = (); $reference_changed = 0; $bad_reference = 0; $bad_citation = 0; ## ?? New. We'll determine these modes from parsing the file $bibtex_mode = 0; $index_mode = 0; LINE: while(<$log_file>) { $line_number++; chomp; if ( $line_number == 1 ){ if ( /^This is / ) { # First line OK\n"; next LINE; } else { die "Error on first line of \"$log_name\". This is not a TeX log file.\n$_"; } } # Handle wrapped lines: # They are lines brutally broken at exactly $log_wrap chars # excluding line-end. my $len = length($_); while ($len == $log_wrap) { my $extra = <$log_file>; chomp $extra; $line_number++; $len = length($extra); $_ .= $extra; } # Check for changed references, bad references and bad citations: if (/Rerun to get/) { warn "\n=== References changed.\n"; $reference_changed = 1; } if (/LaTeX Warning: (Reference[^\001]*undefined)./) { warn "\n=== $1 \n"; $bad_reference = 1; } if (/LaTeX Warning: (Citation[^\001]*undefined)./) { warn "\n=== $1 \n"; $bad_citation = 1; } if ( /^Document Class: / ) { # Latex message next LINE; } if ( /^Output written on / ) { # Latex message next LINE; } if ( /^Underfull / ) { # Latex error/warning next LINE; } if ( /^Overfull / ) { # Latex error/warning next LINE; } if ( /^\(Font\)/ ) { # Font info line next LINE; } if ( /^Package: / ) { # Package sign-on line next LINE; } if ( /^Document Class: / ) { # Class sign-on line next LINE; } if ( /^Writing index file / ) { $index_mode =1; warn "Latexmk: Index file written, so turn on index_mode\n" unless $silent; next LINE; } if ( /^No file .*?\.bbl./ ) { warn "Non-existent bbl file, so turn on bibtex_mode\n $_" unless $bibtex_mode == 1; $bibtex_mode = 1; next LINE; } if ( /^File: ([^\s\[]*) Graphic file \(type / ) { # First line of message from includegraphics/x push @include_graphics_list, $1; next LINE; } if ( /^File: / ) { # Package sign-on line. Includegraphics/x also produces a line # with this signature, but I've already handled it. next LINE; } if (/^\! LaTeX Error: File \`([^\']*)\' not found\./ ) { $includes_missing{$1} = [3]; next LINE; } if (/^\! LaTeX Error: / ) { next LINE; } INCLUDE_CANDIDATE: while ( /\((.*$)/ ) { # Filename found by # '(', then filename, then terminator. # Terminators: obvious candidates: ')': end of reading file # '(': beginning of next file # ' ': space is an obvious separator # ' [': start of page: latex # and pdflatex put a # space before the '[' # '[': start of config file # in pdflatex, after # basefilename. # '{': some kind of grouping # Problem: # All or almost all special characters are allowed in # filenames under some OS, notably UNIX. Luckily most cases # are rare, if only because the special characters need # escaping. BUT 2 important cases are characters that are # natural punctuation # Under MSWin, spaces are common (e.g., "C:\Program Files") # Under VAX/VMS, '[' delimits directory names. This is # tricky to handle. But I think few users use this OS # anymore. # # Solution: use ' [', but not '[' as first try at delimiter. # Then if candidate filename is of form 'name1[name2]', then # try splitting it. If 'name1' and/or 'name2' exists, put # it/them in list, else just put 'name1[name2]' in list. # So form of filename is now: # '(', # then any number of characters that are NOT ')', '(', or '{' # (these form the filename); # then ' [', or ' (', or ')', or end-of-string. # That fails for pdflatex # In log file: # '(' => start of reading of file, followed by filename # ')' => end of reading of file # '[' => start of page (normally preceeded by space) # Remember: # filename (on VAX/VMS) may include '[' and ']' (directory # separators) # filenames (on MS-Win) commonly include space. # First step: replace $_ by whole of line after the '(' # Thus $_ is putative filename followed by other stuff. $_ = $1; ## warn "==='$_'===\n"; if ( /^([^\(^\)^\{]*?)\s\[/ ) { # Use *? in condition: to pick up first ' [' as terminator # 'file [' should give good filename. } elsif ( /^([^\(^\)^\{]*)\s(?=\()/ ) { # Terminator is ' (', but '(' isn't in matched string, # so we keep the '(' ready for the next match } elsif ( /^([^\(^\)^\{]*)(\))/ ) { # Terminator is ')' } else { #Terminator is end-of-string } ## warn " ---'$1'---'$''---\n"; $_ = $'; # Put $_ equal to the unmatched tail of string ' my $include_candidate = $1; $include_candidate =~ s/\s*$//; # Remove trailing space. if ( "$include_candidate" eq "[]" ) { # Part of overfull hbox message next INCLUDE_CANDIDATE; } # Put on list of new include files my @new_includes = ($include_candidate); if ( $include_candidate =~ /^(.+)\[([^\]]+)\]$/ ) { # Construct of form 'file1[file2]', as produced by pdflatex if ( -e $1 ) { # If the first component exists, we probably have the # pdflatex form @new_includes = ($1, $2); } else { # We have something else. # So leave the original candidate in the list } } INCLUDE_NAME: foreach my $include_name (@new_includes) { my ($base, $path, $ext) = fileparse ($include_name, '\.[^\.]*'); if ( $ext eq '.bbl' ) { warn "Input bbl file \"$include_name\", so turn on bibtex_mode\n" unless ($bibtex_mode == 1) || $silent; $bibtex_mode = 1; push @bbl_files, $include_name; } elsif ( $ext eq ".aux" ) { push @aux_files, $include_name; push @ignored_input_files, $include_name; } elsif ( $generated_exts{$ext} ) { #warn "Ignoring '$include_name'\n"; push @ignored_input_files, $include_name; } else { push @include_list, $include_name; } } # INCLUDE_NAME } # INCLUDE_CANDIDATE } # LINE close($log_file); @aux_files = &uniq( sort(@aux_files) ); @ignored_input_files = &uniq( sort(@ignored_input_files) ); if ( $bibtex_mode ) { &parse_aux; push @include_list, @bib_files; } @include_list = &uniq(sort @include_list); @include_graphics_list = &uniq(sort @include_graphics_list); foreach (@include_list) { if ( -e $_ ) { push @existent, $_; } else { $includes_missing{$_} = [1]; } } foreach (@include_graphics_list) { if ( -e $_ ) { push @existent, $_; } else { # I have to work harder finding the file $includes_missing{$_} = [2]; } } my $non_exist = 0; my $not_found = 0; my $missing = 0; foreach (sort keys %includes_missing) { $missing++; my $code = ${$includes_missing{$_}}[0]; if ($code == 1) {$non_exist ++;} if ($code == 2) {$not_found ++;} } @includes = @existent; if ( $diagnostics ) { my $inc = $#include_list + 1; my $exist = $#existent + 1; my $non_exist = $#includes_missing + 1; my $bbl = $#bbl_files + 1; print "$inc included files detected, of which "; print "$exist exist, and $non_exist do not exist.\n"; print "Input files that exist:\n"; foreach (@existent) { print " $_\n";} if ( $#bbl_files >= 0 ) { print "Input bbl files:\n"; foreach (@bbl_files) { print " $_\n"; } } if ( $#ignored_input_files >= 0 ) { print "Other input files that are generated via LaTeX run:\n"; foreach (@ignored_input_files) { print " $_\n"; } } if ( $missing > 0 ) { print "Apparent input files that appear NOT to exist:\n"; print " Some correspond to misunderstood lines in the .log file\n"; print " Some are files that latexmk failed to find\n"; print " Some really don't exist\n"; foreach (sort keys %includes_missing) { print " $_\n"; } } } return 1; } #************************************************************ sub parse_aux # Parse aux_file for bib files. # Return 3 with @bib_files set if aux_file exists, but I couldn't find all the bib_files # Return 2 with @bib_files empty if aux_file exists, but there are no \bibdata # lines. In that case turn off bibtex mode, as side effect. # Return 1 with @bib_files set if aux_file exists # Return 0 and leave @bib_files unchanged in aux_file does not exist (or # cannot be opened) { local($aux_file) = "$root_filename.aux"; if (! open(aux_file) ) { return 0; } @bib_files = (); AUX_LINE: while () { if ( /^\\bibdata\{(.*)\}/ ) { # \\bibdata{comma_separated_list_of_bib_file_names} # (Without the '.bib' extension) push( @bib_files, split /,/, $1 ); } } close(aux_file); if ( $#bib_files eq -1 ) { warn "No .bib files listed in .aux file, so turn off bibtex_mode\n"; $bibtex_mode = 0; return 2; } my $bibret = &find_file_list1( \@bib_files, \@bib_files, '.bib', \@BIBINPUTS ); if ($bibret == 0) { warn "Found bibliography files [@bib_files]\n" unless $silent; } else { warn "Failed to find one or more bibliography files in [@bib_files]\n"; if ($force_mode) { warn "==== Force_mode is on, so I will continue. But there may be problems ===\n"; } else { $failure = -1; $failure_msg = 'Failed to find one or more bib files'; } return 3; } return 1; } #************************************************************ sub update_depend_file { warn "Writing dependency file [$root_filename.dep]\n"; $rc_file = ">$root_filename.dep"; open(rc_file) || die "Latexmk: Unable to open dependency file [$rc_file] for updating\n"; print rc_file '@includes = (\n'; my $first = 1; foreach my $name (@includes) { if (!$first) {print rc_file ",\n";} print rc_file "\'$name\'"; $first = 0; } print rc_file "\n)\n"; print rc_file '@bib_files = (\n'; $first = 1; foreach $name (@bib_files) { if (!$first) {print rc_file ",\n";} print rc_file "\'$name\'"; $first = 0; } print rc_file "\n)\n"; if ($bibtex_mode) { print rc_file '$bibtex_mode = 1;' . "\n"; } if ($index_mode) { print rc_file '$index_mode = 1;' . "\n"; } print rc_file "\$view = \"$view\";\n"; print rc_file "\$need_dvi = $need_dvi;\n"; print rc_file "\$need_ps = $need_ps;\n"; print rc_file "\$need_pdf = $need_pdf;\n"; print rc_file "\$pdf_mode = $pdf_mode;\n"; close rc_file; } #************************************************************ #************************************************************ #************************************************************ # # UTILITIES: # #************************************************************ # Miscellaneous sub show_array { # For use in debugging. $_[0] = label. Rest of @_ is list of items print "$_[0]\n"; shift; foreach (@_){ print " $_\n";} } #************************************************************ sub glob_list { # Glob a collection of filenames. Sort and eliminate duplicates # Usage: e.g., @globbed = glob_list(string, ...); my @globbed = (); foreach (@_) { push @globbed, glob; } return uniq( sort( @globbed ) ); } #************************************************************ # File handling routines: #************************************************************ sub get_latest_mtime # - arguments: each is a filename. # - returns most recent modify time. { my $return_mtime = 0; foreach my $include (@_) { my $include_mtime = &get_mtime($include); # The file $include may not exist. If so ignore it, otherwise # we'll get an undefined variable warning. if ( ($include_mtime) && ($include_mtime > $return_mtime) ) { $return_mtime = $include_mtime; } } return $return_mtime; } #************************************************************ sub get_mtime { my $mtime = (stat($_[0]))[9]; return $mtime; } #************************************************************ # Find file with default extension # Usage: find_file_ext( name, default_ext, ref_to_array_search_path) sub find_file_ext { my $full_filename = shift; my $ext = shift; my $ref_search_path = shift; my $full_filename1 = &find_file($full_filename, $ref_search_path, '1'); #print "Finding \"$full_filename\" with ext \"$ext\" ... "; if (( $full_filename1 eq '' ) || ( ! -e $full_filename1 )) { my $full_filename2 = &find_file("$full_filename.$ext",$ref_search_path,'1'); if (( $full_filename2 ne '' ) && ( -e $full_filename2 )) { $full_filename = $full_filename2; } else { $full_filename = $full_filename1; } } else { $full_filename = $full_filename1; } #print "Found \"$full_filename\".\n"; return $full_filename; } #************************************************************ # given filename and path, return full name of file, or die if none found. # when force_include_mode=1, only warn if an include file was not # found, and return 0 (PvdS). # Usage: find_file(name, ref_to_array_search_path, param) sub find_file { my $name = $_[0]; my $ref_path = $_[1]; my $dir; if ( $name =~ /^\// ) { if ($force_include_mode) { if ( $_[2] eq '' ) { warn "Latexmk: Could not find file [$name]\n"; } return("$name"); } else { if (-e $name) { return("$name"); } die "Latexmk: Could not find file [$name]\n"; } } foreach $dir ( @{$ref_path} ) { #warn "\"$dir\", \"$name\"\n"; if (-e "$dir/$name") { return("$dir/$name"); } } if ($force_include_mode) { if ( $_[2] eq '' ) { warn "Latexmk: Could not find file [$name] in path [@{$ref_path}]\n"; warn " assuming in current directory (./$name)\n"; } return("./$name"); } else { if ( $_[2] ne '' ) { return(''); } # warn "\"$name\", \"$ref_path\", \"$dir\"\n"; die "Latexmk: Could not find file [$name] in path [@{$ref_path}]\n"; } } #************************************************************ # Usage: find_file1(name, ref_to_array_search_path, param) # Modified find_file, which doesn't die. # Given filename and path, return array of: # full name # retcode # On success: full_name = full name with path, retcode = 0 # On failure: full_name = given name, retcode = 1 # If second argument is absent, give warning sub find_file1 { my $name = $_[0]; my $ref_path = $_[1]; my $action = $_[2]; my $dir; if ( $name =~ /^\// ) { # Absolute path if (-e $name) { return( $name, 0 );} else { return( $name, 1 );} } foreach $dir ( @{$ref_path} ) { #warn "\"$dir\", \"$name\"\n"; if (-e "$dir/$name") { return("$dir/$name", 0); } } if ( $_[2] eq '' ) { warn "Latexmk: Could not find file [$name] in path [@{$ref_path}]\n"; } return("$name" , 1); } #************************************************************ sub find_file_list1 # Modified version of find_file_list that doesn't die. # Given output and input arrays of filenames, a file suffix, and a path, # fill the output array with full filenames # Return a status code: # Retcode = 0 on success # Retocde = 1 if at least one file was not found # Usage: find_file_list1( ref_to_output_file_array, # ref_to_input_file_array, # suffix, # ref_to_array_search_path # ) { my $ref_output = $_[0]; my $ref_input = $_[1]; my $suffix = $_[2]; my $ref_search = $_[3]; my @return_list = (); # Generate list in local array, since input # and output arrays may be same my $retcode = 0; foreach my $file (@$ref_input) { my ($tmp_file, $find_retcode) = &find_file1( "$file$suffix", $ref_search ); if ($tmp_file) { push @return_list, $tmp_file; } if ( $find_retcode != 0 ) { $retcode = 1; } } @$ref_output = @return_list; return $retcode; } #************************************************************ sub find_dirs1 { # Same as find_dirs, but argument is single string with directories # separated by $search_path_separator find_dirs( &split_search_path( $search_path_separator, ".", $_[0] ) ); } #************************************************************ sub find_dirs { # @_ is list of directories # return: same list of directories, except that for each directory # name ending in //, a list of all subdirectories (recursive) # is added to the list. # Non-existent directories and non-directories are removed from the list # Trailing "/"s are removed, and multiple "/" are collapsed to single "/". local @result = (); my $find_action = sub { ## Subroutine for use in File::find ## Check to see if we have a directory if (-d) { push @result, $File::Find::name; } }; foreach my $directory (@_) { my $recurse = ( $directory =~ m[//$] ); # Remove all trailing /s, since directory name with trailing / # is not always allowed: $directory =~ s[/+$][]; # Collapse multiple / to single /, since filenames with # multiple slashes are not necessarily valid. $directory =~ s[/+][/]; if ( ! -e $directory ){ next; } elsif ( $recurse ){ # Recursively search directory find( $find_action, $directory ); } else { push @result, $directory; } } return @result; } #************************************************************ sub uniq # Read arguments, delete neighboring items that are identical, # return array of results { my @sort = (); my ($current, $prev); my $first = 1; while (@_) { $current = shift; if ($first || ($current ne $prev) ) { push @sort, $current; $prev = $current; $first = 0; } } return @sort; } #************************************************************ sub copy_file_and_time { # Copy file1 to file2, copying time # I think copy() already does this, but it may depend on version. my $source = shift; my $dest = shift; my $retcode = copy ($source, $dest) and do { my $mtime = get_mtime($dest); utime $mtime, $mtime, $dest; }; return $retcode; } #************************************************************ sub copy_file_keep_time { # Copy file1 to file2, preserving time of file 2 my $source = shift; my $dest = shift; if (-e $dest) { return 1; } my $mtime = get_mtime($dest); my $retcode = copy ($source, $dest) and do { utime $mtime, $mtime, $dest; }; return $retcode; } #************************************************************ sub diff { # diff(filename1, filename2): # Return 2 if either or both files cannot be opened. # 1 if the files are different # 0 if the files are the same my $file1 = new FileHandle; my $file2 = new FileHandle; # Note automatic close of files when they go out of scope. open ($file1, $_[0]) or return 2; open ($file2, $_[1]) or return 2; my $retcode = 0; while ( ( not eof($file1)) || ( not eof($file2) ) ){ if ( <$file1> ne <$file2> ) { $retcode = 1; last; } } return $retcode; } #************************************************************ sub diff_OLDVERSION { # diff(filename1, filename2): # Return 2 if either or both files cannot be opened. # 1 if the files are different # 0 if the files are the same local (*file1, *file2); unless( open (file1, $_[0]) ) { return 2; } unless ( open (file2, $_[1])) { close (file1); return 2; } my $retcode = 0; while ( ( not eof(file1)) && ( not eof(file2) ) ){ if ( ne ) { $retcode = 1; last; } } close (file1); close (file2); return $retcode; } #************************************************************ sub split_search_path { # Usage: &split_search_path( separator, default, string ) # Splits string by separator and returns array of the elements # Allow empty last component. # Replace empty terms by the default. my $separator = $_[0]; my $default = $_[1]; my $search_path = $_[2]; my @list = split( /$separator/, $search_path); if ( $search_path =~ /$separator$/ ) { # If search path ends in a blank item, the split subroutine # won't have picked it up. # So add it to the list by hand: push @list, ""; } # Replace each blank argument (default) by current directory: for ($i = 0; $i <= $#list ; $i++ ) { if ($list[$i] eq "") {$list[$i] = $default;} } return @list; } #************************************************************ #************************************************************ # Process/subprocess routines sub Run { # Usage: Run ("program arguments "); # or Run ("start program arguments"); # or Run ("NONE program arguments"); # First form is just a call to system, and the routine returns after the # program has finished executing. # Second form (with 'start') runs the program detached, as appropriate for # the operating system: It runs "program arguments &" on UNIX, and # "start program arguments" on WIN95 and WINNT. If multiple start # words are at the beginning of the command, the extra ones are removed. # Third form (with 'NONE') does not run anything, but prints an error # message. This is provided to allow program names defined in the # configuration to flag themselves as unimplemented. # Return value is a list (pid, exitcode): # If process is spawned sucessfully, and I know the PID, # return (pid, 0), # else if process is spawned sucessfully, but I do not know the PID, # return (0, 0), # else if process is run, # return (0, exitcode of process) # else (I fail to run the requested process) # return (0, suitable return code) # where return code is 1 if cmdline is null or begins with "NONE" (for # an unimplemented command) # or the return value of the system subroutine. # Split command line into one word per element, separating words by # one (OR MORE) spaces: my @cmdline = split (/ +/, $_[0]); if (! @cmdline ) { warn "Latexmk::Run: Attempt to run a null program."; return (0, 1); } if ( "$cmdline[0]" eq "start" ) { # Run detached. How to do this depends on the OS # But first remove extra starts (which may have been inserted # to force a command to be run detached, when the command # already contained a "start"). while ($cmdline[0] eq "start") {shift (@cmdline);} return &Run_Detached (@cmdline); } elsif ( "$cmdline[0]" eq "NONE" ) { warn "Latexmk::Run: ", "Program not implemented for this version. Command line:\n"; warn " \"@cmdline\"\n"; return (0, 1); } else { # The command is given to system as a single argument, to force shell # metacharacters to be interpreted: return (0, system( join( ' ', @cmdline) ) ); } } #************************************************************ sub Run_Detached { # Usage: Run_Detached ("program arguments "); # Runs program detached. Returns 0 on success, 1 on failure. # Under UNIX use a trick to avoid the program being killed when the # parent process, i.e., me, gets a ctrl/C, which is undesirable for pvc # mode. (The simplest method, system ("program arguments &"), makes the # child process respond to the ctrl/C.) # Return value is a list (pid, exitcode): # If process is spawned sucessfully, and I know the PID, # return (pid, 0), # else if process is spawned sucessfully, but I do not know the PID, # return (0, 0), # else if I fail to spawn a process # return (0, 1) ## warn "Running \"@_\" detached...\n"; if ( "$^O" eq "MSWin32" ){ # Win95, WinNT, etc: Use MS's start command: return (0, system( "start", @_) ); } else { # Assume anything else is UNIX or clone # For this purpose cygwin behaves like UNIX. ## warn "Run_Detached.UNIX: A\n"; my $pid = fork(); ## warn "Run_Detached.UNIX: B pid=$pid\n"; if ( ! defined $pid ) { ## warn "Run_Detached.UNIX: C\n"; warn "Latexmk:: Could not fork to run the following command:\n"; warn " \"@_\"\n"; return (0, 1); } elsif( $pid == 0 ){ ## warn "Run_Detached.UNIX: D\n"; # Forked child process arrives here # Insulate child process from interruption by ctrl/C to kill parent: # setpgrp(0,0); # Perhaps this works if setpgrp doesn't exist # (and therefore gives fatal error): eval{ setpgrp(0,0);}; exec(@_); # Exec never returns; it replaces current process by new process die "Latexmk forked process: could not run the command\n", " \"@_\"\n"; } ##warn "Run_Detached.UNIX: E\n"; # Original process arrives here return ($pid, 0); } # NEVER GET HERE. ##warn "Run_Detached.UNIX: F\n"; } #************************************************************ sub find_process_id { # find_process_id(string) finds id of process containing string and # being run by the present user. Typically the string will be the # name of the process or part of its command line. # On success, this subroutine returns the process ID. # On failure, it returns 0. # This subroutine only works on UNIX systems at the moment. if ( $pid_position < 0 ) { # I cannot do a ps on this system return (0); } my $looking_for = $_[0]; my @ps_output = `$pscmd`; shift(@ps_output); # Discard the header line from ps foreach (@ps_output) { next unless ( /$looking_for/ ) ; my @ps_line = split (' '); return($ps_line[$pid_position]); } # No luck in finding the specified process. return(0); } #************************************************************