Code

Use subdirectories with icon sizes.
[inkscape.git] / buildtool.cpp
index 6c38eddb71775cb33b5582ea2dbb4bf6ec83677a..2b4307ab0f0e2ca6026a7eabc2a4a874d0d32a8b 100644 (file)
@@ -3,6 +3,7 @@
  *
  * Authors:
  *   Bob Jamison
+ *   Jasper van de Gronde
  *
  * Copyright (C) 2006-2008 Bob Jamison
  *
@@ -38,7 +39,7 @@
  *
  */
 
-#define BUILDTOOL_VERSION  "BuildTool v0.9.0"
+#define BUILDTOOL_VERSION  "BuildTool v0.9.9"
 
 #include <stdio.h>
 #include <fcntl.h>
@@ -54,6 +55,8 @@
 #include <map>
 #include <set>
 #include <vector>
+#include <algorithm>
+
 
 #ifdef __WIN32__
 #include <windows.h>
@@ -204,7 +207,7 @@ TREX_API TRexBool trex_getsubexp(TRex* exp, int n, TRexMatch *subexp);
 #include <setjmp.h>
 //#include "trex.h"
 
-#ifdef _UINCODE
+#ifdef _UNICODE
 #define scisprint iswprint
 #define scstrlen wcslen
 #define scprintf wprintf
@@ -688,7 +691,7 @@ static const TRexChar *trex_matchnode(TRex* exp,TRexNode *node,const TRexChar *s
             return cur;
     }                 
     case OP_WB:
-        if(str == exp->_bol && !isspace(*str)
+        if((str == exp->_bol && !isspace(*str))
          || (str == exp->_eol && !isspace(*(str-1)))
          || (!isspace(*str) && isspace(*(str+1)))
          || (isspace(*str) && !isspace(*(str+1))) ) {
@@ -2739,6 +2742,98 @@ bool URI::parse(const String &str)
 //########################################################################
 //########################################################################
 
+//########################################################################
+//# Stat cache to speed up stat requests
+//########################################################################
+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) {
+    //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.result = stat(f.c_str(), &(result.first->second.statInfo));
+    }
+    *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
 //########################################################################
@@ -2776,7 +2871,7 @@ public:
     /**
      *
      */
-    String getDirectory()
+    String getDirectory() const
         { return directory; }
         
     /**
@@ -2794,7 +2889,7 @@ public:
     /**
      *
      */
-    std::vector<String> getFiles()
+    std::vector<String> getFiles() const
         { return files; }
         
     /**
@@ -2806,7 +2901,7 @@ public:
     /**
      *
      */
-    std::vector<String> getIncludes()
+    std::vector<String> getIncludes() const
         { return includes; }
         
     /**
@@ -2818,19 +2913,19 @@ public:
     /**
      *
      */
-    std::vector<String> getExcludes()
+    std::vector<String> getExcludes() const
         { return excludes; }
         
     /**
      *
      */
-    unsigned int size()
+    unsigned int size() const
         { return files.size(); }
         
     /**
      *
      */
-    String operator[](int index)
+    String operator[](int index) const
         { return files[index]; }
         
     /**
@@ -3094,11 +3189,19 @@ protected:
     /**
      *    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;
+
 
 
 
@@ -3242,6 +3345,16 @@ protected:
      */ 
     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
      */ 
@@ -3278,7 +3391,13 @@ private:
     bool lookupProperty(const String &s, String &result);
     
     /**
-     * replace variable refs in a sting like ${a} with their values
+     * called by getSubstitutions().  This is in case a looked-up string
+     * has substitutions also.     
+     */
+    bool getSubstitutionsRecursive(const String &s, String &result, int depth);
+
+    /**
+     * replace variable refs in a string like ${a} with their values
      */
     bool getSubstitutions(const String &s, String &result);
 
@@ -3303,10 +3422,10 @@ public:
      */
     PkgConfig()
         {
-                path   = ".";
-                prefix = "/target";
-                init();
-                }
+         path   = ".";
+         prefix = "/target";
+         init();
+         }
 
     /**
      *
@@ -3521,6 +3640,138 @@ private:
     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
+
+};
+
+
+
 
 
 
@@ -3545,14 +3796,31 @@ void MakeBase::error(const char *fmt, ...)
 void MakeBase::status(const char *fmt, ...)
 {
     va_list args;
-    va_start(args,fmt);
     //fprintf(stdout, " ");
+    va_start(args,fmt);
     vfprintf(stdout, fmt, args);
+    va_end(args);
     fprintf(stdout, "\n");
+    fflush(stdout);
+}
+
+
+/**
+ *  Print a printf()-like formatted trace message
+ */
+void MakeBase::trace(const char *fmt, ...)
+{
+    va_list args;
+    fprintf(stdout, "Make: ");
+    va_start(args,fmt);
+    vfprintf(stdout, fmt, args);
     va_end(args) ;
+    fprintf(stdout, "\n");
+    fflush(stdout);
 }
 
 
