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