-rw-r--r-- | qmake/project.cpp | 987 |
1 files changed, 987 insertions, 0 deletions
diff --git a/qmake/project.cpp b/qmake/project.cpp new file mode 100644 index 0000000..ae24193 --- a/dev/null +++ b/qmake/project.cpp | |||
@@ -0,0 +1,987 @@ | |||
1 | /**************************************************************************** | ||
2 | ** $Id$ | ||
3 | ** | ||
4 | ** Definition of ________ class. | ||
5 | ** | ||
6 | ** Created : 970521 | ||
7 | ** | ||
8 | ** Copyright (C) 1992-2002 Trolltech AS. All rights reserved. | ||
9 | ** | ||
10 | ** This file is part of the network module of the Qt GUI Toolkit. | ||
11 | ** | ||
12 | ** This file may be distributed under the terms of the Q Public License | ||
13 | ** as defined by Trolltech AS of Norway and appearing in the file | ||
14 | ** LICENSE.QPL included in the packaging of this file. | ||
15 | ** | ||
16 | ** This file may be distributed and/or modified under the terms of the | ||
17 | ** GNU General Public License version 2 as published by the Free Software | ||
18 | ** Foundation and appearing in the file LICENSE.GPL included in the | ||
19 | ** packaging of this file. | ||
20 | ** | ||
21 | ** Licensees holding valid Qt Enterprise Edition licenses may use this | ||
22 | ** file in accordance with the Qt Commercial License Agreement provided | ||
23 | ** with the Software. | ||
24 | ** | ||
25 | ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE | ||
26 | ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. | ||
27 | ** | ||
28 | ** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for | ||
29 | ** information about Qt Commercial License Agreements. | ||
30 | ** See http://www.trolltech.com/qpl/ for QPL licensing information. | ||
31 | ** See http://www.trolltech.com/gpl/ for GPL licensing information. | ||
32 | ** | ||
33 | ** Contact info@trolltech.com if any conditions of this licensing are | ||
34 | ** not clear to you. | ||
35 | ** | ||
36 | **********************************************************************/ | ||
37 | |||
38 | #include "project.h" | ||
39 | #include "option.h" | ||
40 | #include <qfile.h> | ||
41 | #include <qdir.h> | ||
42 | #include <qregexp.h> | ||
43 | #include <qtextstream.h> | ||
44 | #include <qvaluestack.h> | ||
45 | #ifdef Q_OS_UNIX | ||
46 | # include <unistd.h> | ||
47 | #endif | ||
48 | #include <stdio.h> | ||
49 | #include <stdlib.h> | ||
50 | |||
51 | #ifdef Q_OS_WIN32 | ||
52 | #define QT_POPEN _popen | ||
53 | #else | ||
54 | #define QT_POPEN popen | ||
55 | #endif | ||
56 | |||
57 | struct parser_info { | ||
58 | QString file; | ||
59 | int line_no; | ||
60 | } parser; | ||
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 | |||
66 | static QString varMap(const QString &x) | ||
67 | { | ||
68 | QString ret(x); | ||
69 | ret.replace(QRegExp("^TMAKE"), "QMAKE"); | ||
70 | if(ret == "INTERFACES") | ||
71 | ret = "FORMS"; | ||
72 | return ret; | ||
73 | } | ||
74 | |||
75 | static QStringList split_arg_list(const QString ¶ms) | ||
76 | { | ||
77 | QStringList args; | ||
78 | int last = 0, parens = 0; | ||
79 | QChar quote = 0; | ||
80 | for(int x = 0; x < (int)params.length(); x++) { | ||
81 | if(params[x] == ')') { | ||
82 | parens--; | ||
83 | } else if(params[x] == '(') { | ||
84 | parens++; | ||
85 | } else if(params[x] == quote) { | ||
86 | quote = 0; | ||
87 | } else if(params[x] == '\'' || params[x] == '"') { | ||
88 | quote = params[x]; | ||
89 | } else if(!parens && !quote && params[x] == ',') { | ||
90 | args << params.mid(last, x - last); | ||
91 | last = x+1; | ||
92 | } | ||
93 | } | ||
94 | if(last != (int)params.length()) | ||
95 | args << params.mid(last); | ||
96 | return args; | ||
97 | } | ||
98 | |||
99 | static QStringList split_value_list(const QString &vals, bool do_semicolon=FALSE) | ||
100 | { | ||
101 | int last = 0; | ||
102 | QStringList ret; | ||
103 | QValueStack<QChar> quote; | ||
104 | for(int x = 0; x < (int)vals.length(); x++) { | ||
105 | if(!quote.isEmpty() && vals[x] == quote.top()) { | ||
106 | quote.pop(); | ||
107 | } else if(vals[x] == '\'' || vals[x] == '"') { | ||
108 | quote.push(vals[x]); | ||
109 | } else if(quote.isEmpty() && | ||
110 | ((do_semicolon && vals[x] == ';') || vals[x] == ' ')) { | ||
111 | ret << vals.mid(last, x - last); | ||
112 | last = x+1; | ||
113 | } | ||
114 | } | ||
115 | if(last != (int)vals.length()) | ||
116 | ret << vals.mid(last); | ||
117 | return ret; | ||
118 | } | ||
119 | |||
120 | QMakeProject::QMakeProject() | ||
121 | { | ||
122 | } | ||
123 | |||
124 | bool | ||
125 | QMakeProject::parse(QString t, QMap<QString, QStringList> &place) | ||
126 | { | ||
127 | QString s = t.simplifyWhiteSpace(); | ||
128 | s.replace(QRegExp("#.*$"), ""); /* bye comments */ | ||
129 | if(s.isEmpty()) /* blank_line */ | ||
130 | return TRUE; | ||
131 | |||
132 | if(s.stripWhiteSpace().left(1) == "}") { | ||
133 | debug_msg(1, "Project Parser: %s:%d : Leaving block %d", parser.file.latin1(), | ||
134 | parser.line_no, scope_block); | ||
135 | test_status = ((scope_flag & (0x01 << scope_block)) ? TestFound : TestSeek); | ||
136 | scope_block--; | ||
137 | s = s.mid(1).stripWhiteSpace(); | ||
138 | if(s.isEmpty()) | ||
139 | return TRUE; | ||
140 | } | ||
141 | if(!(scope_flag & (0x01 << scope_block))) { | ||
142 | /* adjust scope for each block which appears on a single line */ | ||
143 | for(int i = (s.contains('{')-s.contains('}')); i; i--) | ||
144 | scope_flag &= ~(0x01 << (++scope_block)); | ||
145 | debug_msg(1, "Project Parser: %s:%d : Ignored due to block being false.", | ||
146 | parser.file.latin1(), parser.line_no); | ||
147 | return TRUE; | ||
148 | } | ||
149 | |||
150 | QString scope, var, op; | ||
151 | QStringList val; | ||
152 | #define SKIP_WS(d) while(*d && (*d == ' ' || *d == '\t')) d++ | ||
153 | const char *d = s.latin1(); | ||
154 | SKIP_WS(d); | ||
155 | bool scope_failed = FALSE, else_line = FALSE, or_op=FALSE; | ||
156 | int parens = 0, scope_count=0; | ||
157 | while(*d && *d != '=') { | ||
158 | if((*d == '+' || *d == '-' || *d == '*' || *d == '~')) { | ||
159 | if(*(d+1) == '=') { | ||
160 | break; | ||
161 | } else if(*(d+1) == ' ') { | ||
162 | const char *k = d + 1; | ||
163 | SKIP_WS(k); | ||
164 | if(*k == '=') { | ||
165 | QString msg; | ||
166 | qmake_error_msg(*d + "must be followed immediatly by ="); | ||
167 | return FALSE; | ||
168 | } | ||
169 | } | ||
170 | } | ||
171 | |||
172 | if ( *d == '(' ) | ||
173 | ++parens; | ||
174 | else if ( *d == ')' ) | ||
175 | --parens; | ||
176 | |||
177 | if(!parens && (*d == ':' || *d == '{' || *d == ')' || *d == '|')) { | ||
178 | scope_count++; | ||
179 | scope = var.stripWhiteSpace(); | ||
180 | if ( *d == ')' ) | ||
181 | scope += *d; /* need this */ | ||
182 | var = ""; | ||
183 | |||
184 | bool test = scope_failed; | ||
185 | if(scope.lower() == "else") { | ||
186 | if(scope_count != 1 || test_status == TestNone) { | ||
187 | qmake_error_msg("Unexpected " + scope + " ('" + s + "')"); | ||
188 | return FALSE; | ||
189 | } | ||
190 | else_line = TRUE; | ||
191 | test = (test_status == TestSeek); | ||
192 | debug_msg(1, "Project Parser: %s:%d : Else%s %s.", parser.file.latin1(), parser.line_no, | ||
193 | scope == "else" ? "" : QString(" (" + scope + ")").latin1(), | ||
194 | test ? "considered" : "excluded"); | ||
195 | } else { | ||
196 | QString comp_scope = scope; | ||
197 | bool invert_test = (comp_scope.left(1) == "!"); | ||
198 | if(invert_test) | ||
199 | comp_scope = comp_scope.right(comp_scope.length()-1); | ||
200 | int lparen = comp_scope.find('('); | ||
201 | if(or_op || !scope_failed) { | ||
202 | if(lparen != -1) { /* if there is an lparen in the scope, it IS a function */ | ||
203 | int rparen = comp_scope.findRev(')'); | ||
204 | if(rparen == -1) { | ||
205 | QCString error; | ||
206 | error.sprintf("Function missing right paren: %s ('%s')", | ||
207 | comp_scope.latin1(), s.latin1()); | ||
208 | qmake_error_msg(error); | ||
209 | return FALSE; | ||
210 | } | ||
211 | QString func = comp_scope.left(lparen); | ||
212 | test = doProjectTest(func, comp_scope.mid(lparen+1, rparen - lparen - 1), place); | ||
213 | if ( *d == ')' && !*(d+1) ) { | ||
214 | if(invert_test) | ||
215 | test = !test; | ||
216 | test_status = (test ? TestFound : TestSeek); | ||
217 | return TRUE; /* assume we are done */ | ||
218 | } | ||
219 | } else { | ||
220 | test = isActiveConfig(comp_scope.stripWhiteSpace()); | ||
221 | } | ||
222 | if(invert_test) | ||
223 | test = !test; | ||
224 | } | ||
225 | } | ||
226 | if(!test && !scope_failed) | ||
227 | debug_msg(1, "Project Parser: %s:%d : Test (%s) failed.", parser.file.latin1(), | ||
228 | parser.line_no, scope.latin1()); | ||
229 | if(test == or_op) | ||
230 | scope_failed = !test; | ||
231 | or_op = (*d == '|'); | ||
232 | if(*d == '{') { /* scoping block */ | ||
233 | if(!scope_failed) | ||
234 | scope_flag |= (0x01 << (++scope_block)); | ||
235 | else | ||
236 | scope_flag &= ~(0x01 << (++scope_block)); | ||
237 | debug_msg(1, "Project Parser: %s:%d : Entering block %d (%d).", parser.file.latin1(), | ||
238 | parser.line_no, scope_block, !scope_failed); | ||
239 | } | ||
240 | } else { | ||
241 | var += *d; | ||
242 | } | ||
243 | d++; | ||
244 | } | ||
245 | if(!scope_count || (scope_count == 1 && else_line)) | ||
246 | test_status = TestNone; | ||
247 | else if(!else_line || test_status != TestFound) | ||
248 | test_status = (scope_failed ? TestSeek : TestFound); | ||
249 | if(scope_failed) | ||
250 | return TRUE; /* oh well */ | ||
251 | if(!*d) { | ||
252 | if(!var.isEmpty()) | ||
253 | qmake_error_msg("Parse Error ('" + s + "')"); | ||
254 | return var.isEmpty(); /* allow just a scope */ | ||
255 | } | ||
256 | |||
257 | SKIP_WS(d); | ||
258 | for( ; *d && op.find('=') == -1; op += *(d++)); | ||
259 | op.replace(QRegExp("\\s"), ""); | ||
260 | |||
261 | SKIP_WS(d); | ||
262 | QString vals(d); /* vals now contains the space separated list of values */ | ||
263 | int rbraces = vals.contains('}'), lbraces = vals.contains('{'); | ||
264 | if(scope_block && rbraces - lbraces == 1) { | ||
265 | debug_msg(1, "Project Parser: %s:%d : Leaving block %d", parser.file.latin1(), | ||
266 | parser.line_no, scope_block); | ||
267 | test_status = ((scope_flag & (0x01 << scope_block)) ? TestFound : TestSeek); | ||
268 | scope_block--; | ||
269 | vals.truncate(vals.length()-1); | ||
270 | } else if(rbraces != lbraces) { | ||
271 | warn_msg(WarnParser, "Possible braces mismatch {%s} %s:%d", | ||
272 | vals.latin1(), parser.file.latin1(), parser.line_no); | ||
273 | } | ||
274 | doVariableReplace(vals, place); | ||
275 | |||
276 | var = var.stripWhiteSpace(); | ||
277 | #undef SKIP_WS | ||
278 | |||
279 | if(!var.isEmpty() && Option::mkfile::do_preprocess) { | ||
280 | static QString last_file("*none*"); | ||
281 | if(parser.file != last_file) { | ||
282 | fprintf(stderr, "#file %s:%d\n", parser.file.latin1(), parser.line_no); | ||
283 | last_file = parser.file; | ||
284 | } | ||
285 | fprintf(stderr, "%s %s %s\n", var.latin1(), op.latin1(), vals.latin1()); | ||
286 | } | ||
287 | var = varMap(var); //backwards compatability | ||
288 | |||
289 | /* vallist is the broken up list of values */ | ||
290 | QStringList vallist = split_value_list(vals, (var == "DEPENDPATH" || var == "INCLUDEPATH")); | ||
291 | if(!vallist.grep("=").isEmpty()) | ||
292 | warn_msg(WarnParser, "Detected possible line continuation: {%s} %s:%d", | ||
293 | var.latin1(), parser.file.latin1(), parser.line_no); | ||
294 | |||
295 | QStringList &varlist = place[var]; /* varlist is the list in the symbol table */ | ||
296 | debug_msg(1, "Project Parser: %s:%d :%s: :%s: (%s)", parser.file.latin1(), parser.line_no, | ||
297 | var.latin1(), op.latin1(), vallist.join(" :: ").latin1()); | ||
298 | |||
299 | /* now do the operation */ | ||
300 | if(op == "~=") { | ||
301 | if(vallist.count() != 1) { | ||
302 | qmake_error_msg("~= operator only accepts one right hand paramater ('" + | ||
303 | s + "')"); | ||
304 | return FALSE; | ||
305 | } | ||
306 | QString val(vallist.first()); | ||
307 | if(val.length() < 4 || val.at(0) != 's') { | ||
308 | qmake_error_msg("~= operator only can handle s/// function ('" + | ||
309 | s + "')"); | ||
310 | return FALSE; | ||
311 | } | ||
312 | QChar sep = val.at(1); | ||
313 | QStringList func = QStringList::split(sep, val, TRUE); | ||
314 | if(func.count() < 3 || func.count() > 4) { | ||
315 | qmake_error_msg("~= operator only can handle s/// function ('" + | ||
316 | s + "')"); | ||
317 | return FALSE; | ||
318 | } | ||
319 | bool global = FALSE, case_sense = TRUE; | ||
320 | if(func.count() == 4) { | ||
321 | global = func[3].find('g') != -1; | ||
322 | case_sense = func[3].find('i') == -1; | ||
323 | } | ||
324 | QRegExp regexp(func[1], case_sense); | ||
325 | for(QStringList::Iterator varit = varlist.begin(); | ||
326 | varit != varlist.end(); ++varit) { | ||
327 | if((*varit).contains(regexp)) { | ||
328 | (*varit) = (*varit).replace(regexp, func[2]); | ||
329 | if(!global) | ||
330 | break; | ||
331 | } | ||
332 | } | ||
333 | } else { | ||
334 | if(op == "=") { | ||
335 | if(!varlist.isEmpty()) | ||
336 | warn_msg(WarnParser, "Operator=(%s) clears variables previously set: %s:%d", | ||
337 | var.latin1(), parser.file.latin1(), parser.line_no); | ||
338 | varlist.clear(); | ||
339 | } | ||
340 | for(QStringList::Iterator valit = vallist.begin(); | ||
341 | valit != vallist.end(); ++valit) { | ||
342 | if((*valit).isEmpty()) | ||
343 | continue; | ||
344 | if((op == "*=" && !(*varlist.find((*valit)))) || | ||
345 | op == "=" || op == "+=") | ||
346 | varlist.append((*valit)); | ||
347 | else if(op == "-=") | ||
348 | varlist.remove((*valit)); | ||
349 | } | ||
350 | } | ||
351 | if(var == "REQUIRES") /* special case to get communicated to backends! */ | ||
352 | doProjectCheckReqs(vallist, place); | ||
353 | |||
354 | return TRUE; | ||
355 | } | ||
356 | |||
357 | bool | ||
358 | QMakeProject::read(QString file, QMap<QString, QStringList> &place) | ||
359 | { | ||
360 | parser_info pi = parser; | ||
361 | /* scope blocks start at true */ | ||
362 | test_status = TestNone; | ||
363 | scope_flag = 0x01; | ||
364 | scope_block = 0; | ||
365 | |||
366 | file = Option::fixPathToLocalOS(file); | ||
367 | doVariableReplace(file, place); | ||
368 | bool ret = FALSE, using_stdin = FALSE; | ||
369 | QFile qfile; | ||
370 | if(!strcmp(file, "-")) { | ||
371 | qfile.setName(""); | ||
372 | ret = qfile.open(IO_ReadOnly, stdin); | ||
373 | using_stdin = TRUE; | ||
374 | } else { | ||
375 | qfile.setName(file); | ||
376 | ret = qfile.open(IO_ReadOnly); | ||
377 | } | ||
378 | if ( ret ) { | ||
379 | QTextStream t( &qfile ); | ||
380 | QString s, line; | ||
381 | parser.file = file; | ||
382 | parser.line_no = 0; | ||
383 | while ( !t.eof() ) { | ||
384 | parser.line_no++; | ||
385 | line = t.readLine().stripWhiteSpace(); | ||
386 | int prelen = line.length(); | ||
387 | line.replace(QRegExp("#.*$"), ""); // bye comments | ||
388 | if(!line.isEmpty() && line.right(1) == "\\") { | ||
389 | line.truncate(line.length() - 1); | ||
390 | s += line + " "; | ||
391 | } else if(!line.isEmpty() || (line.isEmpty() && !prelen)) { | ||
392 | if(s.isEmpty() && line.isEmpty()) | ||
393 | continue; | ||
394 | if(!line.isEmpty()) | ||
395 | s += line; | ||
396 | if(!s.isEmpty()) { | ||
397 | if(!(ret = parse(s, place))) | ||
398 | break; | ||
399 | s = ""; | ||
400 | } | ||
401 | } | ||
402 | } | ||
403 | if(!using_stdin) | ||
404 | qfile.close(); | ||
405 | } | ||
406 | parser = pi; | ||
407 | return ret; | ||
408 | } | ||
409 | |||
410 | bool | ||
411 | QMakeProject::read(QString project, QString) | ||
412 | { | ||
413 | if(cfile.isEmpty()) { | ||
414 | // hack to get the Option stuff in there | ||
415 | base_vars["QMAKE_EXT_CPP"] = Option::cpp_ext; | ||
416 | base_vars["QMAKE_EXT_H"] = Option::h_ext; | ||
417 | |||
418 | /* parse the cache */ | ||
419 | if(Option::mkfile::do_cache) { | ||
420 | if(Option::mkfile::cachefile.isEmpty()) { //find it as it has not been specified | ||
421 | QString dir = QDir::convertSeparators(Option::output_dir); | ||
422 | while(!QFile::exists((Option::mkfile::cachefile = dir + | ||
423 | QDir::separator() + ".qmake.cache"))) { | ||
424 | dir = dir.left(dir.findRev(QDir::separator())); | ||
425 | if(dir.isEmpty() || dir.find(QDir::separator()) == -1) { | ||
426 | Option::mkfile::cachefile = ""; | ||
427 | break; | ||
428 | } | ||
429 | if(Option::mkfile::cachefile_depth == -1) | ||
430 | Option::mkfile::cachefile_depth = 1; | ||
431 | else | ||
432 | Option::mkfile::cachefile_depth++; | ||
433 | } | ||
434 | } | ||
435 | if(!Option::mkfile::cachefile.isEmpty()) { | ||
436 | read(Option::mkfile::cachefile, cache); | ||
437 | if(Option::mkfile::qmakespec.isEmpty() && !cache["QMAKESPEC"].isEmpty()) | ||
438 | Option::mkfile::qmakespec = cache["QMAKESPEC"].first(); | ||
439 | } | ||
440 | } | ||
441 | /* parse mkspec */ | ||
442 | QStringList mkspec_roots; | ||
443 | /* prefer $QTDIR if it is set */ | ||
444 | /* minor hack here, prefer QMAKESPECDIR -cl */ | ||
445 | |||
446 | if (getenv("QMAKESPECDIR")){ | ||
447 | mkspec_roots << getenv("QMAKESPECDIR"); | ||
448 | } else if (getenv("QTDIR")) { | ||
449 | mkspec_roots << getenv("QTDIR"); | ||
450 | } | ||
451 | mkspec_roots << qInstallPathData(); | ||
452 | if(Option::mkfile::qmakespec.isEmpty()) { | ||
453 | for(QStringList::Iterator it = mkspec_roots.begin(); it != mkspec_roots.end(); ++it) { | ||
454 | QString mkspec = (*it) + QDir::separator() + QString("mkspecs") + | ||
455 | QDir::separator() + "default"; | ||
456 | if(QFile::exists(mkspec)) { | ||
457 | Option::mkfile::qmakespec = mkspec; | ||
458 | break; | ||
459 | } | ||
460 | } | ||
461 | if(Option::mkfile::qmakespec.isEmpty()) { | ||
462 | fprintf(stderr, "QMAKESPEC has not been set, so configuration cannot be deduced.\n"); | ||
463 | return FALSE; | ||
464 | } | ||
465 | } | ||
466 | |||
467 | if(QDir::isRelativePath(Option::mkfile::qmakespec)) { | ||
468 | bool found_mkspec = FALSE; | ||
469 | for(QStringList::Iterator it = mkspec_roots.begin(); it != mkspec_roots.end(); ++it) { | ||
470 | QString mkspec = (*it) + QDir::separator() + QString("mkspecs") + | ||
471 | QDir::separator() + Option::mkfile::qmakespec; | ||
472 | if(QFile::exists(mkspec)) { | ||
473 | found_mkspec = TRUE; | ||
474 | Option::mkfile::qmakespec = mkspec; | ||
475 | break; | ||
476 | } | ||
477 | } | ||
478 | if(!found_mkspec) { | ||
479 | fprintf(stderr, "Could not find mkspecs for your QMAKESPEC after trying:\n\t%s\n", | ||
480 | mkspec_roots.join("\n\t").latin1()); | ||
481 | return FALSE; | ||
482 | } | ||
483 | } | ||
484 | |||
485 | /* parse qmake configuration */ | ||
486 | QString spec = Option::mkfile::qmakespec + QDir::separator() + "qmake.conf"; | ||
487 | debug_msg(1, "QMAKESPEC conf: reading %s", spec.latin1()); | ||
488 | if(!read(spec, base_vars)) { | ||
489 | fprintf(stderr, "Failure to read QMAKESPEC conf file %s.\n", spec.latin1()); | ||
490 | return FALSE; | ||
491 | } | ||
492 | if(Option::mkfile::do_cache && !Option::mkfile::cachefile.isEmpty()) { | ||
493 | debug_msg(1, "QMAKECACHE file: reading %s", Option::mkfile::cachefile.latin1()); | ||
494 | read(Option::mkfile::cachefile, base_vars); | ||
495 | } | ||
496 | |||
497 | /* commandline */ | ||
498 | cfile = project; | ||
499 | parser.line_no = 1; //really arg count now.. duh | ||
500 | parser.file = "(internal)"; | ||
501 | for(QStringList::Iterator it = Option::before_user_vars.begin(); | ||
502 | it != Option::before_user_vars.end(); ++it) { | ||
503 | if(!parse((*it), base_vars)) { | ||
504 | fprintf(stderr, "Argument failed to parse: %s\n", (*it).latin1()); | ||
505 | return FALSE; | ||
506 | } | ||
507 | parser.line_no++; | ||
508 | } | ||
509 | } | ||
510 | |||
511 | /* parse project file */ | ||
512 | debug_msg(1, "Project file: reading %s", project.latin1()); | ||
513 | vars = base_vars; /* start with the base */ | ||
514 | |||
515 | pfile = project; | ||
516 | if(pfile != "-" && !QFile::exists(pfile) && pfile.right(4) != ".pro") | ||
517 | pfile += ".pro"; | ||
518 | |||
519 | if(!read(pfile, vars)) | ||
520 | return FALSE; | ||
521 | |||
522 | parser.line_no = 1; //really arg count now.. duh | ||
523 | parser.file = "(internal)"; | ||
524 | for(QStringList::Iterator it = Option::after_user_vars.begin(); | ||
525 | it != Option::after_user_vars.end(); ++it) { | ||
526 | if(!parse((*it), vars)) { | ||
527 | fprintf(stderr, "Argument failed to parse: %s\n", (*it).latin1()); | ||
528 | return FALSE; | ||
529 | } | ||
530 | parser.line_no++; | ||
531 | } | ||
532 | |||
533 | /* now let the user override the template from an option.. */ | ||
534 | if(!Option::user_template.isEmpty()) { | ||
535 | debug_msg(1, "Overriding TEMPLATE (%s) with: %s", vars["TEMPLATE"].first().latin1(), Option::user_template.latin1()); | ||
536 | vars["TEMPLATE"].clear(); | ||
537 | vars["TEMPLATE"].append(Option::user_template); | ||
538 | } | ||
539 | |||
540 | if(vars["TEMPLATE"].isEmpty()) | ||
541 | vars["TEMPLATE"].append(QString("app")); | ||
542 | else | ||
543 | vars["TEMPLATE"].first().replace(QRegExp("\\.t$"), ""); | ||
544 | if(!Option::user_template_prefix.isEmpty()) | ||
545 | vars["TEMPLATE"].first().prepend(Option::user_template_prefix); | ||
546 | |||
547 | if(vars["TARGET"].isEmpty()) { | ||
548 | // ### why not simply use: | ||
549 | // QFileInfo fi(pfile); | ||
550 | // fi.baseName(); | ||
551 | QString tmp = pfile; | ||
552 | if(tmp.findRev('/') != -1) | ||
553 | tmp = tmp.right( tmp.length() - tmp.findRev('/') - 1 ); | ||
554 | if(tmp.findRev('.') != -1) | ||
555 | tmp = tmp.left(tmp.findRev('.')); | ||
556 | vars["TARGET"].append(tmp); | ||
557 | } | ||
558 | |||
559 | QString test_version = getenv("QTESTVERSION"); | ||
560 | if (!test_version.isEmpty()) { | ||
561 | QString s = vars["TARGET"].first(); | ||
562 | if (s == "qt" || s == "qt-mt" || s == "qte" || s == "qte-mt") { | ||
563 | QString &ver = vars["VERSION"].first(); | ||
564 | // fprintf(stderr,"Current QT version number: " + ver + "\n"); | ||
565 | if (ver != "" && ver != test_version) { | ||
566 | ver = test_version; | ||
567 | fprintf(stderr,"Changed QT version number to " + test_version + "!\n"); | ||
568 | } | ||
569 | } | ||
570 | } | ||
571 | return TRUE; | ||
572 | } | ||
573 | |||
574 | bool | ||
575 | QMakeProject::isActiveConfig(const QString &x) | ||
576 | { | ||
577 | if(x.isEmpty()) | ||
578 | return TRUE; | ||
579 | |||
580 | QRegExp re(x, FALSE, TRUE); | ||
581 | if((Option::target_mode == Option::TARG_MACX_MODE || Option::target_mode == Option::TARG_QNX6_MODE || Option::target_mode == Option::TARG_UNIX_MODE) && | ||
582 | x == "unix") | ||
583 | return TRUE; | ||
584 | else if(Option::target_mode == Option::TARG_MACX_MODE && x == "macx") | ||
585 | return TRUE; | ||
586 | else if(Option::target_mode == Option::TARG_QNX6_MODE && x == "qnx6") | ||
587 | return TRUE; | ||
588 | else if(Option::target_mode == Option::TARG_MAC9_MODE && x == "mac9") | ||
589 | return TRUE; | ||
590 | else if((Option::target_mode == Option::TARG_MAC9_MODE || Option::target_mode == Option::TARG_MACX_MODE) && | ||
591 | x == "mac") | ||
592 | return TRUE; | ||
593 | else if(Option::target_mode == Option::TARG_WIN_MODE && x == "win32") | ||
594 | return TRUE; | ||
595 | |||
596 | |||
597 | QString spec = Option::mkfile::qmakespec.right(Option::mkfile::qmakespec.length() - | ||
598 | (Option::mkfile::qmakespec.findRev(QDir::separator())+1)); | ||
599 | if(re.exactMatch(spec)) | ||
600 | return TRUE; | ||
601 | #ifdef Q_OS_UNIX | ||
602 | else if(spec == "default") { | ||
603 | static char *buffer = NULL; | ||
604 | if(!buffer) | ||
605 | buffer = (char *)malloc(1024); | ||
606 | int l = readlink(Option::mkfile::qmakespec, buffer, 1024); | ||
607 | if(l != -1) { | ||
608 | buffer[l] = '\0'; | ||
609 | QString r = buffer; | ||
610 | if(r.findRev('/') != -1) | ||
611 | r = r.mid(r.findRev('/') + 1); | ||
612 | if(re.exactMatch(r)) | ||
613 | return TRUE; | ||
614 | } | ||
615 | } | ||
616 | #endif | ||
617 | |||
618 | |||
619 | QStringList &configs = vars["CONFIG"]; | ||
620 | for(QStringList::Iterator it = configs.begin(); it != configs.end(); ++it) { | ||
621 | if(re.exactMatch((*it))) | ||
622 | return TRUE; | ||
623 | } | ||
624 | return FALSE; | ||
625 | } | ||
626 | |||
627 | bool | ||
628 | QMakeProject::doProjectTest(QString func, const QString ¶ms, QMap<QString, QStringList> &place) | ||
629 | { | ||
630 | QStringList args = split_arg_list(params); | ||
631 | for(QStringList::Iterator arit = args.begin(); arit != args.end(); ++arit) { | ||
632 | QString tmp = (*arit).stripWhiteSpace(); | ||
633 | if((tmp[0] == '\'' || tmp[0] == '"') && tmp.right(1) == tmp.left(1)) | ||
634 | tmp = tmp.mid(1, tmp.length() - 2); | ||
635 | } | ||
636 | return doProjectTest(func.stripWhiteSpace(), args, place); | ||
637 | } | ||
638 | |||
639 | bool | ||
640 | QMakeProject::doProjectTest(QString func, QStringList args, QMap<QString, QStringList> &place) | ||
641 | { | ||
642 | for(QStringList::Iterator arit = args.begin(); arit != args.end(); ++arit) { | ||
643 | (*arit) = (*arit).stripWhiteSpace(); // blah, get rid of space | ||
644 | doVariableReplace((*arit), place); | ||
645 | } | ||
646 | debug_msg(1, "Running project test: %s( %s )", func.latin1(), args.join("::").latin1()); | ||
647 | |||
648 | if(func == "requires") { | ||
649 | return doProjectCheckReqs(args, place); | ||
650 | } else if(func == "exists") { | ||
651 | if(args.count() != 1) { | ||
652 | fprintf(stderr, "%s:%d: exists(file) requires one argument.\n", parser.file.latin1(), | ||
653 | parser.line_no); | ||
654 | return FALSE; | ||
655 | } | ||
656 | QString file = args.first(); | ||
657 | file = Option::fixPathToLocalOS(file); | ||
658 | doVariableReplace(file, place); | ||
659 | |||
660 | if(QFile::exists(file)) | ||
661 | return TRUE; | ||
662 | //regular expression I guess | ||
663 | QString dirstr = QDir::currentDirPath(); | ||
664 | int slsh = file.findRev(Option::dir_sep); | ||
665 | if(slsh != -1) { | ||
666 | dirstr = file.left(slsh+1); | ||
667 | file = file.right(file.length() - slsh - 1); | ||
668 | } | ||
669 | QDir dir(dirstr, file); | ||
670 | return dir.count() != 0; | ||
671 | } else if(func == "system") { | ||
672 | if(args.count() != 1) { | ||
673 | fprintf(stderr, "%s:%d: system(exec) requires one argument.\n", parser.file.latin1(), | ||
674 | parser.line_no); | ||
675 | return FALSE; | ||
676 | } | ||
677 | return system(args.first().latin1()) == 0; | ||
678 | } else if(func == "contains") { | ||
679 | if(args.count() != 2) { | ||
680 | fprintf(stderr, "%s:%d: contains(var, val) requires two arguments.\n", parser.file.latin1(), | ||
681 | parser.line_no); | ||
682 | return FALSE; | ||
683 | } | ||
684 | QRegExp regx(args[1]); | ||
685 | QStringList &l = place[args[0]]; | ||
686 | for(QStringList::ConstIterator it = l.begin(); it != l.end(); ++it) { | ||
687 | if(regx.exactMatch((*it))) | ||
688 | return TRUE; | ||
689 | } | ||
690 | return FALSE; | ||
691 | } else if(func == "infile") { | ||
692 | if(args.count() < 2 || args.count() > 3) { | ||
693 | fprintf(stderr, "%s:%d: infile(file, var, val) requires at least 2 arguments.\n", | ||
694 | parser.file.latin1(), parser.line_no); | ||
695 | return FALSE; | ||
696 | } | ||
697 | QMakeProject proj; | ||
698 | QString file = args[0]; | ||
699 | doVariableReplace(file, place); | ||
700 | fixEnvVariables(file); | ||
701 | int di = file.findRev(Option::dir_sep); | ||
702 | QDir sunworkshop42workaround = QDir::current(); | ||
703 | QString oldpwd = sunworkshop42workaround.currentDirPath(); | ||
704 | if(di != -1) { | ||
705 | if(!QDir::setCurrent(file.left(file.findRev(Option::dir_sep)))) { | ||
706 | fprintf(stderr, "Cannot find directory: %s\n", file.left(di).latin1()); | ||
707 | return FALSE; | ||
708 | } | ||
709 | file = file.right(file.length() - di - 1); | ||
710 | } | ||
711 | parser_info pi = parser; | ||
712 | bool ret = !proj.read(file, oldpwd); | ||
713 | parser = pi; | ||
714 | if(ret) { | ||
715 | fprintf(stderr, "Error processing project file: %s\n", file.latin1()); | ||
716 | QDir::setCurrent(oldpwd); | ||
717 | return FALSE; | ||
718 | } | ||
719 | if(args.count() == 2) { | ||
720 | ret = !proj.isEmpty(args[1]); | ||
721 | } else { | ||
722 | QRegExp regx(args[2]); | ||
723 | QStringList &l = proj.values(args[1]); | ||
724 | for(QStringList::ConstIterator it = l.begin(); it != l.end(); ++it) { | ||
725 | if(regx.exactMatch((*it))) { | ||
726 | ret = TRUE; | ||
727 | break; | ||
728 | } | ||
729 | } | ||
730 | } | ||
731 | QDir::setCurrent(oldpwd); | ||
732 | return ret; | ||
733 | } else if(func == "count") { | ||
734 | if(args.count() != 2) { | ||
735 | fprintf(stderr, "%s:%d: count(var, count) requires two arguments.\n", parser.file.latin1(), | ||
736 | parser.line_no); | ||
737 | return FALSE; | ||
738 | } | ||
739 | return vars[args[0]].count() == args[1].toUInt(); | ||
740 | } else if(func == "isEmpty") { | ||
741 | if(args.count() != 1) { | ||
742 | fprintf(stderr, "%s:%d: isEmpty(var) requires one argument.\n", parser.file.latin1(), | ||
743 | parser.line_no); | ||
744 | return FALSE; | ||
745 | } | ||
746 | return vars[args[0]].isEmpty(); | ||
747 | } else if(func == "include" || func == "load") { | ||
748 | if(args.count() != 1) { | ||
749 | QString func_desc = "include(file)"; | ||
750 | if(func == "load") | ||
751 | func_desc = "load(feature)"; | ||
752 | fprintf(stderr, "%s:%d: %s requires one argument.\n", parser.file.latin1(), | ||
753 | parser.line_no, func_desc.latin1()); | ||
754 | return FALSE; | ||
755 | } | ||
756 | |||
757 | QString file = args.first(); | ||
758 | file = Option::fixPathToLocalOS(file); | ||
759 | file.replace("\"", ""); | ||
760 | doVariableReplace(file, place); | ||
761 | if(func == "load") { | ||
762 | if(!file.endsWith(Option::prf_ext)) | ||
763 | file += Option::prf_ext; | ||
764 | if(file.find(Option::dir_sep) == -1 || !QFile::exists(file)) { | ||
765 | if(QFile::exists(Option::mkfile::qmakespec + QDir::separator() + file)) { | ||
766 | file.prepend(Option::mkfile::qmakespec + QDir::separator()); | ||
767 | } else { | ||
768 | bool found = FALSE; | ||
769 | QStringList feature_roots; | ||
770 | if(getenv("QTDIR")) | ||
771 | feature_roots << getenv("QTDIR"); | ||
772 | #ifdef QT_INSTALL_PREFIX | ||
773 | feature_roots << QT_INSTALL_PREFIX; | ||
774 | #endif | ||
775 | #ifdef QT_INSTALL_DATA | ||
776 | feature_roots << QT_INSTALL_DATA; | ||
777 | #endif | ||
778 | for(QStringList::Iterator it = feature_roots.begin(); it != feature_roots.end(); ++it) { | ||
779 | QString prf = (*it) + QDir::separator() + QString("mkspecs") + | ||
780 | QDir::separator() + QString("features") + QDir::separator() + file; | ||
781 | if(QFile::exists(prf)) { | ||
782 | found = TRUE; | ||
783 | file = prf; | ||
784 | break; | ||
785 | } | ||
786 | } | ||
787 | if(!found) { | ||
788 | printf("Project LOAD(): Feature %s cannot be found.\n", args.first().latin1()); | ||
789 | exit(3); | ||
790 | } | ||
791 | } | ||
792 | } | ||
793 | } | ||
794 | |||
795 | debug_msg(1, "Project Parser: %s'ing file %s.", func.latin1(), file.latin1()); | ||
796 | parser_info pi = parser; | ||
797 | int sb = scope_block; | ||
798 | int sf = scope_flag; | ||
799 | TestStatus sc = test_status; | ||
800 | bool r = read(file.latin1(), place); | ||
801 | if(r) | ||
802 | vars["QMAKE_INTERNAL_INCLUDED_FILES"].append(file); | ||
803 | parser = pi; | ||
804 | test_status = sc; | ||
805 | scope_flag = sf; | ||
806 | scope_block = sb; | ||
807 | return r; | ||
808 | } else if(func == "error" || func == "message") { | ||
809 | if(args.count() != 1) { | ||
810 | fprintf(stderr, "%s:%d: %s(message) requires one argument.\n", parser.file.latin1(), | ||
811 | parser.line_no, func.latin1()); | ||
812 | return FALSE; | ||
813 | } | ||
814 | QString msg = args.first(); | ||
815 | doVariableReplace(msg, place); | ||
816 | fixEnvVariables(msg); | ||
817 | printf("Project %s: %s\n", func.upper().latin1(), msg.latin1()); | ||
818 | if(func == "message") | ||
819 | return TRUE; | ||
820 | exit(2); | ||
821 | } else { | ||
822 | fprintf(stderr, "%s:%d: Unknown test function: %s\n", parser.file.latin1(), parser.line_no, | ||
823 | func.latin1()); | ||
824 | } | ||
825 | return FALSE; | ||
826 | } | ||
827 | |||
828 | bool | ||
829 | QMakeProject::doProjectCheckReqs(const QStringList &deps, QMap<QString, QStringList> &place) | ||
830 | { | ||
831 | bool ret = FALSE; | ||
832 | for(QStringList::ConstIterator it = deps.begin(); it != deps.end(); ++it) { | ||
833 | QString chk = (*it); | ||
834 | if(chk.isEmpty()) | ||
835 | continue; | ||
836 | bool invert_test = (chk.left(1) == "!"); | ||
837 | if(invert_test) | ||
838 | chk = chk.right(chk.length() - 1); | ||
839 | |||
840 | bool test; | ||
841 | int lparen = chk.find('('); | ||
842 | if(lparen != -1) { /* if there is an lparen in the chk, it IS a function */ | ||
843 | int rparen = chk.findRev(')'); | ||
844 | if(rparen == -1) { | ||
845 | QCString error; | ||
846 | error.sprintf("Function (in REQUIRES) missing right paren: %s", chk.latin1()); | ||
847 | qmake_error_msg(error); | ||
848 | } else { | ||
849 | QString func = chk.left(lparen); | ||
850 | test = doProjectTest(func, chk.mid(lparen+1, rparen - lparen - 1), place); | ||
851 | } | ||
852 | } else { | ||
853 | test = isActiveConfig(chk); | ||
854 | } | ||
855 | if(invert_test) { | ||
856 | chk.prepend("!"); | ||
857 | test = !test; | ||
858 | } | ||
859 | if(!test) { | ||
860 | debug_msg(1, "Project Parser: %s:%d Failed test: REQUIRES = %s", | ||
861 | parser.file.latin1(), parser.line_no, chk.latin1()); | ||
862 | place["QMAKE_FAILED_REQUIREMENTS"].append(chk); | ||
863 | ret = FALSE; | ||
864 | } | ||
865 | } | ||
866 | return ret; | ||
867 | } | ||
868 | |||
869 | |||
870 | QString | ||
871 | QMakeProject::doVariableReplace(QString &str, const QMap<QString, QStringList> &place) | ||
872 | { | ||
873 | for(int x = 0, rep; x < 5; x++) { | ||
874 | QRegExp reg_var; | ||
875 | reg_var.setMinimal(TRUE); | ||
876 | if( x == 0 ) //function blocked out by {}'s | ||
877 | reg_var = QRegExp("\\$\\$\\{([a-zA-Z0-9_]*)\\((\\(.|(.*)\\)*)\\)\\}"); | ||
878 | else if( x == 1 ) //variables blocked out by {}'s | ||
879 | reg_var = QRegExp("\\$\\$\\{([a-zA-Z0-9_\\.-]*)\\}"); | ||
880 | else if(x == 2) //environment | ||
881 | reg_var = QRegExp("\\$\\$\\(([a-zA-Z0-9_\\.-]*)\\)"); | ||
882 | else if(x == 3) //function | ||
883 | reg_var = QRegExp("\\$\\$([a-zA-Z0-9_]*)\\((\\(.|(.*)\\)*)\\)"); | ||
884 | else if(x == 4) //normal variable | ||
885 | reg_var = QRegExp("\\$\\$([a-zA-Z0-9_\\.-]*)"); | ||
886 | while((rep = reg_var.search(str)) != -1) { | ||
887 | QString replacement; | ||
888 | if(x == 2) {//environment | ||
889 | replacement = getenv(reg_var.cap(1)); | ||
890 | } else if(x == 0 || x == 3) { //function | ||
891 | QStringList args = split_arg_list(reg_var.cap(2)); | ||
892 | for(QStringList::Iterator arit = args.begin(); arit != args.end(); ++arit) { | ||
893 | (*arit) = (*arit).stripWhiteSpace(); // blah, get rid of space | ||
894 | doVariableReplace((*arit), place); | ||
895 | } | ||
896 | debug_msg(1, "Running function: %s( %s )", reg_var.cap(1).latin1(), args.join("::").latin1()); | ||
897 | if(reg_var.cap(1).lower() == "member") { | ||
898 | if(args.count() < 1 || args.count() > 2) { | ||
899 | fprintf(stderr, "%s:%d: member(var, place) requires two arguments.\n", | ||
900 | parser.file.latin1(), parser.line_no); | ||
901 | } else { | ||
902 | uint pos = 0; | ||
903 | if(args.count() == 2) | ||
904 | pos = args[1].toInt(); | ||
905 | const QStringList &var = place[varMap(args.first())]; | ||
906 | if(var.count() >= pos) | ||
907 | replacement = var[pos]; | ||
908 | } | ||
909 | } else if(reg_var.cap(1).lower() == "list") { | ||
910 | if(args.count() != 1) { | ||
911 | fprintf(stderr, "%s:%d: list(vals) requires one" | ||
912 | "argument.\n", parser.file.latin1(), parser.line_no); | ||
913 | } else { | ||
914 | static int x = 0; | ||
915 | replacement.sprintf(".QMAKE_INTERNAL_TMP_VAR_%d", x++); | ||
916 | (*((QMap<QString, QStringList>*)&place))[replacement] = split_value_list(args.first()); | ||
917 | } | ||
918 | } else if(reg_var.cap(1).lower() == "join") { | ||
919 | if(args.count() < 1 || args.count() > 4) { | ||
920 | fprintf(stderr, "%s:%d: join(var, glue, before, after) requires four" | ||
921 | "arguments.\n", parser.file.latin1(), parser.line_no); | ||
922 | } else { | ||
923 | QString glue, before, after; | ||
924 | if(args.count() >= 2) | ||
925 | glue = args[1].replace("\"", "" ); | ||
926 | if(args.count() >= 3) | ||
927 | before = args[2].replace("\"", "" ); | ||
928 | if(args.count() == 4) | ||
929 | after = args[3].replace("\"", "" ); | ||
930 | const QStringList &var = place[varMap(args.first())]; | ||
931 | if(!var.isEmpty()) | ||
932 | replacement = before + var.join(glue) + after; | ||
933 | } | ||
934 | } else if(reg_var.cap(1).lower() == "find") { | ||
935 | if(args.count() != 2) { | ||
936 | fprintf(stderr, "%s:%d find(var, str) requires two arguments\n", | ||
937 | parser.file.latin1(), parser.line_no); | ||
938 | } else { | ||
939 | QRegExp regx(args[1]); | ||
940 | const QStringList &var = place[varMap(args.first())]; | ||
941 | for(QStringList::ConstIterator vit = var.begin(); | ||
942 | vit != var.end(); ++vit) { | ||
943 | if(regx.search(*vit) != -1) { | ||
944 | if(!replacement.isEmpty()) | ||
945 | replacement += " "; | ||
946 | replacement += (*vit); | ||
947 | } | ||
948 | } | ||
949 | } | ||
950 | } else if(reg_var.cap(1).lower() == "system") { | ||
951 | if(args.count() != 1) { | ||
952 | fprintf(stderr, "%s:%d system(execut) requires one argument\n", | ||
953 | parser.file.latin1(), parser.line_no); | ||
954 | } else { | ||
955 | char buff[256]; | ||
956 | FILE *proc = QT_POPEN(args.join(" ").latin1(), "r"); | ||
957 | while(proc && !feof(proc)) { | ||
958 | int read_in = fread(buff, 1, 255, proc); | ||
959 | if(!read_in) | ||
960 | break; | ||
961 | for(int i = 0; i < read_in; i++) { | ||
962 | if(buff[i] == '\n' || buff[i] == '\t') | ||
963 | buff[i] = ' '; | ||
964 | } | ||
965 | buff[read_in] = '\0'; | ||
966 | replacement += buff; | ||
967 | } | ||
968 | } | ||
969 | } else { | ||
970 | fprintf(stderr, "%s:%d: Unknown replace function: %s\n", | ||
971 | parser.file.latin1(), parser.line_no, reg_var.cap(1).latin1()); | ||
972 | } | ||
973 | } else { //variable | ||
974 | if(reg_var.cap(1).left(1) == ".") | ||
975 | replacement = ""; | ||
976 | else if(reg_var.cap(1) == "LITERAL_WHITESPACE") | ||
977 | replacement = "\t"; | ||
978 | else | ||
979 | replacement = place[varMap(reg_var.cap(1))].join(" "); | ||
980 | } | ||
981 | debug_msg(2, "Project parser: %d (%s) :: %s -> %s", x, str.latin1(), | ||
982 | reg_var.capturedTexts().join("::").latin1(), replacement.latin1()); | ||
983 | str.replace(rep, reg_var.matchedLength(), replacement); | ||
984 | } | ||
985 | } | ||
986 | return str; | ||
987 | } | ||