+
 /**
  *  Resolve another path relative to this one
  */
@@ -3565,20 +3833,6 @@ String MakeBase::resolve(const String &otherPath)
 }
 
 
-/**
- *  Print a printf()-like formatted trace message
- */
-void MakeBase::trace(const char *fmt, ...)
-{
-    va_list args;
-    va_start(args,fmt);
-    fprintf(stdout, "Make: ");
-    vfprintf(stdout, fmt, args);
-    fprintf(stdout, "\n");
-    va_end(args) ;
-}
-
-
 
 /**
  *  Check if a given string matches a given regex pattern
@@ -3855,6 +4109,9 @@ static String win32LastError()
 
 
 
+
+#ifdef __WIN32__
+
 /**
  * Execute a system call, using pipes to send data to the
  * program's stdin,  and reading stdout and stderr.
@@ -3871,7 +4128,6 @@ bool MakeBase::executeCommand(const String &command,
     outbuf.clear();
     errbuf.clear();
     
-#ifdef __WIN32__
 
     /*
     I really hate having win32 code in this program, but the
@@ -3915,13 +4171,18 @@ bool MakeBase::executeCommand(const String &command,
         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;
@@ -3963,7 +4224,7 @@ bool MakeBase::executeCommand(const String &command,
         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;
@@ -4021,7 +4282,7 @@ bool MakeBase::executeCommand(const String &command,
         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;
@@ -4040,35 +4301,136 @@ bool MakeBase::executeCommand(const String &command,
 
     return ret;
 
-#else //do it unix-style
+} 
+
+#else  /*do it unix style*/
+
+#include <sys/wait.h>
+
+
+
+/**
+ * Execute a system call, using pipes to send data to the
+ * program's stdin,  and reading stdout and stderr.
+ */
+bool MakeBase::executeCommand(const String &command,
+                              const String &inbuf,
+                              String &outbuf,
+                              String &errbuf)
+{
+
+    status("============ cmd ============\n%s\n=============================",
+                command.c_str());
+
+    outbuf.clear();
+    errbuf.clear();
+    
 
-    String s;
-    FILE *f = popen(command.c_str(), "r");
-    int errnum = 0;
-    if (f)
+    int outfds[2];
+    if (pipe(outfds) < 0)
+        return false;
+    int errfds[2];
+    if (pipe(errfds) < 0)
+        return false;
+    int pid = fork();
+    if (pid < 0)
+        {
+        close(outfds[0]);
+        close(outfds[1]);
+        close(errfds[0]);
+        close(errfds[1]);
+        error("launch of command '%s' failed : %s",
+             command.c_str(), strerror(errno));
+        return false;
+        }
+    else if (pid > 0) // parent
         {
-        while (true)
+        close(outfds[1]);
+        close(errfds[1]);
+        }
+    else // == 0, child
+        {
+        close(outfds[0]);
+        dup2(outfds[1], STDOUT_FILENO);
+        close(outfds[1]);
+        close(errfds[0]);
+        dup2(errfds[1], STDERR_FILENO);
+        close(errfds[1]);
+
+        char *args[4];
+        args[0] = (char *)"sh";
+        args[1] = (char *)"-c";
+        args[2] = (char *)command.c_str();
+        args[3] = NULL;
+        execv("/bin/sh", args);
+        exit(EXIT_FAILURE);
+        }
+
+    String outb;
+    String errb;
+
+    int outRead = outfds[0];
+    int errRead = errfds[0];
+    int max = outRead;
+    if (errRead > max)
+        max = errRead;
+
+    bool outOpen = true;
+    bool errOpen = true;
+
+    while (outOpen || errOpen)
+        {
+        char ch;
+        fd_set fdset;
+        FD_ZERO(&fdset);
+        if (outOpen)
+            FD_SET(outRead, &fdset);
+        if (errOpen)
+            FD_SET(errRead, &fdset);
+        int ret = select(max+1, &fdset, NULL, NULL, NULL);
+        if (ret < 0)
+            break;
+        if (FD_ISSET(outRead, &fdset))
             {
-            int ch = fgetc(f);
-            if (ch < 0)
-                break;
-            s.push_back((char)ch);
+            if (read(outRead, &ch, 1) <= 0)
+                { outOpen = false; }
+            else if (ch <= 0)
+                { /* outOpen = false; */ }
+            else
+                { outb.push_back(ch); }
+            }
+        if (FD_ISSET(errRead, &fdset))
+            {
+            if (read(errRead, &ch, 1) <= 0)
+                { errOpen = false; }
+            else if (ch <= 0)
+                { /* errOpen = false; */ }
+            else
+                { errb.push_back(ch); }
             }
-        errnum = pclose(f);
         }
-    outbuf = s;
-    if (errnum != 0)
+
+    int childReturnValue;
+    wait(&childReturnValue);
+
+    close(outRead);
+    close(errRead);
+
+    outbuf = outb;
+    errbuf = errb;
+
+    if (childReturnValue != 0)
         {
         error("exec of command '%s' failed : %s",
-             command.c_str(), strerror(errno));
+             command.c_str(), strerror(childReturnValue));
         return false;
         }
