Monday, May 10, 2010

Code to Generate a Version Number Header (2)

I have recently switched to using Mercurial (Hg) as my version control system of choice so have updated my little tool used to generate a version number header file that other projects can include. I have included both the local and global Hg revision numbers plus I've added an inline function to return the version number as a string. As before, the version number includes the major, minor and daily build numbers. I display the global Hg revision number in my About dialogs in the same way as Qt Creator does (the global Hg revision number is a string).

A typical version header now looks like this:

#ifndef VERSION_H
#define VERSION_H

namespace Version
{
const int MAJOR = 1;
const int MINOR = 6;
const int LOCAL_REVISION = 46;
const int BUILD = 16130;

inline const char* versionString()
{
return "1.6.46.16130";
}

inline const char* globalRevision()
{
return "81a3662fbc71";
}
}

#endif // VERSION_H

The code that produces the header looks like this (apologies for the formatting - the online source code formatter isn't working today):

#include <iostream>
#include <QProcess>
#include <QStringList>
#include <QFile>
#include <QTextStream>
#include <QDate>
#include <QTime>
#include <QFileInfo>
#include <QTemporaryFile>
#include <QtGlobal>
#include <cstdlib>

namespace
{
int getBuildNumber()
{
const QDate today(QDate::currentDate());
return ((today.year() - 1994) * 1000) + today.dayOfYear();
}

int getLocalHgRevision()
{
int revision = 0;
QProcess process;
process.start("hg", QStringList() << "id" << "-n");
if (process.waitForStarted() && process.waitForReadyRead())
{
revision = atoi(process.readAll().constData());
process.waitForFinished();
}
return revision;
}

QString getGlobalHgRevision()
{
QString revision = "";
QProcess process;
process.start("hg", QStringList() << "id" << "-i");
if (process.waitForStarted() && process.waitForReadyRead())
{
revision = QString(process.readAll().constData()).trimmed();
revision.remove('+');
process.waitForFinished();
}
return revision;
}

QByteArray readFile(const QString& fileName)
{
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly))
{
return QByteArray();
}
return file.readAll();
}

int writeFile(const QString& fileName, const int major, const int minor, const int localRevision,
const int buildNumber, const QString& globalRevision)
{
// Create a temp file containing the version info and
// only replace the existing one if they are different
QTemporaryFile tempFile;
if (tempFile.open())
{
QTextStream out(&tempFile);
out << "#ifndef VERSION_H\r\n";
out << "#define VERSION_H\r\n\r\n";
out << "namespace Version\r\n";
out << "{\r\n";
out << "\tconst int MAJOR = " << major << ";\r\n";
out << "\tconst int MINOR = " << minor << ";\r\n";
out << "\tconst int LOCAL_REVISION = " << localRevision << ";\r\n";
out << "\tconst int BUILD = " << buildNumber << ";\r\n\r\n";
out << "\tinline const char* versionString()\r\n";
out << "\t{\r\n";
out << "\t\treturn \"" << major << '.' << minor << '.' << localRevision << '.' << buildNumber << "\";\r\n";
out << "\t}\r\n\r\n";
out << "\tinline const char* globalRevision()\r\n";
out << "\t{\r\n";
out << "\t\treturn \"" << globalRevision << "\";\r\n";
out << "\t}\r\n";
out << "}\r\n\r\n";
out << "#endif // VERSION_H\r\n";

const QString tempFileName = tempFile.fileName();
tempFile.close();

if (!QFile::exists(fileName) || readFile(fileName) != readFile(tempFileName))
{
QFile::remove(fileName);
QFile::copy(tempFileName, fileName);
}

return 0;
}
else
{
std::cout << "Error creating temporary file!" << std::endl;
return 1;
}
}
}

int main(int argc, char *argv[])
{
if (argc != 4)
{
std::cout << "Usage: version major minor filename" << std::endl;
return 1;
}

const int major = atoi(argv[1]);
const int minor = atoi(argv[2]);
const int localRevision = getLocalHgRevision();
const QString globalRevision = getGlobalHgRevision();
const int buildNumber = getBuildNumber();

std::cout << major << '.' << minor << '.' << localRevision << '.' << buildNumber;
std::cout << ' ' << qPrintable(globalRevision) << std::endl;

return writeFile(argv[3], major, minor, localRevision, buildNumber, globalRevision);
}

3 comments:

  1. If using CMake it's very easy to achieve this with configure_file.

    Just FYI.

    ReplyDelete
  2. It would also be relatively easy using shell scripts and exec calls in the .pro file too, but I'm a bit of a sadist. :)

    ReplyDelete
  3. Hey thanks for this post. I really wanted this scripts so thanks again.

    ReplyDelete