/**
* Simple build automation tool.
*
* Authors:
* Bob Jamison
* Jasper van de Gronde
* Johan Engelen
*
* Copyright (C) 2006-2008 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
*/
/**
* To use this file, compile with:
* <pre>
* g++ -O3 buildtool.cpp -o btool.exe -fopenmp
* (or whatever your compiler might be)
* Then
* btool
* or
* btool {target}
*
* Note: if you are using MinGW, and a not very recent version of it,
* gettimeofday() might be missing. If so, just build this file with
* this command:
* g++ -O3 -DNEED_GETTIMEOFDAY buildtool.cpp -o btool.exe -fopenmp
*
*/
#define BUILDTOOL_VERSION "BuildTool v0.9.9multi"
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdarg.h>
#include <sys/stat.h>
#include <time.h>
#include <sys/time.h>
#include <utime.h>
#include <dirent.h>
#include <iostream>
#include <list>
#include <string>
#include <map>
#include <set>
#include <vector>
#include <algorithm>
#ifdef __WIN32__
#define WIN32_LEAN_AND_MEAN
#define NOGDI
#include <windows.h>
#endif
#include <errno.h>
//########################################################################
//# Definition of gettimeofday() for those who don't have it
//########################################################################
#ifdef NEED_GETTIMEOFDAY
#include <sys/timeb.h>
struct timezone {
int tz_minuteswest; /* minutes west of Greenwich */
int tz_dsttime; /* type of dst correction */
};
static int gettimeofday (struct timeval *tv, struct timezone *tz)
{
struct _timeb tb;
if (!tv)
return (-1);
_ftime (&tb);
tv->tv_sec = tb.time;
tv->tv_usec = tb.millitm * 1000 + 500;
if (tz)
{
tz->tz_minuteswest = -60 * _timezone;
tz->tz_dsttime = _daylight;
}
return 0;
}
#endif
namespace buildtool
{
//########################################################################
//########################################################################
//## R E G E X P
//########################################################################
//########################################################################
/**
* This is the SLRE (Super Light Regular Expression library)
* SLRE is an ISO C library that implements a subset of Perl
* regular expression syntax.
*
* See https://github.com/cesanta/slre for details
*
* It's clean code and small size allow us to
* embed it in BuildTool without adding a dependency
*
*/
//begin slre.h
/*
* Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com>
* Copyright (c) 2013 Cesanta Software Limited
* All rights reserved
*
* This library is dual-licensed: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation. For the terms of this
* license, see <http://www.gnu.org/licenses/>.
*
* You are free to use this library under the terms of the GNU General
* Public License, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* Alternatively, you can license this library under a commercial
* license, as set out in <http://cesanta.com/products.html>.
*/
/*
* This is a regular expression library that implements a subset of Perl RE.
* Please refer to README.md for a detailed reference.
*/
#ifndef SLRE_HEADER_DEFINED
#define SLRE_HEADER_DEFINED
#ifdef __cplusplus
extern "C" {
#endif
struct slre_cap {
const char *ptr;
int len;
};
int slre_match(const char *regexp, const char *buf, int buf_len,
struct slre_cap *caps, int num_caps, int flags);
/* Possible flags for slre_match() */
enum { SLRE_IGNORE_CASE = 1 };
/* slre_match() failure codes */
#define SLRE_NO_MATCH -1
#define SLRE_UNEXPECTED_QUANTIFIER -2
#define SLRE_UNBALANCED_BRACKETS -3
#define SLRE_INTERNAL_ERROR -4
#define SLRE_INVALID_CHARACTER_SET -5
#define SLRE_INVALID_METACHARACTER -6
#define SLRE_CAPS_ARRAY_TOO_SMALL -7
#define SLRE_TOO_MANY_BRANCHES -8
#define SLRE_TOO_MANY_BRACKETS -9
#ifdef __cplusplus
}
#endif
#endif /* SLRE_HEADER_DEFINED */
//end slre.h
//start slre.c
/*
* Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com>
* Copyright (c) 2013 Cesanta Software Limited
* All rights reserved
*
* This library is dual-licensed: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation. For the terms of this
* license, see <http://www.gnu.org/licenses/>.
*
* You are free to use this library under the terms of the GNU General
* Public License, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* Alternatively, you can license this library under a commercial
* license, as set out in <http://cesanta.com/products.html>.
*/
#include <stdio.h>
#include <ctype.h>
#include <string.h>
//#include "slre.h"
#define MAX_BRANCHES 100
#define MAX_BRACKETS 100
#define FAIL_IF(condition, error_code) if (condition) return (error_code)
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(ar) (sizeof(ar) / sizeof((ar)[0]))
#endif
#ifdef SLRE_DEBUG
#define DBG(x) printf x
#else
#define DBG(x)
#endif
struct bracket_pair {
const char *ptr; /* Points to the first char after '(' in regex */
int len; /* Length of the text between '(' and ')' */
int branches; /* Index in the branches array for this pair */
int num_branches; /* Number of '|' in this bracket pair */
};
struct branch {
int bracket_index; /* index for 'struct bracket_pair brackets' */
/* array defined below */
const char *schlong; /* points to the '|' character in the regex */
};
struct regex_info {
/*
* Describes all bracket pairs in the regular expression.
* First entry is always present, and grabs the whole regex.
*/
struct bracket_pair brackets[MAX_BRACKETS];
int num_brackets;
/*
* Describes alternations ('|' operators) in the regular expression.
* Each branch falls into a specific branch pair.
*/
struct branch branches[MAX_BRANCHES];
int num_branches;
/* Array of captures provided by the user */
struct slre_cap *caps;
int num_caps;
/* E.g. SLRE_IGNORE_CASE. See enum below */
int flags;
};
static int is_metacharacter(const unsigned char *s) {
static const char *metacharacters = "^$().[]*+?|\\Ssdbfnrtv";
return strchr(metacharacters, *s) != NULL;
}
static int op_len(const char *re) {
return re[0] == '\\' && re[1] == 'x' ? 4 : re[0] == '\\' ? 2 : 1;
}
static int set_len(const char *re, int re_len) {
int len = 0;
while (len < re_len && re[len] != ']') {
len += op_len(re + len);
}
return len <= re_len ? len + 1 : -1;
}
static int get_op_len(const char *re, int re_len) {
return re[0] == '[' ? set_len(re + 1, re_len - 1) + 1 : op_len(re);
}
static int is_quantifier(const char *re) {
return re[0] == '*' || re[0] == '+' || re[0] == '?';
}
static int toi(int x) {
return isdigit(x) ? x - '0' : x - 'W';
}
static int hextoi(const unsigned char *s) {
return (toi(tolower(s[0])) << 4) | toi(tolower(s[1]));
}
static int match_op(const unsigned char *re, const unsigned char *s,
struct regex_info *info) {
int result = 0;
switch (*re) {
case '\\':
/* Metacharacters */
switch (re[1]) {
case 'S': FAIL_IF(isspace(*s), SLRE_NO_MATCH); result++; break;
case 's': FAIL_IF(!isspace(*s), SLRE_NO_MATCH); result++; break;
case 'd': FAIL_IF(!isdigit(*s), SLRE_NO_MATCH); result++; break;
case 'b': FAIL_IF(*s != '\b', SLRE_NO_MATCH); result++; break;
case 'f': FAIL_IF(*s != '\f', SLRE_NO_MATCH); result++; break;
case 'n': FAIL_IF(*s != '\n', SLRE_NO_MATCH); result++; break;
case 'r': FAIL_IF(*s != '\r', SLRE_NO_MATCH); result++; break;
case 't': FAIL_IF(*s != '\t', SLRE_NO_MATCH); result++; break;
case 'v': FAIL_IF(*s != '\v', SLRE_NO_MATCH); result++; break;
case 'x':
/* Match byte, \xHH where HH is hexadecimal byte representaion */
FAIL_IF(hextoi(re + 2) != *s, SLRE_NO_MATCH);
result++;
break;
default:
/* Valid metacharacter check is done in bar() */
FAIL_IF(re[1] != s[0], SLRE_NO_MATCH);
result++;
break;
}
break;
case '|': FAIL_IF(1, SLRE_INTERNAL_ERROR); break;
case '$': FAIL_IF(1, SLRE_NO_MATCH); break;
case '.': result++; break;
default:
if (info->flags & SLRE_IGNORE_CASE) {
FAIL_IF(tolower(*re) != tolower(*s), SLRE_NO_MATCH);
} else {
FAIL_IF(*re != *s, SLRE_NO_MATCH);
}
result++;
break;
}
return result;
}
static int match_set(const char *re, int re_len, const char *s,
struct regex_info *info) {
int len = 0, result = -1, invert = re[0] == '^';
if (invert) re++, re_len--;
while (len <= re_len && re[len] != ']' && result <= 0) {
/* Support character range */
if (re[len] != '-' && re[len + 1] == '-' && re[len + 2] != ']' &&
re[len + 2] != '\0') {
result = info->flags & SLRE_IGNORE_CASE ?
tolower(*s) >= tolower(re[len]) && tolower(*s) <= tolower(re[len + 2]) :
*s >= re[len] && *s <= re[len + 2];
len += 3;
} else {
result = match_op((unsigned char *) re + len, (unsigned char *) s, info);
len += op_len(re + len);
}
}
return (!invert && result > 0) || (invert && result <= 0) ? 1 : -1;
}
static int doh(const char *s, int s_len, struct regex_info *info, int bi);
static int bar(const char *re, int re_len, const char *s, int s_len,
struct regex_info *info, int bi) {
/* i is offset in re, j is offset in s, bi is brackets index */
int i, j, n, step;
for (i = j = 0; i < re_len && j <= s_len; i += step) {
/* Handle quantifiers. Get the length of the chunk. */
step = re[i] == '(' ? info->brackets[bi + 1].len + 2 :
get_op_len(re + i, re_len - i);
DBG(("%s [%.*s] [%.*s] re_len=%d step=%d i=%d j=%d\n", __func__,
re_len - i, re + i, s_len - j, s + j, re_len, step, i, j));
FAIL_IF(is_quantifier(&re[i]), SLRE_UNEXPECTED_QUANTIFIER);
FAIL_IF(step <= 0, SLRE_INVALID_CHARACTER_SET);
if (i + step < re_len && is_quantifier(re + i + step)) {
DBG(("QUANTIFIER: [%.*s]%c [%.*s]\n", step, re + i,
re[i + step], s_len - j, s + j));
if (re[i + step] == '?') {
int result = bar(re + i, step, s + j, s_len - j, info, bi);
j += result > 0 ? result : 0;
i++;
} else if (re[i + step] == '+' || re[i + step] == '*') {
int j2 = j, nj = j, n1, n2 = -1, ni, non_greedy = 0;
/* Points to the regexp code after the quantifier */
ni = i + step + 1;
if (ni < re_len && re[ni] == '?') {
non_greedy = 1;
ni++;
}
do {
if ((n1 = bar(re + i, step, s + j2, s_len - j2, info, bi)) > 0) {
j2 += n1;
}
if (re[i + step] == '+' && n1 < 0) break;
if (ni >= re_len) {
/* After quantifier, there is nothing */
nj = j2;
} else if ((n2 = bar(re + ni, re_len - ni, s + j2,
s_len - j2, info, bi)) >= 0) {
/* Regex after quantifier matched */
nj = j2 + n2;
}
if (nj > j && non_greedy) break;
} while (n1 > 0);
/*
* Even if we found one or more pattern, this branch will be executed,
* changing the next captures.
*/
if (n1 < 0 && n2 < 0 && re[i + step] == '*' &&
(n2 = bar(re + ni, re_len - ni, s + j, s_len - j, info, bi)) > 0) {
nj = j + n2;
}
DBG(("STAR/PLUS END: %d %d %d %d %d\n", j, nj, re_len - ni, n1, n2));
FAIL_IF(re[i + step] == '+' && nj == j, SLRE_NO_MATCH);
/* If while loop body above was not executed for the * quantifier, */
/* make sure the rest of the regex matches */
FAIL_IF(nj == j && ni < re_len && n2 < 0, SLRE_NO_MATCH);
/* Returning here cause we've matched the rest of RE already */
return nj;
}
continue;
}
if (re[i] == '[') {
n = match_set(re + i + 1, re_len - (i + 2), s + j, info);
DBG(("SET %.*s [%.*s] -> %d\n", step, re + i, s_len - j, s + j, n));
FAIL_IF(n <= 0, SLRE_NO_MATCH);
j += n;
} else if (re[i] == '(') {
n = SLRE_NO_MATCH;
bi++;
FAIL_IF(bi >= info->num_brackets, SLRE_INTERNAL_ERROR);
DBG(("CAPTURING [%.*s] [%.*s] [%s]\n",
step, re + i, s_len - j, s + j, re + i + step));
if (re_len - (i + step) <= 0) {
/* Nothing follows brackets */
n = doh(s + j, s_len - j, info, bi);
} else {
int j2;
for (j2 = 0; j2 <= s_len - j; j2++) {
if ((n = doh(s + j, s_len - (j + j2), info, bi)) >= 0 &&
bar(re + i + step, re_len - (i + step),
s + j + n, s_len - (j + n), info, bi) >= 0) break;
}
}
DBG(("CAPTURED [%.*s] [%.*s]:%d\n", step, re + i, s_len - j, s + j, n));
FAIL_IF(n < 0, n);
if (info->caps != NULL && n > 0) {
info->caps[bi - 1].ptr = s + j;
info->caps[bi - 1].len = n;
}
j += n;
} else if (re[i] == '^') {
FAIL_IF(j != 0, SLRE_NO_MATCH);
} else if (re[i] == '$') {
FAIL_IF(j != s_len, SLRE_NO_MATCH);
} else {
FAIL_IF(j >= s_len, SLRE_NO_MATCH);
n = match_op((unsigned char *) (re + i), (unsigned char *) (s + j), info);
FAIL_IF(n <= 0, n);
j += n;
}
}
return j;
}
/* Process branch points */
static int doh(const char *s, int s_len, struct regex_info *info, int bi) {
const struct bracket_pair *b = &info->brackets[bi];
int i = 0, len, result;
const char *p;
do {
p = i == 0 ? b->ptr : info->branches[b->branches + i - 1].schlong + 1;
len = b->num_branches == 0 ? b->len :
i == b->num_branches ? (int) (b->ptr + b->len - p) :
(int) (info->branches[b->branches + i].schlong - p);
DBG(("%s %d %d [%.*s] [%.*s]\n", __func__, bi, i, len, p, s_len, s));
result = bar(p, len, s, s_len, info, bi);
DBG(("%s <- %d\n", __func__, result));
} while (result <= 0 && i++ < b->num_branches); /* At least 1 iteration */
return result;
}
static int baz(const char *s, int s_len, struct regex_info *info) {
int i, result = -1, is_anchored = info->brackets[0].ptr[0] == '^';
for (i = 0; i <= s_len; i++) {
result = doh(s + i, s_len - i, info, 0);
if (result >= 0) {
result += i;
break;
}
if (is_anchored) break;
}
return result;
}
static void setup_branch_points(struct regex_info *info) {
int i, j;
struct branch tmp;
/* First, sort branches. Must be stable, no qsort. Use bubble algo. */
for (i = 0; i < info->num_branches; i++) {
for (j = i + 1; j < info->num_branches; j++) {
if (info->branches[i].bracket_index > info->branches[j].bracket_index) {
tmp = info->branches[i];
info->branches[i] = info->branches[j];
info->branches[j] = tmp;
}
}
}
/*
* For each bracket, set their branch points. This way, for every bracket
* (i.e. every chunk of regex) we know all branch points before matching.
*/
for (i = j = 0; i < info->num_brackets; i++) {
info->brackets[i].num_branches = 0;
info->brackets[i].branches = j;
while (j < info->num_branches && info->branches[j].bracket_index == i) {
info->brackets[i].num_branches++;
j++;
}
}
}
static int foo(const char *re, int re_len, const char *s, int s_len,
struct regex_info *info) {
int i, step, depth = 0;
/* First bracket captures everything */
info->brackets[0].ptr = re;
info->brackets[0].len = re_len;
info->num_brackets = 1;
/* Make a single pass over regex string, memorize brackets and branches */
for (i = 0; i < re_len; i += step) {
step = get_op_len(re + i, re_len - i);
if (re[i] == '|') {
FAIL_IF(info->num_branches >= (int) ARRAY_SIZE(info->branches),
SLRE_TOO_MANY_BRANCHES);
info->branches[info->num_branches].bracket_index =
info->brackets[info->num_brackets - 1].len == -1 ?
info->num_brackets - 1 : depth;
info->branches[info->num_branches].schlong = &re[i];
info->num_branches++;
} else if (re[i] == '\\') {
FAIL_IF(i >= re_len - 1, SLRE_INVALID_METACHARACTER);
if (re[i + 1] == 'x') {
/* Hex digit specification must follow */
FAIL_IF(re[i + 1] == 'x' && i >= re_len - 3,
SLRE_INVALID_METACHARACTER);
FAIL_IF(re[i + 1] == 'x' && !(isxdigit(re[i + 2]) &&
isxdigit(re[i + 3])), SLRE_INVALID_METACHARACTER);
} else {
FAIL_IF(!is_metacharacter((unsigned char *) re + i + 1),
SLRE_INVALID_METACHARACTER);
}
} else if (re[i] == '(') {
FAIL_IF(info->num_brackets >= (int) ARRAY_SIZE(info->brackets),
SLRE_TOO_MANY_BRACKETS);
depth++; /* Order is important here. Depth increments first. */
info->brackets[info->num_brackets].ptr = re + i + 1;
info->brackets[info->num_brackets].len = -1;
info->num_brackets++;
FAIL_IF(info->num_caps > 0 && info->num_brackets - 1 > info->num_caps,
SLRE_CAPS_ARRAY_TOO_SMALL);
} else if (re[i] == ')') {
int ind = info->brackets[info->num_brackets - 1].len == -1 ?
info->num_brackets - 1 : depth;
info->brackets[ind].len = (int) (&re[i] - info->brackets[ind].ptr);
DBG(("SETTING BRACKET %d [%.*s]\n",
ind, info->brackets[ind].len, info->brackets[ind].ptr));
depth--;
FAIL_IF(depth < 0, SLRE_UNBALANCED_BRACKETS);
FAIL_IF(i > 0 && re[i - 1] == '(', SLRE_NO_MATCH);
}
}
FAIL_IF(depth != 0, SLRE_UNBALANCED_BRACKETS);
setup_branch_points(info);
return baz(s, s_len, info);
}
int slre_match(const char *regexp, const char *s, int s_len,
struct slre_cap *caps, int num_caps, int flags) {
struct regex_info info;
/* Initialize info structure */
info.flags = flags;
info.num_brackets = info.num_branches = 0;
info.num_caps = num_caps;
info.caps = caps;
DBG(("========================> [%s] [%.*s]\n", regexp, s_len, s));
return foo(regexp, (int) strlen(regexp), s, s_len, &info);
}
//end slre.c
//########################################################################
//########################################################################
//## E N D R E G E X P
//########################################################################
//########################################################################
//########################################################################
//########################################################################
//## 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()
{
init();
}
Element(const String &nameArg)
{
init();
name = nameArg;
}
Element(const String &nameArg, const String &valueArg)
{
init();
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 (std::size_t i=0 ; i<children.size() ; i++)
delete children[i];
}
virtual String getName()
{ return name; }
virtual String getValue()
{ return value; }
Element *getParent()
{ return parent; }
std::vector<Element *> getChildren()
{ return children; }
std::vector<Element *> findElements(const String &name);
String getAttribute(const String &name);
std::vector<Attribute> &getAttributes()
{ return attributes; }
String getTagAttribute(const String &tagName, const String &attrName);
String getTagValue(const String &tagName);
void addChild(Element *child);
void addAttribute(const String &name, const String &value);
void addNamespace(const String &prefix, const String &namespaceURI);
/**
* Prettyprint an XML tree to an output stream. Elements are indented
* according to element hierarchy.
* @param f a stream to receive the output
* @param elem the element to output
*/
void writeIndented(FILE *f);
/**
* Prettyprint an XML tree to standard output. This is the equivalent of
* writeIndented(stdout).
* @param elem the element to output
*/
void print();
int getLine()
{ return line; }
protected:
void init()
{
parent = NULL;
line = 0;
}
void assign(const Element &other)
{
parent = other.parent;
children = other.children;
attributes = other.attributes;
namespaces = other.namespaces;
name = other.name;
value = other.value;
line = other.line;
}
void findElementsRecursive(std::vector<Element *>&res, const String &name);
void writeIndentedRecursive(FILE *f, int indent);
Element *parent;
std::vector<Element *>children;
std::vector<Attribute> attributes;
std::vector<Namespace> namespaces;
String name;
String value;
int line;
};
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;
}
int countLines(int begin, int end);
void getLineAndColumn(int pos, int *lineNr, int *colNr);
void error(const char *fmt, ...);
int peek(int pos);
int match(int pos, const char *text);
int skipwhite(int 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;
int parselen;
XMLCh *parsebuf;
String cdatabuf;
int currentPosition;
};
//########################################################################
//# 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;
elem->line = line;
std::vector<Element *>::iterator iter;
for (iter = children.begin(); iter != children.end() ; iter++)
{
elem->addChild((*iter)->clone());
}
return elem;
}
void Element::findElementsRecursive(std::vector<Element *>&res, const String &name)
{
if (getName() == name)
{
res.push_back(this);
}
for (std::size_t i=0; i<children.size() ; i++)
children[i]->findElementsRecursive(res, name);
}
std::vector<Element *> Element::findElements(const String &name)
{
std::vector<Element *> res;
findElementsRecursive(res, name);
return res;
}
String Element::getAttribute(const String &name)
{
for (std::size_t i=0 ; i<attributes.size() ; i++)
if (attributes[i].getName() ==name)
return attributes[i].getValue();
return "";
}
String Element::getTagAttribute(const String &tagName, const String &attrName)
{
std::vector<Element *>elems = findElements(tagName);
if (elems.size() <1)
return "";
String res = elems[0]->getAttribute(attrName);
return res;
}
String Element::getTagValue(const String &tagName)
{
std::vector<Element *>elems = findElements(tagName);
if (elems.size() <1)
return "";
String res = elems[0]->getValue();
return res;
}
void Element::addChild(Element *child)
{
if (!child)
return;
child->parent = this;
children.push_back(child);
}
void Element::addAttribute(const String &name, const String &value)
{
Attribute attr(name, value);
attributes.push_back(attr);
}
void Element::addNamespace(const String &prefix, const String &namespaceURI)
{
Namespace ns(prefix, namespaceURI);
namespaces.push_back(ns);
}
void Element::writeIndentedRecursive(FILE *f, int indent)
{
int i;
if (!f)
return;
//Opening tag, and attributes
for (i=0;i<indent;i++)
fputc(' ',f);
fprintf(f,"<%s",name.c_str());
for (std::size_t i=0 ; i<attributes.size() ; i++)
{
fprintf(f," %s=\"%s\"",
attributes[i].getName().c_str(),
attributes[i].getValue().c_str());
}
for (std::size_t i=0 ; i<namespaces.size() ; i++)
{
fprintf(f," xmlns:%s=\"%s\"",
namespaces[i].getPrefix().c_str(),
namespaces[i].getNamespaceURI().c_str());
}
fprintf(f,">\n");
//Between the tags
if (value.size() > 0)
{
for (int i=0;i<indent;i++)
fputc(' ', f);
fprintf(f," %s\n", value.c_str());
}
for (std::size_t i=0 ; i<children.size() ; i++)
children[i]->writeIndentedRecursive(f, indent+2);
//Closing tag
for (int i=0; i<indent; i++)
fputc(' ',f);
fprintf(f,"</%s>\n", name.c_str());
}
void Element::writeIndented(FILE *f)
{
writeIndentedRecursive(f, 0);
}
void Element::print()
{
writeIndented(stdout);
}
//########################################################################
//# P A R S E R
//########################################################################
typedef struct
{
const char *escaped;
char value;
} EntityEntry;
static EntityEntry entities[] =
{
{ "&amp;" , '&' },
{ "&lt;" , '<' },
{ "&gt;" , '>' },
{ "&apos;", '\'' },
{ "&quot;", '"' },
{ 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
std::size_t begin = 0;
for ( ; begin < s.size() ; begin++)
{
if (!isspace(s[begin]))
break;
}
//Find first non-ws char, going in reverse
std::size_t 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;
}
int Parser::countLines(int begin, int end)
{
int count = 0;
for (int i=begin ; i<end ; i++)
{
XMLCh ch = parsebuf[i];
if (ch == '\n' || ch == '\r')
count++;
}
return count;
}
void Parser::getLineAndColumn(int pos, int *lineNr, int *colNr)
{
int line = 1;
int col = 1;
for (long i=0 ; i<pos ; i++)
{
XMLCh ch = parsebuf[i];
if (ch == '\n' || ch == '\r')
{
col = 0;
line ++;
}
else
col++;
}
*lineNr = line;
*colNr = col;
}
void Parser::error(const char *fmt, ...)
{
int lineNr;
int colNr;
getLineAndColumn(currentPosition, &lineNr, &colNr);
va_list args;
fprintf(stderr, "xml error at line %d, column %d:", lineNr, colNr);
va_start(args,fmt);
vfprintf(stderr,fmt,args);
va_end(args) ;
fprintf(stderr, "\n");
}
int Parser::peek(int pos)
{
if (pos >= parselen)
return -1;
currentPosition = pos;
int ch = parsebuf[pos];
//printf("ch:%c\n", ch);
return ch;
}
String Parser::encode(const String &str)
{
String ret;
for (std::size_t i=0 ; i<str.size() ; i++)
{
XMLCh ch = (XMLCh)str[i];
if (ch == '&')
ret.append("&amp;");
else if (ch == '<')
ret.append("&lt;");
else if (ch == '>')
ret.append("&gt;");
else if (ch == '\'')
ret.append("&apos;");
else if (ch == '"')
ret.append("&quot;");
else
ret.push_back(ch);
}
return ret;
}
int Parser::match(int p0, const char *text)
{
int p = p0;
while (*text)
{
if (peek(p) != *text)
return p0;
p++; text++;
}
return p;
}
int Parser::skipwhite(int p)
{
while (p<parselen)
{
int p2 = match(p, "<!--");
if (p2 > p)
{
p = p2;
while (p<parselen)
{
p2 = match(p, "-->");
if (p2 > p)
{
p = p2;
break;
}
p++;
}
}
XMLCh b = peek(p);
if (!isspace(b))
break;
p++;
}
return p;
}
/* modify this to allow all chars for an element or attribute name*/
int Parser::getWord(int p0, String &buf)
{
int p = p0;
while (p<parselen)
{
XMLCh b = peek(p);
if (b<=' ' || b=='/' || b=='>' || b=='=')
break;
buf.push_back(b);
p++;
}
return p;
}
int Parser::getQuoted(int p0, String &buf, int do_i_parse)
{
int p = p0;
if (peek(p) != '"' && peek(p) != '\'')
return p0;
p++;
while ( p<parselen )
{
XMLCh b = peek(p);
if (b=='"' || b=='\'')
break;
if (b=='&' && do_i_parse)
{
bool found = false;
for (EntityEntry *ee = entities ; ee->value ; ee++)
{
int p2 = match(p, ee->escaped);
if (p2>p)
{
buf.push_back(ee->value);
p = p2;
found = true;
break;
}
}
if (!found)
{
error("unterminated entity");
return false;
}
}
else
{
buf.push_back(b);
p++;
}
}
return p;
}
int Parser::parseVersion(int p0)
{
//printf("### parseVersion: %d\n", p0);
int p = p0;
p = skipwhite(p0);
if (peek(p) != '<')
return p0;
p++;
if (p>=parselen || peek(p)!='?')
return p0;
p++;
String buf;
while (p<parselen)
{
XMLCh ch = peek(p);
if (ch=='?')
{
p++;
break;
}
buf.push_back(ch);
p++;
}
if (peek(p) != '>')
return p0;
p++;
//printf("Got version:%s\n",buf.c_str());
return p;
}
int Parser::parseDoctype(int p0)
{
//printf("### parseDoctype: %d\n", p0);
int p = p0;
p = skipwhite(p);
if (p>=parselen || peek(p)!='<')
return p0;
p++;
if (peek(p)!='!' || peek(p+1)=='-')
return p0;
p++;
String buf;
while (p<parselen)
{
XMLCh ch = peek(p);
if (ch=='>')
{
p++;
break;
}
buf.push_back(ch);
p++;
}
//printf("Got doctype:%s\n",buf.c_str());
return p;
}
int Parser::parseElement(int p0, Element *par,int lineNr)
{
int p = p0;
int p2 = p;
p = skipwhite(p);
//## Get open tag
XMLCh ch = peek(p);
if (ch!='<')
return p0;
//int line, col;
//getLineAndColumn(p, &line, &col);
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->line = lineNr + countLines(p0, p);
n->parent = par;
par->addChild(n);
// Get attributes
if (peek(p) != '>')
{
while (p<parselen)
{
p = skipwhite(p);
ch = peek(p);
//printf("ch:%c\n",ch);
if (ch=='>')
break;
else if (ch=='/' && p<parselen+1)
{
p++;
p = skipwhite(p);
ch = peek(p);
if (ch=='>')
{
p++;
//printf("quick close\n");
return p;
}
}
String attrName;
p2 = getWord(p, attrName);
if (p2==p)
break;
//printf("name:%s",buf);
p=p2;
p = skipwhite(p);
ch = peek(p);
//printf("ch:%c\n",ch);
if (ch!='=')
break;
p++;
p = skipwhite(p);
// ch = parsebuf[p];
// printf("ch:%c\n",ch);
String attrVal;
p2 = getQuoted(p, attrVal, true);
p=p2+1;
//printf("name:'%s' value:'%s'\n",attrName.c_str(),attrVal.c_str());
char *namestr = (char *)attrName.c_str();
if (strncmp(namestr, "xmlns:", 6)==0)
n->addNamespace(attrName, attrVal);
else
n->addAttribute(attrName, attrVal);
}
}
bool cdata = false;
p++;
// ### Get intervening data ### */
String data;
while (p<parselen)
{
//# COMMENT
p2 = match(p, "<!--");
if (!cdata && p2>p)
{
p = p2;
while (p<parselen)
{
p2 = match(p, "-->");
if (p2 > p)
{
p = p2;
break;
}
p++;
}
}
ch = peek(p);
//# END TAG
if (ch=='<' && !cdata && peek(p+1)=='/')
{
break;
}
//# CDATA
p2 = match(p, "<![CDATA[");
if (p2 > p)
{
cdata = true;
p = p2;
continue;
}
//# CHILD ELEMENT
if (ch == '<')
{
p2 = parseElement(p, n, lineNr + countLines(p0, p));
if (p2 == p)
{
/*
printf("problem on element:%s. p2:%d p:%d\n",
openTagName.c_str(), p2, p);
*/
return p0;
}
p = p2;
continue;
}
//# ENTITY
if (ch=='&' && !cdata)
{
bool found = false;
for (EntityEntry *ee = entities ; ee->value ; ee++)
{
int p2 = match(p, ee->escaped);
if (p2>p)
{
data.push_back(ee->value);
p = p2;
found = true;
break;
}
}
if (!found)
{
error("unterminated entity");
return -1;
}
continue;
}
//# NONE OF THE ABOVE
data.push_back(ch);
p++;
}/*while*/
n->value = data;
//printf("%d : data:%s\n",p,data.c_str());
//## Get close tag
p = skipwhite(p);
ch = peek(p);
if (ch != '<')
{
error("no < for end tag\n");
return p0;
}
p++;
ch = peek(p);
if (ch != '/')
{
error("no / on end tag");
return p0;
}
p++;
ch = peek(p);
p = skipwhite(p);
String closeTagName;
p = getWord(p, closeTagName);
if (openTagName != closeTagName)
{
error("Mismatched closing tag. Expected </%S>. Got '%S'.",
openTagName.c_str(), closeTagName.c_str());
return p0;
}
p = skipwhite(p);
if (peek(p) != '>')
{
error("no > on end tag for '%s'", closeTagName.c_str());
return p0;
}
p++;
// printf("close element:%s\n",closeTagName.c_str());
p = skipwhite(p);
return p;
}
Element *Parser::parse(XMLCh *buf,int pos,int len)
{
parselen = len;
parsebuf = buf;
Element *rootNode = new Element("root");
pos = parseVersion(pos);
pos = parseDoctype(pos);
pos = parseElement(pos, rootNode, 1);
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, const 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;
const 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__
std::size_t firstChar = 0;
if (path.size() >= 3)
{
if (path[0] == '/' &&
isLetter(path[1]) &&
path[2] == ':')
firstChar++;
}
for (std::size_t i=firstChar ; i<path.size() ; i++)
{
XMLCh ch = (XMLCh) path[i];
if (ch == '/')
npath.push_back((XMLCh)'\\');
else
npath.push_back(ch);
}
#else
npath = path;
#endif
return npath;
}
bool URI::isAbsolute() const
{
return absolute;
}
bool URI::isOpaque() const
{
return opaque;
}
String URI::getQuery() const
{
return query;
}
String URI::getFragment() const
{
return fragment;
}
URI URI::resolve(const URI &other) const
{
//### According to w3c, this is handled in 3 cases
//## 1
if (opaque || other.isAbsolute())
return other;
//## 2
if (other.fragment.size() > 0 &&
other.path.size() == 0 &&
other.scheme == SCHEME_NONE &&
other.authority.size() == 0 &&
other.query.size() == 0 )
{
URI fragUri = *this;
fragUri.fragment = other.fragment;
return fragUri;
}
//## 3 http://www.ietf.org/rfc/rfc2396.txt, section 5.2
URI newUri;
//# 3.1
newUri.scheme = scheme;
newUri.schemeStr = schemeStr;
newUri.query = other.query;
newUri.fragment = other.fragment;
if (other.authority.size() > 0)
{
//# 3.2
if (absolute || other.absolute)
newUri.absolute = true;
newUri.authority = other.authority;
newUri.port = other.port;//part of authority
newUri.path = other.path;
}
else
{
//# 3.3
if (other.absolute)
{
newUri.absolute = true;
newUri.path = other.path;
}
else
{
std::size_t pos = path.find_last_of('/');
if (pos != path.npos)
{
String tpath = path.substr(0, pos+1);
tpath.append(other.path);
newUri.path = tpath;
}
else
newUri.path = other.path;
}
}
newUri.normalize();
return newUri;
}
/**
* This follows the Java URI algorithm:
* 1. All "." segments are removed.
* 2. If a ".." segment is preceded by a non-".." segment
* then both of these segments are removed. This step
* is repeated until it is no longer applicable.
* 3. If the path is relative, and if its first segment
* contains a colon character (':'), then a "." segment
* is prepended. This prevents a relative URI with a path
* such as "a:b/c/d" from later being re-parsed as an
* opaque URI with a scheme of "a" and a scheme-specific
* part of "b/c/d". (Deviation from RFC 2396)
*/
void URI::normalize()
{
std::vector<String> segments;
//## Collect segments
if (path.size()<2)
return;
bool abs = false;
std::size_t pos=0;
if (path[0]=='/')
{
abs = true;
pos++;
}
while (pos < path.size())
{
std::size_t pos2 = path.find('/', pos);
if (pos2==path.npos)
{
String seg = path.substr(pos);
//printf("last segment:%s\n", seg.c_str());
segments.push_back(seg);
break;
}
if (pos2>pos)
{
String seg = path.substr(pos, pos2-pos);
//printf("segment:%s\n", seg.c_str());
segments.push_back(seg);
}
pos = pos2;
pos++;
}
//## Clean up (normalize) segments
bool edited = false;
std::vector<String>::iterator iter;
for (iter=segments.begin() ; iter!=segments.end() ; )
{
String s = *iter;
if (s == ".")
{
iter = segments.erase(iter);
edited = true;
}
else if (s == ".." &&
iter != segments.begin() &&
*(iter-1) != "..")
{
iter--; //back up, then erase two entries
iter = segments.erase(iter);
iter = segments.erase(iter);
edited = true;
}
else
iter++;
}
//## Rebuild path, if necessary
if (edited)
{
path.clear();
if (abs)
{
path.append("/");
}
std::vector<String>::iterator iter;
for (iter=segments.begin() ; iter!=segments.end() ; iter++)
{
if (iter != segments.begin())
path.append("/");
path.append(*iter);
}
}
}
//#########################################################################
//# M E S S A G E S
//#########################################################################
void URI::error(const char *fmt, ...)
{
va_list args;
fprintf(stderr, "URI error: ");
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
fprintf(stderr, "\n");
}
void URI::trace(const char *fmt, ...)
{
va_list args;
fprintf(stdout, "URI: ");
va_start(args, fmt);
vfprintf(stdout, fmt, args);
va_end(args);
fprintf(stdout, "\n");
}
//#########################################################################
//# P A R S I N G
//#########################################################################
int URI::peek(int p)
{
if (p<0 || p>=parselen)
return -1;
return parsebuf[p];
}
int URI::match(int p0, const 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)
{
init();
parselen = str.size();
String tmp;
for (std::size_t i=0 ; i<str.size() ; i++)
{
XMLCh ch = (XMLCh) str[i];
if (ch == '\\')
tmp.push_back((XMLCh)'/');
else
tmp.push_back(ch);
}
parsebuf = (char *) tmp.c_str();
int p = parse(0);
normalize();
if (p < 0)
{
error("Syntax error");
return false;
}
//printf("uri:%s\n", toString().c_str());
//printf("path:%s\n", path.c_str());
return true;
}
//########################################################################
//########################################################################
//## M A K E
//########################################################################
//########################################################################
//########################################################################
//# Stat cache to speed up stat requests
//########################################################################
struct StatResult {
int result;
struct stat statInfo;
};
typedef std::map<String, StatResult> statCacheType;
static statCacheType statCache;
static int cachedStat(const String &f, struct stat *s) {
//printf("Stat path: %s\n", f.c_str());
std::pair<statCacheType::iterator, bool> result = statCache.insert(statCacheType::value_type(f, StatResult()));
if (result.second) {
result.first->second.result = stat(f.c_str(), &(result.first->second.statInfo));
}
*s = result.first->second.statInfo;
return result.first->second.result;
}
static void removeFromStatCache(const String f) {
//printf("Removing from cache: %s\n", f.c_str());
statCache.erase(f);
}
//########################################################################
//# Dir cache to speed up dir requests
//########################################################################
/*struct DirListing {
bool available;
std::vector<String> files;
std::vector<String> dirs;
};
typedef std::map<String, DirListing > dirCacheType;
static dirCacheType dirCache;
static const DirListing &cachedDir(String fullDir)
{
String dirNative = getNativePath(fullDir);
std::pair<dirCacheType::iterator,bool> result = dirCache.insert(dirCacheType::value_type(dirNative, DirListing()));
if (result.second) {
DIR *dir = opendir(dirNative.c_str());
if (!dir)
{
error("Could not open directory %s : %s",
dirNative.c_str(), strerror(errno));
result.first->second.available = false;
}
else
{
result.first->second.available = true;
while (true)
{
struct dirent *de = readdir(dir);
if (!de)
break;
//Get the directory member name
String s = de->d_name;
if (s.size() == 0 || s[0] == '.')
continue;
String childName;
if (dirName.size()>0)
{
childName.append(dirName);
childName.append("/");
}
childName.append(s);
String fullChild = baseDir;
fullChild.append("/");
fullChild.append(childName);
if (isDirectory(fullChild))
{
//trace("directory: %s", childName.c_str());
if (!listFiles(baseDir, childName, res))
return false;
continue;
}
else if (!isRegularFile(fullChild))
{
error("unknown file:%s", childName.c_str());
return false;
}
//all done!
res.push_back(childName);
}
closedir(dir);
}
}
return result.first->second;
}*/
//########################################################################
//# F I L E S E T
//########################################################################
/**
* This is the descriptor for a <fileset> item
*/
class FileSet
{
public:
/**
*
*/
FileSet()
{}
/**
*
*/
FileSet(const FileSet &other)
{ assign(other); }
/**
*
*/
FileSet &operator=(const FileSet &other)
{ assign(other); return *this; }
/**
*
*/
virtual ~FileSet()
{}
/**
*
*/
String getDirectory() const
{ return directory; }
/**
*
*/
void setDirectory(const String &val)
{ directory = val; }
/**
*
*/
void setFiles(const std::vector<String> &val)
{ files = val; }
/**
*
*/
std::vector<String> getFiles() const
{ return files; }
/**
*
*/
void setIncludes(const std::vector<String> &val)
{ includes = val; }
/**
*
*/
std::vector<String> getIncludes() const
{ return includes; }
/**
*
*/
void setExcludes(const std::vector<String> &val)
{ excludes = val; }
/**
*
*/
std::vector<String> getExcludes() const
{ return excludes; }
/**
*
*/
std::size_t size() const
{ return files.size(); }
/**
*
*/
String operator[](int index) const
{ return files[index]; }
/**
*
*/
void clear()
{
directory = "";
files.clear();
includes.clear();
excludes.clear();
}
private:
void assign(const FileSet &other)
{
directory = other.directory;
files = other.files;
includes = other.includes;
excludes = other.excludes;
}
String directory;
std::vector<String> files;
std::vector<String> includes;
std::vector<String> excludes;
};
//########################################################################
//# F I L E L I S T
//########################################################################
/**
* This is a simpler, explicitly-named list of files
*/
class FileList
{
public:
/**
*
*/
FileList()
{}
/**
*
*/
FileList(const FileList &other)
{ assign(other); }
/**
*
*/
FileList &operator=(const FileList &other)
{ assign(other); return *this; }
/**
*
*/
virtual ~FileList()
{}
/**
*
*/
String getDirectory()
{ return directory; }
/**
*
*/
void setDirectory(const String &val)
{ directory = val; }
/**
*
*/
void setFiles(const std::vector<String> &val)
{ files = val; }
/**
*
*/
std::vector<String> getFiles()
{ return files; }
/**
*
*/
std::size_t size()
{ return files.size(); }
/**
*
*/
String operator[](int index)
{ return files[index]; }
/**
*
*/
void clear()
{
directory = "";
files.clear();
}
private:
void assign(const FileList &other)
{
directory = other.directory;
files = other.files;
}
String directory;
std::vector<String> files;
};
//########################################################################
//# M A K E B A S E
//########################################################################
/**
* Base class for all classes in this file
*/
class MakeBase
{
public:
MakeBase()
{ line = 0; }
virtual ~MakeBase()
{}
/**
* Return the URI of the file associated with this object
*/
URI getURI()
{ return uri; }
/**
* Set the uri to the given string
*/
void setURI(const String &uristr)
{ uri.parse(uristr); }
/**
* Set the number of threads that can be used
*/
void setNumThreads(const int num)
{ numThreads = num; }
/**
* Resolve another path relative to this one
*/
String resolve(const String &otherPath);
/**
* replace variable refs like ${a} with their values
* Assume that the string has already been syntax validated
*/
String eval(const String &s, const String &defaultVal);
/**
* replace variable refs like ${a} with their values
* return true or false
* Assume that the string has already been syntax validated
*/
bool evalBool(const String &s, bool defaultVal);
/**
* replace variable refs like ${a} with their values
* return the value parsed as an integer
* Assume that the string has already been syntax validated
*/
int evalInt(const String &s, int defaultVal);
/**
* Get an element attribute, performing substitutions if necessary
*/
bool getAttribute(Element *elem, const String &name, String &result);
/**
* Get an element value, performing substitutions if necessary
*/
bool getValue(Element *elem, String &result);
/**
* Set the current line number in the file
*/
void setLine(int val)
{ line = val; }
/**
* Get the current line number in the file
*/
int getLine()
{ return line; }
/**
* Set a property to a given value
*/
virtual void setProperty(const String &name, const String &val)
{
properties[name] = val;
}
/**
* Return a named property is found, else a null string
*/
virtual String getProperty(const String &name)
{
String val;
std::map<String, String>::iterator iter = properties.find(name);
if (iter != properties.end())
val = iter->second;
String sval;
if (!getSubstitutions(val, sval))
return String();
return sval;
}
/**
* Return true if a named property is found, else false
*/
virtual bool hasProperty(const String &name)
{
std::map<String, String>::iterator iter = properties.find(name);
if (iter == properties.end())
return false;
return true;
}
protected:
/**
* The path to the file associated with this object
*/
URI uri;
/**
* The number of threads that can be used
*/
static int numThreads;
/**
* If this prefix is seen in a substitution, use an environment
* variable.
* example: <property environment="env"/>
* ${env.JAVA_HOME}
*/
String envPrefix;
/**
* If this prefix is seen in a substitution, use as a
* pkg-config 'all' query
* example: <property pkg-config="pc"/>
* ${pc.gtkmm}
*/
String pcPrefix;
/**
* If this prefix is seen in a substitution, use as a
* pkg-config 'cflags' query
* example: <property pkg-config="pcc"/>
* ${pcc.gtkmm}
*/
String pccPrefix;
/**
* If this prefix is seen in a substitution, use as a
* pkg-config 'libs' query
* example: <property pkg-config-libs="pcl"/>
* ${pcl.gtkmm}
*/
String pclPrefix;
/**
* If this prefix is seen in a substitution, use as a
* Bazaar "bzr revno" query
* example: <property subversion="svn"/> ???
* ${bzr.Revision}
*/
String bzrPrefix;
/**
* Print a printf()-like formatted error message
*/
void error(const char *fmt, ...);
/**
* Print a printf()-like formatted trace message
*/
void status(const char *fmt, ...);
/**
* Show target status
*/
void targetstatus(const char *fmt, ...);
/**
* Print a printf()-like formatted trace message
*/
void trace(const char *fmt, ...);
/**
* Check if a given string matches a given regex pattern
*/
bool regexMatch(const String &str, const String &pattern);
/**
*
*/
String getSuffix(const String &fname);
/**
* Break up a string into substrings delimited the characters
* in delimiters. Null-length substrings are ignored
*/
std::vector<String> tokenize(const String &val,
const String &delimiters);
/**
* replace runs of whitespace with a space
*/
String strip(const String &s);
/**
* remove leading whitespace from each line
*/
String leftJustify(const String &s);
/**
* remove leading and trailing whitespace from string
*/
String trim(const String &s);
/**
* Return a lower case version of the given string
*/
String toLower(const String &s);
/**
* Return the native format of the canonical
* path which we store
*/
String getNativePath(const String &path);
/**
* Execute a shell command. Outbuf is a ref to a string
* to catch the result.
*/
bool executeCommand(const String &call,
const String &inbuf,
String &outbuf,
String &errbuf);
/**
* List all directories in a given base and starting directory
* It is usually called like:
* bool ret = listDirectories("src", "", result);
*/
bool listDirectories(const String &baseName,
const String &dirname,
std::vector<String> &res);
/**
* Find all files in the named directory
*/
bool listFiles(const String &baseName,
const String &dirname,
std::vector<String> &result);
/**
* Perform a listing for a fileset
*/
bool listFiles(MakeBase &propRef, FileSet &fileSet);
/**
* Parse a <patternset>
*/
bool parsePatternSet(Element *elem,
MakeBase &propRef,
std::vector<String> &includes,
std::vector<String> &excludes);
/**
* Parse a <fileset> entry, and determine which files
* should be included
*/
bool parseFileSet(Element *elem,
MakeBase &propRef,
FileSet &fileSet);
/**
* Parse a <filelist> entry
*/
bool parseFileList(Element *elem,
MakeBase &propRef,
FileList &fileList);
/**
* Return this object's property list
*/
virtual std::map<String, String> &getProperties()
{ return properties; }
std::map<String, String> properties;
/**
* 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);
/**
* Delete a file
*/
bool removeFile(const String &file);
/**
* Tests if the file exists
*/
bool fileExists(const String &fileName);
/**
* Tests if the file exists and is a regular file
*/
bool isRegularFile(const String &fileName);
/**
* Tests if the file exists and is a directory
*/
bool isDirectory(const String &fileName);
/**
* Tests is the modification date of fileA is newer than fileB
*/
bool isNewerThan(const String &fileA, const String &fileB);
private:
bool pkgConfigRecursive(const String packageName,
const String &path,
const String &prefix,
int query,
String &result,
std::set<String> &deplist);
/**
* utility method to query for "all", "cflags", or "libs" for this package and its
* dependencies. 0, 1, 2
*/
bool pkgConfigQuery(const String &packageName, int query, String &result);
/**
* replace a variable ref like ${a} with a value
*/
bool lookupProperty(const String &s, String &result);
/**
* called by getSubstitutions(). This is in case a looked-up string
* has substitutions also.
*/
bool getSubstitutionsRecursive(const String &s, String &result, int depth);
/**
* replace variable refs in a string like ${a} with their values
*/
bool getSubstitutions(const String &s, String &result);
int line;
};
int MakeBase::numThreads = 1;
/**
* Define the pkg-config class here, since it will be used in MakeBase method
* implementations.
*/
class PkgConfig : public MakeBase
{
public:
/**
*
*/
PkgConfig()
{
path = ".";
prefix = "/target";
init();
}
/**
*
*/
PkgConfig(const PkgConfig &other)
{ assign(other); }
/**
*
*/
PkgConfig &operator=(const PkgConfig &other)
{ assign(other); return *this; }
/**
*
*/
virtual ~PkgConfig()
{ }
/**
*
*/
virtual String getName()
{ return name; }
/**
*
*/
virtual String getPath()
{ return path; }
/**
*
*/
virtual void setPath(const String &val)
{ path = val; }
/**
*
*/
virtual String getPrefix()
{ return prefix; }
/**
* Allow the user to override the prefix in the file
*/
virtual void setPrefix(const String &val)
{ prefix = val; }
/**
*
*/
virtual String getDescription()
{ return description; }
/**
*
*/
virtual String getCflags()
{ return cflags; }
/**
*
*/
virtual String getLibs()
{ return libs; }
/**
*
*/
virtual String getAll()
{
String ret = cflags;
ret.append(" ");
ret.append(libs);
return ret;
}
/**
*
*/
virtual String getVersion()
{ return version; }
/**
*
*/
virtual int getMajorVersion()
{ return majorVersion; }
/**
*
*/
virtual int getMinorVersion()
{ return minorVersion; }
/**
*
*/
virtual int getMicroVersion()
{ return microVersion; }
/**
*
*/
virtual std::map<String, String> &getAttributes()
{ return attrs; }
/**
*
*/
virtual std::vector<String> &getRequireList()
{ return requireList; }
/**
* Read a file for its details
*/
virtual bool readFile(const String &fileName);
/**
* Read a file for its details
*/
virtual bool query(const String &name);
private:
void init()
{
//do not set path and prefix here
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;
path = other.path;
prefix = other.prefix;
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);
/**
* Very important
*/
bool parseRequires();
void parseVersion();
bool parseLine(const String &lineBuf);
bool parse(const String &buf);
void dumpAttrs();
String name;
String path;
String prefix;
String description;
String cflags;
String libs;
String requires;
String version;
int majorVersion;
int minorVersion;
int microVersion;
String fileName;
std::map<String, String> attrs;
std::vector<String> requireList;
char *parsebuf;
int parselen;
};
/**
* Execute the "bzr revno" command and return the result.
* This is a simple, small class.
*/
class BzrRevno : public MakeBase
{
public:
/**
* Safe way. Execute "bzr revno" and return the result.
* Safe from changes in format.
*/
bool query(String &res)
{
String cmd = "bzr revno";
String outString, errString;
bool ret = executeCommand(cmd.c_str(), "", outString, errString);
if (!ret)
{
error("error executing '%s': %s", cmd.c_str(), errString.c_str());
return false;
}
res = outString;
return true;
}
};
/**
* Execute the "svn info" command and parse the result.
* This is a simple, small class. Define here, because it
* is used by MakeBase implementation methods.
*/
class SvnInfo : public MakeBase
{
public:
#if 0
/**
* Safe way. Execute "svn info --xml" and parse the result. Search for
* elements/attributes. Safe from changes in format.
*/
bool query(const String &name, String &res)
{
String cmd = "svn info --xml";
String outString, errString;
bool ret = executeCommand(cmd.c_str(), "", outString, errString);
if (!ret)
{
error("error executing '%s': %s", cmd.c_str(), errString.c_str());
return false;
}
Parser parser;
Element *elem = parser.parse(outString);
if (!elem)
{
error("error parsing 'svn info' xml result: %s", outString.c_str());
return false;
}
res = elem->getTagValue(name);
if (res.size()==0)
{
res = elem->getTagAttribute("entry", name);
}
return true;
}
#else
/**
* Universal way. Parse the file directly. Not so safe from
* changes in format.
*/
bool query(const String &name, String &res)
{
String fileName = resolve(".svn/entries");
String nFileName = getNativePath(fileName);
std::map<String, String> properties;
FILE *f = fopen(nFileName.c_str(), "r");
if (!f)
{
error("could not open SVN 'entries' file");
return false;
}
const char *fieldNames[] =
{
"format-nbr",
"name",
"kind",
"revision",
"url",
"repos",
"schedule",
"text-time",
"checksum",
"committed-date",
"committed-rev",
"last-author",
"has-props",
"has-prop-mods",
"cachable-props",
};
for (int i=0 ; i<15 ; i++)
{
inbuf[0] = '\0';
if (feof(f) || !fgets(inbuf, 255, f))
break;
properties[fieldNames[i]] = trim(inbuf);
}
fclose(f);
res = properties[name];
return true;
}
private:
char inbuf[256];
#endif
};
/**
* Print a printf()-like formatted error message
*/
void MakeBase::error(const char *fmt, ...)
{
va_list args;
va_start(args,fmt);
fprintf(stderr, "Make error line %d: ", line);
vfprintf(stderr, fmt, args);
fprintf(stderr, "\n");
va_end(args) ;
}
/**
* Print a printf()-like formatted trace message
*/
void MakeBase::status(const char *fmt, ...)
{
va_list args;
//fprintf(stdout, " ");
va_start(args,fmt);
vfprintf(stdout, fmt, args);
va_end(args);
fprintf(stdout, "\n");
fflush(stdout);
}
/**
* Print a printf()-like formatted trace message
*/
void MakeBase::trace(const char *fmt, ...)
{
va_list args;
fprintf(stdout, "Make: ");
va_start(args,fmt);
vfprintf(stdout, fmt, args);
va_end(args) ;
fprintf(stdout, "\n");
fflush(stdout);
}
/**
* Resolve another path relative to this one
*/
String MakeBase::resolve(const String &otherPath)
{
URI otherURI(otherPath);
URI fullURI = uri.resolve(otherURI);
String ret = fullURI.toString();
return ret;
}
/**
* Check if a given string matches a given regex pattern
*/
bool MakeBase::regexMatch(const String &str, const String &pattern)
{
int res = slre_match(pattern.c_str(), str.c_str(), str.length(), NULL, 0, SLRE_IGNORE_CASE);
bool ret = true;
if (res < 0)
{
ret = false;
// error cases
if (res < -1)
{
String err;
switch(res)
{
case SLRE_UNEXPECTED_QUANTIFIER:
err = "unexpected quantifier"; break;
case SLRE_UNBALANCED_BRACKETS:
err = "unbalanced brackets"; break;
case SLRE_INTERNAL_ERROR:
err = "internal error"; break;
case SLRE_INVALID_CHARACTER_SET:
err = "invald character set"; break;
case SLRE_INVALID_METACHARACTER:
err = "invalid meta character"; break;
default:
err = "unknown error"; break;
}
error("regex failure (%s) while parsing [%s]!\n", err.c_str(), pattern.c_str());
}
}
return ret;
}
/**
* Return the suffix, if any, of a file name
*/
String MakeBase::getSuffix(const String &fname)
{
if (fname.size() < 2)
return "";
std::size_t pos = fname.find_last_of('.');
if (pos == fname.npos)
return "";
pos++;
String res = fname.substr(pos, fname.size()-pos);
//trace("suffix:%s", res.c_str());
return res;
}
/**
* Break up a string into substrings delimited the characters
* in delimiters. Null-length substrings are ignored
*/
std::vector<String> MakeBase::tokenize(const String &str,
const String &delimiters)
{
std::vector<String> res;
char *del = (char *)delimiters.c_str();
String dmp;
for (std::size_t i=0 ; i<str.size() ; i++)
{
char ch = str[i];
char *p = (char *)0;
for (p=del ; *p ; p++)
if (*p == ch)
break;
if (*p)
{
if (dmp.size() > 0)
{
res.push_back(dmp);
dmp.clear();
}
}
else
{
dmp.push_back(ch);
}
}
//Add tail
if (dmp.size() > 0)
{
res.push_back(dmp);
dmp.clear();
}
return res;
}
/**
* replace runs of whitespace with a single space
*/
String MakeBase::strip(const String &s)
{
int len = s.size();
String stripped;
for (int i = 0 ; i<len ; i++)
{
char ch = s[i];
if (isspace(ch))
{
stripped.push_back(' ');
for ( ; i<len ; i++)
{
ch = s[i];
if (!isspace(ch))
{
stripped.push_back(ch);
break;
}
}
}
else
{
stripped.push_back(ch);
}
}
return stripped;
}
/**
* remove leading whitespace from each line
*/
String MakeBase::leftJustify(const String &s)
{
String out;
int len = s.size();
for (int i = 0 ; i<len ; )
{
char ch;
//Skip to first visible character
while (i<len)
{
ch = s[i];
if (ch == '\n' || ch == '\r'
|| !isspace(ch))
break;
i++;
}
//Copy the rest of the line
while (i<len)
{
ch = s[i];
if (ch == '\n' || ch == '\r')
{
if (ch != '\r')
out.push_back('\n');
i++;
break;
}
else
{
out.push_back(ch);
}
i++;
}
}
return out;
}
/**
* 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
std::size_t begin = 0;
for ( ; begin < s.size() ; begin++)
{
if (!isspace(s[begin]))
break;
}
//Find first non-ws char, going in reverse
std::size_t 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 a lower case version of the given string
*/
String MakeBase::toLower(const String &s)
{
if (s.size()==0)
return s;
String ret;
for(std::size_t i=0; i<s.size() ; i++)
{
ret.push_back(tolower(s[i]));
}
return ret;
}
/**
* Return the native format of the canonical
* path which we store
*/
String MakeBase::getNativePath(const String &path)
{
#ifdef __WIN32__
String npath;
std::size_t firstChar = 0;
if (path.size() >= 3)
{
if (path[0] == '/' &&
isalpha(path[1]) &&
path[2] == ':')
firstChar++;
}
for (std::size_t i=firstChar ; i<path.size() ; i++)
{
char ch = path[i];
if (ch == '/')
npath.push_back('\\');
else
npath.push_back(ch);
}
return npath;
#else
return path;
#endif
}
#ifdef __WIN32__
#include <tchar.h>
static String win32LastError()
{
DWORD dw = GetLastError();
LPVOID str;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
dw,
0,
(LPTSTR) &str,
0, NULL );
LPTSTR p = _tcschr((const char *)str, _T('\r'));
if(p != NULL)
{ // lose CRLF
*p = _T('\0');
}
String ret = (char *)str;
LocalFree(str);
return ret;
}
#endif
#ifdef __WIN32__
/**
* Execute a system call, using pipes to send data to the
* program's stdin, and reading stdout and stderr.
*/
bool MakeBase::executeCommand(const String &command,
const String &inbuf,
String &outbuf,
String &errbuf)
{
// status("============ cmd ============\n%s\n=============================",
// command.c_str());
outbuf.clear();
errbuf.clear();
/*
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());
//# Go to http://msdn2.microsoft.com/en-us/library/ms682499.aspx
//# to see how Win32 pipes work
//# 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 (&outbuf != &errbuf) {
if (!CreatePipe(&stderrRead, &stderrWrite, &saAttr, 0))
{
error("executeProgram: could not create pipe");
delete[] paramBuf;
return false;
}
SetHandleInformation(stderrRead, HANDLE_FLAG_INHERIT, 0);
} else {
stderrRead = stdoutRead;
stderrWrite = stdoutWrite;
}
// Create the process
STARTUPINFO siStartupInfo;
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;
}
delete[] paramBuf;
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 (stdoutWrite != stderrWrite && !CloseHandle(stderrWrite))
{
error("executeCommand: could not close read pipe");
return false;
}
bool lastLoop = false;
while (true)
{
DWORD avail;
DWORD bytesRead;
char readBuf[4096];
//trace("## stderr");
PeekNamedPipe(stderrRead, NULL, 0, NULL, &avail, NULL);
if (avail > 0)
{
bytesRead = 0;
if (avail>4096) avail = 4096;
ReadFile(stderrRead, readBuf, avail, &bytesRead, NULL);
if (bytesRead > 0)
{
for (unsigned int i=0 ; i<bytesRead ; i++)
errbuf.push_back(readBuf[i]);
}
}
//trace("## stdout");
PeekNamedPipe(stdoutRead, NULL, 0, NULL, &avail, NULL);
if (avail > 0)
{
bytesRead = 0;
if (avail>4096) avail = 4096;
ReadFile(stdoutRead, readBuf, avail, &bytesRead, NULL);
if (bytesRead > 0)
{
for (unsigned int i=0 ; i<bytesRead ; i++)
outbuf.push_back(readBuf[i]);
}
}
//Was this the final check after program done?
if (lastLoop)
break;
DWORD exitCode;
GetExitCodeProcess(piProcessInfo.hProcess, &exitCode);
if (exitCode != STILL_ACTIVE)
lastLoop = true;
Sleep(10);
}
//trace("outbuf:%s", outbuf.c_str());
if (!CloseHandle(stdoutRead))
{
error("executeCommand: could not close read pipe");
return false;
}
if (stdoutRead != stderrRead && !CloseHandle(stderrRead))
{
error("executeCommand: could not close read pipe");
return false;
}
DWORD exitCode;
GetExitCodeProcess(piProcessInfo.hProcess, &exitCode);
//trace("exit code:%d", exitCode);
if (exitCode != 0)
{
ret = false;
}
CloseHandle(piProcessInfo.hProcess);
CloseHandle(piProcessInfo.hThread);
return ret;
}
#else /*do it unix style*/
#include <sys/wait.h>
/**
* Execute a system call, using pipes to send data to the
* program's stdin, and reading stdout and stderr.
*/
bool MakeBase::executeCommand(const String &command,
const String &inbuf,
String &outbuf,
String &errbuf)
{
status("============ cmd ============\n%s\n=============================",
command.c_str());
outbuf.clear();
errbuf.clear();
int outfds[2];
if (pipe(outfds) < 0)
return false;
int errfds[2];
if (pipe(errfds) < 0)
return false;
int pid = fork();
if (pid < 0)
{
close(outfds[0]);
close(outfds[1]);
close(errfds[0]);
close(errfds[1]);
error("launch of command '%s' failed : %s",
command.c_str(), strerror(errno));
return false;
}
else if (pid > 0) // parent
{
close(outfds[1]);
close(errfds[1]);
}
else // == 0, child
{
close(outfds[0]);
dup2(outfds[1], STDOUT_FILENO);
close(outfds[1]);
close(errfds[0]);
dup2(errfds[1], STDERR_FILENO);
close(errfds[1]);
char *args[4];
args[0] = (char *)"sh";
args[1] = (char *)"-c";
args[2] = (char *)command.c_str();
args[3] = NULL;
execv("/bin/sh", args);
exit(EXIT_FAILURE);
}
String outb;
String errb;
int outRead = outfds[0];
int errRead = errfds[0];
int max = outRead;
if (errRead > max)
max = errRead;
bool outOpen = true;
bool errOpen = true;
while (outOpen || errOpen)
{
char ch;
fd_set fdset;
FD_ZERO(&fdset);
if (outOpen)
FD_SET(outRead, &fdset);
if (errOpen)
FD_SET(errRead, &fdset);
int ret = select(max+1, &fdset, NULL, NULL, NULL);
if (ret < 0)
break;
if (FD_ISSET(outRead, &fdset))
{
if (read(outRead, &ch, 1) <= 0)
{ outOpen = false; }
else if (ch <= 0)
{ /* outOpen = false; */ }
else
{ outb.push_back(ch); }
}
if (FD_ISSET(errRead, &fdset))
{
if (read(errRead, &ch, 1) <= 0)
{ errOpen = false; }
else if (ch <= 0)
{ /* errOpen = false; */ }
else
{ errb.push_back(ch); }
}
}
int childReturnValue;
wait(&childReturnValue);
close(outRead);
close(errRead);
outbuf = outb;
errbuf = errb;
if (childReturnValue != 0)
{
error("exec of command '%s' failed : %s",
command.c_str(), strerror(childReturnValue));
return false;
}
return true;
}
#endif
bool MakeBase::listDirectories(const String &baseName,
const String &dirName,
std::vector<String> &res)
{
res.push_back(dirName);
String fullPath = baseName;
if (dirName.size()>0)
{
if (dirName[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 (cachedStat(childNative, &finfo)<0)
{
error("cannot stat file:%s", childNative.c_str());
}
else if (S_ISDIR(finfo.st_mode))
{
//trace("directory: %s", childName.c_str());
if (!listDirectories(baseName, childName, res))
return false;
}
}
closedir(dir);
return true;
}
bool MakeBase::listFiles(const String &baseDir,
const String &dirName,
std::vector<String> &res)
{
String fullDir = baseDir;
if (dirName.size()>0)
{
fullDir.append("/");
fullDir.append(dirName);
}
String dirNative = getNativePath(fullDir);
std::vector<String> subdirs;
DIR *dir = opendir(dirNative.c_str());
if (!dir)
{
error("Could not open directory %s : %s",
dirNative.c_str(), strerror(errno));
return false;
}
while (true)
{
struct dirent *de = readdir(dir);
if (!de)
break;
//Get the directory member name
String s = de->d_name;
if (s.size() == 0 || s[0] == '.')
continue;
String childName;
if (dirName.size()>0)
{
childName.append(dirName);
childName.append("/");
}
childName.append(s);
String fullChild = baseDir;
fullChild.append("/");
fullChild.append(childName);
if (isDirectory(fullChild))
{
//trace("directory: %s", childName.c_str());
if (!listFiles(baseDir, childName, res))
return false;
continue;
}
else if (!isRegularFile(fullChild))
{
error("unknown file:%s", childName.c_str());
return false;
}
//all done!
res.push_back(childName);
}
closedir(dir);
return true;
}
/**
* Several different classes extend MakeBase. By "propRef", we mean
* the one holding the properties. Likely "Make" itself
*/
bool MakeBase::listFiles(MakeBase &propRef, FileSet &fileSet)
{
//before doing the list, resolve any property references
//that might have been specified in the directory name, such as ${src}
String fsDir = fileSet.getDirectory();
String dir;
if (!propRef.getSubstitutions(fsDir, dir))
return false;
String baseDir = propRef.resolve(dir);
std::vector<String> fileList;
if (!listFiles(baseDir, "", fileList))
return false;
std::vector<String> includes = fileSet.getIncludes();
std::vector<String> excludes = fileSet.getExcludes();
std::vector<String> incs;
std::vector<String>::iterator iter;
std::sort(fileList.begin(), fileList.end());
//If there are <includes>, then add files to the output
//in the order of the include list
if (includes.size()==0)
incs = fileList;
else
{
for (iter = includes.begin() ; iter != includes.end() ; iter++)
{
String &pattern = *iter;
std::vector<String>::iterator siter;
for (siter = fileList.begin() ; siter != fileList.end() ; siter++)
{
String s = *siter;
if (regexMatch(s, pattern))
{
//trace("INCLUDED:%s", s.c_str());
incs.push_back(s);
}
}
}
}
//Now trim off the <excludes>
std::vector<String> res;
for (iter = incs.begin() ; iter != incs.end() ; iter++)
{
String s = *iter;
bool skipme = false;
std::vector<String>::iterator siter;
for (siter = excludes.begin() ; siter != excludes.end() ; siter++)
{
String &pattern = *siter;
if (regexMatch(s, pattern))
{
//trace("EXCLUDED:%s", s.c_str());
skipme = true;
break;
}
}
if (!skipme)
res.push_back(s);
}
fileSet.setFiles(res);
return true;
}
/**
* 0 == all, 1 = cflags, 2 = libs
*/
bool MakeBase::pkgConfigRecursive(const String packageName,
const String &path,
const String &prefix,
int query,
String &result,
std::set<String> &deplist)
{
PkgConfig pkgConfig;
if (path.size() > 0)
pkgConfig.setPath(path);
if (prefix.size() > 0)
pkgConfig.setPrefix(prefix);
if (!pkgConfig.query(packageName))
return false;
if (query == 0)
result = pkgConfig.getAll();
else if (query == 1)
result = pkgConfig.getCflags();
else
result = pkgConfig.getLibs();
deplist.insert(packageName);
std::vector<String> list = pkgConfig.getRequireList();
for (std::size_t i = 0 ; i<list.size() ; i++)
{
String depPkgName = list[i];
if (deplist.find(depPkgName) != deplist.end())
continue;
String val;
if (!pkgConfigRecursive(depPkgName, path, prefix, query, val, deplist))
{
error("Based on 'requires' attribute of package '%s'", packageName.c_str());
return false;
}
result.append(" ");
result.append(val);
}
return true;
}
bool MakeBase::pkgConfigQuery(const String &packageName, int query, String &result)
{
std::set<String> deplist;
String path = getProperty("pkg-config-path");
if (path.size()>0)
path = resolve(path);
String prefix = getProperty("pkg-config-prefix");
String val;
if (!pkgConfigRecursive(packageName, path, prefix, query, val, deplist))
return false;
result = val;
return true;
}
/**
* replace a variable ref like ${a} with a value
*/
bool MakeBase::lookupProperty(const String &propertyName, String &result)
{
String varname = propertyName;
if (envPrefix.size() > 0 &&
varname.compare(0, envPrefix.size(), envPrefix) == 0)
{
varname = varname.substr(envPrefix.size());
char *envstr = getenv(varname.c_str());
if (!envstr)
{
error("environment variable '%s' not defined", varname.c_str());
return false;
}
result = envstr;
}
else if (pcPrefix.size() > 0 &&
varname.compare(0, pcPrefix.size(), pcPrefix) == 0)
{
varname = varname.substr(pcPrefix.size());
String val;
if (!pkgConfigQuery(varname, 0, val))
return false;
result = val;
}
else if (pccPrefix.size() > 0 &&
varname.compare(0, pccPrefix.size(), pccPrefix) == 0)
{
varname = varname.substr(pccPrefix.size());
String val;
if (!pkgConfigQuery(varname, 1, val))
return false;
result = val;
}
else if (pclPrefix.size() > 0 &&
varname.compare(0, pclPrefix.size(), pclPrefix) == 0)
{
varname = varname.substr(pclPrefix.size());
String val;
if (!pkgConfigQuery(varname, 2, val))
return false;
result = val;
}
else if (bzrPrefix.size() > 0 &&
varname.compare(0, bzrPrefix.size(), bzrPrefix) == 0)
{
varname = varname.substr(bzrPrefix.size());
String val;
//SvnInfo svnInfo;
BzrRevno bzrRevno;
if (varname == "revision")
{
if (!bzrRevno.query(val))
return "";
result = "r"+val;
}
/*if (!svnInfo.query(varname, val))
return false;
result = val;*/
}
else
{
std::map<String, String>::iterator iter;
iter = properties.find(varname);
if (iter != properties.end())
{
result = iter->second;
}
else
{
error("property '%s' not found", varname.c_str());
return false;
}
}
return true;
}
/**
* Analyse a string, looking for any substitutions or other
* things that need resolution
*/
bool MakeBase::getSubstitutionsRecursive(const String &str,
String &result, int depth)
{
if (depth > 10)
{
error("nesting of substitutions too deep (>10) for '%s'",
str.c_str());
return false;
}
String s = trim(str);
int len = (int)s.size();
String val;
for (int i=0 ; i<len ; i++)
{
char ch = s[i];
if (ch == '$' && s[i+1] == '{')
{
String varname;
int j = i+2;
for ( ; j<len ; j++)
{
ch = s[j];
if (ch == '$' && s[j+1] == '{')
{
error("attribute %s cannot have nested variable references",
s.c_str());
return false;
}
else if (ch == '}')
{
varname = trim(varname);
String varval;
if (!lookupProperty(varname, varval))
return false;
String varval2;
//Now see if the answer has ${} in it, too
if (!getSubstitutionsRecursive(varval, varval2, depth + 1))
return false;
val.append(varval2);
break;
}
else
{
varname.push_back(ch);
}
}
i = j;
}
else
{
val.push_back(ch);
}
}
result = val;
return true;
}
/**
* Analyse a string, looking for any substitutions or other
* things that need resilution
*/
bool MakeBase::getSubstitutions(const String &str, String &result)
{
return getSubstitutionsRecursive(str, result, 0);
}
/**
* replace variable refs like ${a} with their values
* Assume that the string has already been syntax validated
*/
String MakeBase::eval(const String &s, const String &defaultVal)
{
if (s.size()==0)
return defaultVal;
String ret;
if (getSubstitutions(s, ret))
return ret;
else
return defaultVal;
}
/**
* replace variable refs like ${a} with their values
* return true or false
* Assume that the string has already been syntax validated
*/
bool MakeBase::evalBool(const String &s, bool defaultVal)
{
if (s.size()==0)
return defaultVal;
String val = eval(s, "false");
if (val.size()==0)
return defaultVal;
if (val == "true" || val == "TRUE")
return true;
else
return false;
}
int MakeBase::evalInt(const String &s, int defaultVal)
{
if (s.size()==0) {
return defaultVal;
}
int val = atoi(s.c_str());
// perhaps some error checking, but... bah! waste of time
return val;
}
/**
* Get a string attribute, testing it for proper syntax and
* property names.
*/
bool MakeBase::getAttribute(Element *elem, const String &name,
String &result)
{
String s = elem->getAttribute(name);
String tmp;
bool ret = getSubstitutions(s, tmp);
if (ret)
result = s; //assign -if- ok
return ret;
}
/**
* Get a string value, testing it for proper syntax and
* property names.
*/
bool MakeBase::getValue(Element *elem, String &result)
{
String s = elem->getValue();
String tmp;
bool ret = getSubstitutions(s, tmp);
if (ret)
result = s; //assign -if- ok
return ret;
}
/**
* Parse a <patternset> entry
*/
bool MakeBase::parsePatternSet(Element *elem,
MakeBase &propRef,
std::vector<String> &includes,
std::vector<String> &excludes
)
{
std::vector<Element *> children = elem->getChildren();
for (std::size_t i=0 ; i<children.size() ; i++)
{
Element *child = children[i];
String tagName = child->getName();
if (tagName == "exclude")
{
String fname;
if (!propRef.getAttribute(child, "name", fname))
return false;
//trace("EXCLUDE: %s", fname.c_str());
excludes.push_back(fname);
}
else if (tagName == "include")
{
String fname;
if (!propRef.getAttribute(child, "name", fname))
return false;
//trace("INCLUDE: %s", fname.c_str());
includes.push_back(fname);
}
}
return true;
}
/**
* Parse a <fileset> entry, and determine which files
* should be included
*/
bool MakeBase::parseFileSet(Element *elem,
MakeBase &propRef,
FileSet &fileSet)
{
String name = elem->getName();
if (name != "fileset")
{
error("expected <fileset>");
return false;
}
std::vector<String> includes;
std::vector<String> excludes;
//A fileset has one implied patternset
if (!parsePatternSet(elem, propRef, includes, excludes))
{
return false;
}
//Look for child tags, including more patternsets
std::vector<Element *> children = elem->getChildren();
for (std::size_t i=0 ; i<children.size() ; i++)
{
Element *child = children[i];
String tagName = child->getName();
if (tagName == "patternset")
{
if (!parsePatternSet(child, propRef, includes, excludes))
{
return false;
}
}
}
String dir;
//Now do the stuff
//Get the base directory for reading file names
if (!propRef.getAttribute(elem, "dir", dir))
return false;
fileSet.setDirectory(dir);
fileSet.setIncludes(includes);
fileSet.setExcludes(excludes);
/*
std::vector<String> fileList;
if (dir.size() > 0)
{
String baseDir = propRef.resolve(dir);
if (!listFiles(baseDir, "", includes, excludes, fileList))
return false;
}
std::sort(fileList.begin(), fileList.end());
result = fileList;
*/
/*
for (std::size_t i=0 ; i<result.size() ; i++)
{
trace("RES:%s", result[i].c_str());
}
*/
return true;
}
/**
* Parse a <filelist> entry. This is far simpler than FileSet,
* since no directory scanning is needed. The file names are listed
* explicitly.
*/
bool MakeBase::parseFileList(Element *elem,
MakeBase &propRef,
FileList &fileList)
{
std::vector<String> fnames;
//Look for child tags, namely "file"
std::vector<Element *> children = elem->getChildren();
for (std::size_t i=0 ; i<children.size() ; i++)
{
Element *child = children[i];
String tagName = child->getName();
if (tagName == "file")
{
String fname = child->getAttribute("name");
if (fname.size()==0)
{
error("<file> element requires name="" attribute");
return false;
}
fnames.push_back(fname);
}
else
{
error("tag <%s> not allowed in <fileset>", tagName.c_str());
return false;
}
}
String dir;
//Get the base directory for reading file names
if (!propRef.getAttribute(elem, "dir", dir))
return false;
fileList.setDirectory(dir);
fileList.setFiles(fnames);
return true;
}
/**
* Create a directory, making intermediate dirs
* if necessary
*/
bool MakeBase::createDirectory(const String &dirname)
{
//trace("## createDirectory: %s", dirname.c_str());
//## first check if it exists
struct stat finfo;
String nativeDir = getNativePath(dirname);
char *cnative = (char *) nativeDir.c_str();
#ifdef __WIN32__
if (strlen(cnative)==2 && cnative[1]==':')
return true;
#endif
if (cachedStat(nativeDir, &finfo)==0)
{
if (!S_ISDIR(finfo.st_mode))
{
error("mkdir: file %s exists but is not a directory",
cnative);
return false;
}
else //exists
{
return true;
}
}
//## 2: pull off the last path segment, if any,
//## to make the dir 'above' this one, if necessary
std::size_t pos = dirname.find_last_of('/');
if (pos>0 && pos != dirname.npos)
{
String subpath = dirname.substr(0, pos);
//A letter root (c:) ?
if (!createDirectory(subpath))
return false;
}
//## 3: now make
#ifdef __WIN32__
if (mkdir(cnative)<0)
#else
if (mkdir(cnative, S_IRWXU | S_IRWXG | S_IRWXO)<0)
#endif
{
error("cannot make directory '%s' : %s",
cnative, strerror(errno));
return false;
}
removeFromStatCache(nativeDir);
return true;
}
/**
* Remove a directory recursively
*/
bool MakeBase::removeDirectory(const String &dirName)
{
char *dname = (char *)dirName.c_str();
DIR *dir = opendir(dname);
if (!dir)
{
//# Let this fail nicely.
return true;
//error("error opening directory %s : %s", dname, strerror(errno));
//return false;
}
while (true)
{
struct dirent *de = readdir(dir);
if (!de)
break;
//Get the directory member name
String s = de->d_name;
if (s.size() == 0 || s[0] == '.')
continue;
String childName;
if (dirName.size() > 0)
{
childName.append(dirName);
childName.append("/");
}
childName.append(s);
struct stat finfo;
String childNative = getNativePath(childName);
char *cnative = (char *)childNative.c_str();
if (cachedStat(childNative, &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 (!removeFile(childName))
{
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;
}
removeFromStatCache(native);
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 (cachedStat(srcNative, &srcinfo)<0)
{
error("source file %s for copy does not exist",
srcNative.c_str());
return false;
}
String destNative = getNativePath(destFile);
struct stat destinfo;
if (cachedStat(destNative, &destinfo)==0)
{
if (destinfo.st_mtime >= srcinfo.st_mtime)
return true;
}
//# 2 prepare a destination directory if necessary
std::size_t 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
#ifndef __WIN32__
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)
{
fclose(srcf);
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);
#else
if (!CopyFile(srcNative.c_str(), destNative.c_str(), false))
{
error("copyFile from %s to %s failed",
srcNative.c_str(), destNative.c_str());
return false;
}
#endif /* __WIN32__ */
removeFromStatCache(destNative);
return true;
}
/**
* Delete a file
*/
bool MakeBase::removeFile(const String &file)
{
String native = getNativePath(file);
if (!fileExists(native))
{
return true;
}
#ifdef WIN32
// On Windows 'remove' will only delete files
if (remove(native.c_str())<0)
{
if (errno==EACCES)
{
error("File %s is read-only", native.c_str());
}
else if (errno==ENOENT)
{
error("File %s does not exist or is a directory", native.c_str());
}
else
{
error("Failed to delete file %s: %s", native.c_str(), strerror(errno));
}
return false;
}
#else
if (!isRegularFile(native))
{
error("File %s does not exist or is not a regular file", native.c_str());
return false;
}
if (remove(native.c_str())<0)
{
if (errno==EACCES)
{
error("File %s is read-only", native.c_str());
}
else
{
error(
errno==EACCES ? "File %s is read-only" :
errno==ENOENT ? "File %s does not exist or is a directory" :
"Failed to delete file %s: %s", native.c_str());
}
return false;
}
#endif
removeFromStatCache(native);
return true;
}
/**
* Tests if the file exists
*/
bool MakeBase::fileExists(const String &fileName)
{
String native = getNativePath(fileName);
struct stat finfo;
//Exists?
if (cachedStat(native, &finfo)<0)
return false;
return true;
}
/**
* Tests if the file exists and is a regular file
*/
bool MakeBase::isRegularFile(const String &fileName)
{
String native = getNativePath(fileName);
struct stat finfo;
//Exists?
if (cachedStat(native, &finfo)<0)
return false;
//check the file mode
if (!S_ISREG(finfo.st_mode))
return false;
return true;
}
/**
* Tests if the file exists and is a directory
*/
bool MakeBase::isDirectory(const String &fileName)
{
String native = getNativePath(fileName);
struct stat finfo;
//Exists?
if (cachedStat(native, &finfo)<0)
return false;
//check the file mode
if (!S_ISDIR(finfo.st_mode))
return false;
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 (cachedStat(nativeA, &infoA)<0)
{
return false;
}
String nativeB = getNativePath(fileB);
struct stat infoB;
//IF dest does not exist, YES, newer
if (cachedStat(nativeB, &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
//########################################################################
/**
* 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.
* Pkg-config is line-oriented, so check for newline
*/
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 != '+' && ch != '.')
break;
ret.push_back((char)ch);
pos++;
}
return pos;
}
bool PkgConfig::parseRequires()
{
if (requires.size() == 0)
return true;
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);
}
return true;
}
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;
std::size_t pos = 0;
std::size_t 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::parseLine(const String &lineBuf)
{
parsebuf = (char *)lineBuf.c_str();
parselen = lineBuf.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 %s", subName.c_str(), prefix.c_str());
if (subName == "prefix" && prefix.size()>0)
{
attrVal.append(prefix);
//trace("prefix override:%s", prefix.c_str());
}
else
{
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;
String attrNameL = toLower(attrName);
if (attrNameL == "name")
name = attrVal;
else if (attrNameL == "description")
description = attrVal;
else if (attrNameL == "cflags")
cflags = attrVal;
else if (attrNameL == "libs")
libs = attrVal;
else if (attrNameL == "requires")
requires = attrVal;
else if (attrNameL == "version")
version = attrVal;
//trace("name:'%s' value:'%s'",
// attrName.c_str(), attrVal.c_str());
}
return true;
}
bool PkgConfig::parse(const String &buf)
{
init();
String line;
int lineNr = 0;
for (std::size_t p=0 ; p<buf.size() ; p++)
{
int ch = buf[p];
if (ch == '\n' || ch == '\r')
{
if (!parseLine(line))
return false;
line.clear();
lineNr++;
}
else
{
line.push_back(ch);
}
}
if (line.size()>0)
{
if (!parseLine(line))
return false;
}
parseRequires();
parseVersion();
return true;
}
void PkgConfig::dumpAttrs()
{
//trace("### PkgConfig attributes for %s", fileName.c_str());
std::map<String, String>::iterator iter;
for (iter=attrs.begin() ; iter!=attrs.end() ; iter++)
{
trace(" %s = %s", iter->first.c_str(), iter->second.c_str());
}
}
bool PkgConfig::readFile(const String &fname)
{
fileName = getNativePath(fname);
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;
}
bool PkgConfig::query(const String &pkgName)
{
name = pkgName;
String fname = path;
fname.append("/");
fname.append(name);
fname.append(".pc");
if (!readFile(fname))
{
error("Cannot find package '%s'. Do you have it installed?",
pkgName.c_str());
return false;
}
return true;
}
//########################################################################
//# D E P T O O L
//########################################################################
/**
* Class which holds information for each file.
*/
class FileRec
{
public:
typedef enum
{
UNKNOWN,
CFILE,
HFILE,
OFILE
} FileType;
/**
* Constructor
*/
FileRec()
{ init(); type = UNKNOWN; }
/**
* Copy constructor
*/
FileRec(const FileRec &other)
{ init(); assign(other); }
/**
* Constructor
*/
FileRec(int typeVal)
{ init(); type = typeVal; }
/**
* Assignment operator
*/
FileRec &operator=(const FileRec &other)
{ init(); assign(other); return *this; }
/**
* Destructor
*/
~FileRec()
{}
/**
* Directory part of the file name
*/
String path;
/**
* Base name, sans directory and suffix
*/
String baseName;
/**
* File extension, such as cpp or h
*/
String suffix;
/**
* Type of file: CFILE, HFILE, OFILE
*/
int type;
/**
* Used to list files ref'd by this one
*/
std::map<String, FileRec *> files;
private:
void init()
{
}
void assign(const FileRec &other)
{
type = other.type;
baseName = other.baseName;
suffix = other.suffix;
files = other.files;
}
};
/**
* Simpler dependency record
*/
class DepRec
{
public:
/**
* Constructor
*/
DepRec()
{init();}
/**
* Copy constructor
*/
DepRec(const DepRec &other)
{init(); assign(other);}
/**
* Constructor
*/
DepRec(const String &fname)
{init(); name = fname; }
/**
* Assignment operator
*/
DepRec &operator=(const DepRec &other)
{init(); assign(other); return *this;}
/**
* Destructor
*/
~DepRec()
{}
/**
* Directory part of the file name
*/
String path;
/**
* Base name, without the path and suffix
*/
String name;
/**
* Suffix of the source
*/
String suffix;
/**
* Used to list files ref'd by this one
*/
std::vector<String> files;
private:
void init()
{
}
void assign(const DepRec &other)
{
path = other.path;
name = other.name;
suffix = other.suffix;
files = other.files; //avoid recursion
}
};
class DepTool : public MakeBase
{
public:
/**
* Constructor
*/
DepTool()
{ init(); }
/**
* Copy constructor
*/
DepTool(const DepTool &other)
{ init(); assign(other); }
/**
* Assignment operator
*/
DepTool &operator=(const DepTool &other)
{ init(); assign(other); return *this; }
/**
* Destructor
*/
~DepTool()
{}
/**
* Reset this section of code
*/
virtual void init();
/**
* Reset this section of code
*/
virtual void assign(const DepTool &other)
{
}
/**
* Sets the source directory which will be scanned
*/
virtual void setSourceDirectory(const String &val)
{ sourceDir = val; }
/**
* Returns the source directory which will be scanned
*/
virtual String getSourceDirectory()
{ return sourceDir; }
/**
* Sets the list of files within the directory to analyze
*/
virtual void setFileList(const std::vector<String> &list)
{ fileList = list; }
/**
* Creates the list of all file names which will be
* candidates for further processing. Reads make.exclude
* to see which files for directories to leave out.
*/
virtual bool createFileList();
/**
* Generates the forward dependency list
*/
virtual bool generateDependencies();
/**
* Generates the forward dependency list, saving the file
*/
virtual bool generateDependencies(const String &);
/**
* Load a dependency file
*/
std::vector<DepRec> loadDepFile(const String &fileName);
/**
* Load a dependency file, generating one if necessary
*/
std::vector<DepRec> getDepFile(const String &fileName,
bool forceRefresh);
/**
* 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, const char *key);
/**
*
*/
bool addIncludeFile(FileRec *frec, const String &fname);
/**
*
*/
bool scanFile(const String &fname, FileRec *frec);
/**
*
*/
bool processDependency(FileRec *ofile, FileRec *include);
/**
*
*/
String sourceDir;
/**
*
*/
std::vector<String> fileList;
/**
*
*/
std::vector<String> directories;
/**
* A list of all files which will be processed for
* dependencies.
*/
std::map<String, FileRec *> allFiles;
/**
* The list of .o files, and the
* dependencies upon them.
*/
std::map<String, FileRec *> oFiles;
int depFileSize;
char *depFileBuf;
static const int readBufSize = 8192;
char readBuf[8193];//byte larger
};
/**
* 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 output file list
std::map<String, FileRec *>::iterator iter;
for (iter=oFiles.begin(); iter!=oFiles.end() ; iter++)
delete iter->second;
oFiles.clear();
//allFiles actually contains the master copies. delete them
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;
std::size_t pos = fullname.find_last_of('/');
if (pos != fullname.npos && pos<fullname.size()-1)
{
path = fullname.substr(0, pos);
pos++;
basename = fullname.substr(pos, fullname.size()-pos);
}
else
{
path = "";
basename = fullname;
}
pos = basename.find_last_of('.');
if (pos != basename.npos && pos<basename.size()-1)
{
suffix = basename.substr(pos+1, basename.size()-pos-1);
basename = basename.substr(0, pos);
}
//trace("parsename:%s %s %s", path.c_str(),
// basename.c_str(), suffix.c_str());
}
/**
* Generate our internal file list.
*/
bool DepTool::createFileList()
{
for (std::size_t i=0 ; i<fileList.size() ; i++)
{
String fileName = fileList[i];
//trace("## FileName:%s", fileName.c_str());
String path;
String basename;
String sfx;
parseName(fileName, path, basename, sfx);
if (sfx == "cpp" || sfx == "c" || sfx == "cxx" ||
sfx == "cc" || sfx == "CC")
{
FileRec *fe = new FileRec(FileRec::CFILE);
fe->path = path;
fe->baseName = basename;
fe->suffix = sfx;
allFiles[fileName] = fe;
}
else if (sfx == "h" || sfx == "hh" ||
sfx == "hpp" || sfx == "hxx")
{
FileRec *fe = new FileRec(FileRec::HFILE);
fe->path = path;
fe->baseName = basename;
fe->suffix = sfx;
allFiles[fileName] = fe;
}
}
if (!listDirectories(sourceDir, "", directories))
return false;
return true;
}
/**
* Get a character from the buffer at pos. If out of range,
* return -1 for safety
*/
int DepTool::get(int pos)
{
if (pos>depFileSize)
return -1;
return depFileBuf[pos];
}
/**
* Skip over all whitespace characters beginning at pos. Return
* the position of the first non-whitespace character.
*/
int DepTool::skipwhite(int pos)
{
while (pos < depFileSize)
{
int ch = get(pos);
if (ch < 0)
break;
if (!isspace(ch))
break;
pos++;
}
return pos;
}
/**
* Parse the buffer beginning at pos, for a word. Fill
* 'ret' with the result. Return the position after the
* word.
*/
int DepTool::getword(int pos, String &ret)
{
while (pos < depFileSize)
{
int ch = get(pos);
if (ch < 0)
break;
if (isspace(ch))
break;
ret.push_back((char)ch);
pos++;
}
return pos;
}
/**
* Return whether the sequence of characters in the buffer
* beginning at pos match the key, for the length of the key
*/
bool DepTool::sequ(int pos, const 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)
{
//# if the name is an exact match to a path name
//# in allFiles, like "myinc.h"
std::map<String, FileRec *>::iterator iter =
allFiles.find(iname);
if (iter != allFiles.end()) //already exists
{
//h file in same dir
FileRec *other = iter->second;
//trace("local: '%s'", iname.c_str());
frec->files[iname] = other;
return true;
}
else
{
//## Ok, it was not found directly
//look in other dirs
std::vector<String>::iterator diter;
for (diter=directories.begin() ;
diter!=directories.end() ; diter++)
{
String dfname = *diter;
dfname.append("/");
dfname.append(iname);
URI fullPathURI(dfname); //normalize path name
String fullPath = fullPathURI.getPath();
if (fullPath[0] == '/')
fullPath = fullPath.substr(1);
//trace("Normalized %s to %s", dfname.c_str(), fullPath.c_str());
iter = allFiles.find(fullPath);
if (iter != allFiles.end())
{
FileRec *other = iter->second;
//trace("other: '%s'", iname.c_str());
frec->files[fullPath] = 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 (!feof(f))
{
int nrbytes = fread(readBuf, 1, readBufSize, f);
readBuf[nrbytes] = '\0';
buf.append(readBuf);
}
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)
{
std::map<String, FileRec *>::iterator iter;
for (iter=include->files.begin() ; iter!=include->files.end() ; iter++)
{
String fname = iter->first;
if (ofile->files.find(fname) != ofile->files.end())
{
//trace("file '%s' already seen", fname.c_str());
continue;
}
FileRec *child = iter->second;
ofile->files[fname] = child;
processDependency(ofile, child);
}
return true;
}
/**
* Generate the file dependency list.
*/
bool DepTool::generateDependencies()
{
std::map<String, FileRec *>::iterator iter;
//# First pass. Scan for all includes
for (iter=allFiles.begin() ; iter!=allFiles.end() ; iter++)
{
FileRec *frec = iter->second;
if (!scanFile(iter->first, frec))
{
//quit?
}
}
//# Second pass. Scan for all includes
for (iter=allFiles.begin() ; iter!=allFiles.end() ; iter++)
{
FileRec *include = iter->second;
if (include->type == FileRec::CFILE)
{
//String cFileName = iter->first;
FileRec *ofile = new FileRec(FileRec::OFILE);
ofile->path = include->path;
ofile->baseName = include->baseName;
ofile->suffix = include->suffix;
String fname = include->path;
if (fname.size()>0)
fname.append("/");
fname.append(include->baseName);
fname.append(".o");
oFiles[fname] = ofile;
//add the .c file first? no, don't
//ofile->files[cFileName] = include;
//trace("ofile:%s", fname.c_str());
processDependency(ofile, include);
}
}
return true;
}
/**
* High-level call to generate deps and optionally save them
*/
bool DepTool::generateDependencies(const String &fileName)
{
if (!createFileList())
return false;
if (!generateDependencies())
return false;
if (!saveDepFile(fileName))
return false;
return true;
}
/**
* This saves the dependency cache.
*/
bool DepTool::saveDepFile(const String &fileName)
{
time_t tim;
time(&tim);
FILE *f = fopen(fileName.c_str(), "w");
if (!f)
{
trace("cannot open '%s' for writing", fileName.c_str());
}
fprintf(f, "<?xml version='1.0'?>\n");
fprintf(f, "<!--\n");
fprintf(f, "########################################################\n");
fprintf(f, "## File: build.dep\n");
fprintf(f, "## Generated by BuildTool at :%s", ctime(&tim));
fprintf(f, "########################################################\n");
fprintf(f, "-->\n");
fprintf(f, "<dependencies source='%s'>\n\n", sourceDir.c_str());
std::map<String, FileRec *>::iterator iter;
for (iter=oFiles.begin() ; iter!=oFiles.end() ; iter++)
{
FileRec *frec = iter->second;
if (frec->type == FileRec::OFILE)
{
fprintf(f, "<object path='%s' name='%s' suffix='%s'>\n",
frec->path.c_str(), frec->baseName.c_str(), frec->suffix.c_str());
std::map<String, FileRec *>::iterator citer;
for (citer=frec->files.begin() ; citer!=frec->files.end() ; citer++)
{
String cfname = citer->first;
fprintf(f, " <dep name='%s'/>\n", cfname.c_str());
}
fprintf(f, "</object>\n\n");
}
}
fprintf(f, "</dependencies>\n");
fprintf(f, "\n");
fprintf(f, "<!--\n");
fprintf(f, "########################################################\n");
fprintf(f, "## E N D\n");
fprintf(f, "########################################################\n");
fprintf(f, "-->\n");
fclose(f);
return true;
}
/**
* This loads the dependency cache.
*/
std::vector<DepRec> DepTool::loadDepFile(const String &depFile)
{
std::vector<DepRec> result;
Parser parser;
Element *root = parser.parseFile(depFile.c_str());
if (!root)
{
//error("Could not open %s for reading", depFile.c_str());
return result;
}
if (root->getChildren().size()==0 ||
root->getChildren()[0]->getName()!="dependencies")
{
error("loadDepFile: main xml element should be <dependencies>");
delete root;
return result;
}
//########## Start parsing
Element *depList = root->getChildren()[0];
std::vector<Element *> objects = depList->getChildren();
for (std::size_t i=0 ; i<objects.size() ; i++)
{
Element *objectElem = objects[i];
String tagName = objectElem->getName();
if (tagName != "object")
{
error("loadDepFile: <dependencies> should have only <object> children");
return result;
}
String objName = objectElem->getAttribute("name");
//trace("object:%s", objName.c_str());
DepRec depObject(objName);
depObject.path = objectElem->getAttribute("path");
depObject.suffix = objectElem->getAttribute("suffix");
//########## DESCRIPTION
std::vector<Element *> depElems = objectElem->getChildren();
for (std::size_t i=0 ; i<depElems.size() ; i++)
{
Element *depElem = depElems[i];
tagName = depElem->getName();
if (tagName != "dep")
{
error("loadDepFile: <object> should have only <dep> children");
return result;
}
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<DepRec>::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<DepRec> DepTool::getDepFile(const String &depFile,
bool forceRefresh)
{
std::vector<DepRec> result;
if (forceRefresh)
{
generateDependencies(depFile);
result = loadDepFile(depFile);
}
else
{
//try once
result = loadDepFile(depFile);
if (result.size() == 0)
{
//fail? try again
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_CC,
TASK_COPY,
TASK_CXXTEST_PART,
TASK_CXXTEST_ROOT,
TASK_CXXTEST_RUN,
TASK_DELETE,
TASK_ECHO,
TASK_JAR,
TASK_JAVAC,
TASK_LINK,
TASK_MAKEFILE,
TASK_MKDIR,
TASK_MSGFMT,
TASK_PKG_CONFIG,
TASK_RANLIB,
TASK_RC,
TASK_SHAREDLIB,
TASK_STATICLIB,
TASK_STRIP,
TASK_TOUCH,
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, int lineNr);
protected:
void init()
{
type = TASK_NONE;
name = "none";
}
void assign(const Task &other)
{
type = other.type;
name = other.name;
}
/**
* Show task status
*/
void taskstatus(const char *fmt, ...)
{
va_list args;
va_start(args,fmt);
fprintf(stdout, " %s : ", name.c_str());
vfprintf(stdout, fmt, args);
fprintf(stdout, "\n");
va_end(args) ;
}
String getAttribute(Element *elem, const String &attrName)
{
String str;
return str;
}
MakeBase &parent;
int type;
String name;
};
/**
* 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";
}
virtual ~TaskCC()
{}
virtual bool isExcludedInc(const String &dirname)
{
for (std::size_t i=0 ; i<excludeInc.size() ; i++)
{
String fname = excludeInc[i];
if (fname == dirname)
return true;
}
return false;
}
virtual bool execute()
{
//evaluate our parameters
String command = parent.eval(commandOpt, "gcc");
String ccCommand = parent.eval(ccCommandOpt, "gcc");
String cxxCommand = parent.eval(cxxCommandOpt, "g++");
String source = parent.eval(sourceOpt, ".");
String dest = parent.eval(destOpt, ".");
String ccflags = parent.eval(flagsOpt, "");
String cxxflags = parent.eval(cxxflagsOpt, "");
String defines = parent.eval(definesOpt, "");
String includes = parent.eval(includesOpt, "");
bool continueOnError = parent.evalBool(continueOnErrorOpt, true);
bool refreshCache = parent.evalBool(refreshCacheOpt, false);
if (!listFiles(parent, fileSet))
return false;
FILE *f = NULL;
f = fopen("compile.lst", "w");
//refreshCache is probably false here, unless specified otherwise
String fullName = parent.resolve("build.dep");
if (refreshCache || isNewerThan(parent.getURI().getPath(), fullName))
{
taskstatus("regenerating C/C++ dependency cache");
refreshCache = true;
}
DepTool depTool;
depTool.setSourceDirectory(source);
depTool.setFileList(fileSet.getFiles());
std::vector<DepRec> deps =
depTool.getDepFile("build.dep", refreshCache);
String incs;
incs.append("-I");
incs.append(parent.resolve("."));
incs.append(" ");
if (includes.size()>0)
{
incs.append(includes);
incs.append(" ");
}
std::set<String> paths;
std::vector<DepRec>::iterator viter;
for (viter=deps.begin() ; viter!=deps.end() ; viter++)
{
DepRec dep = *viter;
if (dep.path.size()>0)
paths.insert(dep.path);
}
if (source.size()>0)
{
incs.append(" -I");
incs.append(parent.resolve(source));
incs.append(" ");
}
std::set<String>::iterator setIter;
for (setIter=paths.begin() ; setIter!=paths.end() ; setIter++)
{
String dirName = *setIter;
//check excludeInc to see if we dont want to include this dir
if (isExcludedInc(dirName))
continue;
incs.append(" -I");
String dname;
if (source.size()>0)
{
dname.append(source);
dname.append("/");
}
dname.append(dirName);
incs.append(parent.resolve(dname));
}
// First create all directories, fails if done in OpenMP parallel loop below... goes superfast anyway, so don't optimize
for (std::size_t fi = 0; fi < deps.size() ; ++fi)
{
DepRec dep = deps[fi];
//## Make paths
String destPath = dest;
if (dep.path.size()>0)
{
destPath.append("/");
destPath.append(dep.path);
}
//## Make sure destination directory exists
if (!createDirectory(destPath))
{
taskstatus("problem creating folder: %s", destPath.c_str());
if (f) {
fclose(f);
}
return false;
}
}
/**
* Compile each of the C files that need it
*/
bool errorOccurred = false;
#ifdef _OPENMP
taskstatus("compile with %d threads in parallel", numThreads);
# pragma omp parallel for num_threads(numThreads)
#endif
for (std::size_t fi = 0; fi < deps.size() ; ++fi)
{
DepRec dep = deps[fi];
//## Select command
String sfx = dep.suffix;
String command = ccCommand;
String flags = ccflags;
if (sfx == "cpp" || sfx == "cxx" || sfx == "c++" ||
sfx == "cc" || sfx == "CC")
{
command = cxxCommand;
flags += " " + cxxflags;
}
//## 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);
}
//## Check whether it needs to be done
String destName;
if (destPath.size()>0)
{
destName.append(destPath);
destName.append("/");
}
destName.append(dep.name);
destName.append(".o");
String destFullName = parent.resolve(destName);
String srcName;
if (srcPath.size()>0)
{
srcName.append(srcPath);
srcName.append("/");
}
srcName.append(dep.name);
srcName.append(".");
srcName.append(dep.suffix);
String srcFullName = parent.resolve(srcName);
bool compileMe = false;
//# First we check if the source is newer than the .o
if (isNewerThan(srcFullName, destFullName))
{
// taskstatus("compile of %s (req. by: %s)",
// destFullName.c_str(), srcFullName.c_str());
fprintf(stdout, "compile %s\n", srcFullName.c_str());
compileMe = true;
}
else
{
//# secondly, we check if any of the included dependencies
//# of the .c/.cpp is newer than the .o
for (std::size_t i=0 ; i<dep.files.size() ; i++)
{
String depName;
if (source.size()>0)
{
depName.append(source);
depName.append("/");
}
depName.append(dep.files[i]);
String depFullName = parent.resolve(depName);
bool depRequires = isNewerThan(depFullName, destFullName);
//trace("%d %s %s\n", depRequires,
// destFullName.c_str(), depFullName.c_str());
if (depRequires)
{
taskstatus("compile %s (%s modified)",
srcFullName.c_str(), depFullName.c_str());
compileMe = true;
break;
}
}
}
if (!compileMe)
{
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;
bool ret = executeCommand(cmd.c_str(), "", outString, errString);
if (f)
{
fprintf(f, "########################### File : %s\n",
srcFullName.c_str());
fprintf(f, "#### COMMAND ###\n");
int col = 0;
for (std::size_t i = 0 ; i < cmd.size() ; i++)
{
char ch = cmd[i];
if (isspace(ch) && col > 63)
{
fputc('\n', f);
col = 0;
}
else
{
fputc(ch, f);
col++;
}
if (col > 76)
{
fputc('\n', f);
col = 0;
}
}
fprintf(f, "\n");
fprintf(f, "#### STDOUT ###\n%s\n", outString.c_str());
fprintf(f, "#### STDERR ###\n%s\n\n", errString.c_str());
fflush(f);
}
if (!ret) {
error("problem compiling: %s", errString.c_str());
errorOccurred = true;
} else if (!errString.empty()) {
fprintf(stdout, "STDERR: \n%s\n", errString.c_str());
}
if (errorOccurred && !continueOnError) {
#ifndef _OPENMP // figure out a way to break the loop here with OpenMP
break;
#endif
}
removeFromStatCache(getNativePath(destFullName));
}
if (f)
{
fclose(f);
}
return !errorOccurred;
}
virtual bool parse(Element *elem)
{
String s;
if (!parent.getAttribute(elem, "command", commandOpt))
return false;
if (commandOpt.size()>0)
{ cxxCommandOpt = ccCommandOpt = commandOpt; }
if (!parent.getAttribute(elem, "cc", ccCommandOpt))
return false;
if (!parent.getAttribute(elem, "cxx", cxxCommandOpt))
return false;
if (!parent.getAttribute(elem, "destdir", destOpt))
return false;
if (!parent.getAttribute(elem, "continueOnError", continueOnErrorOpt))
return false;
if (!parent.getAttribute(elem, "refreshCache", refreshCacheOpt))
return false;
std::vector<Element *> children = elem->getChildren();
for (std::size_t i=0 ; i<children.size() ; i++)
{
Element *child = children[i];
String tagName = child->getName();
if (tagName == "flags")
{
if (!parent.getValue(child, flagsOpt))
return false;
flagsOpt = strip(flagsOpt);
}
else if (tagName == "cxxflags")
{
if (!parent.getValue(child, cxxflagsOpt))
return false;
cxxflagsOpt = strip(cxxflagsOpt);
}
else if (tagName == "includes")
{
if (!parent.getValue(child, includesOpt))
return false;
includesOpt = strip(includesOpt);
}
else if (tagName == "defines")
{
if (!parent.getValue(child, definesOpt))
return false;
definesOpt = strip(definesOpt);
}
else if (tagName == "fileset")
{
if (!parseFileSet(child, parent, fileSet))
return false;
sourceOpt = fileSet.getDirectory();
}
else if (tagName == "excludeinc")
{
if (!parseFileList(child, parent, excludeInc))
return false;
}
}
return true;
}
protected:
String commandOpt;
String ccCommandOpt;
String cxxCommandOpt;
String sourceOpt;
String destOpt;
String flagsOpt;
String cxxflagsOpt;
String definesOpt;
String includesOpt;
String continueOnErrorOpt;
String refreshCacheOpt;
FileSet fileSet;
FileList excludeInc;
};
/**
*
*/
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;
haveFileSet = false;
}
virtual ~TaskCopy()
{}
virtual bool execute()
{
String fileName = parent.eval(fileNameOpt , ".");
String toFileName = parent.eval(toFileNameOpt , ".");
String toDirName = parent.eval(toDirNameOpt , ".");
bool verbose = parent.evalBool(verboseOpt, false);
switch (cptype)
{
case CP_TOFILE:
{
if (fileName.size()>0)
{
taskstatus("%s to %s",
fileName.c_str(), toFileName.c_str());
String fullSource = parent.resolve(fileName);
String fullDest = parent.resolve(toFileName);
if (verbose)
taskstatus("copy %s to file %s", fullSource.c_str(),
fullDest.c_str());
if (!isRegularFile(fullSource))
{
error("copy : file %s does not exist", fullSource.c_str());
return false;
}
if (!isNewerThan(fullSource, fullDest))
{
taskstatus("skipped");
return true;
}
if (!copyFile(fullSource, fullDest))
return false;
taskstatus("1 file copied");
}
return true;
}
case CP_TODIR:
{
if (haveFileSet)
{
if (!listFiles(parent, fileSet))
return false;
String fileSetDir = parent.eval(fileSet.getDirectory(), ".");
taskstatus("%s to %s",
fileSetDir.c_str(), toDirName.c_str());
int nrFiles = 0;
for (std::size_t i=0 ; i<fileSet.size() ; i++)
{
String fileName = fileSet[i];
String sourcePath;
if (fileSetDir.size()>0)
{
sourcePath.append(fileSetDir);
sourcePath.append("/");
}
sourcePath.append(fileName);
String fullSource = parent.resolve(sourcePath);
//Get the immediate parent directory's base name
String baseFileSetDir = fileSetDir;
std::size_t 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());
if (verbose)
taskstatus("copy %s to new dir : %s",
fullSource.c_str(), fullDest.c_str());
if (!isNewerThan(fullSource, fullDest))
{
if (verbose)
taskstatus("copy skipping %s", fullSource.c_str());
continue;
}
if (!copyFile(fullSource, fullDest))
return false;
nrFiles++;
}
taskstatus("%d file(s) copied", nrFiles);
}
else //file source
{
//For file->dir we want only the basename of
//the source appended to the dest dir
taskstatus("%s to %s",
fileName.c_str(), toDirName.c_str());
String baseName = fileName;
std::size_t pos = baseName.find_last_of('/');
if (pos!=baseName.npos && pos<baseName.size()-1)
baseName = baseName.substr(pos+1, baseName.size());
String fullSource = parent.resolve(fileName);
String destPath;
if (toDirName.size()>0)
{
destPath.append(toDirName);
destPath.append("/");
}
destPath.append(baseName);
String fullDest = parent.resolve(destPath);
if (verbose)
taskstatus("file %s to new dir : %s", fullSource.c_str(),
fullDest.c_str());
if (!isRegularFile(fullSource))
{
error("copy : file %s does not exist", fullSource.c_str());
return false;
}
if (!isNewerThan(fullSource, fullDest))
{
taskstatus("skipped");
return true;
}
if (!copyFile(fullSource, fullDest))
return false;
taskstatus("1 file copied");
}
return true;
}
}
return true;
}
virtual bool parse(Element *elem)
{
if (!parent.getAttribute(elem, "file", fileNameOpt))
return false;
if (!parent.getAttribute(elem, "tofile", toFileNameOpt))
return false;
if (toFileNameOpt.size() > 0)
cptype = CP_TOFILE;
if (!parent.getAttribute(elem, "todir", toDirNameOpt))
return false;
if (toDirNameOpt.size() > 0)
cptype = CP_TODIR;
if (!parent.getAttribute(elem, "verbose", verboseOpt))
return false;
haveFileSet = false;
std::vector<Element *> children = elem->getChildren();
for (std::size_t i=0 ; i<children.size() ; i++)
{
Element *child = children[i];
String tagName = child->getName();
if (tagName == "fileset")
{
if (!parseFileSet(child, parent, fileSet))
{
error("problem getting fileset");
return false;
}
haveFileSet = true;
}
}
//Perform validity checks
if (fileNameOpt.size()>0 && fileSet.size()>0)
{
error("<copy> can only have one of : file= and <fileset>");
return false;
}
if (toFileNameOpt.size()>0 && toDirNameOpt.size()>0)
{
error("<copy> can only have one of : tofile= or todir=");
return false;
}
if (haveFileSet && toDirNameOpt.size()==0)
{
error("a <copy> task with a <fileset> must have : todir=");
return false;
}
if (cptype == CP_TOFILE && fileNameOpt.size()==0)
{
error("<copy> tofile= must be associated with : file=");
return false;
}
if (cptype == CP_TODIR && fileNameOpt.size()==0 && !haveFileSet)
{
error("<copy> todir= must be associated with : file= or <fileset>");
return false;
}
return true;
}
private:
int cptype;
bool haveFileSet;
FileSet fileSet;
String fileNameOpt;
String toFileNameOpt;
String toDirNameOpt;
String verboseOpt;
};
/**
* Generate CxxTest files
*/
class TaskCxxTestPart: public Task
{
public:
TaskCxxTestPart(MakeBase &par) : Task(par)
{
type = TASK_CXXTEST_PART;
name = "cxxtestpart";
}
virtual ~TaskCxxTestPart()
{}
virtual bool execute()
{
if (!listFiles(parent, fileSet))
return false;
String fileSetDir = parent.eval(fileSet.getDirectory(), ".");
String fullDest = parent.resolve(parent.eval(destPathOpt, "."));
String cmd = parent.eval(commandOpt, "cxxtestgen.py");
cmd.append(" --part -o ");
cmd.append(fullDest);
unsigned int newFiles = 0;
for (std::size_t i=0 ; i<fileSet.size() ; i++)
{
String fileName = fileSet[i];
if (getSuffix(fileName) != "h")
continue;
String sourcePath;
if (fileSetDir.size()>0)
{
sourcePath.append(fileSetDir);
sourcePath.append("/");
}
sourcePath.append(fileName);
String fullSource = parent.resolve(sourcePath);
cmd.append(" ");
cmd.append(fullSource);
if (isNewerThan(fullSource, fullDest)) newFiles++;
}
if (newFiles>0) {
size_t const lastSlash = fullDest.find_last_of('/');
if (lastSlash != fullDest.npos) {
String directory(fullDest, 0, lastSlash);
if (!createDirectory(directory))
return false;
}
String outString, errString;
if (!executeCommand(cmd.c_str(), "", outString, errString))
{
error("<cxxtestpart> problem: %s", errString.c_str());
return false;
}
removeFromStatCache(getNativePath(fullDest));
}
return true;
}
virtual bool parse(Element *elem)
{
if (!parent.getAttribute(elem, "command", commandOpt))
return false;
if (!parent.getAttribute(elem, "out", destPathOpt))
return false;
std::vector<Element *> children = elem->getChildren();
for (std::size_t i=0 ; i<children.size() ; i++)
{
Element *child = children[i];
String tagName = child->getName();
if (tagName == "fileset")
{
if (!parseFileSet(child, parent, fileSet))
return false;
}
}
return true;
}
private:
String commandOpt;
String destPathOpt;
FileSet fileSet;
};
/**
* Generate the CxxTest root file
*/
class TaskCxxTestRoot: public Task
{
public:
TaskCxxTestRoot(MakeBase &par) : Task(par)
{
type = TASK_CXXTEST_ROOT;
name = "cxxtestroot";
}
virtual ~TaskCxxTestRoot()
{}
virtual bool execute()
{
if (!listFiles(parent, fileSet))
return false;
String fileSetDir = parent.eval(fileSet.getDirectory(), ".");
unsigned int newFiles = 0;
String fullDest = parent.resolve(parent.eval(destPathOpt, "."));
String cmd = parent.eval(commandOpt, "cxxtestgen.py");
cmd.append(" --root -o ");
cmd.append(fullDest);
String templateFile = parent.eval(templateFileOpt, "");
if (templateFile.size()>0) {
String fullTemplate = parent.resolve(templateFile);
cmd.append(" --template=");
cmd.append(fullTemplate);
if (isNewerThan(fullTemplate, fullDest)) newFiles++;
}
for (std::size_t i=0 ; i<fileSet.size() ; i++)
{
String fileName = fileSet[i];
if (getSuffix(fileName) != "h")
continue;
String sourcePath;
if (fileSetDir.size()>0)
{
sourcePath.append(fileSetDir);
sourcePath.append("/");
}
sourcePath.append(fileName);
String fullSource = parent.resolve(sourcePath);
cmd.append(" ");
cmd.append(fullSource);
if (isNewerThan(fullSource, fullDest)) newFiles++;
}
if (newFiles>0) {
size_t const lastSlash = fullDest.find_last_of('/');
if (lastSlash != fullDest.npos) {
String directory(fullDest, 0, lastSlash);
if (!createDirectory(directory))
return false;
}
String outString, errString;
if (!executeCommand(cmd.c_str(), "", outString, errString))
{
error("<cxxtestroot> problem: %s", errString.c_str());
return false;
}
removeFromStatCache(getNativePath(fullDest));
}
return true;
}
virtual bool parse(Element *elem)
{
if (!parent.getAttribute(elem, "command", commandOpt))
return false;
if (!parent.getAttribute(elem, "template", templateFileOpt))
return false;
if (!parent.getAttribute(elem, "out", destPathOpt))
return false;
std::vector<Element *> children = elem->getChildren();
for (std::size_t i=0 ; i<children.size() ; i++)
{
Element *child = children[i];
String tagName = child->getName();
if (tagName == "fileset")
{
if (!parseFileSet(child, parent, fileSet))
return false;
}
}
return true;
}
private:
String commandOpt;
String templateFileOpt;
String destPathOpt;
FileSet fileSet;
};
/**
* Execute the CxxTest test executable
*/
class TaskCxxTestRun: public Task
{
public:
TaskCxxTestRun(MakeBase &par) : Task(par)
{
type = TASK_CXXTEST_RUN;
name = "cxxtestrun";
}
virtual ~TaskCxxTestRun()
{}
virtual bool execute()
{
unsigned int newFiles = 0;
String workingDir = parent.resolve(parent.eval(workingDirOpt, "inkscape"));
String rawCmd = parent.eval(commandOpt, "build/cxxtests");
String cmdExe;
if (fileExists(rawCmd)) {
cmdExe = rawCmd;
} else if (fileExists(rawCmd + ".exe")) {
cmdExe = rawCmd + ".exe";
} else {
error("<cxxtestrun> problem: cxxtests executable not found! (command=\"%s\")", rawCmd.c_str());
}
// Note that the log file names are based on the exact name used to call cxxtests (it uses argv[0] + ".log"/".xml")
if (isNewerThan(cmdExe, rawCmd + ".log") || isNewerThan(cmdExe, rawCmd + ".xml")) newFiles++;
// Prepend the necessary ../'s
String cmd = rawCmd;
unsigned int workingDirDepth = 0;
bool wasSlash = true;
for(size_t i=0; i<workingDir.size(); i++) {
// This assumes no . and .. parts
if (wasSlash && workingDir[i]!='/') workingDirDepth++;
wasSlash = workingDir[i] == '/';
}
for(size_t i=0; i<workingDirDepth; i++) {
cmd = "../" + cmd;
}
if (newFiles>0) {
char olddir[1024];
if (workingDir.size()>0) {
// TODO: Double-check usage of getcwd and handle chdir errors
getcwd(olddir, 1024);
chdir(workingDir.c_str());
}
String outString;
if (!executeCommand(cmd.c_str(), "", outString, outString))
{
error("<cxxtestrun> problem: %s", outString.c_str());
return false;
}
if (workingDir.size()>0) {
// TODO: Handle errors?
chdir(olddir);
}
removeFromStatCache(getNativePath(cmd + ".log"));
removeFromStatCache(getNativePath(cmd + ".xml"));
}
return true;
}
virtual bool parse(Element *elem)
{
if (!parent.getAttribute(elem, "command", commandOpt))
return false;
if (!parent.getAttribute(elem, "workingdir", workingDirOpt))
return false;
return true;
}
private:
String commandOpt;
String workingDirOpt;
};
/**
*
*/
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;
}
virtual ~TaskDelete()
{}
virtual bool execute()
{
String dirName = parent.eval(dirNameOpt, ".");
String fileName = parent.eval(fileNameOpt, ".");
bool verbose = parent.evalBool(verboseOpt, false);
bool quiet = parent.evalBool(quietOpt, false);
bool failOnError = parent.evalBool(failOnErrorOpt, true);
switch (delType)
{
case DEL_FILE:
{
taskstatus("file: %s", fileName.c_str());
String fullName = parent.resolve(fileName);
char *fname = (char *)fullName.c_str();
if (!quiet && verbose)
taskstatus("path: %s", fname);
if (failOnError && !removeFile(fullName))
{
//error("Could not delete file '%s'", fullName.c_str());
return false;
}
return true;
}
case DEL_DIR:
{
taskstatus("dir: %s", dirName.c_str());
String fullDir = parent.resolve(dirName);
if (!quiet && verbose)
taskstatus("path: %s", fullDir.c_str());
if (failOnError && !removeDirectory(fullDir))
{
//error("Could not delete directory '%s'", fullDir.c_str());
return false;
}
return true;
}
}
return true;
}
virtual bool parse(Element *elem)
{
if (!parent.getAttribute(elem, "file", fileNameOpt))
return false;
if (fileNameOpt.size() > 0)
delType = DEL_FILE;
if (!parent.getAttribute(elem, "dir", dirNameOpt))
return false;
if (dirNameOpt.size() > 0)
delType = DEL_DIR;
if (fileNameOpt.size()>0 && dirNameOpt.size()>0)
{
error("<delete> can have one attribute of file= or dir=");
return false;
}
if (fileNameOpt.size()==0 && dirNameOpt.size()==0)
{
error("<delete> must have one attribute of file= or dir=");
return false;
}
if (!parent.getAttribute(elem, "verbose", verboseOpt))
return false;
if (!parent.getAttribute(elem, "quiet", quietOpt))
return false;
if (!parent.getAttribute(elem, "failonerror", failOnErrorOpt))
return false;
return true;
}
private:
int delType;
String dirNameOpt;
String fileNameOpt;
String verboseOpt;
String quietOpt;
String failOnErrorOpt;
};
/**
* Send a message to stdout
*/
class TaskEcho : public Task
{
public:
TaskEcho(MakeBase &par) : Task(par)
{ type = TASK_ECHO; name = "echo"; }
virtual ~TaskEcho()
{}
virtual bool execute()
{
//let message have priority over text
String message = parent.eval(messageOpt, "");
String text = parent.eval(textOpt, "");
if (message.size() > 0)
{
fprintf(stdout, "%s\n", message.c_str());
}
else if (text.size() > 0)
{
fprintf(stdout, "%s\n", text.c_str());
}
return true;
}
virtual bool parse(Element *elem)
{
if (!parent.getValue(elem, textOpt))
return false;
textOpt = leftJustify(textOpt);
if (!parent.getAttribute(elem, "message", messageOpt))
return false;
return true;
}
private:
String messageOpt;
String textOpt;
};
/**
*
*/
class TaskJar : public Task
{
public:
TaskJar(MakeBase &par) : Task(par)
{ type = TASK_JAR; name = "jar"; }
virtual ~TaskJar()
{}
virtual bool execute()
{
String command = parent.eval(commandOpt, "jar");
String basedir = parent.eval(basedirOpt, ".");
String destfile = parent.eval(destfileOpt, ".");
String cmd = command;
cmd.append(" -cf ");
cmd.append(destfile);
cmd.append(" -C ");
cmd.append(basedir);
cmd.append(" .");
String execCmd = cmd;
String outString, errString;
bool ret = executeCommand(execCmd.c_str(), "", outString, errString);
if (!ret)
{
error("<jar> command '%s' failed :\n %s",
execCmd.c_str(), errString.c_str());
return false;
}
removeFromStatCache(getNativePath(destfile));
return true;
}
virtual bool parse(Element *elem)
{
if (!parent.getAttribute(elem, "command", commandOpt))
return false;
if (!parent.getAttribute(elem, "basedir", basedirOpt))
return false;
if (!parent.getAttribute(elem, "destfile", destfileOpt))
return false;
if (basedirOpt.size() == 0 || destfileOpt.size() == 0)
{
error("<jar> required both basedir and destfile attributes to be set");
return false;
}
return true;
}
private:
String commandOpt;
String basedirOpt;
String destfileOpt;
};
/**
*
*/
class TaskJavac : public Task
{
public:
TaskJavac(MakeBase &par) : Task(par)
{
type = TASK_JAVAC; name = "javac";
}
virtual ~TaskJavac()
{}
virtual bool execute()
{
String command = parent.eval(commandOpt, "javac");
String srcdir = parent.eval(srcdirOpt, ".");
String destdir = parent.eval(destdirOpt, ".");
String target = parent.eval(targetOpt, "");
std::vector<String> fileList;
if (!listFiles(srcdir, "", fileList))
{
return false;
}
String cmd = command;
cmd.append(" -d ");
cmd.append(destdir);
cmd.append(" -classpath ");
cmd.append(destdir);
cmd.append(" -sourcepath ");
cmd.append(srcdir);
cmd.append(" ");
if (target.size()>0)
{
cmd.append(" -target ");
cmd.append(target);
cmd.append(" ");
}
String fname = "javalist.btool";
FILE *f = fopen(fname.c_str(), "w");
int count = 0;
for (std::size_t i=0 ; i<fileList.size() ; i++)
{
String fname = fileList[i];
String srcName = fname;
if (fname.size()<6) //x.java
continue;
if (fname.compare(fname.size()-5, 5, ".java") != 0)
continue;
String baseName = fname.substr(0, fname.size()-5);
String destName = baseName;
destName.append(".class");
String fullSrc = srcdir;
fullSrc.append("/");
fullSrc.append(fname);
String fullDest = destdir;
fullDest.append("/");
fullDest.append(destName);
//trace("fullsrc:%s fulldest:%s", fullSrc.c_str(), fullDest.c_str());
if (!isNewerThan(fullSrc, fullDest))
continue;
count++;
fprintf(f, "%s\n", fullSrc.c_str());
}
fclose(f);
if (!count)
{
taskstatus("nothing to do");
return true;
}
taskstatus("compiling %d files", count);
String execCmd = cmd;
execCmd.append("@");
execCmd.append(fname);
String outString, errString;
bool ret = executeCommand(execCmd.c_str(), "", outString, errString);
if (!ret)
{
error("<javac> command '%s' failed :\n %s",
execCmd.c_str(), errString.c_str());
return false;
}
// TODO:
//removeFromStatCache(getNativePath(........));
return true;
}
virtual bool parse(Element *elem)
{
if (!parent.getAttribute(elem, "command", commandOpt))
return false;
if (!parent.getAttribute(elem, "srcdir", srcdirOpt))
return false;
if (!parent.getAttribute(elem, "destdir", destdirOpt))
return false;
if (srcdirOpt.size() == 0 || destdirOpt.size() == 0)
{
error("<javac> required both srcdir and destdir attributes to be set");
return false;
}
if (!parent.getAttribute(elem, "target", targetOpt))
return false;
return true;
}
private:
String commandOpt;
String srcdirOpt;
String destdirOpt;
String targetOpt;
};
/**
*
*/
class TaskLink : public Task
{
public:
TaskLink(MakeBase &par) : Task(par)
{
type = TASK_LINK; name = "link";
}
virtual ~TaskLink()
{}
virtual void UniqueParams(std::string& source) {
size_t prev = 0;
size_t next = 0;
std::list<std::string> thelist;
std::list<std::string>::iterator it;
std::string tstring=" ";
source +=std::string(" "); // else the last token may be lost
while ((next = source.find_first_of(" ", prev)) != std::string::npos){
if (next - prev != 0){
thelist.push_back(source.substr(prev, next - prev));
}
prev = next + 1;
}
thelist.sort();
source.clear();
source +=std::string(" ");
for(it=thelist.begin(); it!=thelist.end();it++){
if(*it != tstring){
tstring = *it;
source +=tstring;
source +=std::string(" ");
}
}
}
virtual bool execute()
{
String command = parent.eval(commandOpt, "g++");
String fileName = parent.eval(fileNameOpt, "");
String flags = parent.eval(flagsOpt, "");
String libs = parent.eval(libsOpt, "");
bool doStrip = parent.evalBool(doStripOpt, false);
String symFileName = parent.eval(symFileNameOpt, "");
String stripCommand = parent.eval(stripCommandOpt, "strip");
String objcopyCommand = parent.eval(objcopyCommandOpt, "objcopy");
if (!listFiles(parent, fileSet))
return false;
String fileSetDir = parent.eval(fileSet.getDirectory(), ".");
//trace("%d files in %s", fileSet.size(), fileSetDir.c_str());
bool doit = false;
String fullTarget = parent.resolve(fileName);
String cmd = command;
cmd.append(" -o ");
cmd.append(fullTarget);
cmd.append(" ");
cmd.append(flags);
for (std::size_t i=0 ; i<fileSet.size() ; i++)
{
cmd.append(" ");
String obj;
if (fileSetDir.size()>0)
{
obj.append(fileSetDir);
obj.append("/");
}
obj.append(fileSet[i]);
String fullObj = parent.resolve(obj);
String nativeFullObj = getNativePath(fullObj);
cmd.append(nativeFullObj);
//trace("link: tgt:%s obj:%s", fullTarget.c_str(),
// fullObj.c_str());
if (isNewerThan(fullObj, fullTarget))
doit = true;
}
cmd.append(" ");
// trim it down to unique elements, reduce command line size
UniqueParams(libs);
cmd.append(libs);
if (!doit)
{
//trace("link not needed");
return true;
}
//trace("LINK cmd:%s", cmd.c_str());
String outbuf, errbuf;
// std::cout << "DEBUG command = " << cmd << std::endl;
if (!executeCommand(cmd.c_str(), "", outbuf, errbuf))
{
error("LINK problem: %s", errbuf.c_str());
return false;
}
removeFromStatCache(getNativePath(fullTarget));
if (symFileName.size()>0)
{
String symFullName = parent.resolve(symFileName);
cmd = objcopyCommand;
cmd.append(" --only-keep-debug ");
cmd.append(getNativePath(fullTarget));
cmd.append(" ");
cmd.append(getNativePath(symFullName));
if (!executeCommand(cmd, "", outbuf, errbuf))
{
error("<strip> symbol file failed : %s", errbuf.c_str());
return false;
}
removeFromStatCache(getNativePath(symFullName));
}
if (doStrip)
{
cmd = stripCommand;
cmd.append(" ");
cmd.append(getNativePath(fullTarget));
if (!executeCommand(cmd, "", outbuf, errbuf))
{
error("<strip> failed : %s", errbuf.c_str());
return false;
}
removeFromStatCache(getNativePath(fullTarget));
}
return true;
}
virtual bool parse(Element *elem)
{
if (!parent.getAttribute(elem, "command", commandOpt))
return false;
if (!parent.getAttribute(elem, "objcopycommand", objcopyCommandOpt))
return false;
if (!parent.getAttribute(elem, "stripcommand", stripCommandOpt))
return false;
if (!parent.getAttribute(elem, "out", fileNameOpt))
return false;
if (!parent.getAttribute(elem, "strip", doStripOpt))
return false;
if (!parent.getAttribute(elem, "symfile", symFileNameOpt))
return false;
std::vector<Element *> children = elem->getChildren();
for (std::size_t i=0 ; i<children.size() ; i++)
{
Element *child = children[i];
String tagName = child->getName();
if (tagName == "fileset")
{
if (!parseFileSet(child, parent, fileSet))
return false;
}
else if (tagName == "flags")
{
if (!parent.getValue(child, flagsOpt))
return false;
flagsOpt = strip(flagsOpt);
}
else if (tagName == "libs")
{
if (!parent.getValue(child, libsOpt))
return false;
libsOpt = strip(libsOpt);
}
}
return true;
}
private:
FileSet fileSet;
String commandOpt;
String fileNameOpt;
String flagsOpt;
String libsOpt;
String doStripOpt;
String symFileNameOpt;
String stripCommandOpt;
String objcopyCommandOpt;
};
/**
* Create a named file
*/
class TaskMakeFile : public Task
{
public:
TaskMakeFile(MakeBase &par) : Task(par)
{ type = TASK_MAKEFILE; name = "makefile"; }
virtual ~TaskMakeFile()
{}
virtual bool execute()
{
String fileName = parent.eval(fileNameOpt, "");
bool force = parent.evalBool(forceOpt, false);
String text = parent.eval(textOpt, "");
taskstatus("%s", fileName.c_str());
String fullName = parent.resolve(fileName);
if (!force && !isNewerThan(parent.getURI().getPath(), fullName))
{
taskstatus("skipped");
return true;
}
String fullNative = getNativePath(fullName);
//trace("fullName:%s", fullName.c_str());
FILE *f = fopen(fullNative.c_str(), "w");
if (!f)
{
error("<makefile> could not open %s for writing : %s",
fullName.c_str(), strerror(errno));
return false;
}
for (std::size_t i=0 ; i<text.size() ; i++)
fputc(text[i], f);
fputc('\n', f);
fclose(f);
removeFromStatCache(fullNative);
return true;
}
virtual bool parse(Element *elem)
{
if (!parent.getAttribute(elem, "file", fileNameOpt))
return false;
if (!parent.getAttribute(elem, "force", forceOpt))
return false;
if (fileNameOpt.size() == 0)
{
error("<makefile> requires 'file=\"filename\"' attribute");
return false;
}
if (!parent.getValue(elem, textOpt))
return false;
textOpt = leftJustify(textOpt);
//trace("dirname:%s", dirName.c_str());
return true;
}
private:
String fileNameOpt;
String forceOpt;
String textOpt;
};
/**
* Create a named directory
*/
class TaskMkDir : public Task
{
public:
TaskMkDir(MakeBase &par) : Task(par)
{ type = TASK_MKDIR; name = "mkdir"; }
virtual ~TaskMkDir()
{}
virtual bool execute()
{
String dirName = parent.eval(dirNameOpt, ".");
taskstatus("%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", dirNameOpt))
return false;
if (dirNameOpt.size() == 0)
{
error("<mkdir> requires 'dir=\"dirname\"' attribute");
return false;
}
return true;
}
private:
String dirNameOpt;
};
/**
* Create a named directory
*/
class TaskMsgFmt: public Task
{
public:
TaskMsgFmt(MakeBase &par) : Task(par)
{ type = TASK_MSGFMT; name = "msgfmt"; }
virtual ~TaskMsgFmt()
{}
virtual bool execute()
{
String command = parent.eval(commandOpt, "msgfmt");
String toDirName = parent.eval(toDirNameOpt, ".");
String outName = parent.eval(outNameOpt, "");
bool owndir = parent.evalBool(owndirOpt, false);
if (!listFiles(parent, fileSet))
return false;
String fileSetDir = fileSet.getDirectory();
//trace("msgfmt: %d", fileSet.size());
for (std::size_t i=0 ; i<fileSet.size() ; i++)
{
String fileName = fileSet[i];
if (getSuffix(fileName) != "po")
continue;
String sourcePath;
if (fileSetDir.size()>0)
{
sourcePath.append(fileSetDir);
sourcePath.append("/");
}
sourcePath.append(fileName);
String fullSource = parent.resolve(sourcePath);
String destPath;
if (toDirName.size()>0)
{
destPath.append(toDirName);
destPath.append("/");
}
if (owndir)
{
String subdir = fileName;
std::size_t pos = subdir.find_last_of('.');
if (pos != subdir.npos)
subdir = subdir.substr(0, pos);
destPath.append(subdir);
destPath.append("/");
}
//Pick the output file name
if (outName.size() > 0)
{
destPath.append(outName);
}
else
{
destPath.append(fileName);
destPath[destPath.size()-2] = 'm';
}
String fullDest = parent.resolve(destPath);
if (!isNewerThan(fullSource, fullDest))
{
//trace("skip %s", fullSource.c_str());
continue;
}
String cmd = command;
cmd.append(" ");
cmd.append(fullSource);
cmd.append(" -o ");
cmd.append(fullDest);
int pos = fullDest.find_last_of('/');
if (pos>0)
{
String fullDestPath = fullDest.substr(0, pos);
if (!createDirectory(fullDestPath))
return false;
}
String outString, errString;
if (!executeCommand(cmd.c_str(), "", outString, errString))
{
error("<msgfmt> problem: %s", errString.c_str());
return false;
}
removeFromStatCache(getNativePath(fullDest));
}
return true;
}
virtual bool parse(Element *elem)
{
if (!parent.getAttribute(elem, "command", commandOpt))
return false;
if (!parent.getAttribute(elem, "todir", toDirNameOpt))
return false;
if (!parent.getAttribute(elem, "out", outNameOpt))
return false;
if (!parent.getAttribute(elem, "owndir", owndirOpt))
return false;
std::vector<Element *> children = elem->getChildren();
for (std::size_t i=0 ; i<children.size() ; i++)
{
Element *child = children[i];
String tagName = child->getName();
if (tagName == "fileset")
{
if (!parseFileSet(child, parent, fileSet))
return false;
}
}
return true;
}
private:
FileSet fileSet;
String commandOpt;
String toDirNameOpt;
String outNameOpt;
String owndirOpt;
};
/**
* Perform a Package-Config query similar to pkg-config
*/
class TaskPkgConfig : public Task
{
public:
typedef enum
{
PKG_CONFIG_QUERY_CFLAGS,
PKG_CONFIG_QUERY_LIBS,
PKG_CONFIG_QUERY_ALL
} QueryTypes;
TaskPkgConfig(MakeBase &par) : Task(par)
{
type = TASK_PKG_CONFIG;
name = "pkg-config";
}
virtual ~TaskPkgConfig()
{}
virtual bool execute()
{
String pkgName = parent.eval(pkgNameOpt, "");
String prefix = parent.eval(prefixOpt, "");
String propName = parent.eval(propNameOpt, "");
String pkgConfigPath = parent.eval(pkgConfigPathOpt,"");
String query = parent.eval(queryOpt, "all");
String path = parent.resolve(pkgConfigPath);
PkgConfig pkgconfig;
pkgconfig.setPath(path);
pkgconfig.setPrefix(prefix);
if (!pkgconfig.query(pkgName))
{
error("<pkg-config> query failed for '%s", name.c_str());
return false;
}
String val = "";
if (query == "cflags")
val = pkgconfig.getCflags();
else if (query == "libs")
val =pkgconfig.getLibs();
else if (query == "all")
val = pkgconfig.getAll();
else
{
error("<pkg-config> unhandled query : %s", query.c_str());
return false;
}
taskstatus("property %s = '%s'", propName.c_str(), val.c_str());
parent.setProperty(propName, val);
return true;
}
virtual bool parse(Element *elem)
{
//# NAME
if (!parent.getAttribute(elem, "name", pkgNameOpt))
return false;
if (pkgNameOpt.size()==0)
{
error("<pkg-config> requires 'name=\"package\"' attribute");
return false;
}
//# PROPERTY
if (!parent.getAttribute(elem, "property", propNameOpt))
return false;
if (propNameOpt.size()==0)
{
error("<pkg-config> requires 'property=\"name\"' attribute");
return false;
}
//# PATH
if (!parent.getAttribute(elem, "path", pkgConfigPathOpt))
return false;
//# PREFIX
if (!parent.getAttribute(elem, "prefix", prefixOpt))
return false;
//# QUERY
if (!parent.getAttribute(elem, "query", queryOpt))
return false;
return true;
}
private:
String queryOpt;
String pkgNameOpt;
String prefixOpt;
String propNameOpt;
String pkgConfigPathOpt;
};
/**
* 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 fileName = parent.eval(fileNameOpt, "");
String command = parent.eval(commandOpt, "ranlib");
String fullName = parent.resolve(fileName);
//trace("fullDir:%s", fullDir.c_str());
String cmd = command;
cmd.append(" ");
cmd.append(fullName);
String outbuf, errbuf;
if (!executeCommand(cmd, "", outbuf, errbuf))
return false;
// TODO:
//removeFromStatCache(getNativePath(fullDest));
return true;
}
virtual bool parse(Element *elem)
{
if (!parent.getAttribute(elem, "command", commandOpt))
return false;
if (!parent.getAttribute(elem, "file", fileNameOpt))
return false;
if (fileNameOpt.size() == 0)
{
error("<ranlib> requires 'file=\"fileNname\"' attribute");
return false;
}
return true;
}
private:
String fileNameOpt;
String commandOpt;
};
/**
* Compile a resource file into a binary object
*/
class TaskRC : public Task
{
public:
TaskRC(MakeBase &par) : Task(par)
{ type = TASK_RC; name = "rc"; }
virtual ~TaskRC()
{}
virtual bool execute()
{
String command = parent.eval(commandOpt, "windres");
String flags = parent.eval(flagsOpt, "");
String fileName = parent.eval(fileNameOpt, "");
String outName = parent.eval(outNameOpt, "");
String fullFile = parent.resolve(fileName);
String fullOut = parent.resolve(outName);
if (!isNewerThan(fullFile, fullOut))
return true;
String cmd = command;
cmd.append(" -o ");
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;
}
removeFromStatCache(getNativePath(fullOut));
return true;
}
virtual bool parse(Element *elem)
{
if (!parent.getAttribute(elem, "command", commandOpt))
return false;
if (!parent.getAttribute(elem, "file", fileNameOpt))
return false;
if (!parent.getAttribute(elem, "out", outNameOpt))
return false;
std::vector<Element *> children = elem->getChildren();
for (std::size_t i=0 ; i<children.size() ; i++)
{
Element *child = children[i];
String tagName = child->getName();
if (tagName == "flags")
{
if (!parent.getValue(child, flagsOpt))
return false;
}
}
return true;
}
private:
String commandOpt;
String flagsOpt;
String fileNameOpt;
String outNameOpt;
};
/**
* Collect .o's into a .so or DLL
*/
class TaskSharedLib : public Task
{
public:
TaskSharedLib(MakeBase &par) : Task(par)
{ type = TASK_SHAREDLIB; name = "dll"; }
virtual ~TaskSharedLib()
{}
virtual bool execute()
{
String command = parent.eval(commandOpt, "dllwrap");
String fileName = parent.eval(fileNameOpt, "");
String defFileName = parent.eval(defFileNameOpt, "");
String impFileName = parent.eval(impFileNameOpt, "");
String libs = parent.eval(libsOpt, "");
//trace("###########HERE %d", fileSet.size());
bool doit = false;
String fullOut = parent.resolve(fileName);
//trace("ar fullout: %s", fullOut.c_str());
if (!listFiles(parent, fileSet))
return false;
String fileSetDir = parent.eval(fileSet.getDirectory(), ".");
for (std::size_t i=0 ; i<fileSet.size() ; i++)
{
String fname;
if (fileSetDir.size()>0)
{
fname.append(fileSetDir);
fname.append("/");
}
fname.append(fileSet[i]);
String fullName = parent.resolve(fname);
//trace("ar : %s/%s", fullOut.c_str(), fullName.c_str());
if (isNewerThan(fullName, fullOut))
doit = true;
}
//trace("Needs it:%d", doit);
if (!doit)
{
return true;
}
String cmd = "dllwrap";
cmd.append(" -o ");
cmd.append(fullOut);
if (defFileName.size()>0)
{
cmd.append(" --def ");
cmd.append(defFileName);
cmd.append(" ");
}
if (impFileName.size()>0)
{
cmd.append(" --implib ");
cmd.append(impFileName);
cmd.append(" ");
}
for (std::size_t i=0 ; i<fileSet.size() ; i++)
{
String fname;
if (fileSetDir.size()>0)
{
fname.append(fileSetDir);
fname.append("/");
}
fname.append(fileSet[i]);
String fullName = parent.resolve(fname);
cmd.append(" ");
cmd.append(fullName);
}
cmd.append(" ");
cmd.append(libs);
String outString, errString;
if (!executeCommand(cmd.c_str(), "", outString, errString))
{
error("<sharedlib> problem: %s", errString.c_str());
return false;
}
removeFromStatCache(getNativePath(fullOut));
return true;
}
virtual bool parse(Element *elem)
{
if (!parent.getAttribute(elem, "command", commandOpt))
return false;
if (!parent.getAttribute(elem, "file", fileNameOpt))
return false;
if (!parent.getAttribute(elem, "import", impFileNameOpt))
return false;
if (!parent.getAttribute(elem, "def", defFileNameOpt))
return false;
std::vector<Element *> children = elem->getChildren();
for (std::size_t i=0 ; i<children.size() ; i++)
{
Element *child = children[i];
String tagName = child->getName();
if (tagName == "fileset")
{
if (!parseFileSet(child, parent, fileSet))
return false;
}
else if (tagName == "libs")
{
if (!parent.getValue(child, libsOpt))
return false;
libsOpt = strip(libsOpt);
}
}
return true;
}
private:
FileSet fileSet;
String commandOpt;
String fileNameOpt;
String defFileNameOpt;
String impFileNameOpt;
String libsOpt;
};
/**
* Run the "ar" command to archive .o's into a .a
*/
class TaskStaticLib : public Task
{
public:
TaskStaticLib(MakeBase &par) : Task(par)
{ type = TASK_STATICLIB; name = "staticlib"; }
virtual ~TaskStaticLib()
{}
virtual bool execute()
{
String command = parent.eval(commandOpt, "ar crv");
String fileName = parent.eval(fileNameOpt, "");
bool doit = false;
String fullOut = parent.resolve(fileName);
//trace("ar fullout: %s", fullOut.c_str());
if (!listFiles(parent, fileSet))
return false;
String fileSetDir = parent.eval(fileSet.getDirectory(), ".");
//trace("###########HERE %s", fileSetDir.c_str());
for (std::size_t i=0 ; i<fileSet.size() ; i++)
{
String fname;
if (fileSetDir.size()>0)
{
fname.append(fileSetDir);
fname.append("/");
}
fname.append(fileSet[i]);
String fullName = parent.resolve(fname);
//trace("ar : %s/%s", fullOut.c_str(), fullName.c_str());
if (isNewerThan(fullName, fullOut))
doit = true;
}
//trace("Needs it:%d", doit);
if (!doit)
{
return true;
}
String cmd = command;
cmd.append(" ");
cmd.append(fullOut);
for (std::size_t i=0 ; i<fileSet.size() ; i++)
{
String fname;
if (fileSetDir.size()>0)
{
fname.append(fileSetDir);
fname.append("/");
}
fname.append(fileSet[i]);
String fullName = parent.resolve(fname);
cmd.append(" ");
cmd.append(fullName);
}
String outString, errString;
if (!executeCommand(cmd.c_str(), "", outString, errString))
{
error("<staticlib> problem: %s", errString.c_str());
return false;
}
removeFromStatCache(getNativePath(fullOut));
return true;
}
virtual bool parse(Element *elem)
{
if (!parent.getAttribute(elem, "command", commandOpt))
return false;
if (!parent.getAttribute(elem, "file", fileNameOpt))
return false;
std::vector<Element *> children = elem->getChildren();
for (std::size_t i=0 ; i<children.size() ; i++)
{
Element *child = children[i];
String tagName = child->getName();
if (tagName == "fileset")
{
if (!parseFileSet(child, parent, fileSet))
return false;
}
}
return true;
}
private:
FileSet fileSet;
String commandOpt;
String fileNameOpt;
};
/**
* Strip an executable
*/
class TaskStrip : public Task
{
public:
TaskStrip(MakeBase &par) : Task(par)
{ type = TASK_STRIP; name = "strip"; }
virtual ~TaskStrip()
{}
virtual bool execute()
{
String command = parent.eval(commandOpt, "strip");
String fileName = parent.eval(fileNameOpt, "");
String symFileName = parent.eval(symFileNameOpt, "");
String fullName = parent.resolve(fileName);
//trace("fullDir:%s", fullDir.c_str());
String cmd;
String outbuf, errbuf;
if (symFileName.size()>0)
{
String symFullName = parent.resolve(symFileName);
cmd = "objcopy --only-keep-debug ";
cmd.append(getNativePath(fullName));
cmd.append(" ");
cmd.append(getNativePath(symFullName));
if (!executeCommand(cmd, "", outbuf, errbuf))
{
error("<strip> symbol file failed : %s", errbuf.c_str());
return false;
}
}
cmd = command;
cmd.append(getNativePath(fullName));
if (!executeCommand(cmd, "", outbuf, errbuf))
{
error("<strip> failed : %s", errbuf.c_str());
return false;
}
removeFromStatCache(getNativePath(fullName));
return true;
}
virtual bool parse(Element *elem)
{
if (!parent.getAttribute(elem, "command", commandOpt))
return false;
if (!parent.getAttribute(elem, "file", fileNameOpt))
return false;
if (!parent.getAttribute(elem, "symfile", symFileNameOpt))
return false;
if (fileNameOpt.size() == 0)
{
error("<strip> requires 'file=\"fileName\"' attribute");
return false;
}
return true;
}
private:
String commandOpt;
String fileNameOpt;
String symFileNameOpt;
};
/**
*
*/
class TaskTouch : public Task
{
public:
TaskTouch(MakeBase &par) : Task(par)
{ type = TASK_TOUCH; name = "touch"; }
virtual ~TaskTouch()
{}
virtual bool execute()
{
String fileName = parent.eval(fileNameOpt, "");
String fullName = parent.resolve(fileName);
String nativeFile = getNativePath(fullName);
if (!isRegularFile(fullName) && !isDirectory(fullName))
{
// S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH
int ret = creat(nativeFile.c_str(), 0666);
if (ret != 0)
{
error("<touch> could not create '%s' : %s",
nativeFile.c_str(), strerror(ret));
return false;
}
return true;
}
int ret = utime(nativeFile.c_str(), (struct utimbuf *)0);
if (ret != 0)
{
error("<touch> could not update the modification time for '%s' : %s",
nativeFile.c_str(), strerror(ret));
return false;
}
removeFromStatCache(nativeFile);
return true;
}
virtual bool parse(Element *elem)
{
//trace("touch parse");
if (!parent.getAttribute(elem, "file", fileNameOpt))
return false;
if (fileNameOpt.size() == 0)
{
error("<touch> requires 'file=\"fileName\"' attribute");
return false;
}
return true;
}
String fileNameOpt;
};
/**
*
*/
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, int lineNr)
{
String tagName = elem->getName();
//trace("task:%s", tagName.c_str());
Task *task = NULL;
if (tagName == "cc")
task = new TaskCC(parent);
else if (tagName == "copy")
task = new TaskCopy(parent);
else if (tagName == "cxxtestpart")
task = new TaskCxxTestPart(parent);
else if (tagName == "cxxtestroot")
task = new TaskCxxTestRoot(parent);
else if (tagName == "cxxtestrun")
task = new TaskCxxTestRun(parent);
else if (tagName == "delete")
task = new TaskDelete(parent);
else if (tagName == "echo")
task = new TaskEcho(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 == "makefile")
task = new TaskMakeFile(parent);
else if (tagName == "mkdir")
task = new TaskMkDir(parent);
else if (tagName == "msgfmt")
task = new TaskMsgFmt(parent);
else if (tagName == "pkg-config")
task = new TaskPkgConfig(parent);
else if (tagName == "ranlib")
task = new TaskRanlib(parent);
else if (tagName == "rc")
task = new TaskRC(parent);
else if (tagName == "sharedlib")
task = new TaskSharedLib(parent);
else if (tagName == "staticlib")
task = new TaskStaticLib(parent);
else if (tagName == "strip")
task = new TaskStrip(parent);
else if (tagName == "touch")
task = new TaskTouch(parent);
else if (tagName == "tstamp")
task = new TaskTstamp(parent);
else
{
error("Unknown task '%s'", tagName.c_str());
return NULL;
}
task->setLine(lineNr);
if (!task->parse(elem))
{
delete task;
return NULL;
}
return task;
}
//########################################################################
//# T A R G E T
//########################################################################
/**
*
*/
class Target : public MakeBase
{
public:
/**
*
*/
Target(Make &par) : parent(par)
{ init(); }
/**
*
*/
Target(const Target &other) : parent(other.parent)
{ init(); assign(other); }
/**
*
*/
Target &operator=(const Target &other)
{ init(); assign(other); return *this; }
/**
*
*/
virtual ~Target()
{ cleanup() ; }
/**
*
*/
virtual Make &getParent()
{ return parent; }
/**
*
*/
virtual String getName()
{ return name; }
/**
*
*/
virtual void setName(const String &val)
{ name = val; }
/**
*
*/
virtual String getDescription()
{ return description; }
/**
*
*/
virtual void setDescription(const String &val)
{ description = val; }
/**
*
*/
virtual void addDependency(const String &val)
{ deps.push_back(val); }
/**
*
*/
virtual void parseDependencies(const String &val)
{ deps = tokenize(val, ", "); }
/**
*
*/
virtual std::vector<String> &getDependencies()
{ return deps; }
/**
*
*/
virtual String getIf()
{ return ifVar; }
/**
*
*/
virtual void setIf(const String &val)
{ ifVar = val; }
/**
*
*/
virtual String getUnless()
{ return unlessVar; }
/**
*
*/
virtual void setUnless(const String &val)
{ unlessVar = val; }
/**
*
*/
virtual void addTask(Task *val)
{ tasks.push_back(val); }
/**
*
*/
virtual std::vector<Task *> &getTasks()
{ return tasks; }
private:
void init()
{
}
void cleanup()
{
tasks.clear();
}
void assign(const Target &other)
{
//parent = other.parent;
name = other.name;
description = other.description;
ifVar = other.ifVar;
unlessVar = other.unlessVar;
deps = other.deps;
tasks = other.tasks;
}
Make &parent;
String name;
String description;
String ifVar;
String unlessVar;
std::vector<String> deps;
std::vector<Task *> tasks;
};
//########################################################################
//# M A K E
//########################################################################
/**
*
*/
class Make : public MakeBase
{
public:
/**
*
*/
Make()
{ init(); }
/**
*
*/
Make(const Make &other)
{ assign(other); }
/**
*
*/
Make &operator=(const Make &other)
{ assign(other); return *this; }
/**
*
*/
virtual ~Make()
{ cleanup(); }
/**
*
*/
virtual std::map<String, Target> &getTargets()
{ return targets; }
/**
*
*/
virtual String version()
{ return BUILDTOOL_VERSION; }
/**
* Overload a <property>
*/
virtual bool specifyProperty(const String &name,
const String &value);
/**
*
*/
virtual bool run();
/**
*
*/
virtual 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<String> &targetsCompleted);
/**
*
*/
bool execute();
/**
*
*/
bool checkTargetDependencies(Target &prop,
std::vector<String> &depList);
/**
*
*/
bool parsePropertyFile(const String &fileName,
const String &prefix);
/**
*
*/
bool parseProperty(Element *elem);
/**
*
*/
bool parseFile();
/**
*
*/
std::vector<String> glob(const String &pattern);
//###############
//# Fields
//###############
String projectName;
String currentTarget;
String defaultTarget;
String specifiedTarget;
String baseDir;
String description;
//std::vector<Property> properties;
std::map<String, Target> targets;
std::vector<Task *> allTasks;
std::map<String, String> specifiedProperties;
};
//########################################################################
//# 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 = "";
envPrefix = "env.";
pcPrefix = "pc.";
pccPrefix = "pcc.";
pclPrefix = "pcl.";
bzrPrefix = "bzr.";
properties.clear();
for (std::size_t i = 0 ; i < allTasks.size() ; i++)
delete allTasks[i];
allTasks.clear();
}
/**
*
*/
void Make::cleanup()
{
for (std::size_t 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<String> Make::glob(const String &pattern)
{
std::vector<String> res;
return res;
}
//########################################################################
//# P U B L I C A P I
//########################################################################
/**
*
*/
bool Make::executeTarget(Target &target,
std::set<String> &targetsCompleted)
{
String name = target.getName();
//First get any dependencies for this target
std::vector<String> deps = target.getDependencies();
for (std::size_t i=0 ; i<deps.size() ; i++)
{
String dep = deps[i];
//Did we do it already? Skip
if (targetsCompleted.find(dep)!=targetsCompleted.end())
continue;
std::map<String, Target> &tgts =
target.getParent().getTargets();
std::map<String, Target>::iterator iter =
tgts.find(dep);
if (iter == tgts.end())
{
error("Target '%s' dependency '%s' not found",
name.c_str(), dep.c_str());
return false;
}
Target depTarget = iter->second;
if (!executeTarget(depTarget, targetsCompleted))
{
return false;
}
}
status("##### Target : %s\n##### %s", name.c_str(),
target.getDescription().c_str());
//Now let's do the tasks
std::vector<Task *> &tasks = target.getTasks();
for (std::size_t i=0 ; i<tasks.size() ; i++)
{
Task *task = tasks[i];
status("--- %s / %s", name.c_str(), task->getName().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<String, Target>::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<String> targetsCompleted;
if (!executeTarget(target, targetsCompleted))
{
return false;
}
status("######## EXECUTE COMPLETE");
return true;
}
/**
*
*/
bool Make::checkTargetDependencies(Target &target,
std::vector<String> &depList)
{
String tgtName = target.getName().c_str();
depList.push_back(tgtName);
std::vector<String> deps = target.getDependencies();
for (std::size_t i=0 ; i<deps.size() ; i++)
{
String dep = deps[i];
//First thing entered was the starting Target
if (dep == depList[0])
{
error("Circular dependency '%s' found at '%s'",
dep.c_str(), tgtName.c_str());
std::vector<String>::iterator diter;
for (diter=depList.begin() ; diter!=depList.end() ; diter++)
{
error(" %s", diter->c_str());
}
return false;
}
std::map<String, Target> &tgts =
target.getParent().getTargets();
std::map<String, Target>::iterator titer = tgts.find(dep);
if (titer == tgts.end())
{
error("Target '%s' dependency '%s' not found",
tgtName.c_str(), dep.c_str());
return false;
}
if (!checkTargetDependencies(titer->second, depList))
{
return false;
}
}
return true;
}
static int getword(int pos, const String &inbuf, String &result)
{
int p = pos;
int len = (int)inbuf.size();
String val;
while (p < len)
{
char ch = inbuf[p];
if (!isalnum(ch) && ch!='.' && ch!='_')
break;
val.push_back(ch);
p++;
}
result = val;
return p;
}
/**
*
*/
bool Make::parsePropertyFile(const String &fileName,
const String &prefix)
{
FILE *f = fopen(fileName.c_str(), "r");
if (!f)
{
error("could not open property file %s", fileName.c_str());
return false;
}
int linenr = 0;
while (!feof(f))
{
char buf[256];
if (!fgets(buf, 255, f))
break;
linenr++;
String s = buf;
s = trim(s);
int len = s.size();
if (len == 0)
continue;
if (s[0] == '#')
continue;
String key;
String val;
int p = 0;
int p2 = getword(p, s, key);
if (p2 <= p)
{
error("property file %s, line %d: expected keyword",
fileName.c_str(), linenr);
fclose(f);
return false;
}
if (prefix.size() > 0)
{
key.insert(0, prefix);
}
//skip whitespace
for (p=p2 ; p<len ; p++)
if (!isspace(s[p]))
break;
if (p>=len || s[p]!='=')
{
error("property file %s, line %d: expected '='",
fileName.c_str(), linenr);
return false;
}
p++;
//skip whitespace
for ( ; p<len ; p++)
if (!isspace(s[p]))
break;
/* This way expects a word after the =
p2 = getword(p, s, val);
if (p2 <= p)
{
error("property file %s, line %d: expected value",
fileName.c_str(), linenr);
return false;
}
*/
// This way gets the rest of the line after the =
if (p>=len)
{
error("property file %s, line %d: expected value",
fileName.c_str(), linenr);
return false;
}
val = s.substr(p);
if (key.size()==0)
continue;
//allow property to be set, even if val=""
//trace("key:'%s' val:'%s'", key.c_str(), val.c_str());
//See if we wanted to overload this property
std::map<String, String>::iterator iter =
specifiedProperties.find(key);
if (iter!=specifiedProperties.end())
{
val = iter->second;
status("overloading property '%s' = '%s'",
key.c_str(), val.c_str());
}
properties[key] = val;
}
fclose(f);
return true;
}
/**
*
*/
bool Make::parseProperty(Element *elem)
{
std::vector<Attribute> &attrs = elem->getAttributes();
for (std::size_t i=0 ; i<attrs.size() ; i++)
{
String attrName = attrs[i].getName();
String attrVal = attrs[i].getValue();
if (attrName == "name")
{
String val;
if (!getAttribute(elem, "value", val))
return false;
if (val.size() > 0)
{
properties[attrVal] = val;
}
else
{
if (!getAttribute(elem, "location", val))
return false;
//let the property exist, even if not defined
properties[attrVal] = val;
}
//See if we wanted to overload this property
std::map<String, String>::iterator iter =
specifiedProperties.find(attrVal);
if (iter != specifiedProperties.end())
{
val = iter->second;
status("overloading property '%s' = '%s'",
attrVal.c_str(), val.c_str());
properties[attrVal] = val;
}
}
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 (attrVal.find('.') != attrVal.npos)
{
error("environment prefix cannot have a '.' in it");
return false;
}
envPrefix = attrVal;
envPrefix.push_back('.');
}
else if (attrName == "pkg-config")
{
if (attrVal.find('.') != attrVal.npos)
{
error("pkg-config prefix cannot have a '.' in it");
return false;
}
pcPrefix = attrVal;
pcPrefix.push_back('.');
}
else if (attrName == "pkg-config-cflags")
{
if (attrVal.find('.') != attrVal.npos)
{
error("pkg-config-cflags prefix cannot have a '.' in it");
return false;
}
pccPrefix = attrVal;
pccPrefix.push_back('.');
}
else if (attrName == "pkg-config-libs")
{
if (attrVal.find('.') != attrVal.npos)
{
error("pkg-config-libs prefix cannot have a '.' in it");
return false;
}
pclPrefix = attrVal;
pclPrefix.push_back('.');
}
else if (attrName == "subversion")
{
if (attrVal.find('.') != attrVal.npos)
{
error("bzr prefix cannot have a '.' in it");
return false;
}
bzrPrefix = attrVal;
bzrPrefix.push_back('.');
}
}
return true;
}
/**
*
*/
bool Make::parseFile()
{
status("######## PARSE : %s", uri.getPath().c_str());
setLine(0);
Parser parser;
Element *root = parser.parseFile(uri.getNativePath());
if (!root)
{
error("Could not open %s for reading",
uri.getNativePath().c_str());
return false;
}
setLine(root->getLine());
if (root->getChildren().size()==0 ||
root->getChildren()[0]->getName()!="project")
{
error("Main xml element should be <project>");
delete root;
return false;
}
//########## Project attributes
Element *project = root->getChildren()[0];
String s = project->getAttribute("name");
if (s.size() > 0)
projectName = s;
s = project->getAttribute("default");
if (s.size() > 0)
defaultTarget = s;
s = project->getAttribute("basedir");
if (s.size() > 0)
baseDir = s;
//######### PARSE MEMBERS
std::vector<Element *> children = project->getChildren();
for (std::size_t i=0 ; i<children.size() ; i++)
{
Element *elem = children[i];
setLine(elem->getLine());
String tagName = elem->getName();
//########## DESCRIPTION
if (tagName == "description")
{
description = parser.trim(elem->getValue());
}
//######### PROPERTY
else if (tagName == "property")
{
if (!parseProperty(elem))
return false;
}
//######### TARGET
else if (tagName == "target")
{
String tname = elem->getAttribute("name");
String tdesc = elem->getAttribute("description");
String tdeps = elem->getAttribute("depends");
String tif = elem->getAttribute("if");
String tunless = elem->getAttribute("unless");
Target target(*this);
target.setName(tname);
target.setDescription(tdesc);
target.parseDependencies(tdeps);
target.setIf(tif);
target.setUnless(tunless);
std::vector<Element *> telems = elem->getChildren();
for (std::size_t i=0 ; i<telems.size() ; i++)
{
Element *telem = telems[i];
Task breeder(*this);
Task *task = breeder.createTask(telem, telem->getLine());
if (!task)
return false;
allTasks.push_back(task);
target.addTask(task);
}
//Check name
if (tname.size() == 0)
{
error("no name for target");
return false;
}
//Check for duplicate name
if (targets.find(tname) != targets.end())
{
error("target '%s' already defined", tname.c_str());
return false;
}
//more work than targets[tname]=target, but avoids default allocator
targets.insert(std::make_pair<String, Target>(tname, target));
}
//######### none of the above
else
{
error("unknown toplevel tag: <%s>", tagName.c_str());
return false;
}
}
std::map<String, Target>::iterator iter;
for (iter = targets.begin() ; iter!= targets.end() ; iter++)
{
Target tgt = iter->second;
std::vector<String> depList;
if (!checkTargetDependencies(tgt, depList))
{
return false;
}
}
delete root;
status("######## PARSE COMPLETE");
return true;
}
/**
* Overload a <property>
*/
bool Make::specifyProperty(const String &name, const String &value)
{
if (specifiedProperties.find(name) != specifiedProperties.end())
{
error("Property %s already specified", name.c_str());
return false;
}
specifiedProperties[name] = value;
return true;
}
/**
*
*/
bool Make::run()
{
if (!parseFile())
return false;
if (!execute())
return false;
return true;
}
/**
* Get a formatted MM:SS.sss time elapsed string
*/
static String
timeDiffString(struct timeval &x, struct timeval &y)
{
long microsX = x.tv_usec;
long secondsX = x.tv_sec;
long microsY = y.tv_usec;
long secondsY = y.tv_sec;
if (microsX < microsY)
{
microsX += 1000000;
secondsX -= 1;
}
int seconds = (int)(secondsX - secondsY);
int millis = (int)((microsX - microsY)/1000);
int minutes = seconds/60;
seconds -= minutes*60;
char buf[80];
snprintf(buf, 79, "%dm %d.%03ds", minutes, seconds, millis);
String ret = buf;
return ret;
}
/**
*
*/
bool Make::run(const String &target)
{
status("####################################################");
status("# %s", version().c_str());
status("####################################################");
struct timeval timeStart, timeEnd;
::gettimeofday(&timeStart, NULL);
specifiedTarget = target;
if (!run())
return false;
::gettimeofday(&timeEnd, NULL);
String timeStr = timeDiffString(timeEnd, timeStart);
status("####################################################");
status("# BuildTool Completed : %s", timeStr.c_str());
status("####################################################");
return true;
}
}// namespace buildtool
//########################################################################
//# M A I N
//########################################################################
typedef buildtool::String String;
/**
* Format an error message in printf() style
*/
static void error(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
fprintf(stderr, "BuildTool error: ");
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n");
va_end(ap);
}
static bool parseProperty(const String &s, String &name, String &val)
{
int len = s.size();
int i;
for (i=0 ; i<len ; i++)
{
char ch = s[i];
if (ch == '=')
break;
name.push_back(ch);
}
if (i>=len || s[i]!='=')
{
error("property requires -Dname=value");
return false;
}
i++;
for ( ; i<len ; i++)
{
char ch = s[i];
val.push_back(ch);
}
return true;
}
/**
* Compare a buffer with a key, for the length of the key
*/
static bool sequ(const String &buf, const char *key)
{
int len = buf.size();
for (int i=0 ; key[i] && i<len ; i++)
{
if (key[i] != buf[i])
return false;
}
return true;
}
static void usage(int argc, char **argv)
{
printf("usage:\n");
printf(" %s [options] [target]\n", argv[0]);
printf("Options:\n");
printf(" -help, -h print this message\n");
printf(" -version print the version information and exit\n");
printf(" -file <file> use given buildfile\n");
printf(" -f <file> ''\n");
printf(" -D<property>=<value> use value for given property\n");
printf(" -j [N] build using N threads or infinite number of threads if no argument\n");
}
/**
* 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::Make make;
String target;
//char *progName = argv[0];
for (int i=1 ; i<argc ; i++)
{
String arg = argv[i];
if (arg.size()>1 && arg[0]=='-')
{
if (arg == "-h" || arg == "-help")
{
usage(argc,argv);
return true;
}
else if (arg == "-version")
{
printf("%s", make.version().c_str());
return true;
}
else if (arg == "-f" || arg == "-file")
{
if (i>=argc-1)
{
usage(argc, argv);
return false;
}
i++; //eat option
make.setURI(argv[i]);
}
else if (arg == "-j")
{
if (i>=argc-1) { // if -j is given as last argument
make.setNumThreads(20); // default to some high value
} else {
i++; //eat option
if (argv[i] && (*argv[i] == '-')) { // if -j is followed by another '-...' option
make.setNumThreads(20); // default to some high value
} else {
make.setNumThreads(atoi(argv[i]));
}
}
}
else if (arg.size()>2 && sequ(arg, "-D"))
{
String s = arg.substr(2, arg.size());
String name, value;
if (!parseProperty(s, name, value))
{
usage(argc, argv);
return false;
}
if (!make.specifyProperty(name, value))
return false;
}
else
{
error("Unknown option:%s", arg.c_str());
return false;
}
}
else
{
if (target.size()>0)
{
error("only one initial target");
usage(argc, argv);
return false;
}
target = arg;
}
}
//We have the options. Now execute them
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<buildtool::FileRec> 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
//########################################################################