-    else
-        return true;
 
-#endif
+    return true;
 } 
 
+#endif
+
 
 
 
@@ -4080,7 +4442,7 @@ bool MakeBase::listDirectories(const String &baseName,
     String fullPath = baseName;
     if (dirName.size()>0)
         {
-        fullPath.append("/");
+        if (dirName[0]!='/') fullPath.append("/");
         fullPath.append(dirName);
         }
     DIR *dir = opendir(fullPath.c_str());
@@ -4103,7 +4465,7 @@ bool MakeBase::listDirectories(const String &baseName,
         fullChildPath.append(childName);
         struct stat finfo;
         String childNative = getNativePath(fullChildPath);
-        if (stat(childNative.c_str(), &finfo)<0)
+        if (cachedStat(childNative, &finfo)<0)
             {
             error("cannot stat file:%s", childNative.c_str());
             }
@@ -4272,7 +4634,7 @@ bool MakeBase::pkgConfigRecursive(const String packageName,
     if (path.size() > 0)
         pkgConfig.setPath(path);
     if (prefix.size() > 0)
-           pkgConfig.setPrefix(prefix);
+        pkgConfig.setPrefix(prefix);
     if (!pkgConfig.query(packageName))
         return false;
     if (query == 0)
@@ -4362,6 +4724,23 @@ bool MakeBase::lookupProperty(const String &propertyName, String &result)
             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;
@@ -4384,10 +4763,17 @@ bool MakeBase::lookupProperty(const String &propertyName, String &result)
 
 /**
  * Analyse a string, looking for any substitutions or other
- * things that need resilution 
+ * things that need resolution 
  */
-bool MakeBase::getSubstitutions(const String &str, String &result)
+bool MakeBase::getSubstitutionsRecursive(const String &str,
+                                         String &result, int depth)
 {
+    if (depth > 10)
+        {
+        error("nesting of substitutions too deep (>10) for '%s'",
+                        str.c_str());
+        return false;
+        }
     String s = trim(str);
     int len = (int)s.size();
     String val;
@@ -4413,7 +4799,11 @@ bool MakeBase::getSubstitutions(const String &str, String &result)
                     String varval;
                     if (!lookupProperty(varname, varval))
                         return false;
-                    val.append(varval);
+                    String varval2;
+                    //Now see if the answer has ${} in it, too
+                    if (!getSubstitutionsRecursive(varval, varval2, depth + 1))
+                        return false;
+                    val.append(varval2);
                     break;
                     }
                 else
@@ -4432,6 +4822,15 @@ bool MakeBase::getSubstitutions(const String &str, String &result)
     return true;
 }
 
+/**
+ * Analyse a string, looking for any substitutions or other
+ * things that need resilution 
+ */
+bool MakeBase::getSubstitutions(const String &str, String &result)
+{
+    return getSubstitutionsRecursive(str, result, 0);
+}
+
 
 
 /**
@@ -4460,10 +4859,12 @@ bool MakeBase::evalBool(const String &s, bool defaultVal)
     if (s.size()==0)
         return defaultVal;
     String val = eval(s, "false");
-    if (s == "true" || s == "TRUE")
+    if (val.size()==0)
+        return defaultVal;
+    if (val == "true" || val == "TRUE")
         return true;
     else
-        return defaultVal;
+        return false;
 }
 
 
@@ -4671,7 +5072,7 @@ bool MakeBase::createDirectory(const String &dirname)
     if (strlen(cnative)==2 && cnative[1]==':')
         return true;
 #endif
-    if (stat(cnative, &finfo)==0)
+    if (cachedStat(nativeDir, &finfo)==0)
         {
         if (!S_ISDIR(finfo.st_mode))
             {
@@ -4707,6 +5108,8 @@ bool MakeBase::createDirectory(const String &dirname)
                  cnative, strerror(errno));
         return false;
         }
+
+    removeFromStatCache(nativeDir);
         
     return true;
 }
@@ -4750,7 +5153,7 @@ bool MakeBase::removeDirectory(const String &dirName)
         struct stat finfo;
         String childNative = getNativePath(childName);
         char *cnative = (char *)childNative.c_str();
-        if (stat(cnative, &finfo)<0)
+        if (cachedStat(childNative, &finfo)<0)
             {
             error("cannot stat file:%s", cnative);
             }
@@ -4769,10 +5172,8 @@ bool MakeBase::removeDirectory(const String &dirName)
         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;
                 }
             }
@@ -4788,6 +5189,8 @@ bool MakeBase::removeDirectory(const String &dirName)
         return false;
         }
 
+    removeFromStatCache(native);
+
     return true;
     
 }
@@ -4801,7 +5204,7 @@ bool MakeBase::copyFile(const String &srcFile, const String &destFile)
     //# 1 Check up-to-date times
     String srcNative = getNativePath(srcFile);
     struct stat srcinfo;
-    if (stat(srcNative.c_str(), &srcinfo)<0)
+    if (cachedStat(srcNative, &srcinfo)<0)
         {
         error("source file %s for copy does not exist",
                  srcNative.c_str());
@@ -4810,7 +5213,7 @@ bool MakeBase::copyFile(const String &srcFile, const String &destFile)
 
     String destNative = getNativePath(destFile);
     struct stat destinfo;
-    if (stat(destNative.c_str(), &destinfo)==0)
+    if (cachedStat(destNative, &destinfo)==0)
         {
         if (destinfo.st_mtime >= srcinfo.st_mtime)
             return true;
@@ -4837,6 +5240,7 @@ bool MakeBase::copyFile(const String &srcFile, const String &destFile)
     FILE *destf = fopen(destNative.c_str(), "wb");
     if (!destf)
         {
+        fclose(srcf);
         error("copyFile cannot open %s for writing", srcNative.c_str());
         return false;
         }
@@ -4863,11 +5267,91 @@ bool MakeBase::copyFile(const String &srcFile, const String &destFile)
         
 #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
@@ -4878,7 +5362,7 @@ bool MakeBase::isRegularFile(const String &fileName)
     struct stat finfo;
     
     //Exists?
-    if (stat(native.c_str(), &finfo)<0)
+    if (cachedStat(native, &finfo)<0)
         return false;
 
 
@@ -4898,7 +5382,7 @@ bool MakeBase::isDirectory(const String &fileName)
     struct stat finfo;
     
     //Exists?
-    if (stat(native.c_str(), &finfo)<0)
+    if (cachedStat(native, &finfo)<0)
         return false;
 
 
@@ -4920,7 +5404,7 @@ bool MakeBase::isNewerThan(const String &fileA, const String &fileB)
     String nativeA = getNativePath(fileA);
     struct stat infoA;
     //IF source does not exist, NOT newer
-    if (stat(nativeA.c_str(), &infoA)<0)
+    if (cachedStat(nativeA, &infoA)<0)
         {
         return false;
         }
@@ -4928,7 +5412,7 @@ bool MakeBase::isNewerThan(const String &fileA, const String &fileB)
     String nativeB = getNativePath(fileB);
     struct stat infoB;
     //IF dest does not exist, YES, newer
-    if (stat(nativeB.c_str(), &infoB)<0)
+    if (cachedStat(nativeB, &infoB)<0)
         {
         return true;
         }
@@ -5271,15 +5755,16 @@ bool PkgConfig::query(const String &pkgName)
     fname.append(".pc");
 
     if (!readFile(fname))
+        {
+        error("Cannot find package '%s'. Do you have it installed?",
+                       pkgName.c_str());
         return false;
+        }
     
     return true;
 }
 
 
-
-
-
 //########################################################################
 //# D E P T O O L
 //########################################################################
@@ -6234,6 +6719,9 @@ public:
         TASK_NONE,
         TASK_CC,
         TASK_COPY,
+        TASK_CXXTEST_PART,
+        TASK_CXXTEST_ROOT,
+        TASK_CXXTEST_RUN,
         TASK_DELETE,
         TASK_ECHO,
         TASK_JAR,
@@ -6610,6 +7098,8 @@ public:
                 }
             if (errorOccurred && !continueOnError)
                 break;
+
+            removeFromStatCache(getNativePath(destFullName));
             }
 
         if (f)
@@ -6738,8 +7228,9 @@ public:
                         fileName.c_str(), toFileName.c_str());
                    String fullSource = parent.resolve(fileName);
                    String fullDest = parent.resolve(toFileName);
-                   //trace("copy %s to file %s", fullSource.c_str(),
-                   //                       fullDest.c_str());
+                   if (verbose)
+                       taskstatus("copy %s to file %s", fullSource.c_str(),
+                                          fullDest.c_str());
                    if (!isRegularFile(fullSource))
                        {
                        error("copy : file %s does not exist", fullSource.c_str());
@@ -6804,11 +7295,13 @@ public:
                        destPath.append(fileName);
                        String fullDest = parent.resolve(destPath);
                        //trace("fileName:%s", fileName.c_str());
-                       //trace("copy %s to new dir : %s", fullSource.c_str(),
-                       //                   fullDest.c_str());
+                       if (verbose)
+                           taskstatus("copy %s to new dir : %s",
+                                 fullSource.c_str(), fullDest.c_str());
                        if (!isNewerThan(fullSource, fullDest))
                            {
-                           //trace("copy skipping %s", fullSource.c_str());
+                           if (verbose)
+                               taskstatus("copy skipping %s", fullSource.c_str());
                            continue;
                            }
                        if (!copyFile(fullSource, fullDest))
@@ -6836,8 +7329,9 @@ public:
                        }
                    destPath.append(baseName);
                    String fullDest = parent.resolve(destPath);
-                   //trace("copy %s to new dir : %s", fullSource.c_str(),
-                   //                       fullDest.c_str());
+                   if (verbose)
+                       taskstatus("file %s to new dir : %s", fullSource.c_str(),
+                                          fullDest.c_str());
                    if (!isRegularFile(fullSource))
                        {
                        error("copy : file %s does not exist", fullSource.c_str());
@@ -6935,6 +7429,301 @@ private:
 };
 
 
+/**
+ * Generate CxxTest files
+ */
+class TaskCxxTestPart: public Task
+{
+public:
+
+    TaskCxxTestPart(MakeBase &par) : Task(par)
+         {
+         type    = TASK_CXXTEST_PART;
+         name    = "cxxtestpart";
+         }
+
+    virtual ~TaskCxxTestPart()
+        {}
+
+    virtual bool execute()
+        {
+        if (!listFiles(parent, fileSet))
+            return false;
+        String fileSetDir = parent.eval(fileSet.getDirectory(), ".");
+                
+        String fullDest = parent.resolve(parent.eval(destPathOpt, "."));
+        String cmd = parent.eval(commandOpt, "cxxtestgen.py");
+        cmd.append(" --part -o ");
+        cmd.append(fullDest);
+
+        unsigned int newFiles = 0;
+        for (unsigned int i=0 ; i<fileSet.size() ; i++)
+            {
+            String fileName = fileSet[i];
+            if (getSuffix(fileName) != "h")
+                continue;
+            String sourcePath;
+            if (fileSetDir.size()>0)
+                {
+                sourcePath.append(fileSetDir);
+                sourcePath.append("/");
+                }
+            sourcePath.append(fileName);
+            String fullSource = parent.resolve(sourcePath);
+
+            cmd.append(" ");
+            cmd.append(fullSource);
+            if (isNewerThan(fullSource, fullDest)) newFiles++;
+            }
+        
+        if (newFiles>0) {
+            size_t const lastSlash = fullDest.find_last_of('/');
+            if (lastSlash != fullDest.npos) {
+                String directory(fullDest, 0, lastSlash);
+                if (!createDirectory(directory))
+                    return false;
+            }
+
+            String outString, errString;
+            if (!executeCommand(cmd.c_str(), "", outString, errString))
+                {
+                error("<cxxtestpart> problem: %s", errString.c_str());
+                return false;
+                }
+            removeFromStatCache(getNativePath(fullDest));
+        }
+
+        return true;
+        }
+
+    virtual bool parse(Element *elem)
+        {
+        if (!parent.getAttribute(elem, "command", commandOpt))
+            return false;
+        if (!parent.getAttribute(elem, "out", destPathOpt))
+            return false;
+            
+        std::vector<Element *> children = elem->getChildren();
+        for (unsigned int i=0 ; i<children.size() ; i++)
+            {
+            Element *child = children[i];
+            String tagName = child->getName();
+            if (tagName == "fileset")
+                {
+                if (!parseFileSet(child, parent, fileSet))
+                    return false;
+                }
+            }
+        return true;
+        }
+
+private:
+
+    String  commandOpt;
+    String  destPathOpt;
+    FileSet fileSet;
+
+};
+
+
+/**
+ * Generate the CxxTest root file
+ */
+class TaskCxxTestRoot: public Task
+{
+public:
+
+    TaskCxxTestRoot(MakeBase &par) : Task(par)
+         {
+         type    = TASK_CXXTEST_ROOT;
+         name    = "cxxtestroot";
+         }
+
+    virtual ~TaskCxxTestRoot()
+        {}
+
+    virtual bool execute()
+        {
+        if (!listFiles(parent, fileSet))
+            return false;
+        String fileSetDir = parent.eval(fileSet.getDirectory(), ".");
+        unsigned int newFiles = 0;
+                
+        String fullDest = parent.resolve(parent.eval(destPathOpt, "."));
+        String cmd = parent.eval(commandOpt, "cxxtestgen.py");
+        cmd.append(" --root -o ");
+        cmd.append(fullDest);
+        String templateFile = parent.eval(templateFileOpt, "");
+        if (templateFile.size()>0) {
+            String fullTemplate = parent.resolve(templateFile);
+            cmd.append(" --template=");
+            cmd.append(fullTemplate);
+            if (isNewerThan(fullTemplate, fullDest)) newFiles++;
+        }
+
+        for (unsigned int i=0 ; i<fileSet.size() ; i++)
+            {
+            String fileName = fileSet[i];
+            if (getSuffix(fileName) != "h")
+                continue;
+            String sourcePath;
+            if (fileSetDir.size()>0)
+                {
+                sourcePath.append(fileSetDir);
+                sourcePath.append("/");
+                }
+            sourcePath.append(fileName);
+            String fullSource = parent.resolve(sourcePath);
+
+            cmd.append(" ");
+            cmd.append(fullSource);
+            if (isNewerThan(fullSource, fullDest)) newFiles++;
+            }
+        
+        if (newFiles>0) {
+            size_t const lastSlash = fullDest.find_last_of('/');
+            if (lastSlash != fullDest.npos) {
+                String directory(fullDest, 0, lastSlash);
+                if (!createDirectory(directory))
+                    return false;
+            }
+
+            String outString, errString;
+            if (!executeCommand(cmd.c_str(), "", outString, errString))
+                {
+                error("<cxxtestroot> problem: %s", errString.c_str());
+                return false;
+                }
+            removeFromStatCache(getNativePath(fullDest));
+        }
+
+        return true;
+        }
+
+    virtual bool parse(Element *elem)
+        {
+        if (!parent.getAttribute(elem, "command", commandOpt))
+            return false;
+        if (!parent.getAttribute(elem, "template", templateFileOpt))
+            return false;
+        if (!parent.getAttribute(elem, "out", destPathOpt))
+            return false;
+            
+        std::vector<Element *> children = elem->getChildren();
+        for (unsigned int i=0 ; i<children.size() ; i++)
+            {
+            Element *child = children[i];
+            String tagName = child->getName();
+            if (tagName == "fileset")
+                {
+                if (!parseFileSet(child, parent, fileSet))
+                    return false;
+                }
+            }
+        return true;
+        }
+
+private:
+
+    String  commandOpt;
+    String  templateFileOpt;
+    String  destPathOpt;
+    FileSet fileSet;
+
+};
+
+
+/**
+ * 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;
+
+};
+
+
 /**
  *
  */
@@ -6966,37 +7755,33 @@ public:
         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:
                 {
-                status("          : %s", fileName.c_str());
+                taskstatus("file: %s", fileName.c_str());
                 String fullName = parent.resolve(fileName);
                 char *fname = (char *)fullName.c_str();
-                //does not exist
-                if (stat(fname, &finfo)<0)
-                    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)
+                if (!quiet && verbose)
+                    taskstatus("path: %s", fname);
+                if (failOnError && !removeFile(fullName))
                     {
-                    error("<delete> failed: %s", strerror(errno));
+                    //error("Could not delete file '%s'", fullName.c_str());
                     return false;
                     }
                 return true;
                 }
             case DEL_DIR:
                 {
-                taskstatus("%s", dirName.c_str());
+                taskstatus("dir: %s", dirName.c_str());
                 String fullDir = parent.resolve(dirName);
-                if (!removeDirectory(fullDir))
+                if (!quiet && verbose)
+                    taskstatus("path: %s", fullDir.c_str());
+                if (failOnError && !removeDirectory(fullDir))
+                    {
+                    //error("Could not delete directory '%s'", fullDir.c_str());
                     return false;
+                    }
                 return true;
                 }
             }
