1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "qmakenodes.h"
27 
28 #include "qmakeproject.h"
29 
30 #include <projectexplorer/buildconfiguration.h>
31 #include <projectexplorer/runconfiguration.h>
32 #include <projectexplorer/target.h>
33 
34 #include <qtsupport/baseqtversion.h>
35 #include <qtsupport/qtkitinformation.h>
36 
37 #include <resourceeditor/resourcenode.h>
38 
39 #include <utils/qtcassert.h>
40 #include <utils/stringutils.h>
41 
42 #include <android/androidconstants.h>
43 #include <ios/iosconstants.h>
44 
45 #include <QJsonDocument>
46 #include <QJsonObject>
47 #include <QJsonParseError>
48 
49 using namespace ProjectExplorer;
50 using namespace Utils;
51 
52 using namespace QmakeProjectManager::Internal;
53 
54 namespace QmakeProjectManager {
55 
56 /*!
57   \class QmakePriFileNode
58   Implements abstract ProjectNode class
59   */
60 
QmakePriFileNode(QmakeBuildSystem * buildSystem,QmakeProFileNode * qmakeProFileNode,const FilePath & filePath,QmakePriFile * pf)61 QmakePriFileNode::QmakePriFileNode(QmakeBuildSystem *buildSystem, QmakeProFileNode *qmakeProFileNode,
62                                    const FilePath &filePath, QmakePriFile *pf) :
63     ProjectNode(filePath),
64     m_buildSystem(buildSystem),
65     m_qmakeProFileNode(qmakeProFileNode),
66     m_qmakePriFile(pf)
67 { }
68 
priFile() const69 QmakePriFile *QmakePriFileNode::priFile() const
70 {
71     if (!m_buildSystem)
72         return nullptr;
73 
74     if (!m_buildSystem->isParsing())
75         return m_qmakePriFile;
76 
77     // During a parsing run the qmakePriFile tree will change, so search for the PriFile and
78     // do not depend on the cached value.
79     // NOTE: This would go away if the node tree would be per-buildsystem
80     return m_buildSystem->rootProFile()->findPriFile(filePath());
81 }
82 
deploysFolder(const QString & folder) const83 bool QmakePriFileNode::deploysFolder(const QString &folder) const
84 {
85     const QmakePriFile *pri = priFile();
86     return pri ? pri->deploysFolder(folder) : false;
87 }
88 
proFileNode() const89 QmakeProFileNode *QmakePriFileNode::proFileNode() const
90 {
91     return m_qmakeProFileNode;
92 }
93 
supportsAction(Node * context,ProjectAction action,const Node * node) const94 bool QmakeBuildSystem::supportsAction(Node *context, ProjectAction action, const Node *node) const
95 {
96     if (auto n = dynamic_cast<QmakePriFileNode *>(context)) { // Covers QmakeProfile, too.
97         if (action == Rename) {
98             const FileNode *fileNode = node->asFileNode();
99             return (fileNode && fileNode->fileType() != FileType::Project)
100                     || dynamic_cast<const ResourceEditor::ResourceTopLevelNode *>(node);
101         }
102 
103         ProjectType t = ProjectType::Invalid;
104         const QmakeProFile *pro = nullptr;
105         if (hasParsingData()) {
106             const FolderNode *folderNode = n;
107             const QmakeProFileNode *proFileNode;
108             while (!(proFileNode = dynamic_cast<const QmakeProFileNode*>(folderNode))) {
109                 folderNode = folderNode->parentFolderNode();
110                 QTC_ASSERT(folderNode, return false);
111             }
112             QTC_ASSERT(proFileNode, return false);
113             pro = proFileNode->proFile();
114             QTC_ASSERT(pro, return false);
115             t = pro->projectType();
116         }
117 
118         switch (t) {
119         case ProjectType::ApplicationTemplate:
120         case ProjectType::StaticLibraryTemplate:
121         case ProjectType::SharedLibraryTemplate:
122         case ProjectType::AuxTemplate: {
123             // TODO: Some of the file types don't make much sense for aux
124             // projects (e.g. cpp). It'd be nice if the "add" action could
125             // work on a subset of the file types according to project type.
126             if (action == AddNewFile)
127                 return true;
128             if (action == EraseFile)
129                 return pro && pro->knowsFile(node->filePath());
130             if (action == RemoveFile)
131                 return !(pro && pro->knowsFile(node->filePath()));
132 
133             bool addExistingFiles = true;
134             if (node->isVirtualFolderType()) {
135                 // A virtual folder, we do what the projectexplorer does
136                 const FolderNode *folder = node->asFolderNode();
137                 if (folder) {
138                     QStringList list;
139                     foreach (FolderNode *f, folder->folderNodes())
140                         list << f->filePath().toString() + QLatin1Char('/');
141                     if (n->deploysFolder(Utils::commonPath(list)))
142                         addExistingFiles = false;
143                 }
144             }
145 
146             addExistingFiles = addExistingFiles && !n->deploysFolder(node->filePath().toString());
147 
148             if (action == AddExistingFile || action == AddExistingDirectory)
149                 return addExistingFiles;
150 
151             break;
152         }
153         case ProjectType::SubDirsTemplate:
154             if (action == AddSubProject || action == AddExistingProject)
155                 return true;
156             break;
157         default:
158             break;
159         }
160 
161         return false;
162     }
163 
164     if (auto n = dynamic_cast<QmakeProFileNode *>(context)) {
165         if (action == RemoveSubProject)
166             return n->parentProjectNode() && !n->parentProjectNode()->asContainerNode();
167     }
168 
169     return BuildSystem::supportsAction(context, action, node);
170 }
171 
canAddSubProject(const QString & proFilePath) const172 bool QmakePriFileNode::canAddSubProject(const QString &proFilePath) const
173 {
174     const QmakePriFile *pri = priFile();
175     return pri ? pri->canAddSubProject(proFilePath) : false;
176 }
177 
addSubProject(const QString & proFilePath)178 bool QmakePriFileNode::addSubProject(const QString &proFilePath)
179 {
180     QmakePriFile *pri = priFile();
181     return pri ? pri->addSubProject(proFilePath) : false;
182 }
183 
removeSubProject(const QString & proFilePath)184 bool QmakePriFileNode::removeSubProject(const QString &proFilePath)
185 {
186     QmakePriFile *pri = priFile();
187     return pri ? pri->removeSubProjects(proFilePath) : false;
188 }
189 
subProjectFileNamePatterns() const190 QStringList QmakePriFileNode::subProjectFileNamePatterns() const
191 {
192     return QStringList("*.pro");
193 }
194 
addFiles(Node * context,const FilePaths & filePaths,FilePaths * notAdded)195 bool QmakeBuildSystem::addFiles(Node *context, const FilePaths &filePaths, FilePaths *notAdded)
196 {
197     if (auto n = dynamic_cast<QmakePriFileNode *>(context)) {
198         QmakePriFile *pri = n->priFile();
199         if (!pri)
200             return false;
201         QList<Node *> matchingNodes = n->findNodes([filePaths](const Node *nn) {
202             return nn->asFileNode() && filePaths.contains(nn->filePath());
203         });
204         matchingNodes = filtered(matchingNodes, [](const Node *n) {
205             for (const Node *parent = n->parentFolderNode(); parent;
206                  parent = parent->parentFolderNode()) {
207                 if (dynamic_cast<const ResourceEditor::ResourceTopLevelNode *>(parent))
208                     return false;
209             }
210             return true;
211         });
212         FilePaths alreadyPresentFiles = transform(matchingNodes, [](const Node *n) { return n->filePath(); });
213         FilePath::removeDuplicates(alreadyPresentFiles);
214 
215         FilePaths actualFilePaths = filePaths;
216         for (const FilePath &e : alreadyPresentFiles)
217             actualFilePaths.removeOne(e);
218         if (notAdded)
219             *notAdded = alreadyPresentFiles;
220         qCDebug(qmakeNodesLog) << Q_FUNC_INFO << "file paths:" << filePaths
221                                << "already present:" << alreadyPresentFiles
222                                << "actual file paths:" << actualFilePaths;
223         return pri->addFiles(actualFilePaths, notAdded);
224     }
225 
226     return BuildSystem::addFiles(context, filePaths, notAdded);
227 }
228 
removeFiles(Node * context,const FilePaths & filePaths,FilePaths * notRemoved)229 RemovedFilesFromProject QmakeBuildSystem::removeFiles(Node *context, const FilePaths &filePaths,
230                                                       FilePaths *notRemoved)
231 {
232     if (auto n = dynamic_cast<QmakePriFileNode *>(context)) {
233         QmakePriFile * const pri = n->priFile();
234         if (!pri)
235             return RemovedFilesFromProject::Error;
236         FilePaths wildcardFiles;
237         FilePaths nonWildcardFiles;
238         for (const FilePath &file : filePaths) {
239             if (pri->proFile()->isFileFromWildcard(file.toString()))
240                 wildcardFiles << file;
241             else
242                 nonWildcardFiles << file;
243         }
244         const bool success = pri->removeFiles(nonWildcardFiles, notRemoved);
245         if (notRemoved)
246             *notRemoved += wildcardFiles;
247         if (!success)
248             return RemovedFilesFromProject::Error;
249         if (!wildcardFiles.isEmpty())
250             return RemovedFilesFromProject::Wildcard;
251         return RemovedFilesFromProject::Ok;
252     }
253 
254     return BuildSystem::removeFiles(context, filePaths, notRemoved);
255 }
256 
deleteFiles(Node * context,const FilePaths & filePaths)257 bool QmakeBuildSystem::deleteFiles(Node *context, const FilePaths &filePaths)
258 {
259     if (auto n = dynamic_cast<QmakePriFileNode *>(context)) {
260         QmakePriFile *pri = n->priFile();
261         return pri ? pri->deleteFiles(filePaths) : false;
262     }
263 
264     return BuildSystem::deleteFiles(context, filePaths);
265 }
266 
canRenameFile(Node * context,const FilePath & oldFilePath,const FilePath & newFilePath)267 bool QmakeBuildSystem::canRenameFile(Node *context,
268                                      const FilePath &oldFilePath,
269                                      const FilePath &newFilePath)
270 {
271     if (auto n = dynamic_cast<QmakePriFileNode *>(context)) {
272         QmakePriFile *pri = n->priFile();
273         return pri ? pri->canRenameFile(oldFilePath, newFilePath) : false;
274     }
275 
276     return BuildSystem::canRenameFile(context, oldFilePath, newFilePath);
277 }
278 
renameFile(Node * context,const FilePath & oldFilePath,const FilePath & newFilePath)279 bool QmakeBuildSystem::renameFile(Node *context,
280                                   const FilePath &oldFilePath,
281                                   const FilePath &newFilePath)
282 {
283     if (auto n = dynamic_cast<QmakePriFileNode *>(context)) {
284         QmakePriFile *pri = n->priFile();
285         return pri ? pri->renameFile(oldFilePath, newFilePath) : false;
286     }
287 
288     return BuildSystem::renameFile(context, oldFilePath, newFilePath);
289 }
290 
addDependencies(Node * context,const QStringList & dependencies)291 bool QmakeBuildSystem::addDependencies(Node *context, const QStringList &dependencies)
292 {
293     if (auto n = dynamic_cast<QmakePriFileNode *>(context)) {
294         if (QmakePriFile * const pri = n->priFile())
295             return pri->addDependencies(dependencies);
296         return false;
297     }
298 
299     return BuildSystem::addDependencies(context, dependencies);
300 }
301 
addNewInformation(const QStringList & files,Node * context) const302 FolderNode::AddNewInformation QmakePriFileNode::addNewInformation(const QStringList &files, Node *context) const
303 {
304     Q_UNUSED(files)
305     return FolderNode::AddNewInformation(filePath().fileName(), context && context->parentProjectNode() == this ? 120 : 90);
306 }
307 
308 /*!
309   \class QmakeProFileNode
310   Implements abstract ProjectNode class
311   */
QmakeProFileNode(QmakeBuildSystem * buildSystem,const FilePath & filePath,QmakeProFile * pf)312 QmakeProFileNode::QmakeProFileNode(QmakeBuildSystem *buildSystem, const FilePath &filePath, QmakeProFile *pf) :
313     QmakePriFileNode(buildSystem, this, filePath, pf)
314 {
315     if (projectType() == ProjectType::ApplicationTemplate) {
316         setProductType(ProductType::App);
317     } else if (projectType() == ProjectType::SharedLibraryTemplate
318                || projectType() == ProjectType::StaticLibraryTemplate) {
319         setProductType(ProductType::Lib);
320     } else if (projectType() != ProjectType::SubDirsTemplate) {
321         setProductType(ProductType::Other);
322     }
323 }
324 
showInSimpleTree() const325 bool QmakeProFileNode::showInSimpleTree() const
326 {
327     return showInSimpleTree(projectType()) || m_buildSystem->project()->rootProjectNode() == this;
328 }
329 
buildKey() const330 QString QmakeProFileNode::buildKey() const
331 {
332     return filePath().toString();
333 }
334 
parseInProgress() const335 bool QmakeProFileNode::parseInProgress() const
336 {
337     QmakeProjectManager::QmakeProFile *pro = proFile();
338     return !pro || pro->parseInProgress();
339 }
340 
validParse() const341 bool QmakeProFileNode::validParse() const
342 {
343     QmakeProjectManager::QmakeProFile *pro = proFile();
344     return pro && pro->validParse();
345 }
346 
build()347 void QmakeProFileNode::build()
348 {
349     m_buildSystem->buildHelper(QmakeBuildSystem::BUILD, false, this, nullptr);
350 }
351 
targetApplications() const352 QStringList QmakeProFileNode::targetApplications() const
353 {
354     QStringList apps;
355     if (includedInExactParse() && projectType() == ProjectType::ApplicationTemplate) {
356         const QString target = targetInformation().target;
357         if (target.startsWith("lib") && target.endsWith(".so"))
358             apps << target.mid(3, target.lastIndexOf('.') - 3);
359         else
360             apps << target;
361     }
362     return apps;
363 }
364 
data(Utils::Id role) const365 QVariant QmakeProFileNode::data(Utils::Id role) const
366 {
367     if (role == Android::Constants::ANDROID_ABIS)
368         return variableValue(Variable::AndroidAbis);
369     if (role == Android::Constants::AndroidPackageSourceDir)
370         return singleVariableValue(Variable::AndroidPackageSourceDir);
371     if (role == Android::Constants::AndroidDeploySettingsFile)
372         return singleVariableValue(Variable::AndroidDeploySettingsFile);
373     if (role == Android::Constants::AndroidExtraLibs)
374         return variableValue(Variable::AndroidExtraLibs);
375     if (role == Android::Constants::AndroidArch)
376         return singleVariableValue(Variable::AndroidArch);
377     if (role == Android::Constants::AndroidSoLibPath) {
378         TargetInformation info = targetInformation();
379         QStringList res = {info.buildDir.toString()};
380         FilePath destDir = info.destDir;
381         if (!destDir.isEmpty()) {
382             destDir = info.buildDir.resolvePath(destDir.path());
383             res.append(destDir.toString());
384         }
385         res.removeDuplicates();
386         return res;
387     }
388 
389     if (role == Android::Constants::AndroidTargets)
390         return {};
391     if (role == Android::Constants::AndroidApk)
392         return {};
393 
394     // We can not use AppMan headers even at build time.
395     if (role == "AppmanPackageDir")
396         return singleVariableValue(Variable::AppmanPackageDir);
397     if (role == "AppmanManifest")
398         return singleVariableValue(Variable::AppmanManifest);
399 
400     if (role == Ios::Constants::IosTarget) {
401         const TargetInformation info = targetInformation();
402         if (info.valid)
403             return info.target;
404     }
405 
406     if (role == Ios::Constants::IosBuildDir) {
407         const TargetInformation info = targetInformation();
408         if (info.valid)
409             return info.buildDir.toString();
410     }
411 
412     if (role == ProjectExplorer::Constants::QT_KEYWORDS_ENABLED)
413         return !proFile()->variableValue(Variable::Config).contains("no_keywords");
414 
415     QTC_CHECK(false);
416     return {};
417 }
418 
setData(Utils::Id role,const QVariant & value) const419 bool QmakeProFileNode::setData(Utils::Id role, const QVariant &value) const
420 {
421     QmakeProFile *pro = proFile();
422     if (!pro)
423         return false;
424     QString scope;
425     int flags = QmakeProjectManager::Internal::ProWriter::ReplaceValues;
426     if (Target *target = m_buildSystem->target()) {
427         QtSupport::BaseQtVersion *version = QtSupport::QtKitAspect::qtVersion(target->kit());
428         if (version && !version->supportsMultipleQtAbis()) {
429             const QString arch = pro->singleVariableValue(Variable::AndroidArch);
430             scope = "contains(ANDROID_TARGET_ARCH," + arch + ')';
431             flags |= QmakeProjectManager::Internal::ProWriter::MultiLine;
432         }
433     }
434 
435     if (role == Android::Constants::AndroidExtraLibs)
436         return pro->setProVariable(QLatin1String(Android::Constants::ANDROID_EXTRA_LIBS),
437                                    value.toStringList(), scope, flags);
438     if (role == Android::Constants::AndroidPackageSourceDir)
439         return pro->setProVariable(QLatin1String(Android::Constants::ANDROID_PACKAGE_SOURCE_DIR),
440                                    {value.toString()}, scope, flags);
441     if (role == Android::Constants::ANDROID_APPLICATION_ARGUMENTS)
442         return pro->setProVariable(QLatin1String(Android::Constants::ANDROID_APPLICATION_ARGUMENTS),
443                                    {value.toString()}, scope, flags);
444 
445     return false;
446 }
447 
proFile() const448 QmakeProFile *QmakeProFileNode::proFile() const
449 {
450     return dynamic_cast<QmakeProFile*>(QmakePriFileNode::priFile());
451 }
452 
makefile() const453 QString QmakeProFileNode::makefile() const
454 {
455     return singleVariableValue(Variable::Makefile);
456 }
457 
objectsDirectory() const458 QString QmakeProFileNode::objectsDirectory() const
459 {
460     return singleVariableValue(Variable::ObjectsDir);
461 }
462 
isDebugAndRelease() const463 bool QmakeProFileNode::isDebugAndRelease() const
464 {
465     const QStringList configValues = variableValue(Variable::Config);
466     return configValues.contains(QLatin1String("debug_and_release"));
467 }
468 
isObjectParallelToSource() const469 bool QmakeProFileNode::isObjectParallelToSource() const
470 {
471     return variableValue(Variable::Config).contains("object_parallel_to_source");
472 }
473 
isQtcRunnable() const474 bool QmakeProFileNode::isQtcRunnable() const
475 {
476     const QStringList configValues = variableValue(Variable::Config);
477     return configValues.contains(QLatin1String("qtc_runnable"));
478 }
479 
includedInExactParse() const480 bool QmakeProFileNode::includedInExactParse() const
481 {
482     const QmakeProFile *pro = proFile();
483     return pro && pro->includedInExactParse();
484 }
485 
addNewInformation(const QStringList & files,Node * context) const486 FolderNode::AddNewInformation QmakeProFileNode::addNewInformation(const QStringList &files, Node *context) const
487 {
488     Q_UNUSED(files)
489     return AddNewInformation(filePath().fileName(), context && context->parentProjectNode() == this ? 120 : 100);
490 }
491 
showInSimpleTree(ProjectType projectType) const492 bool QmakeProFileNode::showInSimpleTree(ProjectType projectType) const
493 {
494     return projectType == ProjectType::ApplicationTemplate
495             || projectType == ProjectType::SharedLibraryTemplate
496             || projectType == ProjectType::StaticLibraryTemplate;
497 }
498 
projectType() const499 ProjectType QmakeProFileNode::projectType() const
500 {
501     const QmakeProFile *pro = proFile();
502     return pro ? pro->projectType() : ProjectType::Invalid;
503 }
504 
variableValue(const Variable var) const505 QStringList QmakeProFileNode::variableValue(const Variable var) const
506 {
507     QmakeProFile *pro = proFile();
508     return pro ? pro->variableValue(var) : QStringList();
509 }
510 
singleVariableValue(const Variable var) const511 QString QmakeProFileNode::singleVariableValue(const Variable var) const
512 {
513     const QStringList &values = variableValue(var);
514     return values.isEmpty() ? QString() : values.first();
515 }
516 
objectExtension() const517 QString QmakeProFileNode::objectExtension() const
518 {
519     QStringList exts = variableValue(Variable::ObjectExt);
520     if (exts.isEmpty())
521         return HostOsInfo::isWindowsHost() ? QLatin1String(".obj") : QLatin1String(".o");
522     return exts.first();
523 }
524 
targetInformation() const525 TargetInformation QmakeProFileNode::targetInformation() const
526 {
527     return proFile() ? proFile()->targetInformation() : TargetInformation();
528 }
529 
530 } // namespace QmakeProjectManager
531