/** * 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 #include #include #include #include #include #include #include #include #include #include #ifdef __WIN32__ #include #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 getChildren() { return children; } std::vector findElements(const String &name); String getAttribute(const String &name); std::vector &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&res, const String &name); void writeIndentedRecursive(FILE *f, int indent); Element *parent; std::vectorchildren; std::vector attributes; std::vector 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::iterator iter; for (iter = children.begin(); iter != children.end() ; iter++) { elem->addChild((*iter)->clone()); } return elem; } void Element::findElementsRecursive(std::vector&res, const String &name) { if (getName() == name) { res.push_back(this); } for (unsigned int i=0; ifindElementsRecursive(res, name); } std::vector Element::findElements(const String &name) { std::vector res; findElementsRecursive(res, name); return res; } String Element::getAttribute(const String &name) { for (unsigned int i=0 ; ielems = findElements(tagName); if (elems.size() <1) return ""; String res = elems[0]->getAttribute(attrName); return res; } String Element::getTagValue(const String &tagName) { std::vectorelems = 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\n"); //Between the tags if (value.size() > 0) { for (int i=0;iwriteIndentedRecursive(f, indent+2); //Closing tag for (int i=0; i\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= 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') 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 p) { p = p2; while (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' || 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 ( pvalue ; 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 || peek(p)!='<') return p0; p++; if (peek(p)!='!' || peek(p+1)=='-') return p0; p++; String buf; while (p') { 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') break; else if (ch=='/' && p') { 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 (pp) { p = p2; while (p"); if (p2 > p) { p = p2; break; } p++; } } ch = peek(p); //# END TAG if (ch=='<' && !cdata && peek(p+1)=='/') { break; } //# CDATA p2 = match(p, " 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 . 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; } /** * */ virtual ~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 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 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::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::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 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 &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 &excludes, std::vector &res); /** * Parse a */ bool getPatternSet(Element *elem, MakeBase &propRef, std::vector &includes, std::vector &excludes); /** * Parse a entry, and determine which files * should be included */ bool getFileSet(Element *elem, MakeBase &propRef, String &dir, std::vector &result); /** * Return this object's property list */ virtual std::map &getProperties() { return properties; } /** * Return a named property if found, else a null string */ virtual String getProperty(const String &name) { String val; std::map::iterator iter; iter = properties.find(name); if (iter != properties.end()) val = iter->second; return val; } std::map 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, " "); 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 MakeBase::tokenize(const String &str, const String &delimiters) { std::vector res; char *del = (char *)delimiters.c_str(); String dmp; for (unsigned int i=0 ; i 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 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, using pipes to send data to the * program's stdin, and reading stdout and stderr. */ bool MakeBase::executeCommand(const String &command, const String &inbuf, String &outbuf, String &errbuf) { status("============ cmd ============\n%s\n=============================", command.c_str()); #ifdef __WIN32__ /* I really hate having win32 code in this program, but the read buffer in command.com and cmd.exe are just too small for the large commands we need for compiling and linking. */ 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 (unsigned int i=0 ; i 0) { DWORD bytesRead = 0; char readBuf[1025]; if (avail>1024) avail = 1024; if (!ReadFile(stdoutRead, readBuf, avail, &bytesRead, NULL) || bytesRead==0) { break; } for (unsigned int i=0 ; i &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 &excludes, std::vector &res) { String fullDir = baseDir; if (dirName.size()>0) { fullDir.append("/"); fullDir.append(dirName); } String dirNative = getNativePath(fullDir); std::vector 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::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 entry */ bool MakeBase::getPatternSet(Element *elem, MakeBase &propRef, std::vector &includes, std::vector &excludes ) { std::vector children = elem->getChildren(); for (unsigned int i=0 ; igetName(); 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 entry, and determine which files * should be included */ bool MakeBase::getFileSet(Element *elem, MakeBase &propRef, String &dir, std::vector &result) { String name = elem->getName(); if (name != "fileset") { error("expected "); return false; } std::vector includes; std::vector excludes; //A fileset has one implied patternset if (!getPatternSet(elem, propRef, includes, excludes)) { return false; } //Look for child tags, including more patternsets std::vector children = elem->getChildren(); for (unsigned int i=0 ; igetName(); if (tagName == "patternset") { if (!getPatternSet(child, propRef, includes, excludes)) { return false; } } } //Now do the stuff //Get the base directory for reading file names if (!propRef.getAttribute(elem, "dir", dir)) return false; std::vector fileList; if (dir.size() > 0) { String baseDir = propRef.resolve(dir); if (!listFiles(baseDir, "", excludes, fileList)) return false; } std::vector::iterator iter; for (iter=includes.begin() ; iter!=includes.end() ; iter++) { String fname = *iter; fileList.push_back(fname); } result = fileList; /* for (unsigned int i=0 ; id_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 &getAttributes() { return attrs; } /** * */ virtual std::vector &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 attrs; std::vector 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::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 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 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 &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 loadDepFile(const String &fileName); /** * Load a dependency file, generating one if necessary */ std::vector 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 fileList; /** * */ std::vector 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 allFiles; /** * The list of .o files, and the * dependencies upon them. */ std::map 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::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 && pospath = 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::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::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::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::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, "\n"); fprintf(f, "\n"); fprintf(f, "\n\n", sourceDir.c_str()); std::map::iterator iter; for (iter=depFiles.begin() ; iter!=depFiles.end() ; iter++) { FileRec *frec = iter->second; if (frec->type == FileRec::OFILE) { fprintf(f, "\n", frec->path.c_str(), frec->baseName.c_str(), frec->suffix.c_str()); std::map::iterator citer; for (citer=frec->files.begin() ; citer!=frec->files.end() ; citer++) { String cfname = citer->first; fprintf(f, " \n", cfname.c_str()); } fprintf(f, "\n\n"); } } fprintf(f, "\n"); fprintf(f, "\n"); fprintf(f, "\n"); fclose(f); return true; } /** * This loads the dependency cache. */ std::vector DepTool::loadDepFile(const String &depFile) { std::vector 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 "); delete root; return result; } //########## Start parsing Element *depList = root->getChildren()[0]; std::vector objects = depList->getChildren(); for (unsigned int i=0 ; igetName(); 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 depElems = objectElem->getChildren(); for (unsigned int i=0 ; igetName(); if (tagName == "dep") { String depName = depElem->getAttribute("name"); //trace(" dep:%s", depName.c_str()); depObject.files.push_back(depName); } } //Insert into the result list, in a sorted manner bool inserted = false; std::vector::iterator iter; for (iter = result.begin() ; iter != result.end() ; iter++) { String vpath = iter->path; vpath.append("/"); vpath.append(iter->name); String opath = depObject.path; opath.append("/"); opath.append(depObject.name); if (vpath > opath) { inserted = true; iter = result.insert(iter, depObject); break; } } if (!inserted) result.push_back(depObject); } } delete root; return result; } /** * This loads the dependency cache. */ std::vector DepTool::getDepFile(const String &depFile) { std::vector 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 ; i0) { 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 ; i0) { fname.append(fileSetDir); fname.append("/"); } fname.append(fileSet[i]); String fullName = parent.resolve(fname); cmd.append(" "); cmd.append(fullName); } 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 children = elem->getChildren(); for (unsigned int i=0 ; igetName(); if (tagName == "fileset") { if (!getFileSet(child, parent, fileSetDir, fileSet)) return false; } } return true; } private: String command; String fileName; String fileSetDir; std::vector 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 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 paths; std::vector::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::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 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 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 children = elem->getChildren(); for (unsigned int i=0 ; igetName(); 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 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) { status(" : %s", fileName.c_str()); String fullSource = parent.resolve(fileName); String fullDest = parent.resolve(toFileName); //trace("copy %s to file %s", fullSource.c_str(), // fullDest.c_str()); if (!isNewerThan(fullSource, fullDest)) { return true; } if (!copyFile(fullSource, fullDest)) return false; status(" : 1 file copied"); } return true; } case CP_TODIR: { if (haveFileSet) { int nrFiles = 0; status(" : %s", fileSetDir.c_str()); for (unsigned int i=0 ; i0) { sourcePath.append(fileSetDir); sourcePath.append("/"); } sourcePath.append(fileName); String fullSource = parent.resolve(sourcePath); //Get the immediate parent directory's base name String baseFileSetDir = fileSetDir; unsigned int pos = baseFileSetDir.find_last_of('/'); if (pos!=baseFileSetDir.npos && 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; nrFiles++; } status(" : %d file(s) copied", nrFiles); } else //file source { //For file->dir we want only the basename of //the source appended to the dest dir status(" : %s", fileName.c_str()); String baseName = fileName; unsigned int pos = baseName.find_last_of('/'); if (pos!=baseName.npos && pos0) { 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; status(" : 1 file copied"); } 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 children = elem->getChildren(); for (unsigned int i=0 ; igetName(); 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(" can only have one of : file= and "); return false; } if (toFileName.size()>0 && toDirName.size()>0) { error(" can only have one of : tofile= or todir="); return false; } if (haveFileSet && toDirName.size()==0) { error("a task with a must have : todir="); return false; } if (cptype == CP_TOFILE && fileName.size()==0) { error(" tofile= must be associated with : file="); return false; } if (cptype == CP_TODIR && fileName.size()==0 && !haveFileSet) { error(" todir= must be associated with : file= or "); return false; } return true; } private: int cptype; String fileName; String fileSetDir; std::vector 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: { status(" : %s", fileName.c_str()); String fullName = parent.resolve(fileName); char *fname = (char *)fullName.c_str(); //does not exist if (stat(fname, &finfo)<0) return true; //exists but is not a regular file if (!S_ISREG(finfo.st_mode)) { error(" failed. '%s' exists and is not a regular file", fname); return false; } if (remove(fname)<0) { error(" failed: %s", strerror(errno)); return false; } return true; } case DEL_DIR: { status(" : %s", dirName.c_str()); String fullDir = parent.resolve(dirName); 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(" 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 ; i0) { 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); if (!doit) { //trace("link not needed"); return true; } //trace("LINK cmd:%s", cmd.c_str()); 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 children = elem->getChildren(); for (unsigned int i=0 ; igetName(); 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 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() { status(" : %s", dirName.c_str()); 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(" 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()); for (unsigned int i=0 ; i0) { 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(" problem: %s", errString.c_str()); return false; } } return true; } virtual bool parse(Element *elem) { if (!parent.getAttribute(elem, "todir", toDirName)) return false; std::vector children = elem->getChildren(); for (unsigned int i=0 ; igetName(); if (tagName == "fileset") { if (!getFileSet(child, parent, fileSetDir, fileSet)) return false; } } return true; } private: String command; String toDirName; String fileSetDir; std::vector 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); 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(" 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); 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 children = elem->getChildren(); for (unsigned int i=0 ; igetName(); 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); 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(" 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 &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 &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 deps; std::vector 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 &getTargets() { return targets; } /** * */ bool run(); /** * */ bool run(const String &target); private: /** * */ void init(); /** * */ void cleanup(); /** * */ void assign(const Make &other); /** * */ bool executeTask(Task &task); /** * */ bool executeTarget(Target &target, std::set &targetsCompleted); /** * */ bool execute(); /** * */ bool checkTargetDependencies(Target &prop, std::vector &depList); /** * */ bool parsePropertyFile(const String &fileName, const String &prefix); /** * */ bool parseProperty(Element *elem); /** * */ bool parseTask(Task &task, Element *elem); /** * */ bool parseFile(); /** * */ std::vector glob(const String &pattern); //############### //# Fields //############### String projectName; String currentTarget; String defaultTarget; String specifiedTarget; String baseDir; String description; String envAlias; //std::vector properties; std::map targets; std::vector allTasks; }; //######################################################################## //# C L A S S M A I N T E N A N C E //######################################################################## /** * */ void Make::init() { uri = "build.xml"; projectName = ""; currentTarget = ""; defaultTarget = ""; specifiedTarget = ""; baseDir = ""; description = ""; envAlias = ""; properties.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; currentTarget = other.currentTarget; defaultTarget = other.defaultTarget; specifiedTarget = other.specifiedTarget; baseDir = other.baseDir; description = other.description; properties = other.properties; } //######################################################################## //# U T I L I T Y T A S K S //######################################################################## /** * Perform a file globbing */ std::vector Make::glob(const String &pattern) { std::vector res; return res; } //######################################################################## //# P U B L I C A P I //######################################################################## /** * */ bool Make::executeTarget(Target &target, std::set &targetsCompleted) { String name = target.getName(); //First get any dependencies for this target std::vector deps = target.getDependencies(); for (unsigned int i=0 ; i &tgts = target.getParent().getTargets(); std::map::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, targetsCompleted)) { return false; } } status("## Target : %s", name.c_str()); //Now let's do the tasks std::vector &tasks = target.getTasks(); for (unsigned int i=0 ; igetName().c_str()); if (!task->execute()) { return false; } } targetsCompleted.insert(name); return true; } /** * Main execute() method. Start here and work * up the dependency tree */ bool Make::execute() { status("######## EXECUTE"); //Determine initial target if (specifiedTarget.size()>0) { currentTarget = specifiedTarget; } else if (defaultTarget.size()>0) { currentTarget = defaultTarget; } else { error("execute: no specified or default target requested"); return false; } std::map::iterator iter = targets.find(currentTarget); if (iter == targets.end()) { error("Initial target '%s' not found", currentTarget.c_str()); return false; } //Now run Target target = iter->second; std::set targetsCompleted; if (!executeTarget(target, targetsCompleted)) { return false; } status("######## EXECUTE COMPLETE"); return true; } /** * */ bool Make::checkTargetDependencies(Target &target, std::vector &depList) { String tgtName = target.getName().c_str(); depList.push_back(tgtName); std::vector deps = target.getDependencies(); for (unsigned int i=0 ; i::iterator diter; for (diter=depList.begin() ; diter!=depList.end() ; diter++) { error(" %s", diter->c_str()); } return false; } std::map &tgts = target.getParent().getTargets(); std::map::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 || s[p]!='=') { error("property file %s, line %d: expected '='", fileName.c_str(), linenr); return false; } p++; //skip whitespace for ( ; 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 &attrs = elem->getAttributes(); for (unsigned int i=0 ; i 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() { status("######## PARSE"); 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 "); 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 children = project->getChildren(); for (unsigned int i=0 ; igetName(); //########## 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 telems = elem->getChildren(); for (unsigned int i=0 ; i(tname, target)); } } std::map::iterator iter; for (iter = targets.begin() ; iter!= targets.end() ; iter++) { Target tgt = iter->second; std::vector depList; if (!checkTargetDependencies(tgt, depList)) { return false; } } delete root; status("######## PARSE COMPLETE"); return true; } /** * */ bool Make::run() { if (!parseFile()) return false; if (!execute()) return false; return true; } /** * */ bool Make::run(const String &target) { status("##################################"); status("# BuildTool"); status("# version 0.2"); status("##################################"); specifiedTarget = target; if (!run()) return false; status("##################################"); status("# BuildTool Completed"); status("##################################"); 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; } /** * Parse the command-line args, get our options, * and run this thing */ static bool parseOptions(int argc, char **argv) { if (argc < 1) { error("Cannot parse arguments"); return false; } buildtool::String buildFile; buildtool::String target; //char *progName = argv[0]; for (int i=1 ; i 0) { make.setURI(buildFile); } if (!make.run(target)) 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 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 //########################################################################