@@ -7126,6 +7911,7 @@ public:
                                       execCmd.c_str(), errString.c_str());
             return false;
             }
+        removeFromStatCache(getNativePath(destfile));
         return true;
         }
 
@@ -7243,6 +8029,8 @@ public:
                                       execCmd.c_str(), errString.c_str());
             return false;
             }
+        // TODO: 
+        //removeFromStatCache(getNativePath(........));
         return true;
         }
 
@@ -7345,6 +8133,7 @@ public:
             error("LINK problem: %s", errbuf.c_str());
             return false;
             }
+        removeFromStatCache(getNativePath(fullTarget));
 
         if (symFileName.size()>0)
             {
@@ -7359,6 +8148,7 @@ public:
                 error("<strip> symbol file failed : %s", errbuf.c_str());
                 return false;
                 }
+            removeFromStatCache(getNativePath(symFullName));
             }
             
         if (doStrip)
@@ -7371,6 +8161,7 @@ public:
                error("<strip> failed : %s", errbuf.c_str());
                return false;
                }
+            removeFromStatCache(getNativePath(fullTarget));
             }
 
         return true;
@@ -7450,13 +8241,14 @@ public:
     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);
@@ -7472,6 +8264,7 @@ public:
             fputc(text[i], f);
         fputc('\n', f);
         fclose(f);
