1 /****************************************************************************
2 ** $Id: qt/project.cpp   3.3.8   edited Jan 11 14:37 $
3 **
4 ** Implementation of QMakeProject class.
5 **
6 ** Copyright (C) 1992-2007 Trolltech ASA.  All rights reserved.
7 **
8 ** This file is part of qmake.
9 **
10 ** This file may be distributed under the terms of the Q Public License
11 ** as defined by Trolltech ASA of Norway and appearing in the file
12 ** LICENSE.QPL included in the packaging of this file.
13 **
14 ** This file may be distributed and/or modified under the terms of the
15 ** GNU General Public License version 2 as published by the Free Software
16 ** Foundation and appearing in the file LICENSE.GPL included in the
17 ** packaging of this file.
18 **
19 ** Licensees holding valid Qt Enterprise Edition licenses may use this
20 ** file in accordance with the Qt Commercial License Agreement provided
21 ** with the Software.
22 **
23 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
24 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
25 **
26 ** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
27 **   information about Qt Commercial License Agreements.
28 ** See http://www.trolltech.com/qpl/ for QPL licensing information.
29 ** See http://www.trolltech.com/gpl/ for GPL licensing information.
30 **
31 ** Contact info@trolltech.com if any conditions of this licensing are
32 ** not clear to you.
33 **
34 **********************************************************************/
35 
36 #include "project.h"
37 #include "property.h"
38 #include "option.h"
39 #include <qfile.h>
40 #include <qdir.h>
41 #include <qregexp.h>
42 #include <qtextstream.h>
43 #include <qvaluestack.h>
44 #ifdef Q_OS_UNIX
45 # include <unistd.h>
46 #endif
47 #include <stdio.h>
48 #include <stdlib.h>
49 
50 #ifdef Q_OS_WIN32
51 #define QT_POPEN _popen
52 #else
53 #define QT_POPEN popen
54 #endif
55 
56 struct parser_info {
57     QString file;
58     int line_no;
59 } parser;
60 
qmake_error_msg(const char * msg)61 static void qmake_error_msg(const char *msg)
62 {
63     fprintf(stderr, "%s:%d: %s\n", parser.file.latin1(), parser.line_no, msg);
64 }
65 
qmake_mkspec_paths()66 QStringList qmake_mkspec_paths()
67 {
68     QStringList ret;
69     const QString concat = QDir::separator() + QString("mkspecs");
70     if(const char *qmakepath = getenv("QMAKEPATH")) {
71 #ifdef Q_OS_WIN
72 	QStringList lst = QStringList::split(';', qmakepath);
73 	for(QStringList::Iterator it = lst.begin(); it != lst.end(); ++it) {
74 	    QStringList lst2 = QStringList::split(':', (*it));
75 	    for(QStringList::Iterator it2 = lst2.begin(); it2 != lst2.end(); ++it2)
76 		ret << ((*it2) + concat);
77 	}
78 #else
79 	QStringList lst = QStringList::split(':', qmakepath);
80 	for(QStringList::Iterator it = lst.begin(); it != lst.end(); ++it)
81 	    ret << ((*it) + concat);
82 #endif
83     }
84     if(const char *qtdir = getenv("QTDIR"))
85 	ret << (QString(qtdir) + concat);
86 #ifdef QT_INSTALL_PREFIX
87     ret << (QT_INSTALL_PREFIX + concat);
88 #endif
89 #if defined(HAVE_QCONFIG_CPP)
90     ret << (qInstallPath() + concat);
91 #endif
92 #ifdef QT_INSTALL_DATA
93     ret << (QT_INSTALL_DATA + concat);
94 #endif
95 #if defined(HAVE_QCONFIG_CPP)
96     ret << (qInstallPathData() + concat);
97 #endif
98 
99     /* prefer $QTDIR if it is set */
100     if (getenv("QTDIR"))
101 	ret << getenv("QTDIR");
102     ret << qInstallPathData();
103     return ret;
104 }
105 
varMap(const QString & x)106 static QString varMap(const QString &x)
107 {
108     QString ret(x);
109     if(ret.startsWith("TMAKE")) //tmake no more!
110 	ret = "QMAKE" + ret.mid(5);
111     else if(ret == "INTERFACES")
112 	ret = "FORMS";
113     else if(ret == "QMAKE_POST_BUILD")
114 	ret = "QMAKE_POST_LINK";
115     else if(ret == "TARGETDEPS")
116 	ret = "POST_TARGETDEPS";
117     else if(ret == "LIBPATH")
118 	ret = "QMAKE_LIBDIR";
119     else if(ret == "QMAKE_EXT_MOC")
120 	ret = "QMAKE_EXT_CPP_MOC";
121     else if(ret == "QMAKE_MOD_MOC")
122 	ret = "QMAKE_H_MOD_MOC";
123     else if(ret == "QMAKE_LFLAGS_SHAPP")
124 	ret = "QMAKE_LFLAGS_APP";
125     else if(ret == "PRECOMPH")
126 	ret = "PRECOMPILED_HEADER";
127     else if(ret == "PRECOMPCPP")
128 	ret = "PRECOMPILED_SOURCE";
129     else if(ret == "INCPATH")
130 	ret = "INCLUDEPATH";
131     return ret;
132 }
133 
split_arg_list(const QString & params)134 static QStringList split_arg_list(const QString &params)
135 {
136     QChar quote = 0;
137     QStringList args;
138     for(int x = 0, last = 0, parens = 0; x <= (int)params.length(); x++) {
139 	if(x == (int)params.length()) {
140 	    QString mid = params.mid(last, x - last).stripWhiteSpace();
141             if(quote) {
142                 if(mid[(int)mid.length()-1] == quote)
143                     mid.truncate(1);
144                 quote = 0;
145             }
146 	    args << mid;
147 	} else if(params[x] == ')') {
148 	    parens--;
149 	} else if(params[x] == '(') {
150 	    parens++;
151 	} else if(params[x] == quote) {
152 	    quote = 0;
153 	} else if(!quote && (params[x] == '\'' || params[x] == '"')) {
154 	    quote = params[x];
155 	} else if(!parens && !quote && params[x] == ',') {
156 	    args << params.mid(last, x - last).stripWhiteSpace();
157 	    last = x+1;
158 	}
159     }
160     return args;
161 }
162 
split_value_list(const QString & vals,bool do_semicolon=FALSE)163 static QStringList split_value_list(const QString &vals, bool do_semicolon=FALSE)
164 {
165     QString build;
166     QStringList ret;
167     QValueStack<QChar> quote;
168     for(int x = 0; x < (int)vals.length(); x++) {
169 	if(x != (int)vals.length()-1 && vals[x] == '\\' && (vals[x+1] == '\'' || vals[x+1] == '"'))
170 	    build += vals[x++]; //get that 'escape'
171 	else if(!quote.isEmpty() && vals[x] == quote.top())
172 	    quote.pop();
173 	else if(vals[x] == '\'' || vals[x] == '"')
174 	    quote.push(vals[x]);
175 
176 	if(quote.isEmpty() && ((do_semicolon && vals[x] == ';') ||  vals[x] == ' ')) {
177 	    ret << build;
178 	    build = "";
179 	} else {
180 	    build += vals[x];
181 	}
182     }
183     if(!build.isEmpty())
184 	ret << build;
185     return ret;
186 }
187 
QMakeProject()188 QMakeProject::QMakeProject()
189 {
190     prop = NULL;
191 }
192 
QMakeProject(QMakeProperty * p)193 QMakeProject::QMakeProject(QMakeProperty *p)
194 {
195     prop = p;
196 }
197 
198 void
reset()199 QMakeProject::reset()
200 {
201     /* scope blocks start at true */
202     test_status = TestNone;
203     scope_flag = 0x01;
204     scope_block = 0;
205 }
206 
207 bool
parse(const QString & t,QMap<QString,QStringList> & place)208 QMakeProject::parse(const QString &t, QMap<QString, QStringList> &place)
209 {
210     QString s = t.simplifyWhiteSpace();
211     int hash_mark = s.find("#");
212     if(hash_mark != -1) //good bye comments
213 	s = s.left(hash_mark);
214     if(s.isEmpty()) /* blank_line */
215 	return TRUE;
216 
217     if(s.stripWhiteSpace().left(1) == "}") {
218 	debug_msg(1, "Project Parser: %s:%d : Leaving block %d", parser.file.latin1(),
219 		  parser.line_no, scope_block);
220 	test_status = ((scope_flag & (0x01 << scope_block)) ? TestFound : TestSeek);
221 	scope_block--;
222 	s = s.mid(1).stripWhiteSpace();
223 	if(s.isEmpty())
224 	    return TRUE;
225     }
226     if(!(scope_flag & (0x01 << scope_block))) {
227 	/* adjust scope for each block which appears on a single line */
228 	for(int i = (s.contains('{')-s.contains('}')); i>0; i--)
229 	    scope_flag &= ~(0x01 << (++scope_block));
230 	debug_msg(1, "Project Parser: %s:%d : Ignored due to block being false.",
231 		  parser.file.latin1(), parser.line_no);
232 	return TRUE;
233     }
234 
235     QString scope, var, op;
236     QStringList val;
237 #define SKIP_WS(d) while(*d && (*d == ' ' || *d == '\t')) d++
238     const char *d = s.latin1();
239     SKIP_WS(d);
240     bool scope_failed = FALSE, else_line = FALSE, or_op=FALSE;
241     int parens = 0, scope_count=0;
242     while(*d) {
243 	if(!parens) {
244 	    if(*d == '=')
245 		break;
246 	    if(*d == '+' || *d == '-' || *d == '*' || *d == '~') {
247 		if(*(d+1) == '=') {
248 		    break;
249 		} else if(*(d+1) == ' ') {
250 		    const char *k = d + 1;
251 		    SKIP_WS(k);
252 		    if(*k == '=') {
253 			QString msg;
254 			qmake_error_msg(*d + "must be followed immediately by =");
255 			return FALSE;
256 		    }
257 		}
258 	    }
259 	}
260 
261 	if ( *d == '(' )
262 	    ++parens;
263 	else if ( *d == ')' )
264 	    --parens;
265 
266 	if(!parens && (*d == ':' || *d == '{' || *d == ')' || *d == '|')) {
267 	    scope_count++;
268 	    scope = var.stripWhiteSpace();
269 	    if ( *d == ')' )
270 		scope += *d; /* need this */
271 	    var = "";
272 
273 	    bool test = scope_failed;
274 	    if(scope.lower() == "else") {
275 		if(scope_count != 1 || test_status == TestNone) {
276 		    qmake_error_msg("Unexpected " + scope + " ('" + s + "')");
277 		    return FALSE;
278 		}
279 		else_line = TRUE;
280 		test = (test_status == TestSeek);
281 		debug_msg(1, "Project Parser: %s:%d : Else%s %s.", parser.file.latin1(), parser.line_no,
282 			  scope == "else" ? "" : QString(" (" + scope + ")").latin1(),
283 			  test ? "considered" : "excluded");
284 	    } else {
285 		QString comp_scope = scope;
286 		bool invert_test = (comp_scope.left(1) == "!");
287 		if(invert_test)
288 		    comp_scope = comp_scope.right(comp_scope.length()-1);
289 		int lparen = comp_scope.find('(');
290 		if(or_op == scope_failed) {
291 		    if(lparen != -1) { /* if there is an lparen in the scope, it IS a function */
292 			int rparen = comp_scope.findRev(')');
293 			if(rparen == -1) {
294 			    QCString error;
295 			    error.sprintf("Function missing right paren: %s ('%s')",
296 					  comp_scope.latin1(), s.latin1());
297 			    qmake_error_msg(error);
298 			    return FALSE;
299 			}
300 			QString func = comp_scope.left(lparen);
301 			test = doProjectTest(func, comp_scope.mid(lparen+1, rparen - lparen - 1), place);
302 			if ( *d == ')' && !*(d+1) ) {
303 			    if(invert_test)
304 				test = !test;
305 			    test_status = (test ? TestFound : TestSeek);
306 			    return TRUE;  /* assume we are done */
307 			}
308 		    } else {
309 			test = isActiveConfig(comp_scope.stripWhiteSpace(), TRUE, &place);
310 		    }
311 		    if(invert_test)
312 			test = !test;
313 		}
314 	    }
315 	    if(!test && !scope_failed)
316 		debug_msg(1, "Project Parser: %s:%d : Test (%s) failed.", parser.file.latin1(),
317 			  parser.line_no, scope.latin1());
318 	    if(test == or_op)
319 		scope_failed = !test;
320 	    or_op = (*d == '|');
321 	    if(*d == '{') { /* scoping block */
322 		if(!scope_failed)
323 		    scope_flag |= (0x01 << (++scope_block));
324 		else
325 		    scope_flag &= ~(0x01 << (++scope_block));
326 		debug_msg(1, "Project Parser: %s:%d : Entering block %d (%d).", parser.file.latin1(),
327 			  parser.line_no, scope_block, !scope_failed);
328 	    }
329 	} else if(!parens && *d == '}') {
330 	    if(!scope_block) {
331 		warn_msg(WarnParser, "Possible braces mismatch %s:%d", parser.file.latin1(), parser.line_no);
332 	    } else {
333 		debug_msg(1, "Project Parser: %s:%d : Leaving block %d", parser.file.latin1(),
334 			  parser.line_no, scope_block);
335 		--scope_block;
336 	    }
337 	} else {
338 	    var += *d;
339 	}
340 	d++;
341     }
342     var = var.stripWhiteSpace();
343     if(!scope_count || (scope_count == 1 && else_line))
344 	test_status = TestNone;
345     else if(!else_line || test_status != TestFound)
346 	test_status = (scope_failed ? TestSeek : TestFound);
347     if(!*d) {
348 	if(!var.stripWhiteSpace().isEmpty())
349 	    qmake_error_msg("Parse Error ('" + s + "')");
350 	return var.isEmpty(); /* allow just a scope */
351     }
352 
353     SKIP_WS(d);
354     for( ; *d && op.find('=') == -1; op += *(d++))
355 	;
356     op.replace(QRegExp("\\s"), "");
357 
358     SKIP_WS(d);
359     QString vals(d); /* vals now contains the space separated list of values */
360     int rbraces = vals.contains('}'), lbraces = vals.contains('{');
361     if(scope_block && rbraces - lbraces == 1) {
362 	debug_msg(1, "Project Parser: %s:%d : Leaving block %d", parser.file.latin1(),
363 		  parser.line_no, scope_block);
364 	test_status = ((scope_flag & (0x01 << scope_block)) ? TestFound : TestSeek);
365 	scope_block--;
366 	vals.truncate(vals.length()-1);
367     } else if(rbraces != lbraces) {
368 	warn_msg(WarnParser, "Possible braces mismatch {%s} %s:%d",
369 		 vals.latin1(), parser.file.latin1(), parser.line_no);
370     }
371     doVariableReplace(vals, place);
372     if(scope_failed)
373 	return TRUE; /* oh well */
374 #undef SKIP_WS
375 
376     if(!var.isEmpty() && Option::mkfile::do_preprocess) {
377 	static QString last_file("*none*");
378 	if(parser.file != last_file) {
379 	    fprintf(stdout, "#file %s:%d\n", parser.file.latin1(), parser.line_no);
380 	    last_file = parser.file;
381 	}
382 	fprintf(stdout, "%s %s %s\n", var.latin1(), op.latin1(), vals.latin1());
383     }
384     var = varMap(var); //backwards compatability
385 
386     /* vallist is the broken up list of values */
387     QStringList vallist = split_value_list(vals, (var == "DEPENDPATH" || var == "INCLUDEPATH"));
388     if(!vallist.grep("=").isEmpty())
389 	warn_msg(WarnParser, "Detected possible line continuation: {%s} %s:%d",
390 		 var.latin1(), parser.file.latin1(), parser.line_no);
391 
392     QStringList &varlist = place[var]; /* varlist is the list in the symbol table */
393     debug_msg(1, "Project Parser: %s:%d :%s: :%s: (%s)", parser.file.latin1(), parser.line_no,
394 	var.latin1(), op.latin1(), vallist.isEmpty() ? "" : vallist.join(" :: ").latin1());
395 
396     /* now do the operation */
397     if(op == "~=") {
398 	if(vallist.count() != 1) {
399 	    qmake_error_msg("~= operator only accepts one right hand paramater ('" +
400 		s + "')");
401 	    return FALSE;
402 	}
403 	QString val(vallist.first());
404 	if(val.length() < 4 || val.at(0) != 's') {
405 	    qmake_error_msg("~= operator only can handle s/// function ('" +
406 		s + "')");
407 	    return FALSE;
408 	}
409 	QChar sep = val.at(1);
410 	QStringList func = QStringList::split(sep, val, TRUE);
411 	if(func.count() < 3 || func.count() > 4) {
412 	    qmake_error_msg("~= operator only can handle s/// function ('" +
413 		s + "')");
414 	    return FALSE;
415 	}
416 	bool global = FALSE, case_sense = TRUE;
417 	if(func.count() == 4) {
418 	    global = func[3].find('g') != -1;
419 	    case_sense = func[3].find('i') == -1;
420 	}
421 	QRegExp regexp(func[1], case_sense);
422 	for(QStringList::Iterator varit = varlist.begin();
423 	    varit != varlist.end(); ++varit) {
424 	    if((*varit).contains(regexp)) {
425 		(*varit) = (*varit).replace(regexp, func[2]);
426 		if(!global)
427 		    break;
428 	    }
429 	}
430     } else {
431 	if(op == "=") {
432 	    if(!varlist.isEmpty())
433 		warn_msg(WarnParser, "Operator=(%s) clears variables previously set: %s:%d",
434 			 var.latin1(), parser.file.latin1(), parser.line_no);
435 	    varlist.clear();
436 	}
437 	for(QStringList::Iterator valit = vallist.begin();
438 	    valit != vallist.end(); ++valit) {
439 	    if((*valit).isEmpty())
440 		continue;
441 	    if((op == "*=" && !(*varlist.find((*valit)))) ||
442 	       op == "=" || op == "+=")
443 		varlist.append((*valit));
444 	    else if(op == "-=")
445 		varlist.remove((*valit));
446 	}
447     }
448     if(var == "REQUIRES") /* special case to get communicated to backends! */
449 	doProjectCheckReqs(vallist, place);
450 
451     return TRUE;
452 }
453 
454 bool
read(const QString & file,QMap<QString,QStringList> & place)455 QMakeProject::read(const QString &file, QMap<QString, QStringList> &place)
456 {
457     parser_info pi = parser;
458     reset();
459 
460     QString filename = Option::fixPathToLocalOS(file);
461     doVariableReplace(filename, place);
462     bool ret = FALSE, using_stdin = FALSE;
463     QFile qfile;
464     if(!strcmp(filename, "-")) {
465 	qfile.setName("");
466 	ret = qfile.open(IO_ReadOnly, stdin);
467 	using_stdin = TRUE;
468     } else {
469 	qfile.setName(filename);
470 	ret = qfile.open(IO_ReadOnly);
471     }
472     if ( ret ) {
473 	QTextStream t( &qfile );
474 	QString s, line;
475 	parser.file = filename;
476 	parser.line_no = 0;
477 	while ( !t.eof() ) {
478 	    parser.line_no++;
479 	    line = t.readLine().stripWhiteSpace();
480 	    int prelen = line.length();
481 
482 	    int hash_mark = line.find("#");
483 	    if(hash_mark != -1) //good bye comments
484 		line = line.left(hash_mark);
485 	    if(!line.isEmpty() && line.right(1) == "\\") {
486 		if (!line.startsWith("#")) {
487 		    line.truncate(line.length() - 1);
488 		    s += line + " ";
489 		}
490 	    } else if(!line.isEmpty() || (line.isEmpty() && !prelen)) {
491 		if(s.isEmpty() && line.isEmpty())
492 		    continue;
493 		if(!line.isEmpty())
494 		    s += line;
495 		if(!s.isEmpty()) {
496 		    if(!(ret = parse(s, place)))
497 			break;
498 		    s = "";
499 		}
500 	    }
501 	}
502 	if(!using_stdin)
503 	    qfile.close();
504     }
505     parser = pi;
506     if(scope_block != 0)
507 	warn_msg(WarnParser, "%s: Unterminated conditional at end of file.",
508 		 file.latin1());
509     return ret;
510 }
511 
512 bool
read(const QString & project,const QString &,uchar cmd)513 QMakeProject::read(const QString &project, const QString &, uchar cmd)
514 {
515     pfile = project;
516     return read(cmd);
517 }
518 
519 bool
read(uchar cmd)520 QMakeProject::read(uchar cmd)
521 {
522     if(cfile.isEmpty()) {
523 	// hack to get the Option stuff in there
524 	base_vars["QMAKE_EXT_CPP"] = Option::cpp_ext;
525 	base_vars["QMAKE_EXT_H"] = Option::h_ext;
526 	if(!Option::user_template_prefix.isEmpty())
527 	    base_vars["TEMPLATE_PREFIX"] = Option::user_template_prefix;
528 
529 	if(cmd & ReadCache && Option::mkfile::do_cache) {	/* parse the cache */
530 	    if(Option::mkfile::cachefile.isEmpty())  { //find it as it has not been specified
531 		QString dir = QDir::convertSeparators(Option::output_dir);
532 		while(!QFile::exists((Option::mkfile::cachefile = dir +
533 				      QDir::separator() + ".qmake.cache"))) {
534 		    dir = dir.left(dir.findRev(QDir::separator()));
535 		    if(dir.isEmpty() || dir.find(QDir::separator()) == -1) {
536 			Option::mkfile::cachefile = "";
537 			break;
538 		    }
539 		    if(Option::mkfile::cachefile_depth == -1)
540 			Option::mkfile::cachefile_depth = 1;
541 		    else
542 			Option::mkfile::cachefile_depth++;
543 		}
544 	    }
545 	    if(!Option::mkfile::cachefile.isEmpty()) {
546 		read(Option::mkfile::cachefile, cache);
547 		if(Option::mkfile::qmakespec.isEmpty() && !cache["QMAKESPEC"].isEmpty())
548 		    Option::mkfile::qmakespec = cache["QMAKESPEC"].first();
549 	    }
550 	}
551 	if(cmd & ReadConf) { 	    /* parse mkspec */
552 	    QStringList mkspec_roots = qmake_mkspec_paths();
553 	    if(Option::mkfile::qmakespec.isEmpty()) {
554 		for(QStringList::Iterator it = mkspec_roots.begin(); it != mkspec_roots.end(); ++it) {
555 		    QString mkspec = (*it) + QDir::separator() + "default";
556                     QFileInfo default_info(mkspec);
557                     if(default_info.exists() && default_info.isSymLink()) {
558 			Option::mkfile::qmakespec = mkspec;
559 			break;
560 		    }
561 		}
562 		if(Option::mkfile::qmakespec.isEmpty()) {
563 		    fprintf(stderr, "QMAKESPEC has not been set, so configuration cannot be deduced.\n");
564 		    return FALSE;
565 		}
566 	    }
567 
568 	    if(QDir::isRelativePath(Option::mkfile::qmakespec)) {
569 		bool found_mkspec = FALSE;
570 		for(QStringList::Iterator it = mkspec_roots.begin(); it != mkspec_roots.end(); ++it) {
571 		    QString mkspec = (*it) + QDir::separator() + Option::mkfile::qmakespec;
572 		    if(QFile::exists(mkspec)) {
573 			found_mkspec = TRUE;
574 			Option::mkfile::qmakespec = mkspec;
575 			break;
576 		    }
577 		}
578 		if(!found_mkspec) {
579 		    fprintf(stderr, "Could not find mkspecs for your QMAKESPEC after trying:\n\t%s\n",
580 			    mkspec_roots.join("\n\t").latin1());
581 		    return FALSE;
582 		}
583 	    }
584 
585 	    /* parse qmake configuration */
586 	    while(Option::mkfile::qmakespec.endsWith(QString(QChar(QDir::separator()))))
587 		Option::mkfile::qmakespec.truncate(Option::mkfile::qmakespec.length()-1);
588 	    QString spec = Option::mkfile::qmakespec + QDir::separator() + "qmake.conf";
589 	    if(!QFile::exists(spec) &&
590 	       QFile::exists(Option::mkfile::qmakespec + QDir::separator() + "tmake.conf"))
591 		spec = Option::mkfile::qmakespec + QDir::separator() + "tmake.conf";
592 	    debug_msg(1, "QMAKESPEC conf: reading %s", spec.latin1());
593 	    if(!read(spec, base_vars)) {
594 		fprintf(stderr, "Failure to read QMAKESPEC conf file %s.\n", spec.latin1());
595 		return FALSE;
596 	    }
597 	    if(Option::mkfile::do_cache && !Option::mkfile::cachefile.isEmpty()) {
598 		debug_msg(1, "QMAKECACHE file: reading %s", Option::mkfile::cachefile.latin1());
599 		read(Option::mkfile::cachefile, base_vars);
600 	    }
601 	}
602 	if(cmd & ReadCmdLine) {
603 	    /* commandline */
604 	    cfile = pfile;
605 	    parser.line_no = 1; //really arg count now.. duh
606 	    parser.file = "(internal)";
607 	    reset();
608 	    for(QStringList::Iterator it = Option::before_user_vars.begin();
609 		it != Option::before_user_vars.end(); ++it) {
610 		if(!parse((*it), base_vars)) {
611 		    fprintf(stderr, "Argument failed to parse: %s\n", (*it).latin1());
612 		    return FALSE;
613 		}
614 		parser.line_no++;
615 	    }
616 	}
617     }
618 
619     vars = base_vars; /* start with the base */
620 
621     if(cmd & ReadProFile) { /* parse project file */
622 	debug_msg(1, "Project file: reading %s", pfile.latin1());
623 	if(pfile != "-" && !QFile::exists(pfile) && !pfile.endsWith(".pro"))
624 	    pfile += ".pro";
625 	if(!read(pfile, vars))
626 	    return FALSE;
627     }
628 
629     if(cmd & ReadPostFiles) { /* parse post files */
630 	const QStringList l = vars["QMAKE_POST_INCLUDE_FILES"];
631 	for(QStringList::ConstIterator it = l.begin(); it != l.end(); ++it)
632 	    read((*it), vars);
633     }
634 
635     if(cmd & ReadCmdLine) {
636 	parser.line_no = 1; //really arg count now.. duh
637 	parser.file = "(internal)";
638 	reset();
639 	for(QStringList::Iterator it = Option::after_user_vars.begin();
640 	    it != Option::after_user_vars.end(); ++it) {
641 	    if(!parse((*it), vars)) {
642 		fprintf(stderr, "Argument failed to parse: %s\n", (*it).latin1());
643 		return FALSE;
644 	    }
645 	    parser.line_no++;
646 	}
647     }
648 
649     /* now let the user override the template from an option.. */
650     if(!Option::user_template.isEmpty()) {
651 	debug_msg(1, "Overriding TEMPLATE (%s) with: %s", vars["TEMPLATE"].first().latin1(),
652 		  Option::user_template.latin1());
653 	vars["TEMPLATE"].clear();
654 	vars["TEMPLATE"].append(Option::user_template);
655     }
656 
657     QStringList &templ = vars["TEMPLATE"];
658     if(templ.isEmpty())
659 	templ.append(QString("app"));
660     else if(vars["TEMPLATE"].first().endsWith(".t"))
661 	templ = QStringList(templ.first().left(templ.first().length() - 2));
662     if ( !Option::user_template_prefix.isEmpty() )
663 	templ.first().prepend(Option::user_template_prefix);
664 
665     if(vars["TARGET"].isEmpty()) {
666 	// ### why not simply use:
667 	// QFileInfo fi(pfile);
668 	// fi.baseName();
669 	QString tmp = pfile;
670 	if(tmp.findRev('/') != -1)
671 	    tmp = tmp.right( tmp.length() - tmp.findRev('/') - 1 );
672 	if(tmp.findRev('.') != -1)
673 	    tmp = tmp.left(tmp.findRev('.'));
674 	vars["TARGET"].append(tmp);
675     }
676 
677     QString test_version = getenv("QTESTVERSION");
678     if (!test_version.isEmpty()) {
679 	QString s = vars["TARGET"].first();
680 	if (s == "qt" || s == "qt-mt" || s == "qte" || s == "qte-mt") {
681 	    QString &ver = vars["VERSION"].first();
682 //	    fprintf(stderr,"Current QT version number: " + ver + "\n");
683 	    if (ver != "" && ver != test_version) {
684 		ver = test_version;
685 		fprintf(stderr,"Changed QT version number to " + test_version + "!\n");
686 	    }
687 	}
688     }
689     return TRUE;
690 }
691 
692 bool
isActiveConfig(const QString & x,bool regex,QMap<QString,QStringList> * place)693 QMakeProject::isActiveConfig(const QString &x, bool regex, QMap<QString, QStringList> *place)
694 {
695     if(x.isEmpty())
696 	return TRUE;
697 
698     if((Option::target_mode == Option::TARG_MACX_MODE || Option::target_mode == Option::TARG_QNX6_MODE ||
699 	Option::target_mode == Option::TARG_UNIX_MODE) && x == "unix")
700 	return TRUE;
701     else if(Option::target_mode == Option::TARG_MACX_MODE && x == "macx")
702 	return TRUE;
703     else if(Option::target_mode == Option::TARG_QNX6_MODE && x == "qnx6")
704 	return TRUE;
705     else if(Option::target_mode == Option::TARG_MAC9_MODE && x == "mac9")
706 	return TRUE;
707     else if((Option::target_mode == Option::TARG_MAC9_MODE || Option::target_mode == Option::TARG_MACX_MODE) &&
708 	    x == "mac")
709 	return TRUE;
710     else if(Option::target_mode == Option::TARG_WIN_MODE && x == "win32")
711 	return TRUE;
712 
713 
714     QRegExp re(x, FALSE, TRUE);
715     QString spec = Option::mkfile::qmakespec.right(Option::mkfile::qmakespec.length() -
716 						   (Option::mkfile::qmakespec.findRev(QDir::separator())+1));
717     if((regex && re.exactMatch(spec)) || (!regex && spec == x))
718 	return TRUE;
719 #ifdef Q_OS_UNIX
720     else if(spec == "default") {
721 	static char *buffer = NULL;
722 	if(!buffer)
723 	    buffer = (char *)malloc(1024);
724 	int l = readlink(Option::mkfile::qmakespec, buffer, 1024);
725 	if(l != -1) {
726 	    buffer[l] = '\0';
727 	    QString r = buffer;
728 	    if(r.findRev('/') != -1)
729 		r = r.mid(r.findRev('/') + 1);
730 	    if((regex && re.exactMatch(r)) || (!regex && r == x))
731 		return TRUE;
732 	}
733     }
734 #endif
735 
736 
737     QStringList &configs = (place ? (*place)["CONFIG"] : vars["CONFIG"]);
738     for(QStringList::Iterator it = configs.begin(); it != configs.end(); ++it) {
739 	if((regex && re.exactMatch((*it))) || (!regex && (*it) == x))
740 	if(re.exactMatch((*it)))
741 	    return TRUE;
742     }
743     return FALSE;
744 }
745 
746 bool
doProjectTest(const QString & func,const QString & params,QMap<QString,QStringList> & place)747 QMakeProject::doProjectTest(const QString& func, const QString &params, QMap<QString, QStringList> &place)
748 {
749     QStringList args = split_arg_list(params);
750     for(QStringList::Iterator arit = args.begin(); arit != args.end(); ++arit) {
751 	QString tmp = (*arit).stripWhiteSpace();
752 	if((tmp[0] == '\'' || tmp[0] == '"') && tmp.right(1) == tmp.left(1))
753 	    tmp = tmp.mid(1, tmp.length() - 2);
754     }
755     return doProjectTest(func.stripWhiteSpace(), args, place);
756 }
757 
758 bool
doProjectTest(const QString & func,QStringList args,QMap<QString,QStringList> & place)759 QMakeProject::doProjectTest(const QString& func, QStringList args, QMap<QString, QStringList> &place)
760 {
761     for(QStringList::Iterator arit = args.begin(); arit != args.end(); ++arit) {
762 	(*arit) = (*arit).stripWhiteSpace(); // blah, get rid of space
763 	doVariableReplace((*arit), place);
764     }
765     debug_msg(1, "Running project test: %s( %s )", func.latin1(), args.join("::").latin1());
766 
767     if(func == "requires") {
768 	return doProjectCheckReqs(args, place);
769     } else if(func == "equals") {
770 	if(args.count() != 2) {
771 	    fprintf(stderr, "%s:%d: equals(variable, value) requires two arguments.\n", parser.file.latin1(),
772 		    parser.line_no);
773 	    return FALSE;
774 	}
775 	QString value = args[1];
776 	if((value.left(1) == "\"" || value.left(1) == "'") && value.right(1) == value.left(1))
777 	    value = value.mid(1, value.length()-2);
778 	return vars[args[0]].join(" ") == value;
779     } else if(func == "exists") {
780 	if(args.count() != 1) {
781 	    fprintf(stderr, "%s:%d: exists(file) requires one argument.\n", parser.file.latin1(),
782 		    parser.line_no);
783 	    return FALSE;
784 	}
785 	QString file = args.first();
786 	file = Option::fixPathToLocalOS(file);
787 	doVariableReplace(file, place);
788 
789 	if(QFile::exists(file))
790 	    return TRUE;
791 	//regular expression I guess
792 	QString dirstr = QDir::currentDirPath();
793 	int slsh = file.findRev(Option::dir_sep);
794 	if(slsh != -1) {
795 	    dirstr = file.left(slsh+1);
796 	    file = file.right(file.length() - slsh - 1);
797 	}
798 	QDir dir(dirstr, file);
799 	return dir.count() != 0;
800     } else if(func == "system") {
801 	if(args.count() != 1) {
802 	    fprintf(stderr, "%s:%d: system(exec) requires one argument.\n", parser.file.latin1(),
803 		    parser.line_no);
804 	    return FALSE;
805 	}
806 	return system(args.first().latin1()) == 0;
807     } else if(func == "contains") {
808 	if(args.count() != 2) {
809 	    fprintf(stderr, "%s:%d: contains(var, val) requires two arguments.\n", parser.file.latin1(),
810 		    parser.line_no);
811 	    return FALSE;
812 	}
813 	QRegExp regx(args[1]);
814 	QStringList &l = place[args[0]];
815 	for(QStringList::ConstIterator it = l.begin(); it != l.end(); ++it) {
816 	    if(regx.exactMatch((*it)))
817 		return TRUE;
818 	}
819 	return FALSE;
820     } else if(func == "infile") {
821 	if(args.count() < 2 || args.count() > 3) {
822 	    fprintf(stderr, "%s:%d: infile(file, var, val) requires at least 2 arguments.\n",
823 		    parser.file.latin1(), parser.line_no);
824 	    return FALSE;
825 	}
826 	QMakeProject proj;
827 	QString file = args[0];
828 	doVariableReplace(file, place);
829 	fixEnvVariables(file);
830 	int di = file.findRev(Option::dir_sep);
831 	QDir sunworkshop42workaround = QDir::current();
832 	QString oldpwd = sunworkshop42workaround.currentDirPath();
833 	if(di != -1) {
834 	    if(!QDir::setCurrent(file.left(file.findRev(Option::dir_sep)))) {
835 		fprintf(stderr, "Cannot find directory: %s\n", file.left(di).latin1());
836 		return FALSE;
837 	    }
838 	    file = file.right(file.length() - di - 1);
839 	}
840 	parser_info pi = parser;
841 	bool ret = !proj.read(file, oldpwd);
842 	parser = pi;
843 	if(ret) {
844 	    fprintf(stderr, "Error processing project file: %s\n", file.latin1());
845 	    QDir::setCurrent(oldpwd);
846 	    return FALSE;
847 	}
848 	if(args.count() == 2) {
849 	    ret = !proj.isEmpty(args[1]);
850 	} else {
851 	    QRegExp regx(args[2]);
852 	    QStringList &l = proj.values(args[1]);
853 	    for(QStringList::ConstIterator it = l.begin(); it != l.end(); ++it) {
854 		if(regx.exactMatch((*it))) {
855 		    ret = TRUE;
856 		    break;
857 		}
858 	    }
859 	}
860 	QDir::setCurrent(oldpwd);
861 	return ret;
862     } else if(func == "count") {
863 	if(args.count() != 2) {
864 	    fprintf(stderr, "%s:%d: count(var, count) requires two arguments.\n", parser.file.latin1(),
865 		    parser.line_no);
866 	    return FALSE;
867 	}
868 	return vars[args[0]].count() == args[1].toUInt();
869     } else if(func == "isEmpty") {
870 	if(args.count() != 1) {
871 	    fprintf(stderr, "%s:%d: isEmpty(var) requires one argument.\n", parser.file.latin1(),
872 		    parser.line_no);
873 	    return FALSE;
874 	}
875 	return vars[args[0]].isEmpty();
876     } else if(func == "include" || func == "load") {
877 	QString seek_var;
878 	if(args.count() == 2 && func == "include") {
879 	    seek_var = args[1];
880 	} else if(args.count() != 1) {
881 	    QString func_desc = "include(file)";
882 	    if(func == "load")
883 		func_desc = "load(feature)";
884 	    fprintf(stderr, "%s:%d: %s requires one argument.\n", parser.file.latin1(),
885 		    parser.line_no, func_desc.latin1());
886 	    return FALSE;
887 	}
888 
889 	QString file = args.first();
890 	file = Option::fixPathToLocalOS(file);
891 	file.replace("\"", "");
892 	doVariableReplace(file, place);
893 	if(func == "load") {
894 	    if(!file.endsWith(Option::prf_ext))
895 		file += Option::prf_ext;
896 	    if(file.find(Option::dir_sep) == -1 || !QFile::exists(file)) {
897 		bool found = FALSE;
898 		const QString concat = QDir::separator() + QString("mkspecs") +
899 				       QDir::separator() + QString("features");
900 		QStringList feature_roots;
901 		if(const char *mkspec_path = getenv("QMAKEFEATURES")) {
902 #ifdef Q_OS_WIN
903 		    QStringList lst = QStringList::split(';', mkspec_path);
904 		    for(QStringList::Iterator it = lst.begin(); it != lst.end(); ++it)
905 			feature_roots += QStringList::split(':', (*it));
906 #else
907 		    feature_roots += QStringList::split(':', mkspec_path);
908 #endif
909 		}
910 		if(const char *qmakepath = getenv("QMAKEPATH")) {
911 #ifdef Q_OS_WIN
912 		    QStringList lst = QStringList::split(';', qmakepath);
913 		    for(QStringList::Iterator it = lst.begin(); it != lst.end(); ++it) {
914 			QStringList lst2 = QStringList::split(':', (*it));
915 			for(QStringList::Iterator it2 = lst2.begin(); it2 != lst2.end(); ++it2)
916 			    feature_roots << ((*it2) + concat);
917 		    }
918 #else
919 		    QStringList lst = QStringList::split(':', qmakepath);
920 		    for(QStringList::Iterator it = lst.begin(); it != lst.end(); ++it)
921 			feature_roots << ((*it) + concat);
922 #endif
923 		}
924 		feature_roots << Option::mkfile::qmakespec;
925 		if(const char *qtdir = getenv("QTDIR"))
926 		    feature_roots << (qtdir + concat);
927 #ifdef QT_INSTALL_PREFIX
928 		feature_roots << (QT_INSTALL_PREFIX + concat);
929 #endif
930 #if defined(HAVE_QCONFIG_CPP)
931 		feature_roots << (qInstallPath() + concat);
932 #endif
933 #ifdef QT_INSTALL_DATA
934 		feature_roots << (QT_INSTALL_DATA + concat);
935 #endif
936 #if defined(HAVE_QCONFIG_CPP)
937 		feature_roots << (qInstallPathData() + concat);
938 #endif
939 		for(QStringList::Iterator it = feature_roots.begin(); it != feature_roots.end(); ++it) {
940 		    QString prf = (*it) + QDir::separator() + file;
941 		    if(QFile::exists(prf)) {
942 			found = TRUE;
943 			file = prf;
944 			break;
945 		    }
946 		}
947 		if(!found) {
948 		    printf("Project LOAD(): Feature %s cannot be found.\n", args.first().latin1());
949 		    exit(3);
950 		}
951 	    }
952 	}
953 	if(QDir::isRelativePath(file)) {
954 	    QStringList include_roots;
955 	    include_roots << Option::output_dir;
956 	    QString pfilewd = QFileInfo(parser.file).dirPath();
957 	    if(!pfilewd.isEmpty() && pfilewd != QDir::currentDirPath() && pfilewd != ".")
958 		include_roots << pfilewd;
959 	    if(Option::output_dir != QDir::currentDirPath())
960 		include_roots << QDir::currentDirPath();
961 	    for(QStringList::Iterator it = include_roots.begin(); it != include_roots.end(); ++it) {
962 		if(QFile::exists((*it) + QDir::separator() + file)) {
963 		    file = (*it) + QDir::separator() + file;
964 		    break;
965 		}
966 	    }
967 	}
968 
969 	if(Option::mkfile::do_preprocess) //nice to see this first..
970 	    fprintf(stderr, "#switching file %s(%s) - %s:%d\n", func.latin1(), file.latin1(),
971 		    parser.file.latin1(), parser.line_no);
972 	debug_msg(1, "Project Parser: %s'ing file %s.", func.latin1(), file.latin1());
973 	QString orig_file = file;
974 	int di = file.findRev(Option::dir_sep);
975 	QDir sunworkshop42workaround = QDir::current();
976 	QString oldpwd = sunworkshop42workaround.currentDirPath();
977 	if(di != -1) {
978 	    if(!QDir::setCurrent(file.left(file.findRev(Option::dir_sep)))) {
979 		fprintf(stderr, "Cannot find directory: %s\n", file.left(di).latin1());
980 		return FALSE;
981 	    }
982 	    file = file.right(file.length() - di - 1);
983 	}
984 	parser_info pi = parser;
985 	int sb = scope_block;
986 	int sf = scope_flag;
987 	TestStatus sc = test_status;
988 	bool r = FALSE;
989 	if(!seek_var.isNull()) {
990 	    QMap<QString, QStringList> tmp;
991 	    if((r = read(file.latin1(), tmp)))
992 		place[seek_var] += tmp[seek_var];
993 	} else {
994 	    r = read(file.latin1(), place);
995 	}
996 	if(r)
997 	    vars["QMAKE_INTERNAL_INCLUDED_FILES"].append(orig_file);
998 	else
999 	    warn_msg(WarnParser, "%s:%d: Failure to include file %s.",
1000 		     pi.file.latin1(), pi.line_no, orig_file.latin1());
1001 	parser = pi;
1002 	test_status = sc;
1003 	scope_flag = sf;
1004 	scope_block = sb;
1005 	QDir::setCurrent(oldpwd);
1006 	return r;
1007     } else if(func == "error" || func == "message") {
1008 	if(args.count() != 1) {
1009 	    fprintf(stderr, "%s:%d: %s(message) requires one argument.\n", parser.file.latin1(),
1010 		    parser.line_no, func.latin1());
1011 	    return FALSE;
1012 	}
1013 	QString msg = args.first();
1014 	if((msg.startsWith("\"") || msg.startsWith("'")) && msg.endsWith(msg.left(1)))
1015 	    msg = msg.mid(1, msg.length()-2);
1016 	msg.replace(QString("${QMAKE_FILE}"), parser.file.latin1());
1017 	msg.replace(QString("${QMAKE_LINE_NUMBER}"), QString::number(parser.line_no));
1018 	msg.replace(QString("${QMAKE_DATE}"), QDateTime::currentDateTime().toString());
1019 	doVariableReplace(msg, place);
1020 	fixEnvVariables(msg);
1021 	fprintf(stderr, "Project %s: %s\n", func.upper().latin1(), msg.latin1());
1022 	if(func == "message")
1023 	    return TRUE;
1024 	exit(2);
1025     } else {
1026 	fprintf(stderr, "%s:%d: Unknown test function: %s\n", parser.file.latin1(), parser.line_no,
1027 		func.latin1());
1028     }
1029     return FALSE;
1030 }
1031 
1032 bool
doProjectCheckReqs(const QStringList & deps,QMap<QString,QStringList> & place)1033 QMakeProject::doProjectCheckReqs(const QStringList &deps, QMap<QString, QStringList> &place)
1034 {
1035     bool ret = FALSE;
1036     for(QStringList::ConstIterator it = deps.begin(); it != deps.end(); ++it) {
1037 	QString chk = (*it);
1038 	if(chk.isEmpty())
1039 	    continue;
1040 	bool invert_test = (chk.left(1) == "!");
1041 	if(invert_test)
1042 	    chk = chk.right(chk.length() - 1);
1043 
1044 	bool test;
1045 	int lparen = chk.find('(');
1046 	if(lparen != -1) { /* if there is an lparen in the chk, it IS a function */
1047 	    int rparen = chk.findRev(')');
1048 	    if(rparen == -1) {
1049 		QCString error;
1050 		error.sprintf("Function (in REQUIRES) missing right paren: %s", chk.latin1());
1051 		qmake_error_msg(error);
1052 	    } else {
1053 		QString func = chk.left(lparen);
1054 		test = doProjectTest(func, chk.mid(lparen+1, rparen - lparen - 1), place);
1055 	    }
1056 	} else {
1057 	    test = isActiveConfig(chk, TRUE, &place);
1058 	}
1059 	if(invert_test) {
1060 	    chk.prepend("!");
1061 	    test = !test;
1062 	}
1063 	if(!test) {
1064 	    debug_msg(1, "Project Parser: %s:%d Failed test: REQUIRES = %s",
1065 		      parser.file.latin1(), parser.line_no, chk.latin1());
1066 	    place["QMAKE_FAILED_REQUIREMENTS"].append(chk);
1067 	    ret = FALSE;
1068 	}
1069     }
1070     return ret;
1071 }
1072 
1073 
1074 QString
doVariableReplace(QString & str,const QMap<QString,QStringList> & place)1075 QMakeProject::doVariableReplace(QString &str, const QMap<QString, QStringList> &place)
1076 {
1077     for(int var_begin, var_last=0; (var_begin = str.find("$$", var_last)) != -1; var_last = var_begin) {
1078 	if(var_begin >= (int)str.length() + 2) {
1079 	    break;
1080 	} else if(var_begin != 0 && str[var_begin-1] == '\\') {
1081 	    str.replace(var_begin-1, 1, "");
1082 	    var_begin += 1;
1083 	    continue;
1084 	}
1085 
1086 	int var_incr = var_begin + 2;
1087 	bool in_braces = FALSE, as_env = FALSE, as_prop = FALSE;
1088 	if(str[var_incr] == '{') {
1089 	    in_braces = TRUE;
1090 	    var_incr++;
1091 	    while(var_incr < (int)str.length() &&
1092 		  (str[var_incr] == ' ' || str[var_incr] == '\t' || str[var_incr] == '\n'))
1093 		var_incr++;
1094 	}
1095 	if(str[var_incr] == '(') {
1096 	    as_env = TRUE;
1097 	    var_incr++;
1098 	} else if(str[var_incr] == '[') {
1099 	    as_prop = TRUE;
1100 	    var_incr++;
1101 	}
1102 	QString val, args;
1103 	while(var_incr < (int)str.length() &&
1104 	      (str[var_incr].isLetter() || str[var_incr].isNumber() || str[var_incr] == '.' || str[var_incr] == '_'))
1105 	    val += str[var_incr++];
1106 	if(as_env) {
1107 	    if(str[var_incr] != ')') {
1108 		var_incr++;
1109 		warn_msg(WarnParser, "%s:%d: Unterminated env-variable replacement '%s' (%s)",
1110 			 parser.file.latin1(), parser.line_no,
1111 			 str.mid(var_begin, QMAX(var_incr - var_begin,
1112 						 (int)str.length())).latin1(), str.latin1());
1113 		var_begin += var_incr;
1114 		continue;
1115 	    }
1116 	    var_incr++;
1117 	} else if(as_prop) {
1118 	    if(str[var_incr] != ']') {
1119 		var_incr++;
1120 		warn_msg(WarnParser, "%s:%d: Unterminated prop-variable replacement '%s' (%s)",
1121 			 parser.file.latin1(), parser.line_no,
1122 			 str.mid(var_begin, QMAX(var_incr - var_begin, int(str.length()))).latin1(), str.latin1());
1123 		var_begin += var_incr;
1124 		continue;
1125 	    }
1126 	    var_incr++;
1127 	} else if(str[var_incr] == '(') { //args
1128 	    for(int parens = 0; var_incr < (int)str.length(); var_incr++) {
1129 		if(str[var_incr] == '(') {
1130 		    parens++;
1131 		    if(parens == 1)
1132 			continue;
1133 		} else if(str[var_incr] == ')') {
1134 		    parens--;
1135 		    if(!parens) {
1136 			var_incr++;
1137 			break;
1138 		    }
1139 		}
1140 		args += str[var_incr];
1141 	    }
1142 	}
1143 	if(var_incr > (int)str.length() || (in_braces && str[var_incr] != '}')) {
1144 	    var_incr++;
1145 	    warn_msg(WarnParser, "%s:%d: Unterminated variable replacement '%s' (%s)",
1146 		     parser.file.latin1(), parser.line_no,
1147 		     str.mid(var_begin, QMAX(var_incr - var_begin,
1148 					     (int)str.length())).latin1(), str.latin1());
1149 	    var_begin += var_incr;
1150 	    continue;
1151 	} else if(in_braces) {
1152 	    var_incr++;
1153 	}
1154 
1155 	QString replacement;
1156 	if(as_env) {
1157 	    replacement = getenv(val);
1158 	} else if(as_prop) {
1159 	    if(prop)
1160 		replacement = prop->value(val);
1161 	} else if(args.isEmpty()) {
1162 	    if(val.left(1) == ".")
1163 		replacement = "";
1164 	    else if(val == "LITERAL_WHITESPACE")
1165 		replacement = "\t";
1166 	    else if(val == "LITERAL_DOLLAR")
1167 		replacement = "$";
1168 	    else if(val == "LITERAL_HASH")
1169 		replacement = "#";
1170 	    else if(val == "PWD")
1171 		replacement = QDir::currentDirPath();
1172 	    else if(val == "DIR_SEPARATOR")
1173 		replacement = Option::dir_sep;
1174 	    else
1175 		replacement = place[varMap(val)].join(" ");
1176 	} else {
1177 	    QStringList arg_list = split_arg_list(doVariableReplace(args, place));
1178 	    debug_msg(1, "Running function: %s( %s )", val.latin1(), arg_list.join("::").latin1());
1179 	    if(val.lower() == "member") {
1180 		if(arg_list.count() < 1 || arg_list.count() > 3) {
1181 		    fprintf(stderr, "%s:%d: member(var, start, end) requires three arguments.\n",
1182 			    parser.file.latin1(), parser.line_no);
1183 		} else {
1184 		    const QStringList &var = place[varMap(arg_list.first())];
1185 		    int start = 0;
1186 		    if(arg_list.count() >= 2)
1187 			start = arg_list[1].toInt();
1188 		    if(start < 0)
1189 			start += int(var.count());
1190 		    int end = start;
1191 		    if(arg_list.count() == 3)
1192 			end = arg_list[2].toInt();
1193 		    if(end < 0)
1194 			end += int(var.count());
1195 		    if(end < start)
1196 			end = start;
1197 		    for(int i = start; i <= end && (int)var.count() >= i; i++) {
1198 			if(!replacement.isEmpty())
1199 			    replacement += " ";
1200 			replacement += var[i];
1201 		    }
1202 		}
1203 	    } else if(val.lower() == "fromfile") {
1204 		if(arg_list.count() != 2) {
1205 		    fprintf(stderr, "%s:%d: fromfile(file, variable) requires two arguments.\n",
1206 			    parser.file.latin1(), parser.line_no);
1207 		} else {
1208 		    QString file = arg_list[0];
1209 		    file = Option::fixPathToLocalOS(file);
1210 		    file.replace("\"", "");
1211 
1212 		    if(QDir::isRelativePath(file)) {
1213 			QStringList include_roots;
1214 			include_roots << Option::output_dir;
1215 			QString pfilewd = QFileInfo(parser.file).dirPath();
1216 			if(pfilewd.isEmpty())
1217 			    include_roots << pfilewd;
1218 			if(Option::output_dir != QDir::currentDirPath())
1219 			    include_roots << QDir::currentDirPath();
1220 			for(QStringList::Iterator it = include_roots.begin(); it != include_roots.end(); ++it) {
1221 			    if(QFile::exists((*it) + QDir::separator() + file)) {
1222 				file = (*it) + QDir::separator() + file;
1223 				break;
1224 			    }
1225 			}
1226 		    }
1227 		    QString orig_file = file;
1228 		    int di = file.findRev(Option::dir_sep);
1229 		    QDir sunworkshop42workaround = QDir::current();
1230 		    QString oldpwd = sunworkshop42workaround.currentDirPath();
1231 		    if(di != -1 && QDir::setCurrent(file.left(file.findRev(Option::dir_sep))))
1232 			file = file.right(file.length() - di - 1);
1233 		    parser_info pi = parser;
1234 		    int sb = scope_block;
1235 		    int sf = scope_flag;
1236 		    TestStatus sc = test_status;
1237 		    QMap<QString, QStringList> tmp;
1238 		    bool r = read(file.latin1(), tmp);
1239 		    if(r) {
1240 			replacement = tmp[arg_list[1]].join(" ");
1241 			vars["QMAKE_INTERNAL_INCLUDED_FILES"].append(orig_file);
1242 		    } else {
1243 			warn_msg(WarnParser, "%s:%d: Failure to include file %s.",
1244 				 pi.file.latin1(), pi.line_no, orig_file.latin1());
1245 		    }
1246 		    parser = pi;
1247 		    test_status = sc;
1248 		    scope_flag = sf;
1249 		    scope_block = sb;
1250 		    QDir::setCurrent(oldpwd);
1251 		}
1252 	    } else if(val.lower() == "eval") {
1253 		for(QStringList::ConstIterator arg_it = arg_list.begin();
1254 		    arg_it != arg_list.end(); ++arg_it) {
1255 		    if(!replacement.isEmpty())
1256 			replacement += " ";
1257 		    replacement += place[(*arg_it)].join(" ");
1258 		}
1259 	    } else if(val.lower() == "list") {
1260 		static int x = 0;
1261 		replacement.sprintf(".QMAKE_INTERNAL_TMP_VAR_%d", x++);
1262 		QStringList &lst = (*((QMap<QString, QStringList>*)&place))[replacement];
1263 		lst.clear();
1264 		for(QStringList::ConstIterator arg_it = arg_list.begin();
1265 		    arg_it != arg_list.end(); ++arg_it)
1266 		    lst += split_value_list((*arg_it));
1267 	    } else if(val.lower() == "join") {
1268 		if(arg_list.count() < 1 || arg_list.count() > 4) {
1269 		    fprintf(stderr, "%s:%d: join(var, glue, before, after) requires four"
1270 			    "arguments.\n", parser.file.latin1(), parser.line_no);
1271 		} else {
1272 		    QString glue, before, after;
1273 		    if(arg_list.count() >= 2)
1274 			glue = arg_list[1].replace("\"", "" );
1275 		    if(arg_list.count() >= 3)
1276 			before = arg_list[2].replace("\"", "" );
1277 		    if(arg_list.count() == 4)
1278 			after = arg_list[3].replace("\"", "" );
1279 		    const QStringList &var = place[varMap(arg_list.first())];
1280 		    if(!var.isEmpty())
1281 			replacement = before + var.join(glue) + after;
1282 		}
1283 	    } else if(val.lower() == "split") {
1284 		if(arg_list.count() < 2 || arg_list.count() > 3) {
1285 		    fprintf(stderr, "%s:%d split(var, sep, join) requires three arguments\n",
1286 			    parser.file.latin1(), parser.line_no);
1287 		} else {
1288 		    QString sep = arg_list[1], join = " ";
1289 		    if(arg_list.count() == 3)
1290 			join = arg_list[2];
1291 		    QStringList var = place[varMap(arg_list.first())];
1292 		    for(QStringList::Iterator vit = var.begin(); vit != var.end(); ++vit) {
1293 			QStringList lst = QStringList::split(sep, (*vit));
1294 			for(QStringList::Iterator spltit = lst.begin(); spltit != lst.end(); ++spltit) {
1295 			    if(!replacement.isEmpty())
1296 				replacement += join;
1297 			    replacement += (*spltit);
1298 			}
1299 		    }
1300 		}
1301 	    } else if(val.lower() == "find") {
1302 		if(arg_list.count() != 2) {
1303 		    fprintf(stderr, "%s:%d find(var, str) requires two arguments\n",
1304 			    parser.file.latin1(), parser.line_no);
1305 		} else {
1306 		    QRegExp regx(arg_list[1]);
1307 		    const QStringList &var = place[varMap(arg_list.first())];
1308 		    for(QStringList::ConstIterator vit = var.begin();
1309 			vit != var.end(); ++vit) {
1310 			if(regx.search(*vit) != -1) {
1311 			    if(!replacement.isEmpty())
1312 				replacement += " ";
1313 			    replacement += (*vit);
1314 			}
1315 		    }
1316 		}
1317 	    } else if(val.lower() == "system") {
1318 		if(arg_list.count() != 1) {
1319 		    fprintf(stderr, "%s:%d system(execut) requires one argument\n",
1320 			    parser.file.latin1(), parser.line_no);
1321 		} else {
1322 		    char buff[256];
1323 		    FILE *proc = QT_POPEN(arg_list.join(" ").latin1(), "r");
1324 		    while(proc && !feof(proc)) {
1325 			int read_in = int(fread(buff, 1, 255, proc));
1326 			if(!read_in)
1327 			    break;
1328 			for(int i = 0; i < read_in; i++) {
1329 			    if(buff[i] == '\n' || buff[i] == '\t')
1330 				buff[i] = ' ';
1331 			}
1332 			buff[read_in] = '\0';
1333 			replacement += buff;
1334 		    }
1335 		}
1336 	    } else if(val.lower() == "files") {
1337 		if(arg_list.count() != 1) {
1338 		    fprintf(stderr, "%s:%d files(pattern) requires one argument\n",
1339 			    parser.file.latin1(), parser.line_no);
1340 		} else {
1341 		    QString dir, regex = arg_list[0];
1342 		    regex = Option::fixPathToLocalOS(regex);
1343 		    regex.replace("\"", "");
1344 		    if(regex.findRev(QDir::separator()) != -1) {
1345 			dir = regex.left(regex.findRev(QDir::separator()) + 1);
1346 			regex = regex.right(regex.length() - dir.length());
1347 		    }
1348 		    QDir d(dir, regex);
1349 		    for(int i = 0; i < (int)d.count(); i++) {
1350 			if(!replacement.isEmpty())
1351 			    replacement += " ";
1352 			replacement += dir + d[i];
1353 		    }
1354 		}
1355 	    } else if(val.lower() == "prompt") {
1356 		if(arg_list.count() != 1) {
1357 		    fprintf(stderr, "%s:%d prompt(question) requires one argument\n",
1358 			    parser.file.latin1(), parser.line_no);
1359 		} else if(projectFile() == "-") {
1360 		    fprintf(stderr, "%s:%d prompt(question) cannot be used when '-o -' is used.\n",
1361 			    parser.file.latin1(), parser.line_no);
1362 		} else {
1363 		    QString msg = arg_list.first();
1364 		    if((msg.startsWith("\"") || msg.startsWith("'")) && msg.endsWith(msg.left(1)))
1365 			msg = msg.mid(1, msg.length()-2);
1366 		    msg.replace(QString("${QMAKE_FILE}"), parser.file.latin1());
1367 		    msg.replace(QString("${QMAKE_LINE_NUMBER}"), QString::number(parser.line_no));
1368 		    msg.replace(QString("${QMAKE_DATE}"), QDateTime::currentDateTime().toString());
1369 		    doVariableReplace(msg, place);
1370 		    fixEnvVariables(msg);
1371 		    if(!msg.endsWith("?"))
1372 			msg += "?";
1373 		    fprintf(stderr, "Project %s: %s ", val.upper().latin1(), msg.latin1());
1374 
1375 		    QFile qfile;
1376 		    if(qfile.open(IO_ReadOnly, stdin)) {
1377 			QTextStream t(&qfile);
1378 			replacement = t.readLine();
1379 		    }
1380 		}
1381 	    } else {
1382 		fprintf(stderr, "%s:%d: Unknown replace function: %s\n",
1383 			parser.file.latin1(), parser.line_no, val.latin1());
1384 	    }
1385 	}
1386 	//actually do replacement now..
1387 	int mlen = var_incr - var_begin;
1388 	debug_msg(2, "Project Parser [var replace]: '%s' :: %s -> %s", str.latin1(),
1389 		  str.mid(var_begin, mlen).latin1(), replacement.latin1());
1390 	str.replace(var_begin, mlen, replacement);
1391 	var_begin += replacement.length();
1392     }
1393     return str;
1394 }
1395