0

I'm using MikTex texify compiler driver and I have a document using the multibib package. The pdflatex compilation generates main.aux (which matches main.tex) and own.aux (generated using multibib). Texify will however only run bibtex on main.aux so that some of the references will not update correctly. I have been using a simple shell script:

bibtex main
bibtex own

which needs to be run everytime I cite a new reference. Is there perhaps a way to either make texify run bibtex twice or a way to duplicate what texify is doing and make a script which performs the whole typesetting process?

0

I looked in the source codes of texify, file mcd.cpp function Driver::Ready(). It looks for the occurrence of "Rerun to get" in the .log file and for changes in the aux files in order to see whether it should re-run latex again. This would be quite simple to script in an Unix environment but since I'm in Windows, I put together the following:

#include <vector>
#include <string>
#include <algorithm>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
#include <stdarg.h>
#include <ctype.h>
#if defined(_WIN32) || defined(_WIN64)
#define NOMINMAX
#include <windows.h>
#else // _WIN32 || _WIN64
// ...
#endif // _WIN32 || _WIN64

#define SHA1HashSize 20

/*
 *  This structure will hold context information for the SHA-1
 *  hashing operation
 */
typedef struct SHA1Context
{
    uint32_t Intermediate_Hash[SHA1HashSize/4]; /* Message Digest  */

    uint32_t Length_Low;            /* Message length in bits      */
    uint32_t Length_High;           /* Message length in bits      */

                               /* Index into message block array   */
    int_least16_t Message_Block_Index;
    uint8_t Message_Block[64];      /* 512-bit message blocks      */

    int Computed;               /* Is the digest computed?         */
    int Corrupted;             /* Is the message digest corrupted? */
} SHA1Context;

/*
 *  Function Prototypes
 */
int SHA1Reset(SHA1Context*);
int SHA1Input(SHA1Context*, const void*, unsigned int);
int SHA1Result(SHA1Context*, uint8_t Message_Digest[SHA1HashSize]);

void Format(std::string &r_s_result, const char *p_s_fmt, ...) // throw(std::bad_alloc)
{
    va_list ap;

    size_t n_size = strlen(p_s_fmt) + 1;
    r_s_result.resize(n_size);
    // alloc str as long as formatting str (should be enough in some cases)

    for(;;) {
        va_start(ap, p_s_fmt);
#if defined(_MSC_VER) && !defined(__MWERKS__)
#if _MSC_VER >= 1400
        int n = _vsnprintf_s(&r_s_result[0], n_size * sizeof(char), _TRUNCATE, p_s_fmt, ap);
#else // _MSC_VER >= 1400
        int n = _vsnprintf(&r_s_result[0], n_size - 1, p_s_fmt, ap);
        // maximum characters to write, not the size of buffer
#endif // _MSC_VER >= 1400
#else // _MSC_VER && !__MWERKS__
        int n = vsnprintf(&r_s_result[0], n_size * sizeof(char), p_s_fmt, ap);
#endif // _MSC_VER && !__MWERKS__
        va_end(ap);
        // try to sprintf

        if(n >= 0 && unsigned(n) < n_size) {
#if defined(_MSC_VER) && !defined(__MWERKS__)
            r_s_result.resize(n);
#else // _MSC_VER && !__MWERKS__
            r_s_result.resize(strlen(r_s_result.data())); // doesn't need c_str(), terminating zero is already there
#endif // _MSC_VER && !__MWERKS__
            assert(r_s_result.length() == strlen(r_s_result.data()));
            return;
        }
        // see if we made it

        if(n >= 0) // glibc 2.1
            n_size = n + 1; // precisely what is needed
        else // glibc 2.0, msvc
            n_size *= 2;

        r_s_result.resize(n_size);
        // realloc to try again
    }
}

/**
 *  @brief gets temporary directory the user can write to
 *  @param[out] r_s_path is the path to the temporary directory, never ends with a slash
 *  @return Returns true on success, false on failure.
 */
