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