+        removeFromStatCache(fullNative);
         return true;
         }
 
@@ -7479,6 +8272,8 @@ public:
         {
         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");
@@ -7494,6 +8289,7 @@ public:
 private:
 
     String fileNameOpt;
+    String forceOpt;
     String textOpt;
 };
 
@@ -7638,6 +8434,7 @@ public:
                 error("<msgfmt> problem: %s", errString.c_str());
                 return false;
                 }
+            removeFromStatCache(getNativePath(fullDest));
             }
 
         return true;
@@ -7812,6 +8609,8 @@ public:
         String outbuf, errbuf;
         if (!executeCommand(cmd, "", outbuf, errbuf))
             return false;
+        // TODO:
+        //removeFromStatCache(getNativePath(fullDest));
         return true;
         }
 
@@ -7838,7 +8637,7 @@ private:
 
 
 /**
- * Compile a resource file into a .res
+ * Compile a resource file into a binary object
  */
 class TaskRC : public Task
 {
@@ -7875,6 +8674,7 @@ public:
             error("RC problem: %s", errString.c_str());
             return false;
             }
+        removeFromStatCache(getNativePath(fullOut));
         return true;
         }
 
@@ -8000,7 +8800,7 @@ public:
             error("<sharedlib> problem: %s", errString.c_str());
             return false;
             }
