1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the qmake application of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file.  Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 
42 #include "projectgenerator.h"
43 #include "option.h"
44 #include <qdatetime.h>
45 #include <qdir.h>
46 #include <qfile.h>
47 #include <qfileinfo.h>
48 #include <qregexp.h>
49 
50 QT_BEGIN_NAMESPACE
51 
project_builtin_regx()52 QString project_builtin_regx() //calculate the builtin regular expression..
53 {
54     QString ret;
55     QStringList builtin_exts;
56     builtin_exts << Option::c_ext << Option::ui_ext << Option::yacc_ext << Option::lex_ext << ".ts" << ".xlf" << ".qrc";
57     builtin_exts += Option::h_ext + Option::cpp_ext;
58     for(int i = 0; i < builtin_exts.size(); ++i) {
59         if(!ret.isEmpty())
60             ret += "; ";
61         ret += QString("*") + builtin_exts[i];
62     }
63     return ret;
64 }
65 
ProjectGenerator()66 ProjectGenerator::ProjectGenerator() : MakefileGenerator(), init_flag(false)
67 {
68 }
69 
70 void
init()71 ProjectGenerator::init()
72 {
73     if(init_flag)
74         return;
75     int file_count = 0;
76     init_flag = true;
77     verifyCompilers();
78 
79     project->read(QMakeProject::ReadFeatures);
80     project->variables()["CONFIG"].clear();
81 
82     QMap<QString, QStringList> &v = project->variables();
83     QString templ = Option::user_template.isEmpty() ? QString("app") : Option::user_template;
84     if(!Option::user_template_prefix.isEmpty())
85         templ.prepend(Option::user_template_prefix);
86     v["TEMPLATE_ASSIGN"] += templ;
87 
88     //figure out target
89     if(Option::output.fileName() == "-")
90         v["TARGET_ASSIGN"] = QStringList("unknown");
91     else
92         v["TARGET_ASSIGN"] = QStringList(QFileInfo(Option::output).baseName());
93 
94     //the scary stuff
95     if(project->first("TEMPLATE_ASSIGN") != "subdirs") {
96         QString builtin_regex = project_builtin_regx();
97         QStringList dirs = Option::projfile::project_dirs;
98         if(Option::projfile::do_pwd) {
99             if(!v["INCLUDEPATH"].contains("."))
100                 v["INCLUDEPATH"] += ".";
101             dirs.prepend(qmake_getpwd());
102         }
103 
104         for(int i = 0; i < dirs.count(); ++i) {
105             QString dir, regex, pd = dirs.at(i);
106             bool add_depend = false;
107             if(exists(pd)) {
108                 QFileInfo fi(fileInfo(pd));
109                 if(fi.isDir()) {
110                     dir = pd;
111                     add_depend = true;
112                     if(dir.right(1) != Option::dir_sep)
113                         dir += Option::dir_sep;
114                     if(Option::recursive == Option::QMAKE_RECURSIVE_YES) {
115                         QStringList files = QDir(dir).entryList(QDir::Files);
116                         for(int i = 0; i < (int)files.count(); i++) {
117                             if(files[i] != "." && files[i] != "..")
118                                 dirs.append(dir + files[i] + QDir::separator() + builtin_regex);
119                         }
120                     }
121                     regex = builtin_regex;
122                 } else {
123                     QString file = pd;
124                     int s = file.lastIndexOf(Option::dir_sep);
125                     if(s != -1)
126                         dir = file.left(s+1);
127                     if(addFile(file)) {
128                         add_depend = true;
129                         file_count++;
130                     }
131                 }
132             } else { //regexp
133                 regex = pd;
134             }
135             if(!regex.isEmpty()) {
136                 int s = regex.lastIndexOf(Option::dir_sep);
137                 if(s != -1) {
138                     dir = regex.left(s+1);
139                     regex = regex.right(regex.length() - (s+1));
140                 }
141                 if(Option::recursive == Option::QMAKE_RECURSIVE_YES) {
142                     QStringList entries = QDir(dir).entryList(QDir::Dirs);
143                     for(int i = 0; i < (int)entries.count(); i++) {
144                         if(entries[i] != "." && entries[i] != "..") {
145                             dirs.append(dir + entries[i] + QDir::separator() + regex);
146                         }
147                     }
148                 }
149                 QStringList files = QDir(dir).entryList(QDir::nameFiltersFromString(regex));
150                 for(int i = 0; i < (int)files.count(); i++) {
151                     QString file = dir + files[i];
152                     if (addFile(file)) {
153                         add_depend = true;
154                         file_count++;
155                     }
156                 }
157             }
158             if(add_depend && !dir.isEmpty() && !v["DEPENDPATH"].contains(dir, Qt::CaseInsensitive)) {
159                 QFileInfo fi(fileInfo(dir));
160                 if(fi.absoluteFilePath() != qmake_getpwd())
161                     v["DEPENDPATH"] += fileFixify(dir);
162             }
163         }
164     }
165     if(!file_count) { //shall we try a subdir?
166         QStringList knownDirs = Option::projfile::project_dirs;
167         if(Option::projfile::do_pwd)
168             knownDirs.prepend(".");
169         const QString out_file = fileFixify(Option::output.fileName());
170         for(int i = 0; i < knownDirs.count(); ++i) {
171             QString pd = knownDirs.at(i);
172             if(exists(pd)) {
173                 QString newdir = pd;
174                 QFileInfo fi(fileInfo(newdir));
175                 if(fi.isDir()) {
176                     newdir = fileFixify(newdir);
177                     QStringList &subdirs = v["SUBDIRS"];
178                     if(exists(fi.filePath() + QDir::separator() + fi.fileName() + Option::pro_ext) &&
179                        !subdirs.contains(newdir, Qt::CaseInsensitive)) {
180                         subdirs.append(newdir);
181                     } else {
182                         QStringList profiles = QDir(newdir).entryList(QStringList("*" + Option::pro_ext), QDir::Files);
183                         for(int i = 0; i < (int)profiles.count(); i++) {
184                             QString nd = newdir;
185                             if(nd == ".")
186                                 nd = "";
187                             else if(!nd.isEmpty() && !nd.endsWith(QString(QChar(QDir::separator()))))
188                                 nd += QDir::separator();
189                             nd += profiles[i];
190                             fileFixify(nd);
191                             if(profiles[i] != "." && profiles[i] != ".." &&
192                                !subdirs.contains(nd, Qt::CaseInsensitive) && !out_file.endsWith(nd))
193                                 subdirs.append(nd);
194                         }
195                     }
196                     if(Option::recursive == Option::QMAKE_RECURSIVE_YES) {
197                         QStringList dirs = QDir(newdir).entryList(QDir::Dirs);
198                         for(int i = 0; i < (int)dirs.count(); i++) {
199                             QString nd = fileFixify(newdir + QDir::separator() + dirs[i]);
200                             if(dirs[i] != "." && dirs[i] != ".." && !knownDirs.contains(nd, Qt::CaseInsensitive))
201                                 knownDirs.append(nd);
202                         }
203                     }
204                 }
205             } else { //regexp
206                 QString regx = pd, dir;
207                 int s = regx.lastIndexOf(Option::dir_sep);
208                 if(s != -1) {
209                     dir = regx.left(s+1);
210                     regx = regx.right(regx.length() - (s+1));
211                 }
212                 QStringList files = QDir(dir).entryList(QDir::nameFiltersFromString(regx), QDir::Dirs);
213                 QStringList &subdirs = v["SUBDIRS"];
214                 for(int i = 0; i < (int)files.count(); i++) {
215                     QString newdir(dir + files[i]);
216                     QFileInfo fi(fileInfo(newdir));
217                     if(fi.fileName() != "." && fi.fileName() != "..") {
218                         newdir = fileFixify(newdir);
219                         if(exists(fi.filePath() + QDir::separator() + fi.fileName() + Option::pro_ext) &&
220                            !subdirs.contains(newdir)) {
221                            subdirs.append(newdir);
222                         } else {
223                             QStringList profiles = QDir(newdir).entryList(QStringList("*" + Option::pro_ext), QDir::Files);
224                             for(int i = 0; i < (int)profiles.count(); i++) {
225                                 QString nd = newdir + QDir::separator() + files[i];
226                                 fileFixify(nd);
227                                 if(files[i] != "." && files[i] != ".." && !subdirs.contains(nd, Qt::CaseInsensitive)) {
228                                     if(newdir + files[i] != Option::output_dir + Option::output.fileName())
229                                         subdirs.append(nd);
230                                 }
231                             }
232                         }
233                         if(Option::recursive == Option::QMAKE_RECURSIVE_YES
234                            && !knownDirs.contains(newdir, Qt::CaseInsensitive))
235                             knownDirs.append(newdir);
236                     }
237                 }
238             }
239         }
240         v["TEMPLATE_ASSIGN"] = QStringList("subdirs");
241         return;
242     }
243 
244     //setup deplist
245     QList<QMakeLocalFileName> deplist;
246     {
247         const QStringList &d = v["DEPENDPATH"];
248         for(int i = 0; i < d.size(); ++i)
249             deplist.append(QMakeLocalFileName(d[i]));
250     }
251     setDependencyPaths(deplist);
252 
253     QStringList &h = v["HEADERS"];
254     bool no_qt_files = true;
255     QString srcs[] = { "SOURCES", "YACCSOURCES", "LEXSOURCES", "FORMS", QString() };
256     for(int i = 0; !srcs[i].isNull(); i++) {
257         const QStringList &l = v[srcs[i]];
258         QMakeSourceFileInfo::SourceFileType type = QMakeSourceFileInfo::TYPE_C;
259         QMakeSourceFileInfo::addSourceFiles(l, QMakeSourceFileInfo::SEEK_DEPS, type);
260         for(int i = 0; i < l.size(); ++i) {
261             QStringList tmp = QMakeSourceFileInfo::dependencies(l[i]);
262             if(!tmp.isEmpty()) {
263                 for(int dep_it = 0; dep_it < tmp.size(); ++dep_it) {
264                     QString dep = tmp[dep_it];
265                     dep = fixPathToQmake(dep);
266                     QString file_dir = dep.section(Option::dir_sep, 0, -2),
267                         file_no_path = dep.section(Option::dir_sep, -1);
268                     if(!file_dir.isEmpty()) {
269                         for(int inc_it = 0; inc_it < deplist.size(); ++inc_it) {
270                             QMakeLocalFileName inc = deplist[inc_it];
271                             if(inc.local() == file_dir && !v["INCLUDEPATH"].contains(inc.real(), Qt::CaseInsensitive))
272                                 v["INCLUDEPATH"] += inc.real();
273                         }
274                     }
275                     if(no_qt_files && file_no_path.indexOf(QRegExp("^q[a-z_0-9].h$")) != -1)
276                         no_qt_files = false;
277                     QString h_ext;
278                     for(int hit = 0; hit < Option::h_ext.size(); ++hit) {
279                         if(dep.endsWith(Option::h_ext.at(hit))) {
280                             h_ext = Option::h_ext.at(hit);
281                             break;
282                         }
283                     }
284                     if(!h_ext.isEmpty()) {
285                         for(int cppit = 0; cppit < Option::cpp_ext.size(); ++cppit) {
286                             QString src(dep.left(dep.length() - h_ext.length()) +
287                                         Option::cpp_ext.at(cppit));
288                             if(exists(src)) {
289                                 QStringList &srcl = v["SOURCES"];
290                                 if(!srcl.contains(src, Qt::CaseInsensitive))
291                                     srcl.append(src);
292                             }
293                         }
294                     } else if(dep.endsWith(Option::lex_ext) &&
295                               file_no_path.startsWith(Option::lex_mod)) {
296                         addConfig("lex_included");
297                     }
298                     if(!h.contains(dep, Qt::CaseInsensitive))
299                         h += dep;
300                 }
301             }
302         }
303     }
304 
305     //strip out files that are actually output from internal compilers (ie temporary files)
306     const QStringList &quc = project->variables()["QMAKE_EXTRA_COMPILERS"];
307     for(QStringList::ConstIterator it = quc.begin(); it != quc.end(); ++it) {
308         QString tmp_out = project->variables()[(*it) + ".output"].first();
309         if(tmp_out.isEmpty())
310             continue;
311 
312         QStringList var_out = project->variables()[(*it) + ".variable_out"];
313         bool defaults = var_out.isEmpty();
314         for(int i = 0; i < var_out.size(); ++i) {
315             QString v = var_out.at(i);
316             if(v.startsWith("GENERATED_")) {
317                 defaults = true;
318                 break;
319             }
320         }
321         if(defaults) {
322             var_out << "SOURCES";
323             var_out << "HEADERS";
324             var_out << "FORMS";
325         }
326         const QStringList &tmp = project->variables()[(*it) + ".input"];
327         for(QStringList::ConstIterator it2 = tmp.begin(); it2 != tmp.end(); ++it2) {
328             QStringList &inputs = project->variables()[(*it2)];
329             for(QStringList::Iterator input = inputs.begin(); input != inputs.end(); ++input) {
330                 QString path = replaceExtraCompilerVariables(tmp_out, (*input), QString());
331                 path = fixPathToQmake(path).section('/', -1);
332                 for(int i = 0; i < var_out.size(); ++i) {
333                     QString v = var_out.at(i);
334                     QStringList &list = project->variables()[v];
335                     for(int src = 0; src < list.size(); ) {
336                         if(list[src] == path || list[src].endsWith("/" + path))
337                             list.removeAt(src);
338                         else
339                             ++src;
340                     }
341                 }
342             }
343         }
344     }
345 }
346 
347 bool
writeMakefile(QTextStream & t)348 ProjectGenerator::writeMakefile(QTextStream &t)
349 {
350     t << "######################################################################" << endl;
351     t << "# Automatically generated by qmake (" << qmake_version() << ") " << QDateTime::currentDateTime().toString() << endl;
352     t << "######################################################################" << endl << endl;
353     if(!Option::user_configs.isEmpty())
354         t << "CONFIG += " << Option::user_configs.join(" ") << endl;
355     int i;
356     for(i = 0; i < Option::before_user_vars.size(); ++i)
357         t << Option::before_user_vars[i] << endl;
358     t << getWritableVar("TEMPLATE_ASSIGN", false);
359     if(project->first("TEMPLATE_ASSIGN") == "subdirs") {
360         t << endl << "# Directories" << "\n"
361           << getWritableVar("SUBDIRS");
362     } else {
363         t << getWritableVar("TARGET_ASSIGN")
364           << getWritableVar("CONFIG", false)
365           << getWritableVar("CONFIG_REMOVE", false)
366           << getWritableVar("DEPENDPATH")
367           << getWritableVar("INCLUDEPATH") << endl;
368 
369         t << "# Input" << "\n";
370         t << getWritableVar("HEADERS")
371           << getWritableVar("FORMS")
372           << getWritableVar("LEXSOURCES")
373           << getWritableVar("YACCSOURCES")
374           << getWritableVar("SOURCES")
375           << getWritableVar("RESOURCES")
376           << getWritableVar("TRANSLATIONS");
377     }
378     for(i = 0; i < Option::after_user_vars.size(); ++i)
379         t << Option::after_user_vars[i] << endl;
380     return true;
381 }
382 
383 bool
addConfig(const QString & cfg,bool add)384 ProjectGenerator::addConfig(const QString &cfg, bool add)
385 {
386     QString where = "CONFIG";
387     if(!add)
388         where = "CONFIG_REMOVE";
389     if(!project->variables()[where].contains(cfg)) {
390         project->variables()[where] += cfg;
391         return true;
392     }
393     return false;
394 }
395 
396 bool
addFile(QString file)397 ProjectGenerator::addFile(QString file)
398 {
399     file = fileFixify(file, qmake_getpwd());
400     QString dir;
401     int s = file.lastIndexOf(Option::dir_sep);
402     if(s != -1)
403         dir = file.left(s+1);
404     if(file.mid(dir.length(), Option::h_moc_mod.length()) == Option::h_moc_mod)
405         return false;
406 
407     QString where;
408     for(int cppit = 0; cppit < Option::cpp_ext.size(); ++cppit) {
409         if(file.endsWith(Option::cpp_ext[cppit])) {
410             where = "SOURCES";
411             break;
412         }
413     }
414     if(where.isEmpty()) {
415         for(int hit = 0; hit < Option::h_ext.size(); ++hit)
416             if(file.endsWith(Option::h_ext.at(hit))) {
417                 where = "HEADERS";
418                 break;
419             }
420     }
421     if(where.isEmpty()) {
422         for(int cit = 0; cit < Option::c_ext.size(); ++cit) {
423             if(file.endsWith(Option::c_ext[cit])) {
424                 where = "SOURCES";
425                 break;
426             }
427         }
428     }
429     if(where.isEmpty()) {
430         if(file.endsWith(Option::ui_ext))
431             where = "FORMS";
432         else if(file.endsWith(Option::lex_ext))
433             where = "LEXSOURCES";
434         else if(file.endsWith(Option::yacc_ext))
435             where = "YACCSOURCES";
436         else if(file.endsWith(".ts") || file.endsWith(".xlf"))
437             where = "TRANSLATIONS";
438         else if(file.endsWith(".qrc"))
439             where = "RESOURCES";
440     }
441 
442     QString newfile = fixPathToQmake(fileFixify(file));
443 
444     QStringList &endList = project->variables()[where];
445     if(!endList.contains(newfile, Qt::CaseInsensitive)) {
446         endList += newfile;
447         return true;
448     }
449     return false;
450 }
451 
452 QString
getWritableVar(const QString & v,bool)453 ProjectGenerator::getWritableVar(const QString &v, bool)
454 {
455     QStringList &vals = project->variables()[v];
456     if(vals.isEmpty())
457         return "";
458 
459     // If values contain spaces, ensure that they are quoted
460     for(QStringList::iterator it = vals.begin(); it != vals.end(); ++it) {
461         if ((*it).contains(' ') && !(*it).startsWith(' '))
462             *it = '\"' + *it + '\"';
463     }
464 
465     QString ret;
466     if(v.endsWith("_REMOVE"))
467         ret = v.left(v.length() - 7) + " -= ";
468     else if(v.endsWith("_ASSIGN"))
469         ret = v.left(v.length() - 7) + " = ";
470     else
471         ret = v + " += ";
472     QString join = vals.join(" ");
473     if(ret.length() + join.length() > 80) {
474         QString spaces;
475         for(int i = 0; i < ret.length(); i++)
476             spaces += " ";
477         join = vals.join(" \\\n" + spaces);
478     }
479     return ret + join + "\n";
480 }
481 
482 bool
openOutput(QFile & file,const QString & build) const483 ProjectGenerator::openOutput(QFile &file, const QString &build) const
484 {
485     QString outdir;
486     if(!file.fileName().isEmpty()) {
487         QFileInfo fi(fileInfo(file.fileName()));
488         if(fi.isDir())
489             outdir = fi.path() + QDir::separator();
490     }
491     if(!outdir.isEmpty() || file.fileName().isEmpty()) {
492         QString dir = qmake_getpwd();
493         int s = dir.lastIndexOf('/');
494         if(s != -1)
495             dir = dir.right(dir.length() - (s + 1));
496         file.setFileName(outdir + dir + Option::pro_ext);
497     }
498     return MakefileGenerator::openOutput(file, build);
499 }
500 
501 
502 QString
fixPathToQmake(const QString & file)503 ProjectGenerator::fixPathToQmake(const QString &file)
504 {
505     QString ret = file;
506     if(Option::dir_sep != QLatin1String("/"))
507         ret = ret.replace(Option::dir_sep, QLatin1String("/"));
508     return ret;
509 }
510 
511 QT_END_NAMESPACE
512