bool Get_TempDirectory(std::string &r_s_path) // throw(std::bad_alloc)
{
#if defined(_WIN32) || defined(_WIN64)
    r_s_path.resize(GetTempPath(0, NULL) + 1);
    if(!GetTempPathA((DWORD)r_s_path.size(), &r_s_path[0])) // size won't exceed DWORD_MAX, since it's read from DWORD
        return false; // something went wrong
    assert(strlen(r_s_path.c_str()) > 0 && r_s_path[strlen(r_s_path.c_str()) - 1] == '\\'); // the returned string ends with a backslash
    r_s_path.resize(strlen(r_s_path.c_str()) - 1); // cut the backslash here
    // get temp path (eg. "c:\\windows\\temp")
#else // _WIN32 || _WIN64
#if 0 // g++ linker warns about using mktemp(), don't want to use that anymore
    char p_s_temp[256] = "/tmp/fileXXXXXX";
    if(!mktemp(p_s_temp)) // do *not* use mkstemp(), do not want the file to be lying around
        return false;
    assert(strrchr(p_s_temp + 1, '/')); // should contain slash
    *(char*)strrchr(p_s_temp + 1, '/') = 0; // erase the last slash (hence the string does not contain it)
    r_s_path = p_s_temp;
    // get temp file name and erase the file name to get the path
#else // 0
    const char *p_s_temp = getenv("TMPDIR"); // environment variable
    // The caller must take care not to modify this string, since that would change the
    // environment of the process. Do not free it either.
    // (e.g. on cluster computers, temp is often directory specific to the job id, like "/tmp/pbs.132048.dm2")

    if(!p_s_temp) {
        if(P_tmpdir)
            p_s_temp = P_tmpdir; // in stdio.h
        else {
            p_s_temp = "/tmp"; // just hope it is there

            /*TFileInfo t_temp_info(p_s_temp);
            if(!t_temp_info.b_exists || !t_temp_info.b_directory)
                return false;*/
            // don't want to depend on Dir.cpp, some apps already use only the header
        }
    }
    // fallbacks if the environment variable is not set

    r_s_path = p_s_temp;
    if(!r_s_path.empty() && r_s_path[r_s_path.length() - 1] == '/')
        r_s_path.erase(r_s_path.end() - 1);
    // get rid of the trailing slash
#endif // 0
#endif // _WIN32 || _WIN64

    /*assert(!b_EndsWithSlash(r_s_path));
    assert(b_Is_Normalized(r_s_path));
    assert(b_Is_Absolute(r_s_path));*/
    // make sure there is no slash at the end, and that the path is normalized

    return true;
}

bool Get_TempFileName(std::string &r_s_temp_file_name, const char *p_s_app_id) // throw(std::bad_alloc)
{
    assert(p_s_app_id && strlen(p_s_app_id));
    // may not be emtpy

#if defined(_WIN32) || defined(_WIN64)
    std::string s_temp_path;
    s_temp_path.resize(GetTempPath(0, NULL) + 1);
    if(!GetTempPathA((DWORD)s_temp_path.size(), &s_temp_path[0])) // size won't exceed DWORD_MAX, since it's read from DWORD
        return false; // something went wrong
    s_temp_path.resize(strlen(s_temp_path.c_str()));
    // get temp path (eg. "c:\windows\temp")

    r_s_temp_file_name.resize(s_temp_path.length() + 16 + strlen(p_s_app_id));
    if(!GetTempFileNameA(s_temp_path.c_str(), p_s_app_id, 0, &r_s_temp_file_name[0]))
        return false; // something went wrong
    r_s_temp_file_name.resize(strlen(r_s_temp_file_name.c_str()));
    // get temp filename
#else // _WIN32 || _WIN64
    std::string s_tmp;
    if(!Get_TempDirectory(s_tmp)) // use "proper" temp (e.g. on cluster computers, temp is often directory specific to the job id, like "/tmp/pbs.132048.dm2")
        return false;
    Format(r_s_temp_file_name, "%s/%sXXXXXX", s_tmp.c_str(), p_s_app_id); // had 8 X's // 2012-07-17 change // t_odo - carry this change to documentation
    // 2013-11-13 changed template to not include the /tmp folder as that might not be the location of tmp
    // make template

    int n_file;
    if((n_file = mkstemp((char*)r_s_temp_file_name.c_str())) < 0)
        return false;
    close(n_file);
    // create temp file
#endif // _WIN32 || _WIN64

    return true;
}

bool ReadFile(std::string &r_s_output, const char *p_s_filename) // throw(std::bad_alloc)
{
    FILE *p_fr;
#if defined(_MSC_VER) && !defined(__MWERKS__) && _MSC_VER >= 1400
    if(fopen_s(&p_fr, p_s_filename, "rb"))
#else //_MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
    if(!(p_fr = fopen(p_s_filename, "rb")))
#endif //_MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
        return false;
    unsigned int n_length;
    if(fseek(p_fr, 0, SEEK_END) ||
       (n_length = ftell(p_fr)) < 0 ||
       fseek(p_fr, 0, SEEK_SET)) {
        fclose(p_fr);
        return false;
    }
    try {
        r_s_output.resize(n_length);
    } catch(std::bad_alloc &r_exc) {
        fclose(p_fr);
        throw r_exc; // rethrow
    }
    if(fread(&r_s_output[0], sizeof(char), n_length, p_fr) != n_length) {
        fclose(p_fr);
        return false;
    }
    fclose(p_fr);
    return true;
}

