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 "qmakebuildconfiguration.h"
27 
28 #include "qmakebuildinfo.h"
29 #include "qmakekitinformation.h"
30 #include "qmakeproject.h"
31 #include "qmakeprojectmanagerconstants.h"
32 #include "qmakenodes.h"
33 #include "qmakesettings.h"
34 #include "qmakestep.h"
35 #include "makefileparse.h"
36 #include "qmakebuildconfiguration.h"
37 
38 #include <android/androidconstants.h>
39 
40 #include <coreplugin/documentmanager.h>
41 #include <coreplugin/icore.h>
42 
43 #include <projectexplorer/buildaspects.h>
44 #include <projectexplorer/buildinfo.h>
45 #include <projectexplorer/buildmanager.h>
46 #include <projectexplorer/buildpropertiessettings.h>
47 #include <projectexplorer/buildsteplist.h>
48 #include <projectexplorer/kit.h>
49 #include <projectexplorer/makestep.h>
50 #include <projectexplorer/projectexplorer.h>
51 #include <projectexplorer/projectexplorerconstants.h>
52 #include <projectexplorer/projectmacroexpander.h>
53 #include <projectexplorer/target.h>
54 #include <projectexplorer/toolchain.h>
55 
56 #include <qtsupport/qtbuildaspects.h>
57 #include <qtsupport/qtkitinformation.h>
58 #include <qtsupport/qtversionmanager.h>
59 
60 #include <utils/mimetypes/mimedatabase.h>
61 #include <utils/qtcassert.h>
62 #include <utils/qtcprocess.h>
63 #include <utils/qtcassert.h>
64 
65 #include <QDebug>
66 #include <QInputDialog>
67 #include <QLoggingCategory>
68 
69 #include <limits>
70 
71 using namespace ProjectExplorer;
72 using namespace QtSupport;
73 using namespace Utils;
74 using namespace QmakeProjectManager::Internal;
75 
76 namespace QmakeProjectManager {
77 
78 class RunSystemAspect : public TriStateAspect
79 {
80     Q_OBJECT
81 public:
RunSystemAspect()82     RunSystemAspect() : TriStateAspect(tr("Run"), tr("Ignore"), tr("Use global setting"))
83     {
84         setSettingsKey("RunSystemFunction");
85         setDisplayName(tr("qmake system() behavior when parsing:"));
86     }
87 };
88 
QmakeExtraBuildInfo()89 QmakeExtraBuildInfo::QmakeExtraBuildInfo()
90 {
91     const BuildPropertiesSettings &settings = ProjectExplorerPlugin::buildPropertiesSettings();
92     config.separateDebugInfo = settings.separateDebugInfo.value();
93     config.linkQmlDebuggingQQ2 = settings.qmlDebugging.value();
94     config.useQtQuickCompiler = settings.qtQuickCompiler.value();
95 }
96 
97 // --------------------------------------------------------------------
98 // Helpers:
99 // --------------------------------------------------------------------
100 
shadowBuildDirectory(const FilePath & proFilePath,const Kit * k,const QString & suffix,BuildConfiguration::BuildType buildType)101 FilePath QmakeBuildConfiguration::shadowBuildDirectory(const FilePath &proFilePath, const Kit *k,
102                                                        const QString &suffix,
103                                                        BuildConfiguration::BuildType buildType)
104 {
105     if (proFilePath.isEmpty())
106         return {};
107 
108     const QString projectName = proFilePath.completeBaseName();
109     ProjectMacroExpander expander(proFilePath, projectName, k, suffix, buildType);
110     FilePath projectDir = Project::projectDirectory(proFilePath);
111     QString buildPath = expander.expand(ProjectExplorerPlugin::buildDirectoryTemplate());
112     return projectDir.resolvePath(buildPath);
113 }
114 
115 const char BUILD_CONFIGURATION_KEY[] = "Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration";
116 
QmakeBuildConfiguration(Target * target,Utils::Id id)117 QmakeBuildConfiguration::QmakeBuildConfiguration(Target *target, Utils::Id id)
118     : BuildConfiguration(target, id)
119 {
120     setConfigWidgetDisplayName(tr("General"));
121     setConfigWidgetHasFrame(true);
122 
123     m_buildSystem = new QmakeBuildSystem(this);
124 
125     appendInitialBuildStep(Constants::QMAKE_BS_ID);
126     appendInitialBuildStep(Constants::MAKESTEP_BS_ID);
127     appendInitialCleanStep(Constants::MAKESTEP_BS_ID);
128 
129     setInitializer([this, target](const BuildInfo &info) {
130         auto qmakeStep = buildSteps()->firstOfType<QMakeStep>();
131         QTC_ASSERT(qmakeStep, return);
132 
133         const QmakeExtraBuildInfo qmakeExtra = info.extraInfo.value<QmakeExtraBuildInfo>();
134         BaseQtVersion *version = QtKitAspect::qtVersion(target->kit());
135 
136         BaseQtVersion::QmakeBuildConfigs config = version->defaultBuildConfig();
137         if (info.buildType == BuildConfiguration::Debug)
138             config |= BaseQtVersion::DebugBuild;
139         else
140             config &= ~BaseQtVersion::DebugBuild;
141 
142         QString additionalArguments = qmakeExtra.additionalArguments;
143         if (!additionalArguments.isEmpty())
144             qmakeStep->setUserArguments(additionalArguments);
145 
146         aspect<SeparateDebugInfoAspect>()->setValue(qmakeExtra.config.separateDebugInfo);
147         aspect<QmlDebuggingAspect>()->setValue(qmakeExtra.config.linkQmlDebuggingQQ2);
148         aspect<QtQuickCompilerAspect>()->setValue(qmakeExtra.config.useQtQuickCompiler);
149 
150         setQMakeBuildConfiguration(config);
151 
152         FilePath directory = info.buildDirectory;
153         if (directory.isEmpty()) {
154             directory = shadowBuildDirectory(target->project()->projectFilePath(),
155                                              target->kit(), info.displayName,
156                                              info.buildType);
157         }
158 
159         setBuildDirectory(directory);
160 
161         if (DeviceTypeKitAspect::deviceTypeId(target->kit())
162                         == Android::Constants::ANDROID_DEVICE_TYPE) {
163             buildSteps()->appendStep(Android::Constants::ANDROID_PACKAGE_INSTALLATION_STEP_ID);
164             buildSteps()->appendStep(Android::Constants::ANDROID_BUILD_APK_ID);
165         }
166 
167         updateCacheAndEmitEnvironmentChanged();
168     });
169 
170     connect(target, &Target::kitChanged,
171             this, &QmakeBuildConfiguration::kitChanged);
172     MacroExpander *expander = macroExpander();
173     expander->registerVariable("Qmake:Makefile", "Qmake makefile", [this]() -> QString {
174         const QString file = makefile();
175         if (!file.isEmpty())
176             return file;
177         return QLatin1String("Makefile");
178     });
179 
180     buildDirectoryAspect()->allowInSourceBuilds(target->project()->projectDirectory());
181     connect(this, &BuildConfiguration::buildDirectoryChanged,
182             this, &QmakeBuildConfiguration::updateProblemLabel);
183     connect(this, &QmakeBuildConfiguration::qmakeBuildConfigurationChanged,
184             this, &QmakeBuildConfiguration::updateProblemLabel);
185     connect(&QmakeSettings::instance(), &QmakeSettings::settingsChanged,
186             this, &QmakeBuildConfiguration::updateProblemLabel);
187     connect(target, &Target::parsingFinished, this, &QmakeBuildConfiguration::updateProblemLabel);
188     connect(target, &Target::kitChanged, this, &QmakeBuildConfiguration::updateProblemLabel);
189 
190     const auto separateDebugInfoAspect = addAspect<SeparateDebugInfoAspect>();
191     connect(separateDebugInfoAspect, &SeparateDebugInfoAspect::changed, this, [this] {
192         emit separateDebugInfoChanged();
193         emit qmakeBuildConfigurationChanged();
194         qmakeBuildSystem()->scheduleUpdateAllNowOrLater();
195     });
196 
197     const auto qmlDebuggingAspect = addAspect<QmlDebuggingAspect>();
198     qmlDebuggingAspect->setKit(target->kit());
199     connect(qmlDebuggingAspect, &QmlDebuggingAspect::changed, this, [this] {
200         emit qmlDebuggingChanged();
201         emit qmakeBuildConfigurationChanged();
202         qmakeBuildSystem()->scheduleUpdateAllNowOrLater();
203     });
204 
205     const auto qtQuickCompilerAspect = addAspect<QtQuickCompilerAspect>();
206     qtQuickCompilerAspect->setKit(target->kit());
207     connect(qtQuickCompilerAspect, &QtQuickCompilerAspect::changed, this, [this] {
208         emit useQtQuickCompilerChanged();
209         emit qmakeBuildConfigurationChanged();
210         qmakeBuildSystem()->scheduleUpdateAllNowOrLater();
211     });
212 
213     addAspect<RunSystemAspect>();
214 }
215 
~QmakeBuildConfiguration()216 QmakeBuildConfiguration::~QmakeBuildConfiguration()
217 {
218     delete m_buildSystem;
219 }
220 
toMap() const221 QVariantMap QmakeBuildConfiguration::toMap() const
222 {
223     QVariantMap map(BuildConfiguration::toMap());
224     map.insert(QLatin1String(BUILD_CONFIGURATION_KEY), int(m_qmakeBuildConfiguration));
225     return map;
226 }
227 
fromMap(const QVariantMap & map)228 bool QmakeBuildConfiguration::fromMap(const QVariantMap &map)
229 {
230     if (!BuildConfiguration::fromMap(map))
231         return false;
232 
233     m_qmakeBuildConfiguration = BaseQtVersion::QmakeBuildConfigs(map.value(QLatin1String(BUILD_CONFIGURATION_KEY)).toInt());
234 
235     m_lastKitState = LastKitState(kit());
236     return true;
237 }
238 
kitChanged()239 void QmakeBuildConfiguration::kitChanged()
240 {
241     LastKitState newState = LastKitState(kit());
242     if (newState != m_lastKitState) {
243         // This only checks if the ids have changed!
244         // For that reason the QmakeBuildConfiguration is also connected
245         // to the toolchain and qtversion managers
246         m_buildSystem->scheduleUpdateAllNowOrLater();
247         m_lastKitState = newState;
248     }
249 }
250 
updateProblemLabel()251 void QmakeBuildConfiguration::updateProblemLabel()
252 {
253     ProjectExplorer::Kit * const k = kit();
254     const QString proFileName = project()->projectFilePath().toString();
255 
256     // Check for Qt version:
257     QtSupport::BaseQtVersion *version = QtSupport::QtKitAspect::qtVersion(k);
258     if (!version) {
259         buildDirectoryAspect()->setProblem(tr("This kit cannot build this project since it "
260                                               "does not define a Qt version."));
261         return;
262     }
263 
264     const auto bs = qmakeBuildSystem();
265     if (QmakeProFile *rootProFile = bs->rootProFile()) {
266         if (rootProFile->parseInProgress() || !rootProFile->validParse()) {
267             buildDirectoryAspect()->setProblem({});
268             return;
269         }
270     }
271 
272     bool targetMismatch = false;
273     bool incompatibleBuild = false;
274     bool allGood = false;
275     // we only show if we actually have a qmake and makestep
276     QString errorString;
277     if (qmakeStep() && makeStep()) {
278         QString makefile = buildDirectory().toString() + QLatin1Char('/');
279         if (this->makefile().isEmpty())
280             makefile.append(QLatin1String("Makefile"));
281         else
282             makefile.append(this->makefile());
283 
284         switch (compareToImportFrom(makefile, &errorString)) {
285         case QmakeBuildConfiguration::MakefileMatches:
286             allGood = true;
287             break;
288         case QmakeBuildConfiguration::MakefileMissing:
289             allGood = true;
290             break;
291         case QmakeBuildConfiguration::MakefileIncompatible:
292             incompatibleBuild = true;
293             break;
294         case QmakeBuildConfiguration::MakefileForWrongProject:
295             targetMismatch = true;
296             break;
297         }
298     }
299 
300     const bool unalignedBuildDir = QmakeSettings::warnAgainstUnalignedBuildDir()
301             && !isBuildDirAtSafeLocation();
302     if (unalignedBuildDir)
303         allGood = false;
304 
305     if (allGood) {
306         Tasks issues;
307         issues = version->reportIssues(proFileName, buildDirectory().toString());
308         Utils::sort(issues);
309 
310         if (!issues.isEmpty()) {
311             QString text = QLatin1String("<nobr>");
312             foreach (const ProjectExplorer::Task &task, issues) {
313                 QString type;
314                 switch (task.type) {
315                 case ProjectExplorer::Task::Error:
316                     type = tr("Error:");
317                     type += QLatin1Char(' ');
318                     break;
319                 case ProjectExplorer::Task::Warning:
320                     type = tr("Warning:");
321                     type += QLatin1Char(' ');
322                     break;
323                 case ProjectExplorer::Task::Unknown:
324                 default:
325                     break;
326                 }
327                 if (!text.endsWith(QLatin1String("br>")))
328                     text.append(QLatin1String("<br>"));
329                 text.append(type + task.description());
330             }
331             buildDirectoryAspect()->setProblem(text);
332             return;
333         }
334     } else if (targetMismatch) {
335         buildDirectoryAspect()->setProblem(tr("The build directory contains a build for "
336                                               "a different project, which will be overwritten."));
337         return;
338     } else if (incompatibleBuild) {
339         buildDirectoryAspect()->setProblem(tr("%1 The build will be overwritten.",
340                                               "%1 error message")
341                                            .arg(errorString));
342         return;
343     } else if (unalignedBuildDir) {
344         buildDirectoryAspect()->setProblem(unalignedBuildDirWarning());
345         return;
346     }
347 
348     buildDirectoryAspect()->setProblem({});
349 }
350 
buildSystem() const351 BuildSystem *QmakeBuildConfiguration::buildSystem() const
352 {
353     return m_buildSystem;
354 }
355 
356 /// If only a sub tree should be build this function returns which sub node
357 /// should be build
358 /// \see QMakeBuildConfiguration::setSubNodeBuild
subNodeBuild() const359 QmakeProFileNode *QmakeBuildConfiguration::subNodeBuild() const
360 {
361     return m_subNodeBuild;
362 }
363 
364 /// A sub node build on builds a sub node of the project
365 /// That is triggered by a right click in the project explorer tree
366 /// The sub node to be build is set via this function immediately before
367 /// calling BuildManager::buildProject( BuildConfiguration * )
368 /// and reset immediately afterwards
369 /// That is m_subNodesBuild is set only temporarly
setSubNodeBuild(QmakeProFileNode * node)370 void QmakeBuildConfiguration::setSubNodeBuild(QmakeProFileNode *node)
371 {
372     m_subNodeBuild = node;
373 }
374 
fileNodeBuild() const375 FileNode *QmakeBuildConfiguration::fileNodeBuild() const
376 {
377     return m_fileNodeBuild;
378 }
379 
setFileNodeBuild(FileNode * node)380 void QmakeBuildConfiguration::setFileNodeBuild(FileNode *node)
381 {
382     m_fileNodeBuild = node;
383 }
384 
makefile() const385 QString QmakeBuildConfiguration::makefile() const
386 {
387     return m_buildSystem->rootProFile()->singleVariableValue(Variable::Makefile);
388 }
389 
qmakeBuildConfiguration() const390 BaseQtVersion::QmakeBuildConfigs QmakeBuildConfiguration::qmakeBuildConfiguration() const
391 {
392     return m_qmakeBuildConfiguration;
393 }
394 
setQMakeBuildConfiguration(BaseQtVersion::QmakeBuildConfigs config)395 void QmakeBuildConfiguration::setQMakeBuildConfiguration(BaseQtVersion::QmakeBuildConfigs config)
396 {
397     if (m_qmakeBuildConfiguration == config)
398         return;
399     m_qmakeBuildConfiguration = config;
400 
401     emit qmakeBuildConfigurationChanged();
402     m_buildSystem->scheduleUpdateAllNowOrLater();
403     emit buildTypeChanged();
404 }
405 
unalignedBuildDirWarning()406 QString QmakeBuildConfiguration::unalignedBuildDirWarning()
407 {
408     return tr("The build directory should be at the same level as the source directory.");
409 }
410 
isBuildDirAtSafeLocation(const QString & sourceDir,const QString & buildDir)411 bool QmakeBuildConfiguration::isBuildDirAtSafeLocation(const QString &sourceDir,
412                                                        const QString &buildDir)
413 {
414     return buildDir.count('/') == sourceDir.count('/');
415 }
416 
isBuildDirAtSafeLocation() const417 bool QmakeBuildConfiguration::isBuildDirAtSafeLocation() const
418 {
419     return isBuildDirAtSafeLocation(project()->projectDirectory().toString(),
420                                     buildDirectory().toString());
421 }
422 
separateDebugInfo() const423 TriState QmakeBuildConfiguration::separateDebugInfo() const
424 {
425     return aspect<SeparateDebugInfoAspect>()->value();
426 }
427 
forceSeparateDebugInfo(bool sepDebugInfo)428 void QmakeBuildConfiguration::forceSeparateDebugInfo(bool sepDebugInfo)
429 {
430     aspect<SeparateDebugInfoAspect>()->setValue(sepDebugInfo
431                                                 ? TriState::Enabled
432                                                 : TriState::Disabled);
433 }
434 
qmlDebugging() const435 TriState QmakeBuildConfiguration::qmlDebugging() const
436 {
437     return aspect<QmlDebuggingAspect>()->value();
438 }
439 
forceQmlDebugging(bool enable)440 void QmakeBuildConfiguration::forceQmlDebugging(bool enable)
441 {
442     aspect<QmlDebuggingAspect>()->setValue(enable ? TriState::Enabled : TriState::Disabled);
443 }
444 
useQtQuickCompiler() const445 TriState QmakeBuildConfiguration::useQtQuickCompiler() const
446 {
447     return aspect<QtQuickCompilerAspect>()->value();
448 }
449 
forceQtQuickCompiler(bool enable)450 void QmakeBuildConfiguration::forceQtQuickCompiler(bool enable)
451 {
452     aspect<QtQuickCompilerAspect>()->setValue(enable ? TriState::Enabled : TriState::Disabled);
453 }
454 
runSystemFunction() const455 bool QmakeBuildConfiguration::runSystemFunction() const
456 {
457     const TriState runSystem = aspect<RunSystemAspect>()->value();
458     if (runSystem == TriState::Enabled)
459         return true;
460     if (runSystem == TriState::Disabled)
461         return false;
462     return QmakeSettings::runSystemFunction();
463 }
464 
configCommandLineArguments() const465 QStringList QmakeBuildConfiguration::configCommandLineArguments() const
466 {
467     QStringList result;
468     BaseQtVersion *version = QtKitAspect::qtVersion(kit());
469     BaseQtVersion::QmakeBuildConfigs defaultBuildConfiguration =
470             version ? version->defaultBuildConfig() : BaseQtVersion::QmakeBuildConfigs(BaseQtVersion::DebugBuild | BaseQtVersion::BuildAll);
471     BaseQtVersion::QmakeBuildConfigs userBuildConfiguration = m_qmakeBuildConfiguration;
472     if ((defaultBuildConfiguration & BaseQtVersion::BuildAll) && !(userBuildConfiguration & BaseQtVersion::BuildAll))
473         result << QLatin1String("CONFIG-=debug_and_release");
474 
475     if (!(defaultBuildConfiguration & BaseQtVersion::BuildAll) && (userBuildConfiguration & BaseQtVersion::BuildAll))
476         result << QLatin1String("CONFIG+=debug_and_release");
477     if ((defaultBuildConfiguration & BaseQtVersion::DebugBuild) && !(userBuildConfiguration & BaseQtVersion::DebugBuild))
478         result << QLatin1String("CONFIG+=release");
479     if (!(defaultBuildConfiguration & BaseQtVersion::DebugBuild) && (userBuildConfiguration & BaseQtVersion::DebugBuild))
480         result << QLatin1String("CONFIG+=debug");
481     return result;
482 }
483 
qmakeStep() const484 QMakeStep *QmakeBuildConfiguration::qmakeStep() const
485 {
486     QMakeStep *qs = nullptr;
487     BuildStepList *bsl = buildSteps();
488     for (int i = 0; i < bsl->count(); ++i)
489         if ((qs = qobject_cast<QMakeStep *>(bsl->at(i))) != nullptr)
490             return qs;
491     return nullptr;
492 }
493 
makeStep() const494 MakeStep *QmakeBuildConfiguration::makeStep() const
495 {
496     MakeStep *ms = nullptr;
497     BuildStepList *bsl = buildSteps();
498     for (int i = 0; i < bsl->count(); ++i)
499         if ((ms = qobject_cast<MakeStep *>(bsl->at(i))) != nullptr)
500             return ms;
501     return nullptr;
502 }
503 
qmakeBuildSystem() const504 QmakeBuildSystem *QmakeBuildConfiguration::qmakeBuildSystem() const
505 {
506     return m_buildSystem;
507 }
508 
509 // Returns true if both are equal.
compareToImportFrom(const QString & makefile,QString * errorString)510 QmakeBuildConfiguration::MakefileState QmakeBuildConfiguration::compareToImportFrom(const QString &makefile, QString *errorString)
511 {
512     const QLoggingCategory &logs = MakeFileParse::logging();
513     qCDebug(logs) << "QMakeBuildConfiguration::compareToImport";
514 
515     QMakeStep *qs = qmakeStep();
516     MakeFileParse parse(makefile, MakeFileParse::Mode::DoNotFilterKnownConfigValues);
517 
518     if (parse.makeFileState() == MakeFileParse::MakefileMissing) {
519         qCDebug(logs) << "**Makefile missing";
520         return MakefileMissing;
521     }
522     if (parse.makeFileState() == MakeFileParse::CouldNotParse) {
523         qCDebug(logs) << "**Makefile incompatible";
524         if (errorString)
525             *errorString = tr("Could not parse Makefile.");
526         return MakefileIncompatible;
527     }
528 
529     if (!qs) {
530         qCDebug(logs) << "**No qmake step";
531         return MakefileMissing;
532     }
533 
534     BaseQtVersion *version = QtKitAspect::qtVersion(kit());
535     if (!version) {
536         qCDebug(logs) << "**No qt version in kit";
537         return MakefileForWrongProject;
538     }
539 
540     const Utils::FilePath projectPath =
541             m_subNodeBuild ? m_subNodeBuild->filePath() : qs->project()->projectFilePath();
542     if (parse.srcProFile() != projectPath.toString()) {
543         qCDebug(logs) << "**Different profile used to generate the Makefile:"
544                       << parse.srcProFile() << " expected profile:" << projectPath;
545         if (errorString)
546             *errorString = tr("The Makefile is for a different project.");
547         return MakefileIncompatible;
548     }
549 
550     if (version->qmakeFilePath() != parse.qmakePath()) {
551         qCDebug(logs) << "**Different Qt versions, buildconfiguration:" << version->qmakeFilePath().toString()
552                       << " Makefile:"<< parse.qmakePath().toString();
553         return MakefileForWrongProject;
554     }
555 
556     // same qtversion
557     BaseQtVersion::QmakeBuildConfigs buildConfig = parse.effectiveBuildConfig(version->defaultBuildConfig());
558     if (qmakeBuildConfiguration() != buildConfig) {
559         qCDebug(logs) << "**Different qmake buildconfigurations buildconfiguration:"
560                       << qmakeBuildConfiguration() << " Makefile:" << buildConfig;
561         if (errorString)
562             *errorString = tr("The build type has changed.");
563         return MakefileIncompatible;
564     }
565 
566     // The qmake Build Configuration are the same,
567     // now compare arguments lists
568     // we have to compare without the spec/platform cmd argument
569     // and compare that on its own
570     QString workingDirectory = QFileInfo(makefile).absolutePath();
571     QStringList actualArgs;
572     QString allArgs = macroExpander()->expandProcessArgs(qs->allArguments(
573         QtKitAspect::qtVersion(target()->kit()), QMakeStep::ArgumentFlag::Expand));
574     // This copies the settings from allArgs to actualArgs (minus some we
575     // are not interested in), splitting them up into individual strings:
576     extractSpecFromArguments(&allArgs, workingDirectory, version, &actualArgs);
577     actualArgs.removeFirst(); // Project file.
578     const QString actualSpec = qs->mkspec();
579 
580     QString qmakeArgs = parse.unparsedArguments();
581     QStringList parsedArgs;
582     QString parsedSpec =
583             extractSpecFromArguments(&qmakeArgs, workingDirectory, version, &parsedArgs);
584 
585     qCDebug(logs) << "  Actual args:" << actualArgs;
586     qCDebug(logs) << "  Parsed args:" << parsedArgs;
587     qCDebug(logs) << "  Actual spec:" << actualSpec;
588     qCDebug(logs) << "  Parsed spec:" << parsedSpec;
589     qCDebug(logs) << "  Actual config:" << qs->deducedArguments();
590     qCDebug(logs) << "  Parsed config:" << parse.config();
591 
592     // Comparing the sorted list is obviously wrong
593     // Though haven written a more complete version
594     // that managed had around 200 lines and yet faild
595     // to be actually foolproof at all, I think it's
596     // not feasible without actually taking the qmake
597     // command line parsing code
598 
599     // Things, sorting gets wrong:
600     // parameters to positional parameters matter
601     //  e.g. -o -spec is different from -spec -o
602     //       -o 1 -spec 2 is diffrent from -spec 1 -o 2
603     // variable assignment order matters
604     // variable assignment vs -after
605     // -norecursive vs. recursive
606     actualArgs.sort();
607     parsedArgs.sort();
608     if (actualArgs != parsedArgs) {
609         qCDebug(logs) << "**Mismatched args";
610         if (errorString)
611             *errorString = tr("The qmake arguments have changed.");
612         return MakefileIncompatible;
613     }
614 
615     if (parse.config() != qs->deducedArguments()) {
616         qCDebug(logs) << "**Mismatched config";
617         if (errorString)
618             *errorString = tr("The qmake arguments have changed.");
619         return MakefileIncompatible;
620     }
621 
622     // Specs match exactly
623     if (actualSpec == parsedSpec) {
624         qCDebug(logs) << "**Matched specs (1)";
625         return MakefileMatches;
626     }
627     // Actual spec is the default one
628 //                    qDebug() << "AS vs VS" << actualSpec << version->mkspec();
629     if ((actualSpec == version->mkspec() || actualSpec == "default")
630             && (parsedSpec == version->mkspec() || parsedSpec == "default" || parsedSpec.isEmpty())) {
631         qCDebug(logs) << "**Matched specs (2)";
632         return MakefileMatches;
633     }
634 
635     qCDebug(logs) << "**Incompatible specs";
636     if (errorString)
637         *errorString = tr("The mkspec has changed.");
638     return MakefileIncompatible;
639 }
640 
extractSpecFromArguments(QString * args,const QString & directory,const BaseQtVersion * version,QStringList * outArgs)641 QString QmakeBuildConfiguration::extractSpecFromArguments(QString *args,
642                                                          const QString &directory, const BaseQtVersion *version,
643                                                          QStringList *outArgs)
644 {
645     FilePath parsedSpec;
646 
647     bool ignoreNext = false;
648     bool nextIsSpec = false;
649     for (ProcessArgs::ArgIterator ait(args); ait.next(); ) {
650         if (ignoreNext) {
651             ignoreNext = false;
652             ait.deleteArg();
653         } else if (nextIsSpec) {
654             nextIsSpec = false;
655             parsedSpec = FilePath::fromUserInput(ait.value());
656             ait.deleteArg();
657         } else if (ait.value() == QLatin1String("-spec") || ait.value() == QLatin1String("-platform")) {
658             nextIsSpec = true;
659             ait.deleteArg();
660         } else if (ait.value() == QLatin1String("-cache")) {
661             // We ignore -cache, because qmake contained a bug that it didn't
662             // mention the -cache in the Makefile.
663             // That means changing the -cache option in the additional arguments
664             // does not automatically rerun qmake. Alas, we could try more
665             // intelligent matching for -cache, but i guess people rarely
666             // do use that.
667             ignoreNext = true;
668             ait.deleteArg();
669         } else if (outArgs && ait.isSimple()) {
670             outArgs->append(ait.value());
671         }
672     }
673 
674     if (parsedSpec.isEmpty())
675         return {};
676 
677     FilePath baseMkspecDir = FilePath::fromUserInput(version->hostDataPath().toString()
678                                                      + "/mkspecs");
679     baseMkspecDir = FilePath::fromString(baseMkspecDir.toFileInfo().canonicalFilePath());
680 
681     // if the path is relative it can be
682     // relative to the working directory (as found in the Makefiles)
683     // or relatively to the mkspec directory
684     // if it is the former we need to get the canonical form
685     // for the other one we don't need to do anything
686     if (parsedSpec.toFileInfo().isRelative()) {
687         if (QFileInfo::exists(directory + QLatin1Char('/') + parsedSpec.toString()))
688             parsedSpec = FilePath::fromUserInput(directory + QLatin1Char('/') + parsedSpec.toString());
689         else
690             parsedSpec = FilePath::fromUserInput(baseMkspecDir.toString() + QLatin1Char('/') + parsedSpec.toString());
691     }
692 
693     QFileInfo f2 = parsedSpec.toFileInfo();
694     while (f2.isSymLink()) {
695         parsedSpec = FilePath::fromString(f2.symLinkTarget());
696         f2.setFile(parsedSpec.toString());
697     }
698 
699     if (parsedSpec.isChildOf(baseMkspecDir)) {
700         parsedSpec = parsedSpec.relativeChildPath(baseMkspecDir);
701     } else {
702         FilePath sourceMkSpecPath = FilePath::fromString(version->sourcePath().toString()
703                                                          + QLatin1String("/mkspecs"));
704         if (parsedSpec.isChildOf(sourceMkSpecPath))
705             parsedSpec = parsedSpec.relativeChildPath(sourceMkSpecPath);
706     }
707     return parsedSpec.toString();
708 }
709 
710 /*!
711   \class QmakeBuildConfigurationFactory
712 */
713 
createBuildInfo(const Kit * k,const FilePath & projectPath,BuildConfiguration::BuildType type)714 static BuildInfo createBuildInfo(const Kit *k, const FilePath &projectPath,
715                  BuildConfiguration::BuildType type)
716 {
717     const BuildPropertiesSettings &settings = ProjectExplorerPlugin::buildPropertiesSettings();
718     BaseQtVersion *version = QtKitAspect::qtVersion(k);
719     QmakeExtraBuildInfo extraInfo;
720     BuildInfo info;
721     QString suffix;
722 
723     if (type == BuildConfiguration::Release) {
724         //: The name of the release build configuration created by default for a qmake project.
725         info.displayName = BuildConfiguration::tr("Release");
726         //: Non-ASCII characters in directory suffix may cause build issues.
727         suffix = QmakeBuildConfiguration::tr("Release", "Shadow build directory suffix");
728         if (settings.qtQuickCompiler.value() == TriState::Default) {
729             if (version && version->isQtQuickCompilerSupported())
730                 extraInfo.config.useQtQuickCompiler = TriState::Enabled;
731         }
732     } else {
733         if (type == BuildConfiguration::Debug) {
734             //: The name of the debug build configuration created by default for a qmake project.
735             info.displayName = BuildConfiguration::tr("Debug");
736             //: Non-ASCII characters in directory suffix may cause build issues.
737             suffix = QmakeBuildConfiguration::tr("Debug", "Shadow build directory suffix");
738         } else if (type == BuildConfiguration::Profile) {
739             //: The name of the profile build configuration created by default for a qmake project.
740             info.displayName = BuildConfiguration::tr("Profile");
741             //: Non-ASCII characters in directory suffix may cause build issues.
742             suffix = QmakeBuildConfiguration::tr("Profile", "Shadow build directory suffix");
743             if (settings.separateDebugInfo.value() == TriState::Default)
744                 extraInfo.config.separateDebugInfo = TriState::Enabled;
745 
746             if (settings.qtQuickCompiler.value() == TriState::Default) {
747                 if (version && version->isQtQuickCompilerSupported())
748                     extraInfo.config.useQtQuickCompiler = TriState::Enabled;
749             }
750         }
751         if (settings.qmlDebugging.value() == TriState::Default) {
752             if (version && version->isQmlDebuggingSupported())
753                 extraInfo.config.linkQmlDebuggingQQ2 = TriState::Enabled;
754         }
755     }
756     info.typeName = info.displayName;
757     // Leave info.buildDirectory unset;
758 
759     // check if this project is in the source directory:
760     if (version && version->isInQtSourceDirectory(projectPath)) {
761         // assemble build directory
762         QString projectDirectory = projectPath.toFileInfo().absolutePath();
763         QDir qtSourceDir = QDir(version->sourcePath().toString());
764         QString relativeProjectPath = qtSourceDir.relativeFilePath(projectDirectory);
765         QString qtBuildDir = version->prefix().toString();
766         QString absoluteBuildPath = QDir::cleanPath(qtBuildDir + QLatin1Char('/') + relativeProjectPath);
767 
768         info.buildDirectory = FilePath::fromString(absoluteBuildPath);
769     } else {
770         info.buildDirectory =
771                 QmakeBuildConfiguration::shadowBuildDirectory(projectPath, k, suffix, type);
772     }
773     info.buildType = type;
774     info.extraInfo = QVariant::fromValue(extraInfo);
775     return info;
776 }
777 
QmakeBuildConfigurationFactory()778 QmakeBuildConfigurationFactory::QmakeBuildConfigurationFactory()
779 {
780     registerBuildConfiguration<QmakeBuildConfiguration>(Constants::QMAKE_BC_ID);
781     setSupportedProjectType(Constants::QMAKEPROJECT_ID);
782     setSupportedProjectMimeTypeName(Constants::PROFILE_MIMETYPE);
783     setIssueReporter([](Kit *k, const QString &projectPath, const QString &buildDir) {
784         QtSupport::BaseQtVersion *version = QtSupport::QtKitAspect::qtVersion(k);
785         Tasks issues;
786         if (version)
787             issues << version->reportIssues(projectPath, buildDir);
788         if (QmakeSettings::warnAgainstUnalignedBuildDir()
789                 && !QmakeBuildConfiguration::isBuildDirAtSafeLocation(
790                     QFileInfo(projectPath).absoluteDir().path(), QDir(buildDir).absolutePath())) {
791             issues.append(BuildSystemTask(Task::Warning,
792                                           QmakeBuildConfiguration::unalignedBuildDirWarning()));
793         }
794         return issues;
795     });
796 
797     setBuildGenerator([](const Kit *k, const FilePath &projectPath, bool forSetup) {
798         QList<BuildInfo> result;
799 
800         BaseQtVersion *qtVersion = QtKitAspect::qtVersion(k);
801 
802         if (forSetup && (!qtVersion || !qtVersion->isValid()))
803             return result;
804 
805         const auto addBuild = [&](BuildConfiguration::BuildType buildType) {
806             BuildInfo info = createBuildInfo(k, projectPath, buildType);
807             if (!forSetup) {
808                 info.displayName.clear(); // ask for a name
809                 info.buildDirectory.clear(); // This depends on the displayName
810             }
811             result << info;
812         };
813 
814         addBuild(BuildConfiguration::Debug);
815         addBuild(BuildConfiguration::Release);
816         if (qtVersion && qtVersion->qtVersion().majorVersion > 4)
817             addBuild(BuildConfiguration::Profile);
818 
819         return result;
820     });
821 }
822 
buildType() const823 BuildConfiguration::BuildType QmakeBuildConfiguration::buildType() const
824 {
825     if (qmakeBuildConfiguration() & BaseQtVersion::DebugBuild)
826         return Debug;
827     if (separateDebugInfo() == TriState::Enabled)
828         return Profile;
829     return Release;
830 }
831 
addToEnvironment(Environment & env) const832 void QmakeBuildConfiguration::addToEnvironment(Environment &env) const
833 {
834     QtSupport::QtKitAspect::addHostBinariesToPath(kit(), env);
835 }
836 
837 QmakeBuildConfiguration::LastKitState::LastKitState() = default;
838 
LastKitState(Kit * k)839 QmakeBuildConfiguration::LastKitState::LastKitState(Kit *k)
840     : m_qtVersion(QtKitAspect::qtVersionId(k)),
841       m_sysroot(SysRootKitAspect::sysRoot(k).toString()),
842       m_mkspec(QmakeKitAspect::mkspec(k))
843 {
844     ToolChain *tc = ToolChainKitAspect::cxxToolChain(k);
845     m_toolchain = tc ? tc->id() : QByteArray();
846 }
847 
operator ==(const LastKitState & other) const848 bool QmakeBuildConfiguration::LastKitState::operator ==(const LastKitState &other) const
849 {
850     return m_qtVersion == other.m_qtVersion
851             && m_toolchain == other.m_toolchain
852             && m_sysroot == other.m_sysroot
853             && m_mkspec == other.m_mkspec;
854 }
855 
operator !=(const LastKitState & other) const856 bool QmakeBuildConfiguration::LastKitState::operator !=(const LastKitState &other) const
857 {
858     return !operator ==(other);
859 }
860 
regenerateBuildFiles(Node * node)861 bool QmakeBuildConfiguration::regenerateBuildFiles(Node *node)
862 {
863     QMakeStep *qs = qmakeStep();
864     if (!qs)
865         return false;
866 
867     qs->setForced(true);
868 
869     BuildManager::buildList(cleanSteps());
870     BuildManager::appendStep(qs, BuildManager::displayNameForStepId(ProjectExplorer::Constants::BUILDSTEPS_CLEAN));
871 
872     QmakeProFileNode *proFile = nullptr;
873     if (node && node != project()->rootProjectNode())
874         proFile = dynamic_cast<QmakeProFileNode *>(node);
875 
876     setSubNodeBuild(proFile);
877 
878     return true;
879 }
880 
restrictNextBuild(const RunConfiguration * rc)881 void QmakeBuildConfiguration::restrictNextBuild(const RunConfiguration *rc)
882 {
883     if (!rc) {
884         setSubNodeBuild(nullptr);
885         return;
886     }
887     const auto productNode = dynamic_cast<QmakeProFileNode *>(rc->productNode());
888     QTC_ASSERT(productNode, return);
889     setSubNodeBuild(productNode);
890 }
891 
892 } // namespace QmakeProjectManager
893 
894 #include <qmakebuildconfiguration.moc>
895