-
+        removeFromStatCache(getNativePath(fullOut));
         return true;
         }
 
@@ -8121,7 +8921,7 @@ public:
             error("<staticlib> problem: %s", errString.c_str());
             return false;
             }
-
+        removeFromStatCache(getNativePath(fullOut));
         return true;
         }
 
@@ -8204,6 +9004,7 @@ public:
             error("<strip> failed : %s", errbuf.c_str());
             return false;
             }
+        removeFromStatCache(getNativePath(fullName));
         return true;
         }
 
@@ -8269,6 +9070,7 @@ public:
                 nativeFile.c_str(), strerror(ret));
             return false;
             }
+        removeFromStatCache(nativeFile);
         return true;
         }
 
@@ -8328,6 +9130,12 @@ Task *Task::createTask(Element *elem, int lineNr)
         task = new TaskCC(parent);
     else if (tagName == "copy")
         task = new TaskCopy(parent);
+    else if (tagName == "cxxtestpart")
+        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")
@@ -8722,6 +9530,7 @@ void Make::init()
     pcPrefix        = "pc.";
     pccPrefix       = "pcc.";
     pclPrefix       = "pcl.";
+    bzrPrefix       = "bzr.";
     properties.clear();
     for (unsigned int i = 0 ; i < allTasks.size() ; i++)
         delete allTasks[i];
@@ -9140,6 +9949,16 @@ bool Make::parseProperty(Element *elem)
             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;