void TrimSpace(std::string &r_s_string)
{
    size_t b = 0, e = r_s_string.length();
    while(e > 0 && isspace(uint8_t(r_s_string[e - 1])))
        -- e;
    while(b < e && isspace(uint8_t(r_s_string[b])))
        ++ b;
    r_s_string.erase(e);
    r_s_string.erase(0, b);
}

void Split(std::vector<std::string> &r_s_dest, const std::string &r_s_string,
    const char *p_s_separator, int n_thresh)
{
    r_s_dest.clear();
    const size_t n_separator_skip = strlen(p_s_separator);

    size_t n_pos = 0;
    size_t n_next_pos;
    while((n_next_pos = r_s_string.find(p_s_separator, n_pos)) != std::string::npos) {
        if(n_thresh < 0 || n_next_pos - n_pos > unsigned(n_thresh)) {
            r_s_dest.resize(r_s_dest.size() + 1);
            std::string &r_s_new = r_s_dest.back();
            r_s_new.insert(r_s_new.begin(), r_s_string.begin() + n_pos,
                r_s_string.begin() + n_next_pos);
        }
        n_pos = n_next_pos + n_separator_skip; // skip over the separator
    }
    if(n_thresh < 0 || r_s_string.length() - n_pos > unsigned(n_thresh)) {
        r_s_dest.resize(r_s_dest.size() + 1);
        std::string &r_s_new = r_s_dest.back();
        r_s_new.insert(r_s_new.begin(), r_s_string.begin() + n_pos,
            r_s_string.end());
    }
}

int main(int n_arg_num, const char **p_arg_list)
{
    try {
        if(n_arg_num != 2) {
            fprintf(stderr, "error: use IsTexReady <jobname>\n");
            return -1;
        }
        const char *p_s_jobname = p_arg_list[1];
        if(!strcmp(p_s_jobname, "-h") || !strcmp(p_s_jobname, "--help") ||
           !strcmp(p_s_jobname, "/?") || !strcmp(p_s_jobname, "--usage")) {
            printf("use: IsTexReady <jobname>\n\n"
                "It will return 0 in case there is no need to re-run tex,\n"
                "1 in case tex should be run once more or -1 on error.\n\n"
                "Run in the same folder where <jobname>.log can be found.\n");
            return 0; // not sure
        }
        // get jobname

        std::string s_logfile_name = std::string(p_s_jobname) + ".log";

        bool b_rerun = false;

        std::string s_logfile;
        if(!ReadFile(s_logfile, s_logfile_name.c_str())) {
            fprintf(stderr, "error: while reading \'%s\'\n", s_logfile_name.c_str());
            b_rerun = true; // should re-run as the log file might be simply missing
        }
        // read logfile

        if(s_logfile.find("Rerun to get") != std::string::npos) {
            printf("IsTexReady: should rerun latex as per the logfile\n");
            b_rerun = true;
        }
        // have "Rerun to get" something, should run again

        std::vector<std::string> aux_list;
        {
            std::string s_aux_list_name;
            if(!Get_TempFileName(s_aux_list_name, "trdy"))
                throw std::runtime_error("Get_TempFileName() failed");
            // get a temp file

#if defined(_WIN32) || defined(_WIN64)
            system(("dir /B /S *.aux *.toc *.lof *.lot *.loa *.lol *.idx > " + s_aux_list_name).c_str());
#else // _WIN32 || _WIN64
            system(("find . -name \'*.aux\' -or -name \'*.toc\' -or -name \'*.lof\' "
                "-or -name *.lot -or -name *.loa -or -name \'*.lol\' -or -name \'*.idx\' > " + s_aux_list_name).c_str());
#endif // _WIN32 || _WIN64
            // list the files in it (aux, toc, list of figures / tables / algorithms / listings, idx)

            std::string s_aux_list;
            if(!ReadFile(s_aux_list, s_aux_list_name.c_str()))
                throw std::runtime_error("getting a list of aux files failed");
            // read it back

            Split(aux_list, s_aux_list, "\n", 0); // split the list by newlines
            std::for_each(aux_list.begin(), aux_list.end(), TrimSpace); // remove leading or trailing spaces
            std::sort(aux_list.begin(), aux_list.end()); // sort the filenames
            aux_list.erase(std::unique(aux_list.begin(), aux_list.end()), aux_list.end()); // remove duplicates (empty entries, whitespace, ...)
            std::vector<std::string>::iterator p_empty_it;
            if((p_empty_it = std::find(aux_list.begin(), aux_list.end(), std::string())) != aux_list.end())
                aux_list.erase(p_empty_it); // erase any empty entries
            // split to the individual entries

            remove(s_aux_list_name.c_str());
            // remove the temp file
        }
        // get a list of all the aux files

        std::string s_hash_string;
        {
            SHA1Context sha1;
            SHA1Reset(&sha1);
            for(size_t i = 0, n = aux_list.size(); i < n; ++ i) {
                SHA1Input(&sha1, aux_list[i].c_str(), aux_list[i].size() * sizeof(char));
                // hash the file name

                std::string s_aux_file;
                if(!ReadFile(s_aux_file, aux_list[i].c_str()))
                    throw std::runtime_error("failed to read one or more aux file(s)");
                SHA1Input(&sha1, s_aux_file.c_str(), s_aux_file.size() * sizeof(char));
                // hash the file contents
            }
            uint8_t Message_Digest[SHA1HashSize];
            SHA1Result(&sha1, Message_Digest);
            for(int i = 0; i < SHA1HashSize; ++ i) {
                std::string s_hash_digit;
                Format(s_hash_digit, "%02x", Message_Digest[i]);
                s_hash_string += s_hash_digit;
            }
        }
        // get SHA1 of the concatenated files

        std::string s_hashes_name = std::string(p_s_jobname) + "_aux.sha1";

        std::string s_hashes;
        if(!b_rerun && !ReadFile(s_hashes, s_hashes_name.c_str())) {
            printf("IsTexReady: should rerun latex as per missing aux file hashes\n");
            b_rerun = true;
        }
        // read aux file hashes. if not found, should run again

        TrimSpace(s_hashes);
        if(!b_rerun && s_hashes != s_hash_string) {
            printf("IsTexReady: should rerun latex as the aux files have changed\n");
            b_rerun = true;
        }

        if(b_rerun) {
            FILE *p_fw;
#if defined(_MSC_VER) && !defined(__MWERKS__) && _MSC_VER >= 1400
            if(fopen_s(&p_fw, s_hashes_name.c_str(), "w"))
#else //_MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
            if(!(p_fw = fopen(s_hashes_name.c_str(), "w")))
#endif //_MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
                throw std::runtime_error("failed to write the digest of the aux files");
            fprintf(p_fw, "%s\n", s_hash_string.c_str());
            fclose(p_fw);
            // save a new hashes file

            return 1;
            // signal to rerun
        }
        // should we rerun?

        printf("IsTexReady: no need to run latex again\n");
    } catch(std::bad_alloc&) {
        fprintf(stderr, "error: uncaught std::bad_alloc\n");
        return -1;
    } catch(std::runtime_error &r_exc) {
        fprintf(stderr, "error: uncaught std::runtime_error: \'%s\'\n", r_exc.what());
        return -1;
    } catch(std::exception&) {
        fprintf(stderr, "error: uncaught std::exception\n");
        return -1;
    }

    return 0; // noneed to run again, all is ready.
}

