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