diff --git a/buildtool.cpp b/buildtool.cpp
--- a/buildtool.cpp
+++ b/buildtool.cpp
*
*/
-#define BUILDTOOL_VERSION "BuildTool v0.9.5"
+#define BUILDTOOL_VERSION "BuildTool v0.9.9"
#include <stdio.h>
#include <fcntl.h>
//########################################################################
//# Stat cache to speed up stat requests
//########################################################################
-typedef std::map<String, std::pair<int,struct stat> > statCacheType;
+struct StatResult {
+ int result;
+ struct stat statInfo;
+};
+typedef std::map<String, StatResult> statCacheType;
static statCacheType statCache;
static int cachedStat(const String &f, struct stat *s) {
- std::pair<statCacheType::iterator, bool> result = statCache.insert(statCacheType::value_type(f, std::pair<int,struct stat>()));
+ //printf("Stat path: %s\n", f.c_str());
+ std::pair<statCacheType::iterator, bool> result = statCache.insert(statCacheType::value_type(f, StatResult()));
if (result.second) {
- result.first->second.first = stat(f.c_str(), &(result.first->second.second));
+ result.first->second.result = stat(f.c_str(), &(result.first->second.statInfo));
}
- *s = result.first->second.second;
- return result.first->second.first;
+ *s = result.first->second.statInfo;
+ return result.first->second.result;
+}
+static void removeFromStatCache(const String f) {
+ //printf("Removing from cache: %s\n", f.c_str());
+ statCache.erase(f);
}
+//########################################################################
+//# Dir cache to speed up dir requests
+//########################################################################
+/*struct DirListing {
+ bool available;
+ std::vector<String> files;
+ std::vector<String> dirs;
+};
+typedef std::map<String, DirListing > dirCacheType;
+static dirCacheType dirCache;
+static const DirListing &cachedDir(String fullDir)
+{
+ String dirNative = getNativePath(fullDir);
+ std::pair<dirCacheType::iterator,bool> result = dirCache.insert(dirCacheType::value_type(dirNative, DirListing()));
+ if (result.second) {
+ DIR *dir = opendir(dirNative.c_str());
+ if (!dir)
+ {
+ error("Could not open directory %s : %s",
+ dirNative.c_str(), strerror(errno));
+ result.first->second.available = false;
+ }
+ else
+ {
+ result.first->second.available = true;
+ while (true)
+ {
+ struct dirent *de = readdir(dir);
+ if (!de)
+ break;
+
+ //Get the directory member name
+ String s = de->d_name;
+ if (s.size() == 0 || s[0] == '.')
+ continue;
+ String childName;
+ if (dirName.size()>0)
+ {
+ childName.append(dirName);
+ childName.append("/");
+ }
+ childName.append(s);
+ String fullChild = baseDir;
+ fullChild.append("/");
+ fullChild.append(childName);
+
+ if (isDirectory(fullChild))
+ {
+ //trace("directory: %s", childName.c_str());
+ if (!listFiles(baseDir, childName, res))
+ return false;
+ continue;
+ }
+ else if (!isRegularFile(fullChild))
+ {
+ error("unknown file:%s", childName.c_str());
+ return false;
+ }
+
+ //all done!
+ res.push_back(childName);
+
+ }
+ closedir(dir);
+ }
+ }
+ return result.first->second;
+}*/
+
//########################################################################
//# F I L E S E T
//########################################################################
/**
* If this prefix is seen in a substitution, use as a
* pkg-config 'libs' query
- * example: <property pkg-config="pcl"/>
+ * example: <property pkg-config-libs="pcl"/>
* ${pcl.gtkmm}
*/
String pclPrefix;
+ /**
+ * If this prefix is seen in a substitution, use as a
+ * Bazaar "bzr revno" query
+ * example: <property subversion="svn"/> ???
+ * ${bzr.Revision}
+ */
+ String bzrPrefix;
+
*/
bool copyFile(const String &srcFile, const String &destFile);
+ /**
+ * Delete a file
+ */
+ bool removeFile(const String &file);
+
+ /**
+ * Tests if the file exists
+ */
+ bool fileExists(const String &fileName);
+
/**
* Tests if the file exists and is a regular file
*/
int parselen;
};
+/**
+ * Execute the "bzr revno" command and return the result.
+ * This is a simple, small class.
+ */
+class BzrRevno : public MakeBase
+{
+public:
+
+ /**
+ * Safe way. Execute "bzr revno" and return the result.
+ * Safe from changes in format.
+ */
+ bool query(String &res)
+ {
+ String cmd = "bzr revno";
+
+ String outString, errString;
+ bool ret = executeCommand(cmd.c_str(), "", outString, errString);
+ if (!ret)
+ {
+ error("error executing '%s': %s", cmd.c_str(), errString.c_str());
+ return false;
+ }
+ res = outString;
+ return true;
+ }
+};
+
+/**
+ * Execute the "svn info" command and parse the result.
+ * This is a simple, small class. Define here, because it
+ * is used by MakeBase implementation methods.
+ */
+class SvnInfo : public MakeBase
+{
+public:
+
+#if 0
+ /**
+ * Safe way. Execute "svn info --xml" and parse the result. Search for
+ * elements/attributes. Safe from changes in format.
+ */
+ bool query(const String &name, String &res)
+ {
+ String cmd = "svn info --xml";
+
+ String outString, errString;
+ bool ret = executeCommand(cmd.c_str(), "", outString, errString);
+ if (!ret)
+ {
+ error("error executing '%s': %s", cmd.c_str(), errString.c_str());
+ return false;
+ }
+ Parser parser;
+ Element *elem = parser.parse(outString);
+ if (!elem)
+ {
+ error("error parsing 'svn info' xml result: %s", outString.c_str());
+ return false;
+ }
+
+ res = elem->getTagValue(name);
+ if (res.size()==0)
+ {
+ res = elem->getTagAttribute("entry", name);
+ }
+ return true;
+ }
+#else
+
+
+ /**
+ * Universal way. Parse the file directly. Not so safe from
+ * changes in format.
+ */
+ bool query(const String &name, String &res)
+ {
+ String fileName = resolve(".svn/entries");
+ String nFileName = getNativePath(fileName);
+
+ std::map<String, String> properties;
+
+ FILE *f = fopen(nFileName.c_str(), "r");
+ if (!f)
+ {
+ error("could not open SVN 'entries' file");
+ return false;
+ }
+
+ const char *fieldNames[] =
+ {
+ "format-nbr",
+ "name",
+ "kind",
+ "revision",
+ "url",
+ "repos",
+ "schedule",
+ "text-time",
+ "checksum",
+ "committed-date",
+ "committed-rev",
+ "last-author",
+ "has-props",
+ "has-prop-mods",
+ "cachable-props",
+ };
+
+ for (int i=0 ; i<15 ; i++)
+ {
+ inbuf[0] = '\0';
+ if (feof(f) || !fgets(inbuf, 255, f))
+ break;
+ properties[fieldNames[i]] = trim(inbuf);
+ }
+ fclose(f);
+
+ res = properties[name];
+
+ return true;
+ }
+
+private:
+
+ char inbuf[256];
+
+#endif
+
+};
+
+
+
return false;
}
SetHandleInformation(stdoutRead, HANDLE_FLAG_INHERIT, 0);
- if (!CreatePipe(&stderrRead, &stderrWrite, &saAttr, 0))
- {
- error("executeProgram: could not create pipe");
- delete[] paramBuf;
- return false;
- }
- SetHandleInformation(stderrRead, HANDLE_FLAG_INHERIT, 0);
+ if (&outbuf != &errbuf) {
+ if (!CreatePipe(&stderrRead, &stderrWrite, &saAttr, 0))
+ {
+ error("executeProgram: could not create pipe");
+ delete[] paramBuf;
+ return false;
+ }
+ SetHandleInformation(stderrRead, HANDLE_FLAG_INHERIT, 0);
+ } else {
+ stderrRead = stdoutRead;
+ stderrWrite = stdoutWrite;
+ }
// Create the process
STARTUPINFO siStartupInfo;
error("executeCommand: could not close read pipe");
return false;
}
- if (!CloseHandle(stderrWrite))
+ if (stdoutWrite != stderrWrite && !CloseHandle(stderrWrite))
{
error("executeCommand: could not close read pipe");
return false;
error("executeCommand: could not close read pipe");
return false;
}
- if (!CloseHandle(stderrRead))
+ if (stdoutRead != stderrRead && !CloseHandle(stderrRead))
{
error("executeCommand: could not close read pipe");
return false;
String fullPath = baseName;
if (dirName.size()>0)
{
- fullPath.append("/");
+ if (dirName[0]!='/') fullPath.append("/");
fullPath.append(dirName);
}
DIR *dir = opendir(fullPath.c_str());
return false;
result = val;
}
+ else if (bzrPrefix.size() > 0 &&
+ varname.compare(0, bzrPrefix.size(), bzrPrefix) == 0)
+ {
+ varname = varname.substr(bzrPrefix.size());
+ String val;
+ //SvnInfo svnInfo;
+ BzrRevno bzrRevno;
+ if (varname == "revision")
+ {
+ if (!bzrRevno.query(val))
+ return "";
+ result = "r"+val;
+ }
+ /*if (!svnInfo.query(varname, val))
+ return false;
+ result = val;*/
+ }
else
{
std::map<String, String>::iterator iter;
cnative, strerror(errno));
return false;
}
+
+ removeFromStatCache(nativeDir);
return true;
}
else
{
//trace("DEL file: %s", childName.c_str());
- if (remove(cnative)<0)
+ if (!removeFile(childName))
{
- error("error deleting %s : %s",
- cnative, strerror(errno));
return false;
}
}
return false;
}
+ removeFromStatCache(native);
+
return true;
}
FILE *destf = fopen(destNative.c_str(), "wb");
if (!destf)
{
+ fclose(srcf);
error("copyFile cannot open %s for writing", srcNative.c_str());
return false;
}
#endif /* __WIN32__ */
+ removeFromStatCache(destNative);
+
+ return true;
+}
+
+
+/**
+ * Delete a file
+ */
+bool MakeBase::removeFile(const String &file)
+{
+ String native = getNativePath(file);
+
+ if (!fileExists(native))
+ {
+ return true;
+ }
+
+#ifdef WIN32
+ // On Windows 'remove' will only delete files
+
+ if (remove(native.c_str())<0)
+ {
+ if (errno==EACCES)
+ {
+ error("File %s is read-only", native.c_str());
+ }
+ else if (errno==ENOENT)
+ {
+ error("File %s does not exist or is a directory", native.c_str());
+ }
+ else
+ {
+ error("Failed to delete file %s: %s", native.c_str(), strerror(errno));
+ }
+ return false;
+ }
+
+#else
+
+ if (!isRegularFile(native))
+ {
+ error("File %s does not exist or is not a regular file", native.c_str());
+ return false;
+ }
+
+ if (remove(native.c_str())<0)
+ {
+ if (errno==EACCES)
+ {
+ error("File %s is read-only", native.c_str());
+ }
+ else
+ {
+ error(
+ errno==EACCES ? "File %s is read-only" :
+ errno==ENOENT ? "File %s does not exist or is a directory" :
+ "Failed to delete file %s: %s", native.c_str());
+ }
+ return false;
+ }
+
+#endif
+
+ removeFromStatCache(native);
return true;
}
+/**
+ * Tests if the file exists
+ */
+bool MakeBase::fileExists(const String &fileName)
+{
+ String native = getNativePath(fileName);
+ struct stat finfo;
+
+ //Exists?
+ if (cachedStat(native, &finfo)<0)
+ return false;
+
+ return true;
+}
+
/**
* Tests if the file exists and is a regular file
}
-
-
-
//########################################################################
//# D E P T O O L
//########################################################################
TASK_COPY,
TASK_CXXTEST_PART,
TASK_CXXTEST_ROOT,
+ TASK_CXXTEST_RUN,
TASK_DELETE,
TASK_ECHO,
TASK_JAR,
}
if (errorOccurred && !continueOnError)
break;
+
+ removeFromStatCache(getNativePath(destFullName));
}
if (f)
error("<cxxtestpart> problem: %s", errString.c_str());
return false;
}
+ removeFromStatCache(getNativePath(fullDest));
}
return true;
error("<cxxtestroot> problem: %s", errString.c_str());
return false;
}
+ removeFromStatCache(getNativePath(fullDest));
}
return true;
};
+/**
+ * Execute the CxxTest test executable
+ */
+class TaskCxxTestRun: public Task
+{
+public:
+
+ TaskCxxTestRun(MakeBase &par) : Task(par)
+ {
+ type = TASK_CXXTEST_RUN;
+ name = "cxxtestrun";
+ }
+
+ virtual ~TaskCxxTestRun()
+ {}
+
+ virtual bool execute()
+ {
+ unsigned int newFiles = 0;
+
+ String workingDir = parent.resolve(parent.eval(workingDirOpt, "inkscape"));
+ String rawCmd = parent.eval(commandOpt, "build/cxxtests");
+
+ String cmdExe;
+ if (fileExists(rawCmd)) {
+ cmdExe = rawCmd;
+ } else if (fileExists(rawCmd + ".exe")) {
+ cmdExe = rawCmd + ".exe";
+ } else {
+ error("<cxxtestrun> problem: cxxtests executable not found! (command=\"%s\")", rawCmd.c_str());
+ }
+ // Note that the log file names are based on the exact name used to call cxxtests (it uses argv[0] + ".log"/".xml")
+ if (isNewerThan(cmdExe, rawCmd + ".log") || isNewerThan(cmdExe, rawCmd + ".xml")) newFiles++;
+
+ // Prepend the necessary ../'s
+ String cmd = rawCmd;
+ unsigned int workingDirDepth = 0;
+ bool wasSlash = true;
+ for(size_t i=0; i<workingDir.size(); i++) {
+ // This assumes no . and .. parts
+ if (wasSlash && workingDir[i]!='/') workingDirDepth++;
+ wasSlash = workingDir[i] == '/';
+ }
+ for(size_t i=0; i<workingDirDepth; i++) {
+ cmd = "../" + cmd;
+ }
+
+ if (newFiles>0) {
+ char olddir[1024];
+ if (workingDir.size()>0) {
+ // TODO: Double-check usage of getcwd and handle chdir errors
+ getcwd(olddir, 1024);
+ chdir(workingDir.c_str());
+ }
+
+ String outString;
+ if (!executeCommand(cmd.c_str(), "", outString, outString))
+ {
+ error("<cxxtestrun> problem: %s", outString.c_str());
+ return false;
+ }
+
+ if (workingDir.size()>0) {
+ // TODO: Handle errors?
+ chdir(olddir);
+ }
+
+ removeFromStatCache(getNativePath(cmd + ".log"));
+ removeFromStatCache(getNativePath(cmd + ".xml"));
+ }
+
+ return true;
+ }
+
+ virtual bool parse(Element *elem)
+ {
+ if (!parent.getAttribute(elem, "command", commandOpt))
+ return false;
+ if (!parent.getAttribute(elem, "workingdir", workingDirOpt))
+ return false;
+ return true;
+ }
+
+private:
+
+ String commandOpt;
+ String workingDirOpt;
+
+};
+
+
/**
*
*/
bool verbose = parent.evalBool(verboseOpt, false);
bool quiet = parent.evalBool(quietOpt, false);
bool failOnError = parent.evalBool(failOnErrorOpt, true);
- struct stat finfo;
switch (delType)
{
case DEL_FILE:
char *fname = (char *)fullName.c_str();
if (!quiet && verbose)
taskstatus("path: %s", fname);
- //does not exist
- if (cachedStat(fullName, &finfo)<0)
+ if (failOnError && !removeFile(fullName))
{
- if (failOnError)
- return false;
- else
- return true;
- }
- //exists but is not a regular file
- if (!S_ISREG(finfo.st_mode))
- {
- error("<delete> failed. '%s' exists and is not a regular file",
- fname);
- return false;
- }
- if (remove(fname)<0)
- {
- error("<delete> failed: %s", strerror(errno));
+ //error("Could not delete file '%s'", fullName.c_str());
return false;
}
return true;
String fullDir = parent.resolve(dirName);
if (!quiet && verbose)
taskstatus("path: %s", fullDir.c_str());
- if (!removeDirectory(fullDir))
+ if (failOnError && !removeDirectory(fullDir))
+ {
+ //error("Could not delete directory '%s'", fullDir.c_str());
return false;
+ }
return true;
}
}
execCmd.c_str(), errString.c_str());
return false;
}
+ removeFromStatCache(getNativePath(destfile));
return true;
}
execCmd.c_str(), errString.c_str());
return false;
}
+ // TODO:
+ //removeFromStatCache(getNativePath(........));
return true;
}
error("LINK problem: %s", errbuf.c_str());
return false;
}
+ removeFromStatCache(getNativePath(fullTarget));
if (symFileName.size()>0)
{
error("<strip> symbol file failed : %s", errbuf.c_str());
return false;
}
+ removeFromStatCache(getNativePath(symFullName));
}
if (doStrip)
error("<strip> failed : %s", errbuf.c_str());
return false;
}
+ removeFromStatCache(getNativePath(fullTarget));
}
return true;
virtual bool execute()
{
String fileName = parent.eval(fileNameOpt, "");
+ bool force = parent.evalBool(forceOpt, false);
String text = parent.eval(textOpt, "");
taskstatus("%s", fileName.c_str());
String fullName = parent.resolve(fileName);
- if (!isNewerThan(parent.getURI().getPath(), fullName))
+ if (!force && !isNewerThan(parent.getURI().getPath(), fullName))
{
- //trace("skipped <makefile>");
+ taskstatus("skipped");
return true;
}
String fullNative = getNativePath(fullName);
fputc(text[i], f);
fputc('\n', f);
fclose(f);
+ removeFromStatCache(fullNative);
return true;
}
{
if (!parent.getAttribute(elem, "file", fileNameOpt))
return false;
+ if (!parent.getAttribute(elem, "force", forceOpt))
+ return false;
if (fileNameOpt.size() == 0)
{
error("<makefile> requires 'file=\"filename\"' attribute");
private:
String fileNameOpt;
+ String forceOpt;
String textOpt;
};
error("<msgfmt> problem: %s", errString.c_str());
return false;
}
+ removeFromStatCache(getNativePath(fullDest));
}
return true;
String outbuf, errbuf;
if (!executeCommand(cmd, "", outbuf, errbuf))
return false;
+ // TODO:
+ //removeFromStatCache(getNativePath(fullDest));
return true;
}
error("RC problem: %s", errString.c_str());
return false;
}
+ removeFromStatCache(getNativePath(fullOut));
return true;
}
error("<sharedlib> problem: %s", errString.c_str());
return false;
}
-
+ removeFromStatCache(getNativePath(fullOut));
return true;
}
error("<staticlib> problem: %s", errString.c_str());
return false;
}
-
+ removeFromStatCache(getNativePath(fullOut));
return true;
}
error("<strip> failed : %s", errbuf.c_str());
return false;
}
+ removeFromStatCache(getNativePath(fullName));
return true;
}
nativeFile.c_str(), strerror(ret));
return false;
}
+ removeFromStatCache(nativeFile);
return true;
}
task = new TaskCxxTestPart(parent);
else if (tagName == "cxxtestroot")
task = new TaskCxxTestRoot(parent);
+ else if (tagName == "cxxtestrun")
+ task = new TaskCxxTestRun(parent);
else if (tagName == "delete")
task = new TaskDelete(parent);
else if (tagName == "echo")
pcPrefix = "pc.";
pccPrefix = "pcc.";
pclPrefix = "pcl.";
+ bzrPrefix = "bzr.";
properties.clear();
for (unsigned int i = 0 ; i < allTasks.size() ; i++)
delete allTasks[i];
pclPrefix = attrVal;
pclPrefix.push_back('.');
}
+ else if (attrName == "subversion")
+ {
+ if (attrVal.find('.') != attrVal.npos)
+ {
+ error("bzr prefix cannot have a '.' in it");
+ return false;
+ }
+ bzrPrefix = attrVal;
+ bzrPrefix.push_back('.');
+ }
}
return true;