// ------------------------------------------------------------------------------------------------

// SHA-1 due to https://tools.ietf.org/html/rfc3174

#ifndef _SHA_enum_
#define _SHA_enum_
enum
{
    shaSuccess = 0,
    shaNull,            /* Null pointer parameter */
    shaInputTooLong,    /* input data too long */
    shaStateError       /* called Input after Result */
};
#endif

TODO - paste sha1.c from https://tools.ietf.org/html/rfc3174 in here 

Just build that as a console application IsTexReady.exe and then the script can be modified to:

@echo off

IsTexReady.exe main > nul
rem run once just to hash the aux files, ignore the results

set N=0

:again

bibtex main
bibtex own
pdflatex -synctex=1 main
rem run tex (use -synctex=1 to get clickable cross-referencing between tex and pdf)

set /A N=%N%+1

IsTexReady.exe main
rem run again, see if log prompts to re-run or whether the aux files changed since the beginning

if %ERRORLEVEL% == 1 (

if %N% == 5 (
echo "maximum number of iterations reached"
exit
)
rem see if the maximum number of iterations would be exceeded

goto again
)
rem see if we need to loop

echo "done in %N% iterations"

It then re-runs latex as many times as needed. It saves a SHA1 hash of the aux files to detect changes rather than making a copy of the aux files the way texify does. Note that texify also has a limitation to the maximum number of loops (default 5).

The source codes should work in Unix as well (build with g++ --ansi and maybe fix a few issues since I haven't tested it). But then again, you could do a similar script in pure bash using grep, ls, for and md5sum.

0

The correct and ultimately portable way would be to use the rerunfilecheck package. Then all you need to do is:

bibtex main
bibtex own
#makeindex main # or not
pdflatex -synctex=1 main

and then check main.log for occurrences of Rerun to get or Rerun LaTeX. If found, loop the script again.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.