summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: e173002)
raw | patch | inline | side by side (parent: e173002)
author | ishmal <ishmal@users.sourceforge.net> | |
Wed, 15 Nov 2006 17:09:15 +0000 (17:09 +0000) | ||
committer | ishmal <ishmal@users.sourceforge.net> | |
Wed, 15 Nov 2006 17:09:15 +0000 (17:09 +0000) |
build.xml | [new file with mode: 0644] | patch | blob |
buildtool.cpp | [new file with mode: 0644] | patch | blob |
diff --git a/build.xml b/build.xml
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,267 @@
+<project name="Sands" default="dist" basedir=".">\r
+ <description>\r
+ Build file for Sands office data tool\r
+ </description>\r
+\r
+ <!-- set global properties for this build -->\r
+ <property name="version" location="0.45"/>\r
+ <property name="src" location="src"/>\r
+ <property name="gtk" location="c:/gtk28"/>\r
+ <property name="lib" location="lib"/>\r
+ <property name="build" location="build"/>\r
+ <property name="dist" location="dist"/>\r
+\r
+ <target name="init">\r
+ <!-- Create the build directory structure used by compile -->\r
+ <mkdir dir="${build}"/>\r
+ <copy file="config.h.mingw" tofile="config.h"/>\r
+ </target>\r
+\r
+ <target name="compile" depends="init"\r
+ description="compile the source " >\r
+ <!-- Compile from source to build -->\r
+ <cc cc="gcc" cxx="g++" destdir="${build}/obj">\r
+ <fileset dir="${src}">\r
+ <!-- THINGS TO EXCLUDE -->\r
+ <exclude name="ast"/>\r
+ <exclude name="bonobo"/>\r
+ <exclude name="deptool.cpp"/>\r
+ <exclude name="dialogs/filedialog-win32.cpp"/>\r
+ <exclude name="display/testnr.cpp"/>\r
+ <exclude name="display/bezier-utils-test.cpp"/>\r
+ <exclude name="dom/work"/>\r
+ <exclude name="dom/odf/SvgOdg.cpp"/>\r
+ <exclude name="extension/api.cpp"/>\r
+ <exclude name="extension/dxf2svg"/>\r
+ <exclude name="extension/internal/gnome.cpp"/>\r
+ <exclude name="extension/script/bindtest.cpp"/>\r
+ <exclude name="extension/script/cpptest.cpp"/>\r
+ <exclude name="extension/plugin"/>\r
+ <exclude name="extract-uri-test.cpp"/>\r
+ <exclude name="helper/units-test.cpp"/>\r
+ <exclude name="inkview.cpp"/>\r
+ <exclude name="libnr/in-svg-plane-test.cpp"/>\r
+ <exclude name="libnr/nr-compose-reference.cpp"/>\r
+ <exclude name="libnr/nr-compose-test.cpp"/>\r
+ <exclude name="libnr/nr-matrix-test.cpp"/>\r
+ <exclude name="libnr/nr-point-fns-test.cpp"/>\r
+ <exclude name="libnr/nr-rotate-fns-test.cpp"/>\r
+ <exclude name="libnr/nr-rotate-test.cpp"/>\r
+ <exclude name="libnr/nr-scale-test.cpp"/>\r
+ <exclude name="libnr/nr-translate-test.cpp"/>\r
+ <exclude name="libnr/nr-types-test.cpp"/>\r
+ <exclude name="livarot/Path-test.cpp"/>\r
+ <exclude name="mod360-test.cpp"/>\r
+ <exclude name="trace/potrace/potest.cpp"/>\r
+ <exclude name="round-test.cpp"/>\r
+ <exclude name="sp-gradient-test.cpp"/>\r
+ <exclude name="svg/ftos.cpp"/>\r
+ <exclude name="utest"/>\r
+ <exclude name="widgets/test-widgets.cpp"/>\r
+ <exclude name="xml/quote-test.cpp"/>\r
+ <exclude name="xml/repr-action-test.cpp"/>\r
+ <exclude name="io/streamtest.cpp"/>\r
+ <!--JABBER-->\r
+ <exclude name="pedro/pedrogui.cpp"/>\r
+ <exclude name="pedro/pedrogui.h"/>\r
+ <exclude name="pedro/work"/>\r
+ <!--WHITEBOARD-->\r
+ <exclude name="ui/dialog/session-player.cpp"/>\r
+ <exclude name="ui/dialog/whiteboard-connect.cpp"/>\r
+ <exclude name="ui/dialog/whiteboard-sharewithchat.cpp"/>\r
+ <exclude name="ui/dialog/whiteboard-sharewithuser.cpp"/>\r
+ <exclude name="dialogs/whiteboard-connect-dialog.cpp"/>\r
+ <exclude name="dialogs/whiteboard-common-dialog.cpp"/>\r
+ <exclude name="dialogs/whiteboard-sharewithchat-dialog.cpp"/>\r
+ <exclude name="dialogs/whiteboard-sharewithuser-dialog.cpp"/>\r
+ <exclude name="jabber_whiteboard/node-tracker.cpp"/>\r
+ <exclude name="jabber_whiteboard/node-utilities.cpp"/>\r
+ <!--WHITEBOARD-->\r
+ <exclude name="removeoverlap/placement_SolveVPSC.cpp"/>\r
+ <exclude name="removeoverlap/placement_SolveVPSC.h"/>\r
+ <exclude name="removeoverlap/test.cpp"/>\r
+ <exclude name="removeoverlap/remove_rectangle_overlap-test.cpp"/>\r
+ <exclude name="removeoverlap/remove_rectangle_overlap-test.h"/>\r
+ </fileset>\r
+ <flags>\r
+ -Wall -g -O3\r
+ -mms-bitfields\r
+ </flags>\r
+ <defines>\r
+ -DVERSION=\"${version}\"\r
+ -DHAVE_CONFIG_H\r
+ -DXP_WIN <!-- for JS -->\r
+ -D_INTL_REDIRECT_INLINE\r
+ -DWITH_INKBOARD -DHAVE_SSL <!-- inkboard -->\r
+ </defines>\r
+ <includes>\r
+ -I${gtk}/include\r
+ <!-- GTK / GTKMM -->\r
+ -I${gtk}/include/glibmm-2.4\r
+ -I${gtk}/lib/glibmm-2.4/include\r
+ -I${gtk}/include/gtkmm-2.4\r
+ -I${gtk}/lib/gtkmm-2.4/include\r
+ -I${gtk}/include/gdkmm-2.4\r
+ -I${gtk}/lib/gdkmm-2.4/include\r
+ -I${gtk}/include/pangomm-1.4\r
+ -I${gtk}/include/atkmm-1.6\r
+ -I${gtk}/include/sigc++-2.0\r
+ -I${gtk}/lib/sigc++-2.0/include\r
+ -I${gtk}/include/gtk-2.0\r
+ -I${gtk}/lib/gtk-2.0/include\r
+ -I${gtk}/include/atk-1.0\r
+ -I${gtk}/include/pango-1.0\r
+ -I${gtk}/include/glib-2.0\r
+ -I${gtk}/lib/glib-2.0/include\r
+ <!-- OTHER -->\r
+ -I${gtk}/include/libxml2 \r
+ -I${gtk}/include/freetype2\r
+ -I${gtk}/include/cairo\r
+ <!-- PERL -->\r
+ -Wno-comment -I${gtk}/perl/lib/CORE\r
+ <!-- PYTHON -->\r
+ -I${gtk}/python/include\r
+ </includes>\r
+ </cc>\r
+ </target>\r
+ \r
+ <target name="lib" depends="compile">\r
+ <ar file="${build}/libinkscape.a">\r
+ <fileset dir="${build}/obj">\r
+ <exclude name="main,o"/>\r
+ <exclude name="winmain,o"/>\r
+ </fileset>\r
+ </ar>\r
+ </target>\r
+\r
+ <target name="i18n" depends="compile">\r
+ <msgfmt todir="${build}/po">\r
+ <fileset dir="po">\r
+ </fileset>\r
+ </msgfmt>\r
+ </target>\r
+\r
+ <target name="link" depends="lib">\r
+ <rc command="windres -o" \r
+ file="${src}/inkscape.rc"\r
+ out="${build}/inkres.o">\r
+ <flags>\r
+ --include-dir=${src}\r
+ </flags>\r
+ </rc>\r
+ <link command="g++" out="${build}/inkscape.exe">\r
+ <flags>\r
+ </flags>\r
+ <fileset>\r
+ <include name="${build}/inkres.o"/>\r
+ <include name="${build}/obj/main.o"/>\r
+ <include name="${build}/obj/winmain.o"/>\r
+ <include name="${build}/libinkscape.a"/>\r
+ </fileset>\r
+ <libs>\r
+ -L${gtk}/lib\r
+ -lgtkmm-2.4 -lgdkmm-2.4 -lglibmm-2.4\r
+ -latkmm-1.6 -lpangomm-1.4 -lsigc-2.0\r
+ -lgtk-win32-2.0 -lgdk-win32-2.0 -latk-1.0\r
+ -lgdk_pixbuf-2.0\r
+ -lpangocairo-1.0 -lpangoft2-1.0 -lpangowin32-1.0 -lpango-1.0\r
+ -lgobject-2.0 -lgmodule-2.0 -lgthread-2.0 -lglib-2.0 -lcairo\r
+ <!-- PERL -->\r
+ -L${gtk}/perl/lib/CORE -lperl58\r
+ <!-- PYTHON -->\r
+ -L${gtk}/python/libs -lpython24\r
+ ${gtk}/bin/libxml2.dll\r
+ ${gtk}/lib/iconv.lib\r
+ -lfreetype.dll -lfontconfig.dll\r
+ -llcms.dll\r
+ -lssl -lcrypto\r
+ -lpng -lpopt ${gtk}/lib/zdll.lib\r
+ -lgc -mwindows -lws2_32 -lintl -lm\r
+ </libs>\r
+ </link>\r
+ </target>\r
+\r
+ <target name="dist" depends="link"\r
+ description="generate the distribution" >\r
+ <!-- Create the distribution directory -->\r
+ <copy file="${build}/inkscape.exe" todir="${dist}"/>\r
+ <copy file="AUTHORS" todir="${dist}"/>\r
+ <copy file="COPYING" todir="${dist}"/>\r
+ <copy file="COPYING.LIB" todir="${dist}"/>\r
+ <copy file="NEWS" todir="${dist}"/>\r
+ <copy file="README" todir="${dist}"/>\r
+ <copy file="TRANSLATORS" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/libatkmm-1.6-1.dll" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/libglibmm-2.4-1.dll" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/libgtkmm-2.4-1.dll" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/libgdkmm-2.4-1.dll" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/libpangomm-1.4-1.dll" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/libsigc-2.0-0.dll" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/freetype6.dll" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/libatk-1.0-0.dll" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/libgdk-win32-2.0-0.dll" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/libgdk_pixbuf-2.0-0.dll" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/libglib-2.0-0.dll" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/libgmodule-2.0-0.dll" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/libgobject-2.0-0.dll" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/libgtk-win32-2.0-0.dll" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/libgthread-2.0-0.dlll" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/libcairo-2.dll" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/libpangocairo-1.0-0.dll" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/libpango-1.0-0.dll" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/libpangoft2-1.0-0.dll" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/libpangowin32-1.0-0.dll" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/freetype6.dll" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/libfontconfig-1.dll" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/libxml2.dll" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/xmlparse.dll" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/jpeg62.dll" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/libintl-2.dll" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/libintl-2.dll" tofile="${dist}/intl.dll"/>\r
+ <copy file="${gtk}/bin/libpng13.dlll" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/msvcr70.dll" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/msvcr71.dll" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/zlib1.dll" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/iconv.dll" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/popt1.dll" todir="${dist}"/>\r
+ <copy file="${gtk}/bin/liblcms-1.dll" todir="${dist}"/>\r
+\r
+ <!-- MSGFMT files -->\r
+ <copy todir="${dist}"> <fileset dir="${build}/po"/> </copy>\r
+\r
+ <!-- GTK -->\r
+ <copy todir="${dist}"> <fileset dir="${gtk}/etc"/> </copy>\r
+ <copy file="${gtk}/share/themes/MS-Windows/gtk-2.0/gtkrc" todir="${dist}/etc/gtk-2.0"/>\r
+ <copy todir="${dist}/lib"> <fileset dir="${gtk}/lib/gtk-2.0"/> </copy>\r
+ <copy todir="${dist}/lib"> <fileset dir="${gtk}/lib/glib-2.0"/> </copy>\r
+ <copy todir="${dist}/lib"> <fileset dir="${gtk}/lib/locale"/> </copy>\r
+ <copy todir="${dist}/lib"> <fileset dir="${gtk}/lib/pango"/> </copy>\r
+ <copy todir="${dist}"> <fileset dir="share"/> </copy>\r
+ <copy todir="${dist}/share"> <fileset dir="${gtk}/share/themes"/> </copy>\r
+ <mkdir dir="${dist}/data"/>\r
+ <mkdir dir="${dist}/locale"/>\r
+ <mkdir dir="${dist}/modules"/>\r
+ <mkdir dir="${dist}/plugins"/>\r
+ <copy file="${gtk}/bin/gdb.exe" todir="${dist}"/>\r
+\r
+ <!-- PERL -->\r
+ <copy file="${gtk}/perl/bin/perl58.dll" todir="${dist}"/>\r
+\r
+ <!-- PYTHON -->\r
+ <copy file="${gtk}/python/python24.dll" todir="${dist}"/>\r
+ <copy file="${gtk}/python/python.exe" todir="${dist}/python"/>\r
+ <copy todir="${dist}/python"> <fileset dir="${gtk}/python/Lib"/> </copy>\r
+ <copy todir="${dist}/python"> <fileset dir="${gtk}/python/DLLs"/> </copy>\r
+ <copy todir="${dist}/python"> <fileset dir="${gtk}/python/Scripts"/> </copy>\r
+\r
+\r
+ </target>\r
+\r
+ <target name="clean"\r
+ description="clean up" >\r
+ <!-- Delete the ${build} and ${dist} directory trees -->\r
+ <delete dir="${build}"/>\r
+ <delete dir="${dist}"/>\r
+ </target>\r
+</project>\r
+\r
diff --git a/buildtool.cpp b/buildtool.cpp
--- /dev/null
+++ b/buildtool.cpp
@@ -0,0 +1,6732 @@
+/**
+ * Simple build automation tool.
+ *
+ * Authors:
+ * Bob Jamison
+ *
+ * Copyright (C) 2006 Bob Jamison
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+
+
+
+
+
+
+
+
+
+
+*/
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <sys/time.h>
+#include <dirent.h>
+
+
+#include <string>
+#include <map>
+#include <set>
+#include <vector>
+
+#ifdef __WIN32__
+#include <windows.h>
+#endif
+
+
+
+namespace buildtool
+{
+
+
+
+
+
+
+//########################################################################
+//########################################################################
+//## X M L
+//########################################################################
+//########################################################################
+
+// Note: This mini-dom library comes from Pedro, another little project
+// of mine.
+
+typedef std::string String;
+typedef unsigned int XMLCh;
+
+
+class Namespace
+{
+public:
+ Namespace()
+ {}
+
+ Namespace(const String &prefixArg, const String &namespaceURIArg)
+ {
+ prefix = prefixArg;
+ namespaceURI = namespaceURIArg;
+ }
+
+ Namespace(const Namespace &other)
+ {
+ assign(other);
+ }
+
+ Namespace &operator=(const Namespace &other)
+ {
+ assign(other);
+ return *this;
+ }
+
+ virtual ~Namespace()
+ {}
+
+ virtual String getPrefix()
+ { return prefix; }
+
+ virtual String getNamespaceURI()
+ { return namespaceURI; }
+
+protected:
+
+ void assign(const Namespace &other)
+ {
+ prefix = other.prefix;
+ namespaceURI = other.namespaceURI;
+ }
+
+ String prefix;
+ String namespaceURI;
+
+};
+
+class Attribute
+{
+public:
+ Attribute()
+ {}
+
+ Attribute(const String &nameArg, const String &valueArg)
+ {
+ name = nameArg;
+ value = valueArg;
+ }
+
+ Attribute(const Attribute &other)
+ {
+ assign(other);
+ }
+
+ Attribute &operator=(const Attribute &other)
+ {
+ assign(other);
+ return *this;
+ }
+
+ virtual ~Attribute()
+ {}
+
+ virtual String getName()
+ { return name; }
+
+ virtual String getValue()
+ { return value; }
+
+protected:
+
+ void assign(const Attribute &other)
+ {
+ name = other.name;
+ value = other.value;
+ }
+
+ String name;
+ String value;
+
+};
+
+
+class Element
+{
+friend class Parser;
+
+public:
+ Element()
+ {
+ parent = NULL;
+ }
+
+ Element(const String &nameArg)
+ {
+ parent = NULL;
+ name = nameArg;
+ }
+
+ Element(const String &nameArg, const String &valueArg)
+ {
+ parent = NULL;
+ name = nameArg;
+ value = valueArg;
+ }
+
+ Element(const Element &other)
+ {
+ assign(other);
+ }
+
+ Element &operator=(const Element &other)
+ {
+ assign(other);
+ return *this;
+ }
+
+ virtual Element *clone();
+
+ virtual ~Element()
+ {
+ for (unsigned int i=0 ; i<children.size() ; i++)
+ delete children[i];
+ }
+
+ virtual String getName()
+ { return name; }
+
+ virtual String getValue()
+ { return value; }
+
+ Element *getParent()
+ { return parent; }
+
+ std::vector<Element *> getChildren()
+ { return children; }
+
+ std::vector<Element *> findElements(const String &name);
+
+ String getAttribute(const String &name);
+
+ std::vector<Attribute> &getAttributes()
+ { return attributes; }
+
+ String getTagAttribute(const String &tagName, const String &attrName);
+
+ String getTagValue(const String &tagName);
+
+ void addChild(Element *child);
+
+ void addAttribute(const String &name, const String &value);
+
+ void addNamespace(const String &prefix, const String &namespaceURI);
+
+
+ /**
+ * Prettyprint an XML tree to an output stream. Elements are indented
+ * according to element hierarchy.
+ * @param f a stream to receive the output
+ * @param elem the element to output
+ */
+ void writeIndented(FILE *f);
+
+ /**
+ * Prettyprint an XML tree to standard output. This is the equivalent of
+ * writeIndented(stdout).
+ * @param elem the element to output
+ */
+ void print();
+
+protected:
+
+ void assign(const Element &other)
+ {
+ parent = other.parent;
+ children = other.children;
+ attributes = other.attributes;
+ namespaces = other.namespaces;
+ name = other.name;
+ value = other.value;
+ }
+
+ void findElementsRecursive(std::vector<Element *>&res, const String &name);
+
+ void writeIndentedRecursive(FILE *f, int indent);
+
+ Element *parent;
+
+ std::vector<Element *>children;
+
+ std::vector<Attribute> attributes;
+ std::vector<Namespace> namespaces;
+
+ String name;
+ String value;
+
+};
+
+
+
+
+
+class Parser
+{
+public:
+ /**
+ * Constructor
+ */
+ Parser()
+ { init(); }
+
+ virtual ~Parser()
+ {}
+
+ /**
+ * Parse XML in a char buffer.
+ * @param buf a character buffer to parse
+ * @param pos position to start parsing
+ * @param len number of chars, from pos, to parse.
+ * @return a pointer to the root of the XML document;
+ */
+ Element *parse(const char *buf,int pos,int len);
+
+ /**
+ * Parse XML in a char buffer.
+ * @param buf a character buffer to parse
+ * @param pos position to start parsing
+ * @param len number of chars, from pos, to parse.
+ * @return a pointer to the root of the XML document;
+ */
+ Element *parse(const String &buf);
+
+ /**
+ * Parse a named XML file. The file is loaded like a data file;
+ * the original format is not preserved.
+ * @param fileName the name of the file to read
+ * @return a pointer to the root of the XML document;
+ */
+ Element *parseFile(const String &fileName);
+
+ /**
+ * Utility method to preprocess a string for XML
+ * output, escaping its entities.
+ * @param str the string to encode
+ */
+ static String encode(const String &str);
+
+ /**
+ * Removes whitespace from beginning and end of a string
+ */
+ String trim(const String &s);
+
+private:
+
+ void init()
+ {
+ keepGoing = true;
+ currentNode = NULL;
+ parselen = 0;
+ parsebuf = NULL;
+ currentPosition = 0;
+ }
+
+ void getLineAndColumn(long pos, long *lineNr, long *colNr);
+
+ void error(char *fmt, ...);
+
+ int peek(long pos);
+
+ int match(long pos, const char *text);
+
+ int skipwhite(long p);
+
+ int getWord(int p0, String &buf);
+
+ int getQuoted(int p0, String &buf, int do_i_parse);
+
+ int parseVersion(int p0);
+
+ int parseDoctype(int p0);
+
+ int parseElement(int p0, Element *par,int depth);
+
+ Element *parse(XMLCh *buf,int pos,int len);
+
+ bool keepGoing;
+ Element *currentNode;
+ long parselen;
+ XMLCh *parsebuf;
+ String cdatabuf;
+ long currentPosition;
+ int colNr;
+
+};
+
+
+
+
+//########################################################################
+//# E L E M E N T
+//########################################################################
+
+Element *Element::clone()
+{
+ Element *elem = new Element(name, value);
+ elem->parent = parent;
+ elem->attributes = attributes;
+ elem->namespaces = namespaces;
+
+ std::vector<Element *>::iterator iter;
+ for (iter = children.begin(); iter != children.end() ; iter++)
+ {
+ elem->addChild((*iter)->clone());
+ }
+ return elem;
+}
+
+
+void Element::findElementsRecursive(std::vector<Element *>&res, const String &name)
+{
+ if (getName() == name)
+ {
+ res.push_back(this);
+ }
+ for (unsigned int i=0; i<children.size() ; i++)
+ children[i]->findElementsRecursive(res, name);
+}
+
+std::vector<Element *> Element::findElements(const String &name)
+{
+ std::vector<Element *> res;
+ findElementsRecursive(res, name);
+ return res;
+}
+
+String Element::getAttribute(const String &name)
+{
+ for (unsigned int i=0 ; i<attributes.size() ; i++)
+ if (attributes[i].getName() ==name)
+ return attributes[i].getValue();
+ return "";
+}
+
+String Element::getTagAttribute(const String &tagName, const String &attrName)
+{
+ std::vector<Element *>elems = findElements(tagName);
+ if (elems.size() <1)
+ return "";
+ String res = elems[0]->getAttribute(attrName);
+ return res;
+}
+
+String Element::getTagValue(const String &tagName)
+{
+ std::vector<Element *>elems = findElements(tagName);
+ if (elems.size() <1)
+ return "";
+ String res = elems[0]->getValue();
+ return res;
+}
+
+void Element::addChild(Element *child)
+{
+ if (!child)
+ return;
+ child->parent = this;
+ children.push_back(child);
+}
+
+
+void Element::addAttribute(const String &name, const String &value)
+{
+ Attribute attr(name, value);
+ attributes.push_back(attr);
+}
+
+void Element::addNamespace(const String &prefix, const String &namespaceURI)
+{
+ Namespace ns(prefix, namespaceURI);
+ namespaces.push_back(ns);
+}
+
+void Element::writeIndentedRecursive(FILE *f, int indent)
+{
+ int i;
+ if (!f)
+ return;
+ //Opening tag, and attributes
+ for (i=0;i<indent;i++)
+ fputc(' ',f);
+ fprintf(f,"<%s",name.c_str());
+ for (unsigned int i=0 ; i<attributes.size() ; i++)
+ {
+ fprintf(f," %s=\"%s\"",
+ attributes[i].getName().c_str(),
+ attributes[i].getValue().c_str());
+ }
+ for (unsigned int i=0 ; i<namespaces.size() ; i++)
+ {
+ fprintf(f," xmlns:%s=\"%s\"",
+ namespaces[i].getPrefix().c_str(),
+ namespaces[i].getNamespaceURI().c_str());
+ }
+ fprintf(f,">\n");
+
+ //Between the tags
+ if (value.size() > 0)
+ {
+ for (int i=0;i<indent;i++)
+ fputc(' ', f);
+ fprintf(f," %s\n", value.c_str());
+ }
+
+ for (unsigned int i=0 ; i<children.size() ; i++)
+ children[i]->writeIndentedRecursive(f, indent+2);
+
+ //Closing tag
+ for (int i=0; i<indent; i++)
+ fputc(' ',f);
+ fprintf(f,"</%s>\n", name.c_str());
+}
+
+void Element::writeIndented(FILE *f)
+{
+ writeIndentedRecursive(f, 0);
+}
+
+void Element::print()
+{
+ writeIndented(stdout);
+}
+
+
+//########################################################################
+//# P A R S E R
+//########################################################################
+
+
+
+typedef struct
+ {
+ char *escaped;
+ char value;
+ } EntityEntry;
+
+static EntityEntry entities[] =
+{
+ { "&" , '&' },
+ { "<" , '<' },
+ { ">" , '>' },
+ { "'", '\'' },
+ { """, '"' },
+ { NULL , '\0' }
+};
+
+
+
+/**
+ * Removes whitespace from beginning and end of a string
+ */
+String Parser::trim(const String &s)
+{
+ if (s.size() < 1)
+ return s;
+
+ //Find first non-ws char
+ unsigned int begin = 0;
+ for ( ; begin < s.size() ; begin++)
+ {
+ if (!isspace(s[begin]))
+ break;
+ }
+
+ //Find first non-ws char, going in reverse
+ unsigned int end = s.size() - 1;
+ for ( ; end > begin ; end--)
+ {
+ if (!isspace(s[end]))
+ break;
+ }
+ //trace("begin:%d end:%d", begin, end);
+
+ String res = s.substr(begin, end-begin+1);
+ return res;
+}
+
+void Parser::getLineAndColumn(long pos, long *lineNr, long *colNr)
+{
+ long line = 1;
+ long col = 1;
+ for (long i=0 ; i<pos ; i++)
+ {
+ XMLCh ch = parsebuf[i];
+ if (ch == '\n' || ch == '\r')
+ {
+ col = 0;
+ line ++;
+ }
+ else
+ col++;
+ }
+ *lineNr = line;
+ *colNr = col;
+
+}
+
+
+void Parser::error(char *fmt, ...)
+{
+ long lineNr;
+ long colNr;
+ getLineAndColumn(currentPosition, &lineNr, &colNr);
+ va_list args;
+ fprintf(stderr, "xml error at line %ld, column %ld:", lineNr, colNr);
+ va_start(args,fmt);
+ vfprintf(stderr,fmt,args);
+ va_end(args) ;
+ fprintf(stderr, "\n");
+}
+
+
+
+int Parser::peek(long pos)
+{
+ if (pos >= parselen)
+ return -1;
+ currentPosition = pos;
+ int ch = parsebuf[pos];
+ //printf("ch:%c\n", ch);
+ return ch;
+}
+
+
+
+String Parser::encode(const String &str)
+{
+ String ret;
+ for (unsigned int i=0 ; i<str.size() ; i++)
+ {
+ XMLCh ch = (XMLCh)str[i];
+ if (ch == '&')
+ ret.append("&");
+ else if (ch == '<')
+ ret.append("<");
+ else if (ch == '>')
+ ret.append(">");
+ else if (ch == '\'')
+ ret.append("'");
+ else if (ch == '"')
+ ret.append(""");
+ else
+ ret.push_back(ch);
+
+ }
+ return ret;
+}
+
+
+int Parser::match(long p0, const char *text)
+{
+ int p = p0;
+ while (*text)
+ {
+ if (peek(p) != *text)
+ return p0;
+ p++; text++;
+ }
+ return p;
+}
+
+
+
+int Parser::skipwhite(long p)
+{
+
+ while (p<parselen)
+ {
+ int p2 = match(p, "<!--");
+ if (p2 > p)
+ {
+ p = p2;
+ while (p<parselen)
+ {
+ p2 = match(p, "-->");
+ if (p2 > p)
+ {
+ p = p2;
+ break;
+ }
+ p++;
+ }
+ }
+ XMLCh b = peek(p);
+ if (!isspace(b))
+ break;
+ p++;
+ }
+ return p;
+}
+
+/* modify this to allow all chars for an element or attribute name*/
+int Parser::getWord(int p0, String &buf)
+{
+ int p = p0;
+ while (p<parselen)
+ {
+ XMLCh b = peek(p);
+ if (b<=' ' || b=='/' || b=='>' || b=='=')
+ break;
+ buf.push_back(b);
+ p++;
+ }
+ return p;
+}
+
+int Parser::getQuoted(int p0, String &buf, int do_i_parse)
+{
+
+ int p = p0;
+ if (peek(p) != '"' && peek(p) != '\'')
+ return p0;
+ p++;
+
+ while ( p<parselen )
+ {
+ XMLCh b = peek(p);
+ if (b=='"' || b=='\'')
+ break;
+ if (b=='&' && do_i_parse)
+ {
+ bool found = false;
+ for (EntityEntry *ee = entities ; ee->value ; ee++)
+ {
+ int p2 = match(p, ee->escaped);
+ if (p2>p)
+ {
+ buf.push_back(ee->value);
+ p = p2;
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ {
+ error("unterminated entity");
+ return false;
+ }
+ }
+ else
+ {
+ buf.push_back(b);
+ p++;
+ }
+ }
+ return p;
+}
+
+int Parser::parseVersion(int p0)
+{
+ //printf("### parseVersion: %d\n", p0);
+
+ int p = p0;
+
+ p = skipwhite(p0);
+
+ if (peek(p) != '<')
+ return p0;
+
+ p++;
+ if (p>=parselen || peek(p)!='?')
+ return p0;
+
+ p++;
+
+ String buf;
+
+ while (p<parselen)
+ {
+ XMLCh ch = peek(p);
+ if (ch=='?')
+ {
+ p++;
+ break;
+ }
+ buf.push_back(ch);
+ p++;
+ }
+
+ if (peek(p) != '>')
+ return p0;
+ p++;
+
+ //printf("Got version:%s\n",buf.c_str());
+ return p;
+}
+
+int Parser::parseDoctype(int p0)
+{
+ //printf("### parseDoctype: %d\n", p0);
+
+ int p = p0;
+ p = skipwhite(p);
+
+ if (p>=parselen || peek(p)!='<')
+ return p0;
+
+ p++;
+
+ if (peek(p)!='!' || peek(p+1)=='-')
+ return p0;
+ p++;
+
+ String buf;
+ while (p<parselen)
+ {
+ XMLCh ch = peek(p);
+ if (ch=='>')
+ {
+ p++;
+ break;
+ }
+ buf.push_back(ch);
+ p++;
+ }
+
+ //printf("Got doctype:%s\n",buf.c_str());
+ return p;
+}
+
+int Parser::parseElement(int p0, Element *par,int depth)
+{
+
+ int p = p0;
+
+ int p2 = p;
+
+ p = skipwhite(p);
+
+ //## Get open tag
+ XMLCh ch = peek(p);
+ if (ch!='<')
+ return p0;
+
+ p++;
+
+ String openTagName;
+ p = skipwhite(p);
+ p = getWord(p, openTagName);
+ //printf("####tag :%s\n", openTagName.c_str());
+ p = skipwhite(p);
+
+ //Add element to tree
+ Element *n = new Element(openTagName);
+ n->parent = par;
+ par->addChild(n);
+
+ // Get attributes
+ if (peek(p) != '>')
+ {
+ while (p<parselen)
+ {
+ p = skipwhite(p);
+ ch = peek(p);
+ //printf("ch:%c\n",ch);
+ if (ch=='>')
+ break;
+ else if (ch=='/' && p<parselen+1)
+ {
+ p++;
+ p = skipwhite(p);
+ ch = peek(p);
+ if (ch=='>')
+ {
+ p++;
+ //printf("quick close\n");
+ return p;
+ }
+ }
+ String attrName;
+ p2 = getWord(p, attrName);
+ if (p2==p)
+ break;
+ //printf("name:%s",buf);
+ p=p2;
+ p = skipwhite(p);
+ ch = peek(p);
+ //printf("ch:%c\n",ch);
+ if (ch!='=')
+ break;
+ p++;
+ p = skipwhite(p);
+ // ch = parsebuf[p];
+ // printf("ch:%c\n",ch);
+ String attrVal;
+ p2 = getQuoted(p, attrVal, true);
+ p=p2+1;
+ //printf("name:'%s' value:'%s'\n",attrName.c_str(),attrVal.c_str());
+ char *namestr = (char *)attrName.c_str();
+ if (strncmp(namestr, "xmlns:", 6)==0)
+ n->addNamespace(attrName, attrVal);
+ else
+ n->addAttribute(attrName, attrVal);
+ }
+ }
+
+ bool cdata = false;
+
+ p++;
+ // ### Get intervening data ### */
+ String data;
+ while (p<parselen)
+ {
+ //# COMMENT
+ p2 = match(p, "<!--");
+ if (!cdata && p2>p)
+ {
+ p = p2;
+ while (p<parselen)
+ {
+ p2 = match(p, "-->");
+ if (p2 > p)
+ {
+ p = p2;
+ break;
+ }
+ p++;
+ }
+ }
+
+ ch = peek(p);
+ //# END TAG
+ if (ch=='<' && !cdata && peek(p+1)=='/')
+ {
+ break;
+ }
+ //# CDATA
+ p2 = match(p, "<![CDATA[");
+ if (p2 > p)
+ {
+ cdata = true;
+ p = p2;
+ continue;
+ }
+
+ //# CHILD ELEMENT
+ if (ch == '<')
+ {
+ p2 = parseElement(p, n, depth+1);
+ if (p2 == p)
+ {
+ /*
+ printf("problem on element:%s. p2:%d p:%d\n",
+ openTagName.c_str(), p2, p);
+ */
+ return p0;
+ }
+ p = p2;
+ continue;
+ }
+ //# ENTITY
+ if (ch=='&' && !cdata)
+ {
+ bool found = false;
+ for (EntityEntry *ee = entities ; ee->value ; ee++)
+ {
+ int p2 = match(p, ee->escaped);
+ if (p2>p)
+ {
+ data.push_back(ee->value);
+ p = p2;
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ {
+ error("unterminated entity");
+ return -1;
+ }
+ continue;
+ }
+
+ //# NONE OF THE ABOVE
+ data.push_back(ch);
+ p++;
+ }/*while*/
+
+
+ n->value = data;
+ //printf("%d : data:%s\n",p,data.c_str());
+
+ //## Get close tag
+ p = skipwhite(p);
+ ch = peek(p);
+ if (ch != '<')
+ {
+ error("no < for end tag\n");
+ return p0;
+ }
+ p++;
+ ch = peek(p);
+ if (ch != '/')
+ {
+ error("no / on end tag");
+ return p0;
+ }
+ p++;
+ ch = peek(p);
+ p = skipwhite(p);
+ String closeTagName;
+ p = getWord(p, closeTagName);
+ if (openTagName != closeTagName)
+ {
+ error("Mismatched closing tag. Expected </%S>. Got '%S'.",
+ openTagName.c_str(), closeTagName.c_str());
+ return p0;
+ }
+ p = skipwhite(p);
+ if (peek(p) != '>')
+ {
+ error("no > on end tag for '%s'", closeTagName.c_str());
+ return p0;
+ }
+ p++;
+ // printf("close element:%s\n",closeTagName.c_str());
+ p = skipwhite(p);
+ return p;
+}
+
+
+
+
+Element *Parser::parse(XMLCh *buf,int pos,int len)
+{
+ parselen = len;
+ parsebuf = buf;
+ Element *rootNode = new Element("root");
+ pos = parseVersion(pos);
+ pos = parseDoctype(pos);
+ pos = parseElement(pos, rootNode, 0);
+ return rootNode;
+}
+
+
+Element *Parser::parse(const char *buf, int pos, int len)
+{
+ XMLCh *charbuf = new XMLCh[len + 1];
+ long i = 0;
+ for ( ; i < len ; i++)
+ charbuf[i] = (XMLCh)buf[i];
+ charbuf[i] = '\0';
+
+ Element *n = parse(charbuf, pos, len);
+ delete[] charbuf;
+ return n;
+}
+
+Element *Parser::parse(const String &buf)
+{
+ long len = (long)buf.size();
+ XMLCh *charbuf = new XMLCh[len + 1];
+ long i = 0;
+ for ( ; i < len ; i++)
+ charbuf[i] = (XMLCh)buf[i];
+ charbuf[i] = '\0';
+
+ Element *n = parse(charbuf, 0, len);
+ delete[] charbuf;
+ return n;
+}
+
+Element *Parser::parseFile(const String &fileName)
+{
+
+ //##### LOAD INTO A CHAR BUF, THEN CONVERT TO XMLCh
+ FILE *f = fopen(fileName.c_str(), "rb");
+ if (!f)
+ return NULL;
+
+ struct stat statBuf;
+ if (fstat(fileno(f),&statBuf)<0)
+ {
+ fclose(f);
+ return NULL;
+ }
+ long filelen = statBuf.st_size;
+
+ //printf("length:%d\n",filelen);
+ XMLCh *charbuf = new XMLCh[filelen + 1];
+ for (XMLCh *p=charbuf ; !feof(f) ; p++)
+ {
+ *p = (XMLCh)fgetc(f);
+ }
+ fclose(f);
+ charbuf[filelen] = '\0';
+
+
+ /*
+ printf("nrbytes:%d\n",wc_count);
+ printf("buf:%ls\n======\n",charbuf);
+ */
+ Element *n = parse(charbuf, 0, filelen);
+ delete[] charbuf;
+ return n;
+}
+
+
+
+//########################################################################
+//########################################################################
+//## E N D X M L
+//########################################################################
+//########################################################################
+
+
+//########################################################################
+//########################################################################
+//## U R I
+//########################################################################
+//########################################################################
+
+//This would normally be a call to a UNICODE function
+#define isLetter(x) isalpha(x)
+
+/**
+ * A class that implements the W3C URI resource reference.
+ */
+class URI
+{
+public:
+
+ typedef enum
+ {
+ SCHEME_NONE =0,
+ SCHEME_DATA,
+ SCHEME_HTTP,
+ SCHEME_HTTPS,
+ SCHEME_FTP,
+ SCHEME_FILE,
+ SCHEME_LDAP,
+ SCHEME_MAILTO,
+ SCHEME_NEWS,
+ SCHEME_TELNET
+ } SchemeTypes;
+
+ /**
+ *
+ */
+ URI()
+ {
+ init();
+ }
+
+ /**
+ *
+ */
+ URI(const String &str)
+ {
+ init();
+ parse(str);
+ }
+
+
+ /**
+ *
+ */
+ URI(const char *str)
+ {
+ init();
+ String domStr = str;
+ parse(domStr);
+ }
+
+
+ /**
+ *
+ */
+ URI(const URI &other)
+ {
+ init();
+ assign(other);
+ }
+
+
+ /**
+ *
+ */
+ URI &operator=(const URI &other)
+ {
+ init();
+ assign(other);
+ return *this;
+ }
+
+
+ /**
+ *
+ */
+ ~URI()
+ {}
+
+
+
+ /**
+ *
+ */
+ virtual bool parse(const String &str);
+
+ /**
+ *
+ */
+ virtual String toString() const;
+
+ /**
+ *
+ */
+ virtual int getScheme() const;
+
+ /**
+ *
+ */
+ virtual String getSchemeStr() const;
+
+ /**
+ *
+ */
+ virtual String getAuthority() const;
+
+ /**
+ * Same as getAuthority, but if the port has been specified
+ * as host:port , the port will not be included
+ */
+ virtual String getHost() const;
+
+ /**
+ *
+ */
+ virtual int getPort() const;
+
+ /**
+ *
+ */
+ virtual String getPath() const;
+
+ /**
+ *
+ */
+ virtual String getNativePath() const;
+
+ /**
+ *
+ */
+ virtual bool isAbsolute() const;
+
+ /**
+ *
+ */
+ virtual bool isOpaque() const;
+
+ /**
+ *
+ */
+ virtual String getQuery() const;
+
+ /**
+ *
+ */
+ virtual String getFragment() const;
+
+ /**
+ *
+ */
+ virtual URI resolve(const URI &other) const;
+
+ /**
+ *
+ */
+ virtual void normalize();
+
+private:
+
+ /**
+ *
+ */
+ void init()
+ {
+ parsebuf = NULL;
+ parselen = 0;
+ scheme = SCHEME_NONE;
+ schemeStr = "";
+ port = 0;
+ authority = "";
+ path = "";
+ absolute = false;
+ opaque = false;
+ query = "";
+ fragment = "";
+ }
+
+
+ /**
+ *
+ */
+ void assign(const URI &other)
+ {
+ scheme = other.scheme;
+ schemeStr = other.schemeStr;
+ authority = other.authority;
+ port = other.port;
+ path = other.path;
+ absolute = other.absolute;
+ opaque = other.opaque;
+ query = other.query;
+ fragment = other.fragment;
+ }
+
+ int scheme;
+
+ String schemeStr;
+
+ String authority;
+
+ bool portSpecified;
+
+ int port;
+
+ String path;
+
+ bool absolute;
+
+ bool opaque;
+
+ String query;
+
+ String fragment;
+
+ void error(const char *fmt, ...);
+
+ void trace(const char *fmt, ...);
+
+
+ int peek(int p);
+
+ int match(int p, char *key);
+
+ int parseScheme(int p);
+
+ int parseHierarchicalPart(int p0);
+
+ int parseQuery(int p0);
+
+ int parseFragment(int p0);
+
+ int parse(int p);
+
+ char *parsebuf;
+
+ int parselen;
+
+};
+
+
+
+typedef struct
+{
+ int ival;
+ char *sval;
+ int port;
+} LookupEntry;
+
+LookupEntry schemes[] =
+{
+ { URI::SCHEME_DATA, "data:", 0 },
+ { URI::SCHEME_HTTP, "http:", 80 },
+ { URI::SCHEME_HTTPS, "https:", 443 },
+ { URI::SCHEME_FTP, "ftp", 12 },
+ { URI::SCHEME_FILE, "file:", 0 },
+ { URI::SCHEME_LDAP, "ldap:", 123 },
+ { URI::SCHEME_MAILTO, "mailto:", 25 },
+ { URI::SCHEME_NEWS, "news:", 117 },
+ { URI::SCHEME_TELNET, "telnet:", 23 },
+ { 0, NULL, 0 }
+};
+
+
+String URI::toString() const
+{
+ String str = schemeStr;
+ if (authority.size() > 0)
+ {
+ str.append("//");
+ str.append(authority);
+ }
+ str.append(path);
+ if (query.size() > 0)
+ {
+ str.append("?");
+ str.append(query);
+ }
+ if (fragment.size() > 0)
+ {
+ str.append("#");
+ str.append(fragment);
+ }
+ return str;
+}
+
+
+int URI::getScheme() const
+{
+ return scheme;
+}
+
+String URI::getSchemeStr() const
+{
+ return schemeStr;
+}
+
+
+String URI::getAuthority() const
+{
+ String ret = authority;
+ if (portSpecified && port>=0)
+ {
+ char buf[7];
+ snprintf(buf, 6, ":%6d", port);
+ ret.append(buf);
+ }
+ return ret;
+}
+
+String URI::getHost() const
+{
+ return authority;
+}
+
+int URI::getPort() const
+{
+ return port;
+}
+
+
+String URI::getPath() const
+{
+ return path;
+}
+
+String URI::getNativePath() const
+{
+ String npath;
+#ifdef __WIN32__
+ unsigned int firstChar = 0;
+ if (path.size() >= 3)
+ {
+ if (path[0] == '/' &&
+ isLetter(path[1]) &&
+ path[2] == ':')
+ firstChar++;
+ }
+ for (unsigned int i=firstChar ; i<path.size() ; i++)
+ {
+ XMLCh ch = (XMLCh) path[i];
+ if (ch == '/')
+ npath.push_back((XMLCh)'\\');
+ else
+ npath.push_back(ch);
+ }
+#else
+ npath = path;
+#endif
+ return npath;
+}
+
+
+bool URI::isAbsolute() const
+{
+ return absolute;
+}
+
+bool URI::isOpaque() const
+{
+ return opaque;
+}
+
+
+String URI::getQuery() const
+{
+ return query;
+}
+
+
+String URI::getFragment() const
+{
+ return fragment;
+}
+
+
+URI URI::resolve(const URI &other) const
+{
+ //### According to w3c, this is handled in 3 cases
+
+ //## 1
+ if (opaque || other.isAbsolute())
+ return other;
+
+ //## 2
+ if (other.fragment.size() > 0 &&
+ other.path.size() == 0 &&
+ other.scheme == SCHEME_NONE &&
+ other.authority.size() == 0 &&
+ other.query.size() == 0 )
+ {
+ URI fragUri = *this;
+ fragUri.fragment = other.fragment;
+ return fragUri;
+ }
+
+ //## 3 http://www.ietf.org/rfc/rfc2396.txt, section 5.2
+ URI newUri;
+ //# 3.1
+ newUri.scheme = scheme;
+ newUri.schemeStr = schemeStr;
+ newUri.query = other.query;
+ newUri.fragment = other.fragment;
+ if (other.authority.size() > 0)
+ {
+ //# 3.2
+ if (absolute || other.absolute)
+ newUri.absolute = true;
+ newUri.authority = other.authority;
+ newUri.port = other.port;//part of authority
+ newUri.path = other.path;
+ }
+ else
+ {
+ //# 3.3
+ if (other.absolute)
+ {
+ newUri.absolute = true;
+ newUri.path = other.path;
+ }
+ else
+ {
+ unsigned int pos = path.find_last_of('/');
+ if (pos != path.npos)
+ {
+ String tpath = path.substr(0, pos+1);
+ tpath.append(other.path);
+ newUri.path = tpath;
+ }
+ else
+ newUri.path = other.path;
+ }
+ }
+
+ newUri.normalize();
+ return newUri;
+}
+
+
+/**
+ * This follows the Java URI algorithm:
+ * 1. All "." segments are removed.
+ * 2. If a ".." segment is preceded by a non-".." segment
+ * then both of these segments are removed. This step
+ * is repeated until it is no longer applicable.
+ * 3. If the path is relative, and if its first segment
+ * contains a colon character (':'), then a "." segment
+ * is prepended. This prevents a relative URI with a path
+ * such as "a:b/c/d" from later being re-parsed as an
+ * opaque URI with a scheme of "a" and a scheme-specific
+ * part of "b/c/d". (Deviation from RFC 2396)
+ */
+void URI::normalize()
+{
+ std::vector<String> segments;
+
+ //## Collect segments
+ if (path.size()<2)
+ return;
+ bool abs = false;
+ unsigned int pos=0;
+ if (path[0]=='/')
+ {
+ abs = true;
+ pos++;
+ }
+ while (pos < path.size())
+ {
+ unsigned int pos2 = path.find('/', pos);
+ if (pos2==path.npos)
+ {
+ String seg = path.substr(pos);
+ //printf("last segment:%s\n", seg.c_str());
+ segments.push_back(seg);
+ break;
+ }
+ if (pos2>pos)
+ {
+ String seg = path.substr(pos, pos2-pos);
+ //printf("segment:%s\n", seg.c_str());
+ segments.push_back(seg);
+ }
+ pos = pos2;
+ pos++;
+ }
+
+ //## Clean up (normalize) segments
+ bool edited = false;
+ std::vector<String>::iterator iter;
+ for (iter=segments.begin() ; iter!=segments.end() ; )
+ {
+ String s = *iter;
+ if (s == ".")
+ {
+ iter = segments.erase(iter);
+ edited = true;
+ }
+ else if (s == ".." &&
+ iter != segments.begin() &&
+ *(iter-1) != "..")
+ {
+ iter--; //back up, then erase two entries
+ iter = segments.erase(iter);
+ iter = segments.erase(iter);
+ edited = true;
+ }
+ else
+ iter++;
+ }
+
+ //## Rebuild path, if necessary
+ if (edited)
+ {
+ path.clear();
+ if (abs)
+ {
+ path.append("/");
+ }
+ std::vector<String>::iterator iter;
+ for (iter=segments.begin() ; iter!=segments.end() ; iter++)
+ {
+ if (iter != segments.begin())
+ path.append("/");
+ path.append(*iter);
+ }
+ }
+
+}
+
+
+
+//#########################################################################
+//# M E S S A G E S
+//#########################################################################
+
+void URI::error(const char *fmt, ...)
+{
+ va_list args;
+ fprintf(stderr, "URI error: ");
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+ fprintf(stderr, "\n");
+}
+
+void URI::trace(const char *fmt, ...)
+{
+ va_list args;
+ fprintf(stdout, "URI: ");
+ va_start(args, fmt);
+ vfprintf(stdout, fmt, args);
+ va_end(args);
+ fprintf(stdout, "\n");
+}
+
+
+
+//#########################################################################
+//# P A R S I N G
+//#########################################################################
+
+
+
+int URI::peek(int p)
+{
+ if (p<0 || p>=parselen)
+ return -1;
+ return parsebuf[p];
+}
+
+
+
+int URI::match(int p0, char *key)
+{
+ int p = p0;
+ while (p < parselen)
+ {
+ if (*key == '\0')
+ return p;
+ else if (*key != parsebuf[p])
+ break;
+ p++; key++;
+ }
+ return p0;
+}
+
+//#########################################################################
+//# Parsing is performed according to:
+//# http://www.gbiv.com/protocols/uri/rfc/rfc3986.html#components
+//#########################################################################
+
+int URI::parseScheme(int p0)
+{
+ int p = p0;
+ for (LookupEntry *entry = schemes; entry->sval ; entry++)
+ {
+ int p2 = match(p, entry->sval);
+ if (p2 > p)
+ {
+ schemeStr = entry->sval;
+ scheme = entry->ival;
+ port = entry->port;
+ p = p2;
+ return p;
+ }
+ }
+
+ return p;
+}
+
+
+int URI::parseHierarchicalPart(int p0)
+{
+ int p = p0;
+ int ch;
+
+ //# Authority field (host and port, for example)
+ int p2 = match(p, "//");
+ if (p2 > p)
+ {
+ p = p2;
+ portSpecified = false;
+ String portStr;
+ while (p < parselen)
+ {
+ ch = peek(p);
+ if (ch == '/')
+ break;
+ else if (ch == ':')
+ portSpecified = true;
+ else if (portSpecified)
+ portStr.push_back((XMLCh)ch);
+ else
+ authority.push_back((XMLCh)ch);
+ p++;
+ }
+ if (portStr.size() > 0)
+ {
+ char *pstr = (char *)portStr.c_str();
+ char *endStr;
+ long val = strtol(pstr, &endStr, 10);
+ if (endStr > pstr) //successful parse?
+ port = val;
+ }
+ }
+
+ //# Are we absolute?
+ ch = peek(p);
+ if (isLetter(ch) && peek(p+1)==':')
+ {
+ absolute = true;
+ path.push_back((XMLCh)'/');
+ }
+ else if (ch == '/')
+ {
+ absolute = true;
+ if (p>p0) //in other words, if '/' is not the first char
+ opaque = true;
+ path.push_back((XMLCh)ch);
+ p++;
+ }
+
+ while (p < parselen)
+ {
+ ch = peek(p);
+ if (ch == '?' || ch == '#')
+ break;
+ path.push_back((XMLCh)ch);
+ p++;
+ }
+
+ return p;
+}
+
+int URI::parseQuery(int p0)
+{
+ int p = p0;
+ int ch = peek(p);
+ if (ch != '?')
+ return p0;
+
+ p++;
+ while (p < parselen)
+ {
+ ch = peek(p);
+ if (ch == '#')
+ break;
+ query.push_back((XMLCh)ch);
+ p++;
+ }
+
+
+ return p;
+}
+
+int URI::parseFragment(int p0)
+{
+
+ int p = p0;
+ int ch = peek(p);
+ if (ch != '#')
+ return p0;
+
+ p++;
+ while (p < parselen)
+ {
+ ch = peek(p);
+ if (ch == '?')
+ break;
+ fragment.push_back((XMLCh)ch);
+ p++;
+ }
+
+
+ return p;
+}
+
+
+int URI::parse(int p0)
+{
+
+ int p = p0;
+
+ int p2 = parseScheme(p);
+ if (p2 < 0)
+ {
+ error("Scheme");
+ return -1;
+ }
+ p = p2;
+
+
+ p2 = parseHierarchicalPart(p);
+ if (p2 < 0)
+ {
+ error("Hierarchical part");
+ return -1;
+ }
+ p = p2;
+
+ p2 = parseQuery(p);
+ if (p2 < 0)
+ {
+ error("Query");
+ return -1;
+ }
+ p = p2;
+
+
+ p2 = parseFragment(p);
+ if (p2 < 0)
+ {
+ error("Fragment");
+ return -1;
+ }
+ p = p2;
+
+ return p;
+
+}
+
+
+
+bool URI::parse(const String &str)
+{
+
+ parselen = str.size();
+
+ String tmp;
+ for (unsigned int i=0 ; i<str.size() ; i++)
+ {
+ XMLCh ch = (XMLCh) str[i];
+ if (ch == '\\')
+ tmp.push_back((XMLCh)'/');
+ else
+ tmp.push_back(ch);
+ }
+ parsebuf = (char *) tmp.c_str();
+
+
+ int p = parse(0);
+ normalize();
+
+ if (p < 0)
+ {
+ error("Syntax error");
+ return false;
+ }
+
+ //printf("uri:%s\n", toString().c_str());
+ //printf("path:%s\n", path.c_str());
+
+ return true;
+
+}
+
+
+
+
+
+
+
+
+//########################################################################
+//########################################################################
+//## M A K E
+//########################################################################
+//########################################################################
+
+
+
+//########################################################################
+//# M A K E B A S E
+//########################################################################
+/**
+ * Base class for all classes in this file
+ */
+class MakeBase
+{
+public:
+ MakeBase()
+ {}
+ virtual ~MakeBase()
+ {}
+
+ /**
+ * Return the URI of the file associated with this object
+ */
+ URI getURI()
+ { return uri; }
+
+ /**
+ * Set the uri to the given string
+ */
+ void setURI(const String &uristr)
+ { uri.parse(uristr); }
+
+ /**
+ * Resolve another path relative to this one
+ */
+ String resolve(const String &otherPath);
+
+ /**
+ * Get an element attribute, performing substitutions if necessary
+ */
+ bool getAttribute(Element *elem, const String &name, String &result);
+
+ /**
+ * Get an element value, performing substitutions if necessary
+ */
+ bool getValue(Element *elem, String &result);
+
+protected:
+
+ /**
+ * The path to the file associated with this object
+ */
+ URI uri;
+
+
+ /**
+ * Print a printf()-like formatted error message
+ */
+ void error(char *fmt, ...);
+
+ /**
+ * Print a printf()-like formatted trace message
+ */
+ void status(char *fmt, ...);
+
+ /**
+ * Print a printf()-like formatted trace message
+ */
+ void trace(char *fmt, ...);
+
+ /**
+ *
+ */
+ String getSuffix(const String &fname);
+
+ /**
+ * Break up a string into substrings delimited the characters
+ * in delimiters. Null-length substrings are ignored
+ */
+ std::vector<String> tokenize(const String &val,
+ const String &delimiters);
+
+ /**
+ * remove leading and trailing whitespace from string
+ */
+ String trim(const String &s);
+
+ /**
+ * Return the native format of the canonical
+ * path which we store
+ */
+ String getNativePath(const String &path);
+
+ /**
+ * Execute a shell command. Outbuf is a ref to a string
+ * to catch the result.
+ */
+ bool executeCommand(const String &call,
+ const String &inbuf,
+ String &outbuf,
+ String &errbuf);
+ /**
+ * List all directories in a given base and starting directory
+ * It is usually called like:
+ * bool ret = listDirectories("src", "", result);
+ */
+ bool listDirectories(const String &baseName,
+ const String &dirname,
+ std::vector<String> &res);
+
+ /**
+ * Find all files in the named directory whose short names (no path) match
+ * the given regex pattern
+ */
+ bool listFiles(const String &baseName,
+ const String &dirname,
+ std::vector<String> &excludes,
+ std::vector<String> &res);
+
+ /**
+ * Parse a <patternset>
+ */
+ bool getPatternSet(Element *elem,
+ MakeBase &propRef,
+ std::vector<String> &includes,
+ std::vector<String> &excludes);
+
+ /**
+ * Parse a <fileset> entry, and determine which files
+ * should be included
+ */
+ bool getFileSet(Element *elem,
+ MakeBase &propRef,
+ String &dir,
+ std::vector<String> &result);
+
+ /**
+ * Return this object's property list
+ */
+ virtual std::map<String, String> &getProperties()
+ { return properties; }
+
+ /**
+ * Return a named property if found, else a null string
+ */
+ virtual String getProperty(const String &name)
+ {
+ String val;
+ std::map<String, String>::iterator iter;
+ iter = properties.find(name);
+ if (iter != properties.end())
+ val = iter->second;
+ return val;
+ }
+
+
+ std::map<String, String> properties;
+
+ /**
+ * Turn 'true' and 'false' into boolean values
+ */
+ bool getBool(const String &str, bool &val);
+
+ /**
+ * Create a directory, making intermediate dirs
+ * if necessary
+ */
+ bool createDirectory(const String &dirname);
+
+ /**
+ * Delete a directory and its children if desired
+ */
+ bool removeDirectory(const String &dirName);
+
+ /**
+ * Copy a file from one name to another. Perform only if needed
+ */
+ bool copyFile(const String &srcFile, const String &destFile);
+
+ /**
+ * Tests is the modification date of fileA is newer than fileB
+ */
+ bool isNewerThan(const String &fileA, const String &fileB);
+
+private:
+
+ /**
+ * replace variable refs like ${a} with their values
+ */
+ bool getSubstitutions(const String &s, String &result);
+
+
+
+};
+
+
+
+
+/**
+ * Print a printf()-like formatted error message
+ */
+void MakeBase::error(char *fmt, ...)
+{
+ va_list args;
+ va_start(args,fmt);
+ fprintf(stderr, "Make error: ");
+ vfprintf(stderr, fmt, args);
+ fprintf(stderr, "\n");
+ va_end(args) ;
+}
+
+
+
+/**
+ * Print a printf()-like formatted trace message
+ */
+void MakeBase::status(char *fmt, ...)
+{
+ va_list args;
+ va_start(args,fmt);
+ fprintf(stdout, "Make: ");
+ vfprintf(stdout, fmt, args);
+ fprintf(stdout, "\n");
+ va_end(args) ;
+}
+
+
+
+/**
+ * Resolve another path relative to this one
+ */
+String MakeBase::resolve(const String &otherPath)
+{
+ URI otherURI(otherPath);
+ URI fullURI = uri.resolve(otherURI);
+ String ret = fullURI.toString();
+ return ret;
+}
+
+
+/**
+ * Print a printf()-like formatted trace message
+ */
+void MakeBase::trace(char *fmt, ...)
+{
+ va_list args;
+ va_start(args,fmt);
+ fprintf(stdout, "Make: ");
+ vfprintf(stdout, fmt, args);
+ fprintf(stdout, "\n");
+ va_end(args) ;
+}
+
+
+/**
+ * Return the suffix, if any, of a file name
+ */
+String MakeBase::getSuffix(const String &fname)
+{
+ if (fname.size() < 2)
+ return "";
+ unsigned int pos = fname.find_last_of('.');
+ if (pos == fname.npos)
+ return "";
+ pos++;
+ String res = fname.substr(pos, fname.size()-pos);
+ //trace("suffix:%s", res.c_str());
+ return res;
+}
+
+
+
+/**
+ * Break up a string into substrings delimited the characters
+ * in delimiters. Null-length substrings are ignored
+ */
+std::vector<String> MakeBase::tokenize(const String &str,
+ const String &delimiters)
+{
+
+ std::vector<String> res;
+ char *del = (char *)delimiters.c_str();
+ String dmp;
+ for (unsigned int i=0 ; i<str.size() ; i++)
+ {
+ char ch = str[i];
+ char *p = (char *)0;
+ for (p=del ; *p ; p++)
+ if (*p == ch)
+ break;
+ if (*p)
+ {
+ if (dmp.size() > 0)
+ {
+ res.push_back(dmp);
+ dmp.clear();
+ }
+ }
+ else
+ {
+ dmp.push_back(ch);
+ }
+ }
+ //Add tail
+ if (dmp.size() > 0)
+ {
+ res.push_back(dmp);
+ dmp.clear();
+ }
+
+ return res;
+}
+
+
+
+/**
+ * Removes whitespace from beginning and end of a string
+ */
+String MakeBase::trim(const String &s)
+{
+ if (s.size() < 1)
+ return s;
+
+ //Find first non-ws char
+ unsigned int begin = 0;
+ for ( ; begin < s.size() ; begin++)
+ {
+ if (!isspace(s[begin]))
+ break;
+ }
+
+ //Find first non-ws char, going in reverse
+ unsigned int end = s.size() - 1;
+ for ( ; end > begin ; end--)
+ {
+ if (!isspace(s[end]))
+ break;
+ }
+ //trace("begin:%d end:%d", begin, end);
+
+ String res = s.substr(begin, end-begin+1);
+ return res;
+}
+
+/**
+ * Return the native format of the canonical
+ * path which we store
+ */
+String MakeBase::getNativePath(const String &path)
+{
+#ifdef __WIN32__
+ String npath;
+ unsigned int firstChar = 0;
+ if (path.size() >= 3)
+ {
+ if (path[0] == '/' &&
+ isalpha(path[1]) &&
+ path[2] == ':')
+ firstChar++;
+ }
+ for (unsigned int i=firstChar ; i<path.size() ; i++)
+ {
+ char ch = path[i];
+ if (ch == '/')
+ npath.push_back('\\');
+ else
+ npath.push_back(ch);
+ }
+ return npath;
+#else
+ return path;
+#endif
+}
+
+
+#ifdef __WIN32__
+#include <tchar.h>
+
+static String win32LastError()
+{
+
+ DWORD dw = GetLastError();
+
+ LPVOID str;
+ FormatMessage(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM,
+ NULL,
+ dw,
+ 0,
+ (LPTSTR) &str,
+ 0, NULL );
+ LPTSTR p = _tcschr((const char *)str, _T('\r'));
+ if(p != NULL)
+ { // lose CRLF
+ *p = _T('\0');
+ }
+ String ret = (char *)str;
+ LocalFree(str);
+
+ return ret;
+}
+#endif
+
+
+
+/**
+ * Execute a system call via the shell
+ */
+bool MakeBase::executeCommand(const String &command,
+ const String &inbuf,
+ String &outbuf,
+ String &errbuf)
+{
+#ifdef __WIN32__
+
+ bool ret = true;
+
+ //# Allocate a separate buffer for safety
+ char *paramBuf = new char[command.size() + 1];
+ if (!paramBuf)
+ {
+ error("executeCommand cannot allocate command buffer");
+ return false;
+ }
+ strcpy(paramBuf, (char *)command.c_str());
+
+ //# Create pipes
+ SECURITY_ATTRIBUTES saAttr;
+ saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
+ saAttr.bInheritHandle = TRUE;
+ saAttr.lpSecurityDescriptor = NULL;
+ HANDLE stdinRead, stdinWrite;
+ HANDLE stdoutRead, stdoutWrite;
+ HANDLE stderrRead, stderrWrite;
+ if (!CreatePipe(&stdinRead, &stdinWrite, &saAttr, 0))
+ {
+ error("executeProgram: could not create pipe");
+ delete[] paramBuf;
+ return false;
+ }
+ SetHandleInformation(stdinWrite, HANDLE_FLAG_INHERIT, 0);
+ if (!CreatePipe(&stdoutRead, &stdoutWrite, &saAttr, 0))
+ {
+ error("executeProgram: could not create pipe");
+ delete[] paramBuf;
+ 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);
+
+ // Create the process
+ STARTUPINFO siStartupInfo;
+ PROCESS_INFORMATION piProcessInfo;
+ memset(&siStartupInfo, 0, sizeof(siStartupInfo));
+ memset(&piProcessInfo, 0, sizeof(piProcessInfo));
+ siStartupInfo.cb = sizeof(siStartupInfo);
+ siStartupInfo.hStdError = stderrWrite;
+ siStartupInfo.hStdOutput = stdoutWrite;
+ siStartupInfo.hStdInput = stdinRead;
+ siStartupInfo.dwFlags |= STARTF_USESTDHANDLES;
+
+ if (!CreateProcess(NULL, paramBuf, NULL, NULL, true,
+ 0, NULL, NULL, &siStartupInfo,
+ &piProcessInfo))
+ {
+ error("executeCommand : could not create process : %s",
+ win32LastError().c_str());
+ ret = false;
+ }
+
+ DWORD bytesWritten;
+ if (inbuf.size()>0 &&
+ !WriteFile(stdinWrite, inbuf.c_str(), inbuf.size(),
+ &bytesWritten, NULL))
+ {
+ error("executeCommand: could not write to pipe");
+ return false;
+ }
+ if (!CloseHandle(stdinWrite))
+ {
+ error("executeCommand: could not close write pipe");
+ return false;
+ }
+ if (!CloseHandle(stdoutWrite))
+ {
+ error("executeCommand: could not close read pipe");
+ return false;
+ }
+ if (!CloseHandle(stderrWrite))
+ {
+ error("executeCommand: could not close read pipe");
+ return false;
+ }
+ while (true)
+ {
+ //trace("## stderr");
+ DWORD avail;
+ if (!PeekNamedPipe(stderrRead, NULL, 0, NULL, &avail, NULL))
+ break;
+ if (avail > 0)
+ {
+ DWORD bytesRead = 0;
+ char readBuf[1025];
+ if (avail>1024) avail = 1024;
+ if (!ReadFile(stderrRead, readBuf, avail, &bytesRead, NULL)
+ || bytesRead == 0)
+ {
+ break;
+ }
+ for (int i=0 ; i<bytesRead ; i++)
+ errbuf.push_back(readBuf[i]);
+ }
+ //trace("## stdout");
+ if (!PeekNamedPipe(stdoutRead, NULL, 0, NULL, &avail, NULL))
+ break;
+ if (avail > 0)
+ {
+ DWORD bytesRead = 0;
+ char readBuf[1025];
+ if (avail>1024) avail = 1024;
+ if (!ReadFile(stdoutRead, readBuf, avail, &bytesRead, NULL)
+ || bytesRead==0)
+ {
+ break;
+ }
+ for (int i=0 ; i<bytesRead ; i++)
+ outbuf.push_back(readBuf[i]);
+ }
+ DWORD exitCode;
+ GetExitCodeProcess(piProcessInfo.hProcess, &exitCode);
+ if (exitCode != STILL_ACTIVE)
+ break;
+ Sleep(500);
+ }
+ //trace("outbuf:%s", outbuf.c_str());
+ if (!CloseHandle(stdoutRead))
+ {
+ error("executeCommand: could not close read pipe");
+ return false;
+ }
+ if (!CloseHandle(stderrRead))
+ {
+ error("executeCommand: could not close read pipe");
+ return false;
+ }
+
+ DWORD exitCode;
+ GetExitCodeProcess(piProcessInfo.hProcess, &exitCode);
+ //trace("exit code:%d", exitCode);
+ if (exitCode != 0)
+ {
+ ret = false;
+ }
+
+ // Clean up
+ CloseHandle(piProcessInfo.hProcess);
+ CloseHandle(piProcessInfo.hThread);
+
+
+ return ret;
+
+#else //do it unix-style
+
+ String s;
+ FILE *f = popen(command.c_str(), "r");
+ int errnum = 0;
+ if (f)
+ {
+ while (true)
+ {
+ int ch = fgetc(f);
+ if (ch < 0)
+ break;
+ s.push_back((char)ch);
+ }
+ errnum = pclose(f);
+ }
+ outbuf = s;
+ if (errnum < 0)
+ {
+ error("exec of command '%s' failed : %s",
+ command.c_str(), strerror(errno));
+ return false;
+ }
+ else
+ return true;
+
+#endif
+}
+
+
+
+
+bool MakeBase::listDirectories(const String &baseName,
+ const String &dirName,
+ std::vector<String> &res)
+{
+ res.push_back(dirName);
+ String fullPath = baseName;
+ if (dirName.size()>0)
+ {
+ fullPath.append("/");
+ fullPath.append(dirName);
+ }
+ DIR *dir = opendir(fullPath.c_str());
+ 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 = dirName;
+ childName.append("/");
+ childName.append(s);
+
+ String fullChildPath = baseName;
+ fullChildPath.append("/");
+ fullChildPath.append(childName);
+ struct stat finfo;
+ String childNative = getNativePath(fullChildPath);
+ if (stat(childNative.c_str(), &finfo)<0)
+ {
+ error("cannot stat file:%s", childNative.c_str());
+ }
+ else if (S_ISDIR(finfo.st_mode))
+ {
+ //trace("directory: %s", childName.c_str());
+ if (!listDirectories(baseName, childName, res))
+ return false;
+ }
+ }
+ closedir(dir);
+
+ return true;
+}
+
+
+bool MakeBase::listFiles(const String &baseDir,
+ const String &dirName,
+ std::vector<String> &excludes,
+ std::vector<String> &res)
+{
+ String fullDir = baseDir;
+ if (dirName.size()>0)
+ {
+ fullDir.append("/");
+ fullDir.append(dirName);
+ }
+ String dirNative = getNativePath(fullDir);
+
+ std::vector<String> subdirs;
+ DIR *dir = opendir(dirNative.c_str());
+ 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 (std::find(excludes.begin(), excludes.end(), childName)
+ != excludes.end())
+ {
+ //trace("EXCLUDED:%s", childName.c_str());
+ continue;
+ }
+
+ struct stat finfo;
+ String nativeName = getNativePath(fullChild);
+ if (stat(nativeName.c_str(), &finfo)<0)
+ {
+ error("cannot stat file:%s", childName.c_str());
+ return false;
+ }
+ else if (S_ISDIR(finfo.st_mode))
+ {
+ //trace("directory: %s", childName.c_str());
+ if (!listFiles(baseDir, childName, excludes, res))
+ return false;
+ }
+ else if (!S_ISREG(finfo.st_mode))
+ {
+ trace("not regular: %s", childName.c_str());
+ }
+ else
+ {
+ res.push_back(childName);
+ }
+ }
+ closedir(dir);
+
+ return true;
+}
+
+
+
+
+
+
+
+
+bool MakeBase::getSubstitutions(const String &str, String &result)
+{
+ String s = trim(str);
+ int len = (int)s.size();
+ String val;
+ for (int i=0 ; i<len ; i++)
+ {
+ char ch = s[i];
+ if (ch == '$' && s[i+1] == '{')
+ {
+ String varname;
+ int j = i+2;
+ for ( ; j<len ; j++)
+ {
+ ch = s[j];
+ if (ch == '$' && s[j+1] == '{')
+ {
+ error("attribute %s cannot have nested variable references",
+ s.c_str());
+ return false;
+ }
+ else if (ch == '}')
+ {
+ std::map<String, String>::iterator iter;
+ iter = properties.find(trim(varname));
+ if (iter != properties.end())
+ {
+ val.append(iter->second);
+ }
+ else
+ {
+ error("property ${%s} not found", varname.c_str());
+ return false;
+ }
+ break;
+ }
+ else
+ {
+ varname.push_back(ch);
+ }
+ }
+ i = j;
+ }
+ else
+ {
+ val.push_back(ch);
+ }
+ }
+ result = val;
+ return true;
+}
+
+
+bool MakeBase::getAttribute(Element *elem, const String &name,
+ String &result)
+{
+ String s = elem->getAttribute(name);
+ return getSubstitutions(s, result);
+}
+
+
+bool MakeBase::getValue(Element *elem, String &result)
+{
+ String s = elem->getValue();
+ int len = s.size();
+ //Replace all runs of whitespace with a single space
+ String stripped;
+ for (int i = 0 ; i<len ; i++)
+ {
+ char ch = s[i];
+ if (isspace(ch))
+ {
+ stripped.push_back(' ');
+ for ( ; i<len ; i++)
+ {
+ ch = s[i];
+ if (!isspace(ch))
+ {
+ stripped.push_back(ch);
+ break;
+ }
+ }
+ }
+ else
+ {
+ stripped.push_back(ch);
+ }
+ }
+ return getSubstitutions(stripped, result);
+}
+
+
+/**
+ * Turn 'true' and 'false' into boolean values
+ */
+bool MakeBase::getBool(const String &str, bool &val)
+{
+ if (str == "true")
+ val = true;
+ else if (str == "false")
+ val = false;
+ else
+ {
+ error("expected 'true' or 'false'. found '%s'", str.c_str());
+ return false;
+ }
+ return true;
+}
+
+
+
+
+/**
+ * Parse a <patternset> entry
+ */
+bool MakeBase::getPatternSet(Element *elem,
+ MakeBase &propRef,
+ std::vector<String> &includes,
+ std::vector<String> &excludes
+ )
+{
+ 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 == "exclude")
+ {
+ String fname;
+ if (!propRef.getAttribute(child, "name", fname))
+ return false;
+ //trace("EXCLUDE: %s", fname.c_str());
+ excludes.push_back(fname);
+ }
+ else if (tagName == "include")
+ {
+ String fname;
+ if (!propRef.getAttribute(child, "name", fname))
+ return false;
+ //trace("INCLUDE: %s", fname.c_str());
+ includes.push_back(fname);
+ }
+ }
+
+ return true;
+}
+
+
+
+
+/**
+ * Parse a <fileset> entry, and determine which files
+ * should be included
+ */
+bool MakeBase::getFileSet(Element *elem,
+ MakeBase &propRef,
+ String &dir,
+ std::vector<String> &result)
+{
+ String name = elem->getName();
+ if (name != "fileset")
+ {
+ error("expected <fileset>");
+ return false;
+ }
+
+
+ std::vector<String> includes;
+ std::vector<String> excludes;
+
+ //A fileset has one implied patternset
+ if (!getPatternSet(elem, propRef, includes, excludes))
+ {
+ return false;
+ }
+ //Look for child tags, including more patternsets
+ 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 == "patternset")
+ {
+ if (!getPatternSet(child, propRef, includes, excludes))
+ {
+ return false;
+ }
+ }
+ }
+
+ //Now do the stuff
+ //Get the base directory for reading file names
+ bool doDir = true;
+ if (!propRef.getAttribute(elem, "dir", dir))
+ return false;
+
+ std::vector<String> fileList;
+ if (dir.size() > 0)
+ {
+ String baseDir = propRef.resolve(dir);
+ if (!listFiles(baseDir, "", excludes, fileList))
+ return false;
+ }
+
+ std::vector<String>::iterator iter;
+ for (iter=includes.begin() ; iter!=includes.end() ; iter++)
+ {
+ String fname = *iter;
+ fileList.push_back(fname);
+ }
+
+ result = fileList;
+
+ /*
+ for (unsigned int i=0 ; i<result.size() ; i++)
+ {
+ trace("RES:%s", result[i].c_str());
+ }
+ */
+
+ std::sort(fileList.begin(), fileList.end());
+
+ return true;
+}
+
+
+
+/**
+ * Create a directory, making intermediate dirs
+ * if necessary
+ */
+bool MakeBase::createDirectory(const String &dirname)
+{
+ //trace("## createDirectory: %s", dirname.c_str());
+ //## first check if it exists
+ struct stat finfo;
+ String nativeDir = getNativePath(dirname);
+ char *cnative = (char *) nativeDir.c_str();
+ if (stat(dirname.c_str(), &finfo)==0)
+ {
+ if (!S_ISDIR(finfo.st_mode))
+ {
+ error("mkdir: file %s exists but is not a directory",
+ cnative);
+ return false;
+ }
+ else //exists
+ {
+ return true;
+ }
+ }
+
+ //## 2: pull off the last path segment, if any,
+ //## to make the dir 'above' this one, if necessary
+ unsigned int pos = dirname.find_last_of('/');
+ if (pos != dirname.npos)
+ {
+ String subpath = dirname.substr(0, pos);
+ if (!createDirectory(subpath))
+ return false;
+ }
+
+ //## 3: now make
+ if (mkdir(cnative)<0)
+ {
+ error("cannot make directory %s", cnative);
+ return false;
+ }
+
+ return true;
+}
+
+
+/**
+ * Remove a directory recursively
+ */
+bool MakeBase::removeDirectory(const String &dirName)
+{
+ char *dname = (char *)dirName.c_str();
+
+ DIR *dir = opendir(dname);
+ if (!dir)
+ {
+ //# Let this fail nicely.
+ return true;
+ //error("error opening directory %s : %s", dname, strerror(errno));
+ //return false;
+ }
+
+ 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);
+
+
+ struct stat finfo;
+ String childNative = getNativePath(childName);
+ char *cnative = (char *)childNative.c_str();
+ if (stat(cnative, &finfo)<0)
+ {
+ error("cannot stat file:%s", cnative);
+ }
+ else if (S_ISDIR(finfo.st_mode))
+ {
+ //trace("DEL dir: %s", childName.c_str());
+ if (!removeDirectory(childName))
+ {
+ return false;
+ }
+ }
+ else if (!S_ISREG(finfo.st_mode))
+ {
+ trace("not regular: %s", cnative);
+ }
+ else
+ {
+ //trace("DEL file: %s", childName.c_str());
+ if (remove(cnative)<0)
+ {
+ error("error deleting %s : %s",
+ cnative, strerror(errno));
+ return false;
+ }
+ }
+ }
+ closedir(dir);
+
+ //Now delete the directory
+ String native = getNativePath(dirName);
+ if (rmdir(native.c_str())<0)
+ {
+ error("could not delete directory %s : %s",
+ native.c_str() , strerror(errno));
+ return false;
+ }
+
+ return true;
+
+}
+
+
+/**
+ * Copy a file from one name to another. Perform only if needed
+ */
+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)
+ {
+ error("source file %s for copy does not exist",
+ srcNative.c_str());
+ return false;
+ }
+
+ String destNative = getNativePath(destFile);
+ struct stat destinfo;
+ if (stat(destNative.c_str(), &destinfo)==0)
+ {
+ if (destinfo.st_mtime >= srcinfo.st_mtime)
+ return true;
+ }
+
+ //# 2 prepare a destination directory if necessary
+ unsigned int pos = destFile.find_last_of('/');
+ if (pos != destFile.npos)
+ {
+ String subpath = destFile.substr(0, pos);
+ if (!createDirectory(subpath))
+ return false;
+ }
+
+ //# 3 do the data copy
+ FILE *srcf = fopen(srcNative.c_str(), "rb");
+ if (!srcf)
+ {
+ error("copyFile cannot open '%s' for reading", srcNative.c_str());
+ return false;
+ }
+ FILE *destf = fopen(destNative.c_str(), "wb");
+ if (!destf)
+ {
+ error("copyFile cannot open %s for writing", srcNative.c_str());
+ return false;
+ }
+
+ while (!feof(srcf))
+ {
+ int ch = fgetc(srcf);
+ if (ch<0)
+ break;
+ fputc(ch, destf);
+ }
+
+ fclose(destf);
+ fclose(srcf);
+
+
+
+ return true;
+}
+
+
+
+/**
+ * Tests is the modification of fileA is newer than fileB
+ */
+bool MakeBase::isNewerThan(const String &fileA, const String &fileB)
+{
+ //trace("isNewerThan:'%s' , '%s'", fileA.c_str(), fileB.c_str());
+ String nativeA = getNativePath(fileA);
+ struct stat infoA;
+ //IF source does not exist, NOT newer
+ if (stat(nativeA.c_str(), &infoA)<0)
+ {
+ return false;
+ }
+
+ String nativeB = getNativePath(fileB);
+ struct stat infoB;
+ //IF dest does not exist, YES, newer
+ if (stat(nativeB.c_str(), &infoB)<0)
+ {
+ return true;
+ }
+
+ //check the actual times
+ if (infoA.st_mtime > infoB.st_mtime)
+ {
+ return true;
+ }
+
+ return false;
+}
+
+
+//########################################################################
+//# P K G C O N F I G
+//########################################################################
+
+/**
+ *
+ */
+class PkgConfig : public MakeBase
+{
+
+public:
+
+ /**
+ *
+ */
+ PkgConfig()
+ { init(); }
+
+ /**
+ *
+ */
+ PkgConfig(const String &namearg)
+ { init(); name = namearg; }
+
+ /**
+ *
+ */
+ PkgConfig(const PkgConfig &other)
+ { assign(other); }
+
+ /**
+ *
+ */
+ PkgConfig &operator=(const PkgConfig &other)
+ { assign(other); return *this; }
+
+ /**
+ *
+ */
+ virtual ~PkgConfig()
+ { }
+
+ /**
+ *
+ */
+ virtual String getName()
+ { return name; }
+
+ /**
+ *
+ */
+ virtual String getDescription()
+ { return description; }
+
+ /**
+ *
+ */
+ virtual String getCflags()
+ { return cflags; }
+
+ /**
+ *
+ */
+ virtual String getLibs()
+ { return libs; }
+
+ /**
+ *
+ */
+ virtual String getVersion()
+ { return version; }
+
+ /**
+ *
+ */
+ virtual int getMajorVersion()
+ { return majorVersion; }
+
+ /**
+ *
+ */
+ virtual int getMinorVersion()
+ { return minorVersion; }
+
+ /**
+ *
+ */
+ virtual int getMicroVersion()
+ { return microVersion; }
+
+ /**
+ *
+ */
+ virtual std::map<String, String> &getAttributes()
+ { return attrs; }
+
+ /**
+ *
+ */
+ virtual std::vector<String> &getRequireList()
+ { return requireList; }
+
+ virtual bool readFile(const String &fileName);
+
+private:
+
+ void init()
+ {
+ name = "";
+ description = "";
+ cflags = "";
+ libs = "";
+ requires = "";
+ version = "";
+ majorVersion = 0;
+ minorVersion = 0;
+ microVersion = 0;
+ fileName = "";
+ attrs.clear();
+ requireList.clear();
+ }
+
+ void assign(const PkgConfig &other)
+ {
+ name = other.name;
+ description = other.description;
+ cflags = other.cflags;
+ libs = other.libs;
+ requires = other.requires;
+ version = other.version;
+ majorVersion = other.majorVersion;
+ minorVersion = other.minorVersion;
+ microVersion = other.microVersion;
+ fileName = other.fileName;
+ attrs = other.attrs;
+ requireList = other.requireList;
+ }
+
+
+
+ int get(int pos);
+
+ int skipwhite(int pos);
+
+ int getword(int pos, String &ret);
+
+ void parseRequires();
+
+ void parseVersion();
+
+ bool parse(const String &buf);
+
+ void dumpAttrs();
+
+ String name;
+
+ String description;
+
+ String cflags;
+
+ String libs;
+
+ String requires;
+
+ String version;
+
+ int majorVersion;
+
+ int minorVersion;
+
+ int microVersion;
+
+ String fileName;
+
+ std::map<String, String> attrs;
+
+ std::vector<String> requireList;
+
+ char *parsebuf;
+ int parselen;
+};
+
+
+/**
+ * Get a character from the buffer at pos. If out of range,
+ * return -1 for safety
+ */
+int PkgConfig::get(int pos)
+{
+ if (pos>parselen)
+ return -1;
+ return parsebuf[pos];
+}
+
+
+
+/**
+ * Skip over all whitespace characters beginning at pos. Return
+ * the position of the first non-whitespace character.
+ */
+int PkgConfig::skipwhite(int pos)
+{
+ while (pos < parselen)
+ {
+ int ch = get(pos);
+ if (ch < 0)
+ break;
+ if (!isspace(ch))
+ break;
+ pos++;
+ }
+ return pos;
+}
+
+
+/**
+ * Parse the buffer beginning at pos, for a word. Fill
+ * 'ret' with the result. Return the position after the
+ * word.
+ */
+int PkgConfig::getword(int pos, String &ret)
+{
+ while (pos < parselen)
+ {
+ int ch = get(pos);
+ if (ch < 0)
+ break;
+ if (!isalnum(ch) && ch != '_' && ch != '-'&& ch != '.')
+ break;
+ ret.push_back((char)ch);
+ pos++;
+ }
+ return pos;
+}
+
+void PkgConfig::parseRequires()
+{
+ if (requires.size() == 0)
+ return;
+ parsebuf = (char *)requires.c_str();
+ parselen = requires.size();
+ int pos = 0;
+ while (pos < parselen)
+ {
+ pos = skipwhite(pos);
+ String val;
+ int pos2 = getword(pos, val);
+ if (pos2 == pos)
+ break;
+ pos = pos2;
+ //trace("val %s", val.c_str());
+ requireList.push_back(val);
+ }
+}
+
+static int getint(const String str)
+{
+ char *s = (char *)str.c_str();
+ char *ends = NULL;
+ long val = strtol(s, &ends, 10);
+ if (ends == s)
+ return 0L;
+ else
+ return val;
+}
+
+void PkgConfig::parseVersion()
+{
+ if (version.size() == 0)
+ return;
+ String s1, s2, s3;
+ unsigned int pos = 0;
+ unsigned int pos2 = version.find('.', pos);
+ if (pos2 == version.npos)
+ {
+ s1 = version;
+ }
+ else
+ {
+ s1 = version.substr(pos, pos2-pos);
+ pos = pos2;
+ pos++;
+ if (pos < version.size())
+ {
+ pos2 = version.find('.', pos);
+ if (pos2 == version.npos)
+ {
+ s2 = version.substr(pos, version.size()-pos);
+ }
+ else
+ {
+ s2 = version.substr(pos, pos2-pos);
+ pos = pos2;
+ pos++;
+ if (pos < version.size())
+ s3 = version.substr(pos, pos2-pos);
+ }
+ }
+ }
+
+ majorVersion = getint(s1);
+ minorVersion = getint(s2);
+ microVersion = getint(s3);
+ //trace("version:%d.%d.%d", majorVersion,
+ // minorVersion, microVersion );
+}
+
+
+bool PkgConfig::parse(const String &buf)
+{
+ init();
+
+ parsebuf = (char *)buf.c_str();
+ parselen = buf.size();
+ int pos = 0;
+
+
+ while (pos < parselen)
+ {
+ String attrName;
+ pos = skipwhite(pos);
+ int ch = get(pos);
+ if (ch == '#')
+ {
+ //comment. eat the rest of the line
+ while (pos < parselen)
+ {
+ ch = get(pos);
+ if (ch == '\n' || ch < 0)
+ break;
+ pos++;
+ }
+ continue;
+ }
+ pos = getword(pos, attrName);
+ if (attrName.size() == 0)
+ continue;
+ pos = skipwhite(pos);
+ ch = get(pos);
+ if (ch != ':' && ch != '=')
+ {
+ error("expected ':' or '='");
+ return false;
+ }
+ pos++;
+ pos = skipwhite(pos);
+ String attrVal;
+ while (pos < parselen)
+ {
+ ch = get(pos);
+ if (ch == '\n' || ch < 0)
+ break;
+ else if (ch == '$' && get(pos+1) == '{')
+ {
+ //# this is a ${substitution}
+ pos += 2;
+ String subName;
+ while (pos < parselen)
+ {
+ ch = get(pos);
+ if (ch < 0)
+ {
+ error("unterminated substitution");
+ return false;
+ }
+ else if (ch == '}')
+ break;
+ else
+ subName.push_back((char)ch);
+ pos++;
+ }
+ //trace("subName:%s", subName.c_str());
+ String subVal = attrs[subName];
+ //trace("subVal:%s", subVal.c_str());
+ attrVal.append(subVal);
+ }
+ else
+ attrVal.push_back((char)ch);
+ pos++;
+ }
+
+ attrVal = trim(attrVal);
+ attrs[attrName] = attrVal;
+
+ if (attrName == "Name")
+ name = attrVal;
+ else if (attrName == "Description")
+ description = attrVal;
+ else if (attrName == "Cflags")
+ cflags = attrVal;
+ else if (attrName == "Libs")
+ libs = attrVal;
+ else if (attrName == "Requires")
+ requires = attrVal;
+ else if (attrName == "Version")
+ version = attrVal;
+
+ //trace("name:'%s' value:'%s'",
+ // attrName.c_str(), attrVal.c_str());
+ }
+
+
+ parseRequires();
+ parseVersion();
+
+ return true;
+}
+
+void PkgConfig::dumpAttrs()
+{
+ trace("### PkgConfig attributes for %s", fileName.c_str());
+ std::map<String, String>::iterator iter;
+ for (iter=attrs.begin() ; iter!=attrs.end() ; iter++)
+ {
+ trace(" %s = %s", iter->first.c_str(), iter->second.c_str());
+ }
+}
+
+
+bool PkgConfig::readFile(const String &fileNameArg)
+{
+ fileName = fileNameArg;
+
+ FILE *f = fopen(fileName.c_str(), "r");
+ if (!f)
+ {
+ error("cannot open file '%s' for reading", fileName.c_str());
+ return false;
+ }
+ String buf;
+ while (true)
+ {
+ int ch = fgetc(f);
+ if (ch < 0)
+ break;
+ buf.push_back((char)ch);
+ }
+ fclose(f);
+
+ trace("####### File:\n%s", buf.c_str());
+ if (!parse(buf))
+ {
+ return false;
+ }
+
+ dumpAttrs();
+
+ return true;
+}
+
+
+
+
+
+//########################################################################
+//# D E P T O O L
+//########################################################################
+
+
+
+/**
+ * Class which holds information for each file.
+ */
+class FileRec
+{
+public:
+
+ typedef enum
+ {
+ UNKNOWN,
+ CFILE,
+ HFILE,
+ OFILE
+ } FileType;
+
+ /**
+ * Constructor
+ */
+ FileRec()
+ {init(); type = UNKNOWN;}
+
+ /**
+ * Copy constructor
+ */
+ FileRec(const FileRec &other)
+ {init(); assign(other);}
+ /**
+ * Constructor
+ */
+ FileRec(int typeVal)
+ {init(); type = typeVal;}
+ /**
+ * Assignment operator
+ */
+ FileRec &operator=(const FileRec &other)
+ {init(); assign(other); return *this;}
+
+
+ /**
+ * Destructor
+ */
+ ~FileRec()
+ {}
+
+ /**
+ * Directory part of the file name
+ */
+ String path;
+
+ /**
+ * Base name, sans directory and suffix
+ */
+ String baseName;
+
+ /**
+ * File extension, such as cpp or h
+ */
+ String suffix;
+
+ /**
+ * Type of file: CFILE, HFILE, OFILE
+ */
+ int type;
+
+ /**
+ * Used to list files ref'd by this one
+ */
+ std::map<String, FileRec *> files;
+
+
+private:
+
+ void init()
+ {
+ }
+
+ void assign(const FileRec &other)
+ {
+ type = other.type;
+ baseName = other.baseName;
+ suffix = other.suffix;
+ files = other.files;
+ }
+
+};
+
+
+
+/**
+ * Simpler dependency record
+ */
+class DepRec
+{
+public:
+
+ /**
+ * Constructor
+ */
+ DepRec()
+ {init();}
+
+ /**
+ * Copy constructor
+ */
+ DepRec(const DepRec &other)
+ {init(); assign(other);}
+ /**
+ * Constructor
+ */
+ DepRec(const String &fname)
+ {init(); name = fname; }
+ /**
+ * Assignment operator
+ */
+ DepRec &operator=(const DepRec &other)
+ {init(); assign(other); return *this;}
+
+
+ /**
+ * Destructor
+ */
+ ~DepRec()
+ {}
+
+ /**
+ * Directory part of the file name
+ */
+ String path;
+
+ /**
+ * Base name, without the path and suffix
+ */
+ String name;
+
+ /**
+ * Suffix of the source
+ */
+ String suffix;
+
+
+ /**
+ * Used to list files ref'd by this one
+ */
+ std::vector<String> files;
+
+
+private:
+
+ void init()
+ {
+ }
+
+ void assign(const DepRec &other)
+ {
+ path = other.path;
+ name = other.name;
+ suffix = other.suffix;
+ files = other.files;
+ }
+
+};
+
+
+class DepTool : public MakeBase
+{
+public:
+
+ /**
+ * Constructor
+ */
+ DepTool()
+ {init();}
+
+ /**
+ * Copy constructor
+ */
+ DepTool(const DepTool &other)
+ {init(); assign(other);}
+
+ /**
+ * Assignment operator
+ */
+ DepTool &operator=(const DepTool &other)
+ {init(); assign(other); return *this;}
+
+
+ /**
+ * Destructor
+ */
+ ~DepTool()
+ {}
+
+
+ /**
+ * Reset this section of code
+ */
+ virtual void init();
+
+ /**
+ * Reset this section of code
+ */
+ virtual void assign(const DepTool &other)
+ {
+ }
+
+ /**
+ * Sets the source directory which will be scanned
+ */
+ virtual void setSourceDirectory(const String &val)
+ { sourceDir = val; }
+
+ /**
+ * Returns the source directory which will be scanned
+ */
+ virtual String getSourceDirectory()
+ { return sourceDir; }
+
+ /**
+ * Sets the list of files within the directory to analyze
+ */
+ virtual void setFileList(const std::vector<String> &list)
+ { fileList = list; }
+
+ /**
+ * Creates the list of all file names which will be
+ * candidates for further processing. Reads make.exclude
+ * to see which files for directories to leave out.
+ */
+ virtual bool createFileList();
+
+
+ /**
+ * Generates the forward dependency list
+ */
+ virtual bool generateDependencies();
+
+
+ /**
+ * Generates the forward dependency list, saving the file
+ */
+ virtual bool generateDependencies(const String &);
+
+
+ /**
+ * Load a dependency file
+ */
+ std::vector<DepRec> loadDepFile(const String &fileName);
+
+ /**
+ * Load a dependency file, generating one if necessary
+ */
+ std::vector<DepRec> getDepFile(const String &fileName);
+
+ /**
+ * Save a dependency file
+ */
+ bool saveDepFile(const String &fileName);
+
+
+private:
+
+
+ /**
+ *
+ */
+ void parseName(const String &fullname,
+ String &path,
+ String &basename,
+ String &suffix);
+
+ /**
+ *
+ */
+ int get(int pos);
+
+ /**
+ *
+ */
+ int skipwhite(int pos);
+
+ /**
+ *
+ */
+ int getword(int pos, String &ret);
+
+ /**
+ *
+ */
+ bool sequ(int pos, char *key);
+
+ /**
+ *
+ */
+ bool addIncludeFile(FileRec *frec, const String &fname);
+
+ /**
+ *
+ */
+ bool scanFile(const String &fname, FileRec *frec);
+
+ /**
+ *
+ */
+ bool processDependency(FileRec *ofile,
+ FileRec *include,
+ int depth);
+
+ /**
+ *
+ */
+ String sourceDir;
+
+ /**
+ *
+ */
+ std::vector<String> fileList;
+
+ /**
+ *
+ */
+ std::vector<String> directories;
+
+ /**
+ * A list of all files which will be processed for
+ * dependencies. This is the only list that has the actual
+ * records. All other lists have pointers to these records.
+ */
+ std::map<String, FileRec *> allFiles;
+
+ /**
+ * The list of .o files, and the
+ * dependencies upon them.
+ */
+ std::map<String, FileRec *> depFiles;
+
+ int depFileSize;
+ char *depFileBuf;
+
+
+};
+
+
+
+
+
+/**
+ * Clean up after processing. Called by the destructor, but should
+ * also be called before the object is reused.
+ */
+void DepTool::init()
+{
+ sourceDir = ".";
+
+ fileList.clear();
+ directories.clear();
+
+ //clear refs
+ depFiles.clear();
+ //clear records
+ std::map<String, FileRec *>::iterator iter;
+ for (iter=allFiles.begin() ; iter!=allFiles.end() ; iter++)
+ delete iter->second;
+
+ allFiles.clear();
+
+}
+
+
+
+
+/**
+ * Parse a full path name into path, base name, and suffix
+ */
+void DepTool::parseName(const String &fullname,
+ String &path,
+ String &basename,
+ String &suffix)
+{
+ if (fullname.size() < 2)
+ return;
+
+ unsigned int pos = fullname.find_last_of('/');
+ if (pos != fullname.npos && pos<fullname.size()-1)
+ {
+ path = fullname.substr(0, pos);
+ pos++;
+ basename = fullname.substr(pos, fullname.size()-pos);
+ }
+ else
+ {
+ path = "";
+ basename = fullname;
+ }
+
+ pos = basename.find_last_of('.');
+ if (pos != basename.npos && pos<basename.size()-1)
+ {
+ suffix = basename.substr(pos+1, basename.size()-pos-1);
+ basename = basename.substr(0, pos);
+ }
+
+ //trace("parsename:%s %s %s", path.c_str(),
+ // basename.c_str(), suffix.c_str());
+}
+
+
+
+/**
+ * Generate our internal file list.
+ */
+bool DepTool::createFileList()
+{
+
+ for (unsigned int i=0 ; i<fileList.size() ; i++)
+ {
+ String fileName = fileList[i];
+ //trace("## FileName:%s", fileName.c_str());
+ String path;
+ String basename;
+ String sfx;
+ parseName(fileName, path, basename, sfx);
+ if (sfx == "cpp" || sfx == "c" || sfx == "cxx" ||
+ sfx == "cc" || sfx == "CC")
+ {
+ FileRec *fe = new FileRec(FileRec::CFILE);
+ fe->path = path;
+ fe->baseName = basename;
+ fe->suffix = sfx;
+ allFiles[fileName] = fe;
+ }
+ else if (sfx == "h" || sfx == "hh" ||
+ sfx == "hpp" || sfx == "hxx")
+ {
+ FileRec *fe = new FileRec(FileRec::HFILE);
+ fe->path = path;
+ fe->baseName = basename;
+ fe->suffix = sfx;
+ allFiles[fileName] = fe;
+ }
+ }
+
+ if (!listDirectories(sourceDir, "", directories))
+ return false;
+
+ return true;
+}
+
+
+
+
+
+/**
+ * Get a character from the buffer at pos. If out of range,
+ * return -1 for safety
+ */
+int DepTool::get(int pos)
+{
+ if (pos>depFileSize)
+ return -1;
+ return depFileBuf[pos];
+}
+
+
+
+/**
+ * Skip over all whitespace characters beginning at pos. Return
+ * the position of the first non-whitespace character.
+ */
+int DepTool::skipwhite(int pos)
+{
+ while (pos < depFileSize)
+ {
+ int ch = get(pos);
+ if (ch < 0)
+ break;
+ if (!isspace(ch))
+ break;
+ pos++;
+ }
+ return pos;
+}
+
+
+/**
+ * Parse the buffer beginning at pos, for a word. Fill
+ * 'ret' with the result. Return the position after the
+ * word.
+ */
+int DepTool::getword(int pos, String &ret)
+{
+ while (pos < depFileSize)
+ {
+ int ch = get(pos);
+ if (ch < 0)
+ break;
+ if (isspace(ch))
+ break;
+ ret.push_back((char)ch);
+ pos++;
+ }
+ return pos;
+}
+
+/**
+ * Return whether the sequence of characters in the buffer
+ * beginning at pos match the key, for the length of the key
+ */
+bool DepTool::sequ(int pos, char *key)
+{
+ while (*key)
+ {
+ if (*key != get(pos))
+ return false;
+ key++; pos++;
+ }
+ return true;
+}
+
+
+
+/**
+ * Add an include file name to a file record. If the name
+ * is not found in allFiles explicitly, try prepending include
+ * directory names to it and try again.
+ */
+bool DepTool::addIncludeFile(FileRec *frec, const String &iname)
+{
+
+ std::map<String, FileRec *>::iterator iter =
+ allFiles.find(iname);
+ if (iter != allFiles.end()) //already exists
+ {
+ //h file in same dir
+ FileRec *other = iter->second;
+ //trace("local: '%s'", iname.c_str());
+ frec->files[iname] = other;
+ return true;
+ }
+ else
+ {
+ //look in other dirs
+ std::vector<String>::iterator diter;
+ for (diter=directories.begin() ;
+ diter!=directories.end() ; diter++)
+ {
+ String dfname = *diter;
+ dfname.append("/");
+ dfname.append(iname);
+ iter = allFiles.find(dfname);
+ if (iter != allFiles.end())
+ {
+ FileRec *other = iter->second;
+ //trace("other: '%s'", iname.c_str());
+ frec->files[dfname] = other;
+ return true;
+ }
+ }
+ }
+ return true;
+}
+
+
+
+/**
+ * Lightly parse a file to find the #include directives. Do
+ * a bit of state machine stuff to make sure that the directive
+ * is valid. (Like not in a comment).
+ */
+bool DepTool::scanFile(const String &fname, FileRec *frec)
+{
+ String fileName;
+ if (sourceDir.size() > 0)
+ {
+ fileName.append(sourceDir);
+ fileName.append("/");
+ }
+ fileName.append(fname);
+ String nativeName = getNativePath(fileName);
+ FILE *f = fopen(nativeName.c_str(), "r");
+ if (!f)
+ {
+ error("Could not open '%s' for reading", fname.c_str());
+ return false;
+ }
+ String buf;
+ while (true)
+ {
+ int ch = fgetc(f);
+ if (ch < 0)
+ break;
+ buf.push_back((char)ch);
+ }
+ fclose(f);
+
+ depFileSize = buf.size();
+ depFileBuf = (char *)buf.c_str();
+ int pos = 0;
+
+
+ while (pos < depFileSize)
+ {
+ //trace("p:%c", get(pos));
+
+ //# Block comment
+ if (get(pos) == '/' && get(pos+1) == '*')
+ {
+ pos += 2;
+ while (pos < depFileSize)
+ {
+ if (get(pos) == '*' && get(pos+1) == '/')
+ {
+ pos += 2;
+ break;
+ }
+ else
+ pos++;
+ }
+ }
+ //# Line comment
+ else if (get(pos) == '/' && get(pos+1) == '/')
+ {
+ pos += 2;
+ while (pos < depFileSize)
+ {
+ if (get(pos) == '\n')
+ {
+ pos++;
+ break;
+ }
+ else
+ pos++;
+ }
+ }
+ //# #include! yaay
+ else if (sequ(pos, "#include"))
+ {
+ pos += 8;
+ pos = skipwhite(pos);
+ String iname;
+ pos = getword(pos, iname);
+ if (iname.size()>2)
+ {
+ iname = iname.substr(1, iname.size()-2);
+ addIncludeFile(frec, iname);
+ }
+ }
+ else
+ {
+ pos++;
+ }
+ }
+
+ return true;
+}
+
+
+
+/**
+ * Recursively check include lists to find all files in allFiles to which
+ * a given file is dependent.
+ */
+bool DepTool::processDependency(FileRec *ofile,
+ FileRec *include,
+ int depth)
+{
+ std::map<String, FileRec *>::iterator iter;
+ for (iter=include->files.begin() ; iter!=include->files.end() ; iter++)
+ {
+ String fname = iter->first;
+ if (ofile->files.find(fname) != ofile->files.end())
+ {
+ //trace("file '%s' already seen", fname.c_str());
+ continue;
+ }
+ FileRec *child = iter->second;
+ ofile->files[fname] = child;
+
+ processDependency(ofile, child, depth+1);
+ }
+
+
+ return true;
+}
+
+
+
+
+
+/**
+ * Generate the file dependency list.
+ */
+bool DepTool::generateDependencies()
+{
+ std::map<String, FileRec *>::iterator iter;
+ //# First pass. Scan for all includes
+ for (iter=allFiles.begin() ; iter!=allFiles.end() ; iter++)
+ {
+ FileRec *frec = iter->second;
+ if (!scanFile(iter->first, frec))
+ {
+ //quit?
+ }
+ }
+
+ //# Second pass. Scan for all includes
+ for (iter=allFiles.begin() ; iter!=allFiles.end() ; iter++)
+ {
+ FileRec *include = iter->second;
+ if (include->type == FileRec::CFILE)
+ {
+ String cFileName = iter->first;
+ FileRec *ofile = new FileRec(FileRec::OFILE);
+ ofile->path = include->path;
+ ofile->baseName = include->baseName;
+ ofile->suffix = include->suffix;
+ String fname = include->path;
+ if (fname.size()>0)
+ fname.append("/");
+ fname.append(include->baseName);
+ fname.append(".o");
+ depFiles[fname] = ofile;
+ //add the .c file first? no, don't
+ //ofile->files[cFileName] = include;
+
+ //trace("ofile:%s", fname.c_str());
+
+ processDependency(ofile, include, 0);
+ }
+ }
+
+
+ return true;
+}
+
+
+
+/**
+ * High-level call to generate deps and optionally save them
+ */
+bool DepTool::generateDependencies(const String &fileName)
+{
+ if (!createFileList())
+ return false;
+ if (!generateDependencies())
+ return false;
+ if (!saveDepFile(fileName))
+ return false;
+ return true;
+}
+
+
+/**
+ * This saves the dependency cache.
+ */
+bool DepTool::saveDepFile(const String &fileName)
+{
+ time_t tim;
+ time(&tim);
+
+ FILE *f = fopen(fileName.c_str(), "w");
+ if (!f)
+ {
+ trace("cannot open '%s' for writing", fileName.c_str());
+ }
+ fprintf(f, "<?xml version='1.0'?>\n");
+ fprintf(f, "<!--\n");
+ fprintf(f, "########################################################\n");
+ fprintf(f, "## File: build.dep\n");
+ fprintf(f, "## Generated by BuildTool at :%s", ctime(&tim));
+ fprintf(f, "########################################################\n");
+ fprintf(f, "-->\n");
+
+ fprintf(f, "<dependencies source='%s'>\n\n", sourceDir.c_str());
+ std::map<String, FileRec *>::iterator iter;
+ for (iter=depFiles.begin() ; iter!=depFiles.end() ; iter++)
+ {
+ FileRec *frec = iter->second;
+ if (frec->type == FileRec::OFILE)
+ {
+ fprintf(f, "<object path='%s' name='%s' suffix='%s'>\n",
+ frec->path.c_str(), frec->baseName.c_str(), frec->suffix.c_str());
+ std::map<String, FileRec *>::iterator citer;
+ for (citer=frec->files.begin() ; citer!=frec->files.end() ; citer++)
+ {
+ String cfname = citer->first;
+ fprintf(f, " <dep name='%s'/>\n", cfname.c_str());
+ }
+ fprintf(f, "</object>\n\n");
+ }
+ }
+
+ fprintf(f, "</dependencies>\n");
+ fprintf(f, "\n");
+ fprintf(f, "<!--\n");
+ fprintf(f, "########################################################\n");
+ fprintf(f, "## E N D\n");
+ fprintf(f, "########################################################\n");
+ fprintf(f, "-->\n");
+
+ fclose(f);
+
+ return true;
+}
+
+
+
+
+/**
+ * This loads the dependency cache.
+ */
+std::vector<DepRec> DepTool::loadDepFile(const String &depFile)
+{
+ std::vector<DepRec> result;
+
+ Parser parser;
+ Element *root = parser.parseFile(depFile.c_str());
+ if (!root)
+ {
+ error("Could not open %s for reading", depFile.c_str());
+ return result;
+ }
+
+ if (root->getChildren().size()==0 ||
+ root->getChildren()[0]->getName()!="dependencies")
+ {
+ error("Main xml element should be <dependencies>");
+ delete root;
+ return result;
+ }
+
+ //########## Start parsing
+ Element *depList = root->getChildren()[0];
+
+ std::vector<Element *> objects = depList->getChildren();
+ for (unsigned int i=0 ; i<objects.size() ; i++)
+ {
+ Element *objectElem = objects[i];
+ String tagName = objectElem->getName();
+ if (tagName == "object")
+ {
+ String objName = objectElem->getAttribute("name");
+ //trace("object:%s", objName.c_str());
+ DepRec depObject(objName);
+ depObject.path = objectElem->getAttribute("path");
+ depObject.suffix = objectElem->getAttribute("suffix");
+ //########## DESCRIPTION
+ std::vector<Element *> depElems = objectElem->getChildren();
+ for (unsigned int i=0 ; i<depElems.size() ; i++)
+ {
+ Element *depElem = depElems[i];
+ tagName = depElem->getName();
+ if (tagName == "dep")
+ {
+ String depName = depElem->getAttribute("name");
+ //trace(" dep:%s", depName.c_str());
+ depObject.files.push_back(depName);
+ }
+ }
+ result.push_back(depObject);
+ }
+ }
+
+ delete root;
+
+ return result;
+}
+
+
+/**
+ * This loads the dependency cache.
+ */
+std::vector<DepRec> DepTool::getDepFile(const String &depFile)
+{
+ std::vector<DepRec> result = loadDepFile(depFile);
+ if (result.size() == 0)
+ {
+ generateDependencies(depFile);
+ result = loadDepFile(depFile);
+ }
+ return result;
+}
+
+
+
+
+//########################################################################
+//# T A S K
+//########################################################################
+//forward decl
+class Target;
+class Make;
+
+/**
+ *
+ */
+class Task : public MakeBase
+{
+
+public:
+
+ typedef enum
+ {
+ TASK_NONE,
+ TASK_AR,
+ TASK_CC,
+ TASK_COPY,
+ TASK_DELETE,
+ TASK_JAR,
+ TASK_JAVAC,
+ TASK_LINK,
+ TASK_MKDIR,
+ TASK_MSGFMT,
+ TASK_RANLIB,
+ TASK_RC,
+ TASK_STRIP,
+ TASK_TSTAMP
+ } TaskType;
+
+
+ /**
+ *
+ */
+ Task(MakeBase &par) : parent(par)
+ { init(); }
+
+ /**
+ *
+ */
+ Task(const Task &other) : parent(other.parent)
+ { init(); assign(other); }
+
+ /**
+ *
+ */
+ Task &operator=(const Task &other)
+ { assign(other); return *this; }
+
+ /**
+ *
+ */
+ virtual ~Task()
+ { }
+
+
+ /**
+ *
+ */
+ virtual MakeBase &getParent()
+ { return parent; }
+
+ /**
+ *
+ */
+ virtual int getType()
+ { return type; }
+
+ /**
+ *
+ */
+ virtual void setType(int val)
+ { type = val; }
+
+ /**
+ *
+ */
+ virtual String getName()
+ { return name; }
+
+ /**
+ *
+ */
+ virtual bool execute()
+ { return true; }
+
+ /**
+ *
+ */
+ virtual bool parse(Element *elem)
+ { return true; }
+
+ /**
+ *
+ */
+ Task *createTask(Element *elem);
+
+
+protected:
+
+ void init()
+ {
+ type = TASK_NONE;
+ name = "none";
+ }
+
+ void assign(const Task &other)
+ {
+ type = other.type;
+ name = other.name;
+ }
+
+ String getAttribute(Element *elem, const String &attrName)
+ {
+ String str;
+ return str;
+ }
+
+ MakeBase &parent;
+
+ int type;
+
+ String name;
+};
+
+
+
+
+/**
+ * Run the "ar" command to archive .o's into a .a
+ */
+class TaskAr : public Task
+{
+public:
+
+ TaskAr(MakeBase &par) : Task(par)
+ {
+ type = TASK_AR; name = "ar";
+ command = "ar crv";
+ }
+
+ virtual ~TaskAr()
+ {}
+
+ virtual bool execute()
+ {
+ //trace("###########HERE %d", fileSet.size());
+ bool doit = false;
+
+ String fullOut = parent.resolve(fileName);
+ //trace("ar fullout: %s", fullOut.c_str());
+
+
+ for (unsigned int i=0 ; i<fileSet.size() ; i++)
+ {
+ String fname;
+ if (fileSetDir.size()>0)
+ {
+ fname.append(fileSetDir);
+ fname.append("/");
+ }
+ fname.append(fileSet[i]);
+ String fullName = parent.resolve(fname);
+ //trace("ar : %s/%s", fullOut.c_str(), fullName.c_str());
+ if (isNewerThan(fullName, fullOut))
+ doit = true;
+ }
+ trace("Needs it:%d", doit);
+ if (!doit)
+ {
+ return true;
+ }
+
+ String cmd = command;
+ cmd.append(" ");
+ cmd.append(fullOut);
+ for (unsigned int i=0 ; i<fileSet.size() ; i++)
+ {
+ String fname;
+ if (fileSetDir.size()>0)
+ {
+ fname.append(fileSetDir);
+ fname.append("/");
+ }
+ fname.append(fileSet[i]);
+ String fullName = parent.resolve(fname);
+
+ cmd.append(" ");
+ cmd.append(fullName);
+ }
+ trace("AR %d: %s", fileSet.size(), cmd.c_str());
+ String outString, errString;
+ if (!executeCommand(cmd.c_str(), "", outString, errString))
+ {
+ error("AR problem: %s", errString.c_str());
+ return false;
+ }
+
+ return true;
+ }
+
+ virtual bool parse(Element *elem)
+ {
+ if (!parent.getAttribute(elem, "file", fileName))
+ 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 (!getFileSet(child, parent, fileSetDir, fileSet))
+ return false;
+ }
+ }
+ return true;
+ }
+
+private:
+
+ String command;
+ String fileName;
+ String fileSetDir;
+ std::vector<String> fileSet;
+
+};
+
+
+/**
+ * This task runs the C/C++ compiler. The compiler is invoked
+ * for all .c or .cpp files which are newer than their correcsponding
+ * .o files.
+ */
+class TaskCC : public Task
+{
+public:
+
+ TaskCC(MakeBase &par) : Task(par)
+ {
+ type = TASK_CC; name = "cc";
+ ccCommand = "gcc";
+ cxxCommand = "g++";
+ source = ".";
+ dest = ".";
+ flags = "";
+ defines = "";
+ includes = "";
+ sourceFiles.clear();
+ }
+
+ virtual ~TaskCC()
+ {}
+
+ virtual bool execute()
+ {
+ DepTool depTool;
+ depTool.setSourceDirectory(source);
+ depTool.setFileList(sourceFiles);
+ std::vector<DepRec> deps = depTool.getDepFile("build.dep");
+
+ String incs;
+ incs.append("-I");
+ incs.append(parent.resolve("."));
+ incs.append(" ");
+ if (includes.size()>0)
+ {
+ incs.append(includes);
+ incs.append(" ");
+ }
+ std::set<String> paths;
+ std::vector<DepRec>::iterator viter;
+ for (viter=deps.begin() ; viter!=deps.end() ; viter++)
+ {
+ DepRec dep = *viter;
+ if (dep.path.size()>0)
+ paths.insert(dep.path);
+ }
+ if (source.size()>0)
+ {
+ incs.append(" -I");
+ incs.append(parent.resolve(source));
+ incs.append(" ");
+ }
+ std::set<String>::iterator setIter;
+ for (setIter=paths.begin() ; setIter!=paths.end() ; setIter++)
+ {
+ incs.append(" -I");
+ String dname;
+ if (source.size()>0)
+ {
+ dname.append(source);
+ dname.append("/");
+ }
+ dname.append(*setIter);
+ incs.append(parent.resolve(dname));
+ }
+ std::vector<String> cfiles;
+ for (viter=deps.begin() ; viter!=deps.end() ; viter++)
+ {
+ DepRec dep = *viter;
+
+ //## Select command
+ String sfx = dep.suffix;
+ String command = ccCommand;
+ if (sfx == "cpp" || sfx == "c++" || sfx == "cc"
+ || sfx == "CC")
+ command = cxxCommand;
+
+ //## Make paths
+ String destPath = dest;
+ String srcPath = source;
+ if (dep.path.size()>0)
+ {
+ destPath.append("/");
+ destPath.append(dep.path);
+ srcPath.append("/");
+ srcPath.append(dep.path);
+ }
+ //## Make sure destination directory exists
+ if (!createDirectory(destPath))
+ return false;
+
+ //## Check whether it needs to be done
+ String destFullName = destPath;
+ destFullName.append("/");
+ destFullName.append(dep.name);
+ destFullName.append(".o");
+ String srcFullName = srcPath;
+ srcFullName.append("/");
+ srcFullName.append(dep.name);
+ srcFullName.append(".");
+ srcFullName.append(dep.suffix);
+ if (!isNewerThan(srcFullName, destFullName))
+ {
+ //trace("%s skipped", srcFullName.c_str());
+ continue;
+ }
+
+ //## Assemble the command
+ String cmd = command;
+ cmd.append(" -c ");
+ cmd.append(flags);
+ cmd.append(" ");
+ cmd.append(defines);
+ cmd.append(" ");
+ cmd.append(incs);
+ cmd.append(" ");
+ cmd.append(srcFullName);
+ cmd.append(" -o ");
+ cmd.append(destFullName);
+
+ //## Execute the command
+ trace("cmd: %s", cmd.c_str());
+ String outString, errString;
+ if (!executeCommand(cmd.c_str(), "", outString, errString))
+ {
+ error("problem compiling: %s", errString.c_str());
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ virtual bool parse(Element *elem)
+ {
+ String s;
+ if (!parent.getAttribute(elem, "command", s))
+ return false;
+ if (s.size()>0) { ccCommand = s; cxxCommand = s; }
+ if (!parent.getAttribute(elem, "cc", s))
+ return false;
+ if (s.size()>0) ccCommand = s;
+ if (!parent.getAttribute(elem, "cxx", s))
+ return false;
+ if (s.size()>0) cxxCommand = s;
+ if (!parent.getAttribute(elem, "destdir", s))
+ return false;
+ if (s.size()>0) dest = s;
+
+ 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 == "flags")
+ {
+ if (!parent.getValue(child, flags))
+ return false;
+ }
+ else if (tagName == "includes")
+ {
+ if (!parent.getValue(child, includes))
+ return false;
+ }
+ else if (tagName == "defines")
+ {
+ if (!parent.getValue(child, defines))
+ return false;
+ }
+ else if (tagName == "fileset")
+ {
+ if (!getFileSet(child, parent, source, sourceFiles))
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+protected:
+
+ String ccCommand;
+ String cxxCommand;
+ String source;
+ String dest;
+ String flags;
+ String defines;
+ String includes;
+ std::vector<String> sourceFiles;
+
+};
+
+
+
+/**
+ *
+ */
+class TaskCopy : public Task
+{
+public:
+
+ typedef enum
+ {
+ CP_NONE,
+ CP_TOFILE,
+ CP_TODIR
+ } CopyType;
+
+ TaskCopy(MakeBase &par) : Task(par)
+ {
+ type = TASK_COPY; name = "copy";
+ cptype = CP_NONE;
+ verbose = false;
+ haveFileSet = false;
+ }
+
+ virtual ~TaskCopy()
+ {}
+
+ virtual bool execute()
+ {
+ switch (cptype)
+ {
+ case CP_TOFILE:
+ {
+ if (fileName.size()>0)
+ {
+ String fullSource = parent.resolve(fileName);
+ String fullDest = parent.resolve(toFileName);
+ //trace("copy %s to file %s", fullSource.c_str(),
+ // fullDest.c_str());
+ if (!isNewerThan(fullSource, fullDest))
+ {
+ return true;
+ }
+ if (!copyFile(fullSource, fullDest))
+ return false;
+ }
+ return true;
+ }
+ case CP_TODIR:
+ {
+ if (haveFileSet)
+ {
+ for (unsigned int i=0 ; i<fileSet.size() ; i++)
+ {
+ String fileName = fileSet[i];
+
+ String sourcePath;
+ if (fileSetDir.size()>0)
+ {
+ sourcePath.append(fileSetDir);
+ sourcePath.append("/");
+ }
+ sourcePath.append(fileName);
+ String fullSource = parent.resolve(sourcePath);
+
+ //Get the immediate parent directory's base name
+ String baseFileSetDir = fileSetDir;
+ int pos = baseFileSetDir.find_last_of('/');
+ if (pos>0 && pos < baseFileSetDir.size()-1)
+ baseFileSetDir =
+ baseFileSetDir.substr(pos+1,
+ baseFileSetDir.size());
+ //Now make the new path
+ String destPath;
+ if (toDirName.size()>0)
+ {
+ destPath.append(toDirName);
+ destPath.append("/");
+ }
+ if (baseFileSetDir.size()>0)
+ {
+ destPath.append(baseFileSetDir);
+ destPath.append("/");
+ }
+ 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 (!isNewerThan(fullSource, fullDest))
+ {
+ //trace("copy skipping %s", fullSource.c_str());
+ continue;
+ }
+ if (!copyFile(fullSource, fullDest))
+ return false;
+ }
+ }
+ else //file source
+ {
+ //For file->dir we want only the basename of
+ //the source appended to the dest dir
+ String baseName = fileName;
+ int pos = baseName.find_last_of('/');
+ if (pos > 0 && pos<baseName.size()-1)
+ baseName = baseName.substr(pos+1, baseName.size());
+ String fullSource = parent.resolve(fileName);
+ String destPath;
+ if (toDirName.size()>0)
+ {
+ destPath.append(toDirName);
+ destPath.append("/");
+ }
+ destPath.append(baseName);
+ String fullDest = parent.resolve(destPath);
+ //trace("copy %s to new dir : %s", fullSource.c_str(),
+ // fullDest.c_str());
+ if (!isNewerThan(fullSource, fullDest))
+ {
+ return true;
+ }
+ if (!copyFile(fullSource, fullDest))
+ return false;
+ }
+ return true;
+ }
+ }
+ return true;
+ }
+
+
+ virtual bool parse(Element *elem)
+ {
+ if (!parent.getAttribute(elem, "file", fileName))
+ return false;
+ if (!parent.getAttribute(elem, "tofile", toFileName))
+ return false;
+ if (toFileName.size() > 0)
+ cptype = CP_TOFILE;
+ if (!parent.getAttribute(elem, "todir", toDirName))
+ return false;
+ if (toDirName.size() > 0)
+ cptype = CP_TODIR;
+ String ret;
+ if (!parent.getAttribute(elem, "verbose", ret))
+ return false;
+ if (ret.size()>0 && !getBool(ret, verbose))
+ return false;
+
+ haveFileSet = 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 (!getFileSet(child, parent, fileSetDir, fileSet))
+ {
+ error("problem getting fileset");
+ return false;
+ }
+ haveFileSet = true;
+ }
+ }
+
+ //Perform validity checks
+ if (fileName.size()>0 && fileSet.size()>0)
+ {
+ error("<copy> can only have one of : file= and <fileset>");
+ return false;
+ }
+ if (toFileName.size()>0 && toDirName.size()>0)
+ {
+ error("<copy> can only have one of : tofile= or todir=");
+ return false;
+ }
+ if (haveFileSet && toDirName.size()==0)
+ {
+ error("a <copy> task with a <fileset> must have : todir=");
+ return false;
+ }
+ if (cptype == CP_TOFILE && fileName.size()==0)
+ {
+ error("<copy> tofile= must be associated with : file=");
+ return false;
+ }
+ if (cptype == CP_TODIR && fileName.size()==0 && !haveFileSet)
+ {
+ error("<copy> todir= must be associated with : file= or <fileset>");
+ return false;
+ }
+
+ return true;
+ }
+
+private:
+
+ int cptype;
+ String fileName;
+ String fileSetDir;
+ std::vector<String> fileSet;
+ String toFileName;
+ String toDirName;
+ bool verbose;
+ bool haveFileSet;
+};
+
+
+/**
+ *
+ */
+class TaskDelete : public Task
+{
+public:
+
+ typedef enum
+ {
+ DEL_FILE,
+ DEL_DIR,
+ DEL_FILESET
+ } DeleteType;
+
+ TaskDelete(MakeBase &par) : Task(par)
+ {
+ type = TASK_DELETE;
+ name = "delete";
+ delType = DEL_FILE;
+ verbose = false;
+ quiet = false;
+ failOnError = true;
+ }
+
+ virtual ~TaskDelete()
+ {}
+
+ virtual bool execute()
+ {
+ struct stat finfo;
+ switch (delType)
+ {
+ case DEL_FILE:
+ {
+ 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)
+ {
+ error("<delete> failed: %s", strerror(errno));
+ return false;
+ }
+ return true;
+ }
+ case DEL_DIR:
+ {
+ String fullDir = parent.resolve(dirName);
+ char *dname = (char *)fullDir.c_str();
+ if (!removeDirectory(fullDir))
+ return false;
+ return true;
+ }
+ }
+ return true;
+ }
+
+ virtual bool parse(Element *elem)
+ {
+ if (!parent.getAttribute(elem, "file", fileName))
+ return false;
+ if (fileName.size() > 0)
+ delType = DEL_FILE;
+ if (!parent.getAttribute(elem, "dir", dirName))
+ return false;
+ if (dirName.size() > 0)
+ delType = DEL_DIR;
+ if (fileName.size()>0 && dirName.size()>0)
+ {
+ error("<delete> can only have one attribute of file= or dir=");
+ return false;
+ }
+ String ret;
+ if (!parent.getAttribute(elem, "verbose", ret))
+ return false;
+ if (ret.size()>0 && !getBool(ret, verbose))
+ return false;
+ if (!parent.getAttribute(elem, "quiet", ret))
+ return false;
+ if (ret.size()>0 && !getBool(ret, quiet))
+ return false;
+ if (!parent.getAttribute(elem, "failonerror", ret))
+ return false;
+ if (ret.size()>0 && !getBool(ret, failOnError))
+ return false;
+ return true;
+ }
+
+private:
+
+ int delType;
+ String dirName;
+ String fileName;
+ bool verbose;
+ bool quiet;
+ bool failOnError;
+};
+
+
+/**
+ *
+ */
+class TaskJar : public Task
+{
+public:
+
+ TaskJar(MakeBase &par) : Task(par)
+ { type = TASK_JAR; name = "jar"; }
+
+ virtual ~TaskJar()
+ {}
+
+ virtual bool execute()
+ {
+ return true;
+ }
+
+ virtual bool parse(Element *elem)
+ {
+ return true;
+ }
+};
+
+
+/**
+ *
+ */
+class TaskJavac : public Task
+{
+public:
+
+ TaskJavac(MakeBase &par) : Task(par)
+ { type = TASK_JAVAC; name = "javac"; }
+
+ virtual ~TaskJavac()
+ {}
+
+ virtual bool execute()
+ {
+ return true;
+ }
+
+ virtual bool parse(Element *elem)
+ {
+ return true;
+ }
+};
+
+
+/**
+ *
+ */
+class TaskLink : public Task
+{
+public:
+
+ TaskLink(MakeBase &par) : Task(par)
+ {
+ type = TASK_LINK; name = "link";
+ command = "g++";
+ }
+
+ virtual ~TaskLink()
+ {}
+
+ virtual bool execute()
+ {
+ bool doit = false;
+ String fullTarget = parent.resolve(fileName);
+ String cmd = command;
+ cmd.append(" -o ");
+ cmd.append(fullTarget);
+ cmd.append(" ");
+ cmd.append(flags);
+ for (unsigned int i=0 ; i<fileSet.size() ; i++)
+ {
+ cmd.append(" ");
+ String obj;
+ if (fileSetDir.size()>0)
+ {
+ obj.append(fileSetDir);
+ obj.append("/");
+ }
+ obj.append(fileSet[i]);
+ String fullObj = parent.resolve(obj);
+ cmd.append(fullObj);
+ if (isNewerThan(fullObj, fullTarget))
+ doit = true;
+ }
+ cmd.append(" ");
+ cmd.append(libs);
+ trace("LINK cmd:%s", cmd.c_str());
+ if (!doit)
+ {
+ trace("link not needed");
+ return true;
+ }
+
+ String outString, errString;
+ if (!executeCommand(cmd.c_str(), "", outString, errString))
+ {
+ error("LINK problem: %s", errString.c_str());
+ return false;
+ }
+ return true;
+ }
+
+ virtual bool parse(Element *elem)
+ {
+ if (!parent.getAttribute(elem, "command", command))
+ return false;
+ if (!parent.getAttribute(elem, "out", fileName))
+ 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 (!getFileSet(child, parent, fileSetDir, fileSet))
+ return false;
+ }
+ else if (tagName == "flags")
+ {
+ if (!parent.getValue(child, flags))
+ return false;
+ }
+ else if (tagName == "libs")
+ {
+ if (!parent.getValue(child, libs))
+ return false;
+ }
+ }
+ return true;
+ }
+
+private:
+
+ String command;
+ String fileName;
+ String flags;
+ String libs;
+ String fileSetDir;
+ std::vector<String> fileSet;
+
+};
+
+
+
+/**
+ * Create a named directory
+ */
+class TaskMkDir : public Task
+{
+public:
+
+ TaskMkDir(MakeBase &par) : Task(par)
+ { type = TASK_MKDIR; name = "mkdir"; }
+
+ virtual ~TaskMkDir()
+ {}
+
+ virtual bool execute()
+ {
+ String fullDir = parent.resolve(dirName);
+ //trace("fullDir:%s", fullDir.c_str());
+ if (!createDirectory(fullDir))
+ return false;
+ return true;
+ }
+
+ virtual bool parse(Element *elem)
+ {
+ if (!parent.getAttribute(elem, "dir", dirName))
+ return false;
+ if (dirName.size() == 0)
+ {
+ error("<mkdir> requires 'dir=\"dirname\"' attribute");
+ return false;
+ }
+ //trace("dirname:%s", dirName.c_str());
+ return true;
+ }
+
+private:
+
+ String dirName;
+};
+
+
+
+/**
+ * Create a named directory
+ */
+class TaskMsgFmt: public Task
+{
+public:
+
+ TaskMsgFmt(MakeBase &par) : Task(par)
+ {
+ type = TASK_MSGFMT;
+ name = "msgfmt";
+ command = "msgfmt";
+ }
+
+ virtual ~TaskMsgFmt()
+ {}
+
+ virtual bool execute()
+ {
+ //trace("msgfmt: %d", fileSet.size());
+ bool doit = false;
+
+ String fullDest = parent.resolve(toDirName);
+
+ for (unsigned int i=0 ; i<fileSet.size() ; i++)
+ {
+ String fileName = fileSet[i];
+ if (getSuffix(fileName) != "po")
+ continue;
+ String sourcePath;
+ if (fileSetDir.size()>0)
+ {
+ sourcePath.append(fileSetDir);
+ sourcePath.append("/");
+ }
+ sourcePath.append(fileName);
+ String fullSource = parent.resolve(sourcePath);
+
+ String destPath;
+ if (toDirName.size()>0)
+ {
+ destPath.append(toDirName);
+ destPath.append("/");
+ }
+ destPath.append(fileName);
+ destPath[destPath.size()-2] = 'm';
+ String fullDest = parent.resolve(destPath);
+
+ if (!isNewerThan(fullSource, fullDest))
+ {
+ //trace("skip %s", fullSource.c_str());
+ continue;
+ }
+
+ String cmd = command;
+ cmd.append(" ");
+ cmd.append(fullSource);
+ cmd.append(" -o ");
+ cmd.append(fullDest);
+
+ int pos = fullDest.find_last_of('/');
+ if (pos>0)
+ {
+ String fullDestPath = fullDest.substr(0, pos);
+ if (!createDirectory(fullDestPath))
+ return false;
+ }
+
+ String outString, errString;
+ if (!executeCommand(cmd.c_str(), "", outString, errString))
+ {
+ error("<msgfmt> problem: %s", errString.c_str());
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ virtual bool parse(Element *elem)
+ {
+ if (!parent.getAttribute(elem, "todir", toDirName))
+ 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 (!getFileSet(child, parent, fileSetDir, fileSet))
+ return false;
+ }
+ }
+ return true;
+ }
+
+private:
+
+ String command;
+ String toDirName;
+ String fileSetDir;
+ std::vector<String> fileSet;
+
+};
+
+
+
+
+
+/**
+ * Process an archive to allow random access
+ */
+class TaskRanlib : public Task
+{
+public:
+
+ TaskRanlib(MakeBase &par) : Task(par)
+ { type = TASK_RANLIB; name = "ranlib"; }
+
+ virtual ~TaskRanlib()
+ {}
+
+ virtual bool execute()
+ {
+ String fullName = parent.resolve(fileName);
+ //trace("fullDir:%s", fullDir.c_str());
+ String cmd = "ranlib ";
+ cmd.append(fullName);
+ trace("<ranlib> cmd:%s", cmd.c_str());
+ String outbuf, errbuf;
+ if (!executeCommand(cmd, "", outbuf, errbuf))
+ return false;
+ return true;
+ }
+
+ virtual bool parse(Element *elem)
+ {
+ if (!parent.getAttribute(elem, "file", fileName))
+ return false;
+ if (fileName.size() == 0)
+ {
+ error("<ranlib> requires 'file=\"fileNname\"' attribute");
+ return false;
+ }
+ return true;
+ }
+
+private:
+
+ String fileName;
+};
+
+
+
+/**
+ * Run the "ar" command to archive .o's into a .a
+ */
+class TaskRC : public Task
+{
+public:
+
+ TaskRC(MakeBase &par) : Task(par)
+ {
+ type = TASK_RC; name = "rc";
+ command = "windres -o";
+ }
+
+ virtual ~TaskRC()
+ {}
+
+ virtual bool execute()
+ {
+ String fullFile = parent.resolve(fileName);
+ String fullOut = parent.resolve(outName);
+ if (!isNewerThan(fullFile, fullOut))
+ return true;
+ String cmd = command;
+ cmd.append(" ");
+ cmd.append(fullOut);
+ cmd.append(" ");
+ cmd.append(flags);
+ cmd.append(" ");
+ cmd.append(fullFile);
+
+ trace("cmd: %s", cmd.c_str());
+ String outString, errString;
+ if (!executeCommand(cmd.c_str(), "", outString, errString))
+ {
+ error("RC problem: %s", errString.c_str());
+ return false;
+ }
+ return true;
+ }
+
+ virtual bool parse(Element *elem)
+ {
+ if (!parent.getAttribute(elem, "command", command))
+ return false;
+ if (!parent.getAttribute(elem, "file", fileName))
+ return false;
+ if (!parent.getAttribute(elem, "out", outName))
+ 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 == "flags")
+ {
+ if (!parent.getValue(child, flags))
+ return false;
+ }
+ }
+ return true;
+ }
+
+private:
+
+ String command;
+ String flags;
+ String fileName;
+ String outName;
+
+};
+
+
+
+/**
+ * Strip an executable
+ */
+class TaskStrip : public Task
+{
+public:
+
+ TaskStrip(MakeBase &par) : Task(par)
+ { type = TASK_STRIP; name = "strip"; }
+
+ virtual ~TaskStrip()
+ {}
+
+ virtual bool execute()
+ {
+ String fullName = parent.resolve(fileName);
+ //trace("fullDir:%s", fullDir.c_str());
+ String cmd = "strip ";
+ cmd.append(fullName);
+ trace("<strip> cmd:%s", cmd.c_str());
+ String outbuf, errbuf;
+ if (!executeCommand(cmd, "", outbuf, errbuf))
+ return false;
+ return true;
+ }
+
+ virtual bool parse(Element *elem)
+ {
+ if (!parent.getAttribute(elem, "file", fileName))
+ return false;
+ if (fileName.size() == 0)
+ {
+ error("<strip> requires 'file=\"fileNname\"' attribute");
+ return false;
+ }
+ return true;
+ }
+
+private:
+
+ String fileName;
+};
+
+
+/**
+ *
+ */
+class TaskTstamp : public Task
+{
+public:
+
+ TaskTstamp(MakeBase &par) : Task(par)
+ { type = TASK_TSTAMP; name = "tstamp"; }
+
+ virtual ~TaskTstamp()
+ {}
+
+ virtual bool execute()
+ {
+ return true;
+ }
+
+ virtual bool parse(Element *elem)
+ {
+ trace("tstamp parse");
+ return true;
+ }
+};
+
+
+
+/**
+ *
+ */
+Task *Task::createTask(Element *elem)
+{
+ String tagName = elem->getName();
+ //trace("task:%s", tagName.c_str());
+ Task *task = NULL;
+ if (tagName == "ar")
+ task = new TaskAr(parent);
+ else if (tagName == "cc")
+ task = new TaskCC(parent);
+ else if (tagName == "copy")
+ task = new TaskCopy(parent);
+ else if (tagName == "delete")
+ task = new TaskDelete(parent);
+ else if (tagName == "jar")
+ task = new TaskJar(parent);
+ else if (tagName == "javac")
+ task = new TaskJavac(parent);
+ else if (tagName == "link")
+ task = new TaskLink(parent);
+ else if (tagName == "mkdir")
+ task = new TaskMkDir(parent);
+ else if (tagName == "msgfmt")
+ task = new TaskMsgFmt(parent);
+ else if (tagName == "ranlib")
+ task = new TaskRanlib(parent);
+ else if (tagName == "rc")
+ task = new TaskRC(parent);
+ else if (tagName == "strip")
+ task = new TaskStrip(parent);
+ else if (tagName == "tstamp")
+ task = new TaskTstamp(parent);
+ else
+ {
+ error("Unknown task '%s'", tagName.c_str());
+ return NULL;
+ }
+
+ if (!task->parse(elem))
+ {
+ delete task;
+ return NULL;
+ }
+ return task;
+}
+
+
+
+//########################################################################
+//# T A R G E T
+//########################################################################
+
+/**
+ *
+ */
+class Target : public MakeBase
+{
+
+public:
+
+ /**
+ *
+ */
+ Target(Make &par) : parent(par)
+ { init(); }
+
+ /**
+ *
+ */
+ Target(const Target &other) : parent(other.parent)
+ { init(); assign(other); }
+
+ /**
+ *
+ */
+ Target &operator=(const Target &other)
+ { init(); assign(other); return *this; }
+
+ /**
+ *
+ */
+ virtual ~Target()
+ { cleanup() ; }
+
+
+ /**
+ *
+ */
+ virtual Make &getParent()
+ { return parent; }
+
+ /**
+ *
+ */
+ virtual String getName()
+ { return name; }
+
+ /**
+ *
+ */
+ virtual void setName(const String &val)
+ { name = val; }
+
+ /**
+ *
+ */
+ virtual String getDescription()
+ { return description; }
+
+ /**
+ *
+ */
+ virtual void setDescription(const String &val)
+ { description = val; }
+
+ /**
+ *
+ */
+ virtual void addDependency(const String &val)
+ { deps.push_back(val); }
+
+ /**
+ *
+ */
+ virtual void parseDependencies(const String &val)
+ { deps = tokenize(val, ", "); }
+
+ /**
+ *
+ */
+ virtual std::vector<String> &getDependencies()
+ { return deps; }
+
+ /**
+ *
+ */
+ virtual String getIf()
+ { return ifVar; }
+
+ /**
+ *
+ */
+ virtual void setIf(const String &val)
+ { ifVar = val; }
+
+ /**
+ *
+ */
+ virtual String getUnless()
+ { return unlessVar; }
+
+ /**
+ *
+ */
+ virtual void setUnless(const String &val)
+ { unlessVar = val; }
+
+ /**
+ *
+ */
+ virtual void addTask(Task *val)
+ { tasks.push_back(val); }
+
+ /**
+ *
+ */
+ virtual std::vector<Task *> &getTasks()
+ { return tasks; }
+
+private:
+
+ void init()
+ {
+ }
+
+ void cleanup()
+ {
+ tasks.clear();
+ }
+
+ void assign(const Target &other)
+ {
+ //parent = other.parent;
+ name = other.name;
+ description = other.description;
+ ifVar = other.ifVar;
+ unlessVar = other.unlessVar;
+ deps = other.deps;
+ tasks = other.tasks;
+ }
+
+ Make &parent;
+
+ String name;
+
+ String description;
+
+ String ifVar;
+
+ String unlessVar;
+
+ std::vector<String> deps;
+
+ std::vector<Task *> tasks;
+
+};
+
+
+
+
+
+
+
+
+//########################################################################
+//# M A K E
+//########################################################################
+
+
+/**
+ *
+ */
+class Make : public MakeBase
+{
+
+public:
+
+ /**
+ *
+ */
+ Make()
+ { init(); }
+
+ /**
+ *
+ */
+ Make(const Make &other)
+ { assign(other); }
+
+ /**
+ *
+ */
+ Make &operator=(const Make &other)
+ { assign(other); return *this; }
+
+ /**
+ *
+ */
+ virtual ~Make()
+ { cleanup(); }
+
+ /**
+ *
+ */
+ virtual std::map<String, Target> &getTargets()
+ { return targets; }
+
+
+ /**
+ *
+ */
+ bool run();
+
+ /**
+ *
+ */
+ bool run(const std::vector<String> &targets);
+
+
+
+private:
+
+ /**
+ *
+ */
+ void init();
+
+ /**
+ *
+ */
+ void cleanup();
+
+ /**
+ *
+ */
+ void assign(const Make &other);
+
+ /**
+ *
+ */
+ bool executeTask(Task &task);
+
+
+ /**
+ *
+ */
+ bool executeTarget(Target &target);
+
+
+ /**
+ *
+ */
+ bool execute();
+
+ /**
+ *
+ */
+ bool checkTargetDependencies(Target &prop,
+ std::set<String> &depList);
+
+ /**
+ *
+ */
+ bool parsePropertyFile(const String &fileName,
+ const String &prefix);
+
+ /**
+ *
+ */
+ bool parseProperty(Element *elem);
+
+ /**
+ *
+ */
+ bool parseTask(Task &task, Element *elem);
+
+ /**
+ *
+ */
+ bool parseFile();
+
+ /**
+ *
+ */
+ std::vector<String> glob(const String &pattern);
+
+
+ //###############
+ //# Fields
+ //###############
+
+ String projectName;
+
+ String defaultTarget;
+
+ String currentTarget;
+
+ String baseDir;
+
+ String description;
+
+ String envAlias;
+
+
+ std::vector<String> specifiedTargets;
+
+ //std::vector<Property> properties;
+
+ std::map<String, Target> targets;
+
+ std::vector<Task *> allTasks;
+
+
+};
+
+
+//########################################################################
+//# C L A S S M A I N T E N A N C E
+//########################################################################
+
+/**
+ *
+ */
+void Make::init()
+{
+ uri = "build.xml";
+ projectName = "";
+ defaultTarget = "";
+ currentTarget = "";
+ baseDir = "";
+ description = "";
+ envAlias = "";
+ specifiedTargets.clear();
+ properties.clear();
+ targets.clear();
+ for (unsigned int i = 0 ; i < allTasks.size() ; i++)
+ delete allTasks[i];
+ allTasks.clear();
+}
+
+
+
+/**
+ *
+ */
+void Make::cleanup()
+{
+ for (unsigned int i = 0 ; i < allTasks.size() ; i++)
+ delete allTasks[i];
+ allTasks.clear();
+}
+
+
+
+/**
+ *
+ */
+void Make::assign(const Make &other)
+{
+ uri = other.uri;
+ projectName = other.projectName;
+ specifiedTargets = other.specifiedTargets;
+ defaultTarget = other.defaultTarget;
+ currentTarget = other.currentTarget;
+ baseDir = other.baseDir;
+ description = other.description;
+ properties = other.properties;
+ targets = other.targets;
+}
+
+
+
+//########################################################################
+//# U T I L I T Y T A S K S
+//########################################################################
+
+/**
+ * Perform a file globbing
+ */
+std::vector<String> Make::glob(const String &pattern)
+{
+ std::vector<String> res;
+ return res;
+}
+
+
+//########################################################################
+//# P U B L I C A P I
+//########################################################################
+
+
+
+/**
+ *
+ */
+bool Make::executeTarget(Target &target)
+{
+
+ String name = target.getName();
+
+ //First get any dependencies for this target
+ std::vector<String> deps = target.getDependencies();
+ for (unsigned int i=0 ; i<deps.size() ; i++)
+ {
+ String dep = deps[i];
+ std::map<String, Target> &tgts =
+ target.getParent().getTargets();
+ std::map<String, Target>::iterator iter =
+ tgts.find(dep);
+ if (iter == tgts.end())
+ {
+ error("Target '%s' dependency '%s' not found",
+ name.c_str(), dep.c_str());
+ return false;
+ }
+ Target depTarget = iter->second;
+ if (!executeTarget(depTarget))
+ {
+ return false;
+ }
+ }
+
+ trace("##### Target : %s", name.c_str());
+
+ //Now let's do the tasks
+ std::vector<Task *> &tasks = target.getTasks();
+ for (unsigned int i=0 ; i<tasks.size() ; i++)
+ {
+ Task *task = tasks[i];
+ trace("----- Task : %s", task->getName().c_str());
+ if (!task->execute())
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+
+
+/**
+ *
+ */
+bool Make::execute()
+{
+ trace("##### EXECUTE");
+ //# First let us list what targets have been requested
+ std::vector<String> tgts = specifiedTargets;
+ if (tgts.size() == 0)
+ {
+ trace("getting default targets");
+ if (defaultTarget.size() == 0)
+ {
+ error("No target specified");
+ return false;
+ }
+ else
+ tgts.push_back(defaultTarget);
+ }
+
+ //# Now let us execute them (probably just 1)
+ for (unsigned int i=0 ; i<tgts.size() ; i++)
+ {
+ currentTarget = tgts[i];
+ std::map<String, Target>::iterator iter = targets.find(currentTarget);
+ if (iter == targets.end())
+ {
+ error("Target '%s' not found", currentTarget.c_str());
+ return false;
+ }
+ Target target = iter->second;
+ if (!executeTarget(target))
+ {
+ return false;
+ }
+ }
+
+ status("Done executing");
+ return true;
+}
+
+
+
+
+/**
+ *
+ */
+bool Make::checkTargetDependencies(Target &target,
+ std::set<String> &depList)
+{
+ String tgtName = target.getName().c_str();
+ depList.insert(tgtName);
+
+ std::vector<String> deps = target.getDependencies();
+ for (unsigned int i=0 ; i<deps.size() ; i++)
+ {
+ String dep = deps[i];
+ std::set<String>::iterator diter = depList.find(dep);
+ if (diter != depList.end())
+ {
+ error("Circular dependency '%s' found at '%s'",
+ dep.c_str(), tgtName.c_str());
+ return false;
+ }
+
+ std::map<String, Target> &tgts =
+ target.getParent().getTargets();
+ std::map<String, Target>::iterator titer = tgts.find(dep);
+ if (titer == tgts.end())
+ {
+ error("Target '%s' dependency '%s' not found",
+ tgtName.c_str(), dep.c_str());
+ return false;
+ }
+ if (!checkTargetDependencies(titer->second, depList))
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+
+
+
+
+static int getword(int pos, const String &inbuf, String &result)
+{
+ int p = pos;
+ int len = (int)inbuf.size();
+ String val;
+ while (p < len)
+ {
+ char ch = inbuf[p];
+ if (!isalnum(ch) && ch!='.' && ch!='_')
+ break;
+ val.push_back(ch);
+ p++;
+ }
+ result = val;
+ return p;
+}
+
+
+
+
+/**
+ *
+ */
+bool Make::parsePropertyFile(const String &fileName,
+ const String &prefix)
+{
+ FILE *f = fopen(fileName.c_str(), "r");
+ if (!f)
+ {
+ error("could not open property file %s", fileName.c_str());
+ return false;
+ }
+ int linenr = 0;
+ while (!feof(f))
+ {
+ char buf[256];
+ if (!fgets(buf, 255, f))
+ break;
+ linenr++;
+ String s = buf;
+ s = trim(s);
+ int len = s.size();
+ if (len == 0)
+ continue;
+ if (s[0] == '#')
+ continue;
+ String key;
+ String val;
+ int p = 0;
+ int p2 = getword(p, s, key);
+ if (p2 <= p)
+ {
+ error("property file %s, line %d: expected keyword",
+ fileName.c_str(), linenr);
+ return false;
+ }
+ if (prefix.size() > 0)
+ {
+ key.insert(0, prefix);
+ }
+
+ //skip whitespace
+ for (p=p2 ; p<len ; p++)
+ if (!isspace(s[p]))
+ break;
+
+ if (p>=len || s[p]!='=')
+ {
+ error("property file %s, line %d: expected '='",
+ fileName.c_str(), linenr);
+ return false;
+ }
+ p++;
+
+ //skip whitespace
+ for ( ; p<len ; p++)
+ if (!isspace(s[p]))
+ break;
+
+ /* This way expects a word after the =
+ p2 = getword(p, s, val);
+ if (p2 <= p)
+ {
+ error("property file %s, line %d: expected value",
+ fileName.c_str(), linenr);
+ return false;
+ }
+ */
+ // This way gets the rest of the line after the =
+ if (p>=len)
+ {
+ error("property file %s, line %d: expected value",
+ fileName.c_str(), linenr);
+ return false;
+ }
+ val = s.substr(p);
+ if (key.size()==0 || val.size()==0)
+ continue;
+
+ //trace("key:'%s' val:'%s'", key.c_str(), val.c_str());
+ properties[key] = val;
+ }
+ fclose(f);
+ return true;
+}
+
+
+
+
+/**
+ *
+ */
+bool Make::parseProperty(Element *elem)
+{
+ std::vector<Attribute> &attrs = elem->getAttributes();
+ for (unsigned int i=0 ; i<attrs.size() ; i++)
+ {
+ String attrName = attrs[i].getName();
+ String attrVal = attrs[i].getValue();
+
+ if (attrName == "name")
+ {
+ String val;
+ if (!getAttribute(elem, "value", val))
+ return false;
+ if (val.size() > 0)
+ {
+ properties[attrVal] = val;
+ continue;
+ }
+ if (!getAttribute(elem, "location", val))
+ return false;
+ if (val.size() > 0)
+ {
+ //TODO: process a path relative to build.xml
+ properties[attrVal] = val;
+ continue;
+ }
+ }
+ else if (attrName == "file")
+ {
+ String prefix;
+ if (!getAttribute(elem, "prefix", prefix))
+ return false;
+ if (prefix.size() > 0)
+ {
+ if (prefix[prefix.size()-1] != '.')
+ prefix.push_back('.');
+ }
+ if (!parsePropertyFile(attrName, prefix))
+ return false;
+ }
+ else if (attrName == "environment")
+ {
+ if (envAlias.size() > 0)
+ {
+ error("environment property can only be set once");
+ return false;
+ }
+ envAlias = attrVal;
+ }
+ }
+
+ return true;
+}
+
+
+
+
+/**
+ *
+ */
+bool Make::parseFile()
+{
+ Parser parser;
+ Element *root = parser.parseFile(uri.getNativePath());
+ if (!root)
+ {
+ error("Could not open %s for reading",
+ uri.getNativePath().c_str());
+ return false;
+ }
+
+ if (root->getChildren().size()==0 ||
+ root->getChildren()[0]->getName()!="project")
+ {
+ error("Main xml element should be <project>");
+ delete root;
+ return false;
+ }
+
+ //########## Project attributes
+ Element *project = root->getChildren()[0];
+ String s = project->getAttribute("name");
+ if (s.size() > 0)
+ projectName = s;
+ s = project->getAttribute("default");
+ if (s.size() > 0)
+ defaultTarget = s;
+ s = project->getAttribute("basedir");
+ if (s.size() > 0)
+ baseDir = s;
+
+ //######### PARSE MEMBERS
+ std::vector<Element *> children = project->getChildren();
+ for (unsigned int i=0 ; i<children.size() ; i++)
+ {
+ Element *elem = children[i];
+ String tagName = elem->getName();
+
+ //########## DESCRIPTION
+ if (tagName == "description")
+ {
+ description = parser.trim(elem->getValue());
+ }
+
+ //######### PROPERTY
+ else if (tagName == "property")
+ {
+ if (!parseProperty(elem))
+ return false;
+ }
+
+ //######### TARGET
+ else if (tagName == "target")
+ {
+ String tname = elem->getAttribute("name");
+ String tdesc = elem->getAttribute("description");
+ String tdeps = elem->getAttribute("depends");
+ String tif = elem->getAttribute("if");
+ String tunless = elem->getAttribute("unless");
+ Target target(*this);
+ target.setName(tname);
+ target.setDescription(tdesc);
+ target.parseDependencies(tdeps);
+ target.setIf(tif);
+ target.setUnless(tunless);
+ std::vector<Element *> telems = elem->getChildren();
+ for (unsigned int i=0 ; i<telems.size() ; i++)
+ {
+ Element *telem = telems[i];
+ Task breeder(*this);
+ Task *task = breeder.createTask(telem);
+ if (!task)
+ return false;
+ allTasks.push_back(task);
+ target.addTask(task);
+ }
+
+ //Check name
+ if (tname.size() == 0)
+ {
+ error("no name for target");
+ return false;
+ }
+ //Check for duplicate name
+ if (targets.find(tname) != targets.end())
+ {
+ error("target '%s' already defined", tname.c_str());
+ return false;
+ }
+ //more work than targets[tname]=target, but avoids default allocator
+ targets.insert(std::make_pair<String, Target>(tname, target));
+ }
+
+ }
+
+ std::map<String, Target>::iterator iter;
+ for (iter = targets.begin() ; iter!= targets.end() ; iter++)
+ {
+ Target tgt = iter->second;
+ std::set<String> depList;
+ if (!checkTargetDependencies(tgt, depList))
+ {
+ return false;
+ }
+ }
+
+
+ delete root;
+ status("Done parsing");
+ return true;
+}
+
+
+/**
+ *
+ */
+bool Make::run()
+{
+ if (!parseFile())
+ return false;
+ if (!execute())
+ return false;
+ return true;
+}
+
+
+/**
+ *
+ */
+bool Make::run(const std::vector<String> &targets)
+{
+ specifiedTargets = targets;
+ if (!run())
+ return false;
+ return true;
+}
+
+
+
+
+
+
+
+}// namespace buildtool
+//########################################################################
+//# M A I N
+//########################################################################
+
+/**
+ * Format an error message in printf() style
+ */
+static void error(char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ fprintf(stderr, "BuildTool error: ");
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ va_end(ap);
+}
+
+
+/**
+ * Compare a buffer with a key, for the length of the key
+ */
+static bool sequ(const buildtool::String &buf, char *key)
+{
+ for (int i=0 ; key[i] ; i++)
+ {
+ if (key[i] != buf[i])
+ return false;
+ }
+ return true;
+}
+
+
+static bool parseOptions(int argc, char **argv)
+{
+ if (argc < 1)
+ {
+ error("Cannot parse arguments");
+ return false;
+ }
+
+ buildtool::String buildFile;
+ std::vector<buildtool::String> targets;
+
+ //char *progName = argv[0];
+ for (int i=1 ; i<argc ; i++)
+ {
+ buildtool::String arg = argv[i];
+ if (sequ(arg, "--"))
+ {
+ if (sequ(arg, "--file=") && arg.size()>7)
+ {
+ buildFile = arg.substr(7, arg.size()-7);
+ }
+ else
+ {
+ error("Unknown option:%s", arg.c_str());
+ return false;
+ }
+ }
+ else if (sequ(arg, "-"))
+ {
+ for (unsigned int p=1 ; p<arg.size() ; p++)
+ {
+ int ch = arg[p];
+ if (0)//put options here
+ {
+ }
+ else
+ {
+ error("Unknown option '%c'", ch);
+ return false;
+ }
+ }
+ }
+ else
+ {
+ buildtool::String target = arg;
+ targets.push_back(target);
+ }
+ }
+
+ //We have the options. Now execute them
+ buildtool::Make make;
+ if (buildFile.size() > 0)
+ {
+ make.setURI(buildFile);
+ }
+ if (!make.run(targets))
+ return false;
+
+ return true;
+}
+
+static bool runMake()
+{
+ buildtool::Make make;
+ if (!make.run())
+ return false;
+ return true;
+}
+
+/*
+static bool pkgConfigTest()
+{
+ buildtool::PkgConfig pkgConfig;
+ if (!pkgConfig.readFile("gtk+-2.0.pc"))
+ return false;
+ return true;
+}
+
+
+
+static bool depTest()
+{
+ buildtool::DepTool deptool;
+ deptool.setSourceDirectory("/dev/ink/inkscape/src");
+ if (!deptool.generateDependencies("build.dep"))
+ return false;
+ std::vector<buildtool::DepRec> res =
+ deptool.loadDepFile("build.dep");
+ if (res.size() == 0)
+ return false;
+ return true;
+}
+
+static bool popenTest()
+{
+ buildtool::Make make;
+ buildtool::String out, err;
+ bool ret = make.executeCommand("gcc xx.cpp", "", out, err);
+ printf("Popen test:%d '%s' '%s'\n", ret, out.c_str(), err.c_str());
+ return true;
+}
+
+
+static bool propFileTest()
+{
+ buildtool::Make make;
+ make.parsePropertyFile("test.prop", "test.");
+ return true;
+}
+*/
+
+int main(int argc, char **argv)
+{
+
+ if (!parseOptions(argc, argv))
+ return 1;
+ /*
+ if (!popenTest())
+ return 1;
+
+ if (!depTest())
+ return 1;
+ if (!propFileTest())
+ return 1;
+ if (runMake())
+ return 1;
+ */
+ return 0;
+}
+
+
+//########################################################################
+//# E N D
+//########################################################################
+
+