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 "qmakestep.h"
27 
28 #include "qmakemakestep.h"
29 #include "qmakebuildconfiguration.h"
30 #include "qmakekitinformation.h"
31 #include "qmakenodes.h"
32 #include "qmakeparser.h"
33 #include "qmakeproject.h"
34 #include "qmakeprojectmanagerconstants.h"
35 #include "qmakesettings.h"
36 
37 #include <android/androidconstants.h>
38 
39 #include <projectexplorer/buildmanager.h>
40 #include <projectexplorer/buildsteplist.h>
41 #include <projectexplorer/gnumakeparser.h>
42 #include <projectexplorer/processparameters.h>
43 #include <projectexplorer/projectexplorer.h>
44 #include <projectexplorer/runconfigurationaspects.h>
45 #include <projectexplorer/target.h>
46 #include <projectexplorer/toolchain.h>
47 
48 #include <coreplugin/icore.h>
49 #include <coreplugin/icontext.h>
50 #include <qtsupport/qtkitinformation.h>
51 #include <qtsupport/qtversionmanager.h>
52 #include <qtsupport/qtsupportconstants.h>
53 
54 #include <ios/iosconstants.h>
55 
56 #include <utils/algorithm.h>
57 #include <utils/hostosinfo.h>
58 #include <utils/layoutbuilder.h>
59 #include <utils/qtcprocess.h>
60 #include <utils/utilsicons.h>
61 #include <utils/variablechooser.h>
62 
63 #include <QDir>
64 #include <QLabel>
65 #include <QListWidget>
66 #include <QMessageBox>
67 #include <QPlainTextEdit>
68 
69 using namespace QtSupport;
70 using namespace ProjectExplorer;
71 using namespace Utils;
72 
73 using namespace QmakeProjectManager::Internal;
74 
75 namespace QmakeProjectManager {
76 
77 const char QMAKE_ARGUMENTS_KEY[] = "QtProjectManager.QMakeBuildStep.QMakeArguments";
78 const char QMAKE_FORCED_KEY[] = "QtProjectManager.QMakeBuildStep.QMakeForced";
79 const char QMAKE_SELECTED_ABIS_KEY[] = "QtProjectManager.QMakeBuildStep.SelectedAbis";
80 
QMakeStep(BuildStepList * bsl,Id id)81 QMakeStep::QMakeStep(BuildStepList *bsl, Id id)
82     : AbstractProcessStep(bsl, id)
83 {
84     setLowPriority();
85 
86     m_buildType = addAspect<SelectionAspect>();
87     m_buildType->setDisplayStyle(SelectionAspect::DisplayStyle::ComboBox);
88     m_buildType->setDisplayName(tr("qmake build configuration:"));
89     m_buildType->addOption(tr("Debug"));
90     m_buildType->addOption(tr("Release"));
91 
92     m_userArgs = addAspect<ArgumentsAspect>();
93     m_userArgs->setSettingsKey(QMAKE_ARGUMENTS_KEY);
94     m_userArgs->setLabelText(tr("Additional arguments:"));
95 
96     m_effectiveCall = addAspect<StringAspect>();
97     m_effectiveCall->setDisplayStyle(StringAspect::TextEditDisplay);
98     m_effectiveCall->setLabelText(tr("Effective qmake call:"));
99     m_effectiveCall->setReadOnly(true);
100     m_effectiveCall->setUndoRedoEnabled(false);
101     m_effectiveCall->setEnabled(true);
102 
103     auto updateSummary = [this] {
104         BaseQtVersion *qtVersion = QtKitAspect::qtVersion(target()->kit());
105         if (!qtVersion)
106             return tr("<b>qmake:</b> No Qt version set. Cannot run qmake.");
107         const QString program = qtVersion->qmakeFilePath().fileName();
108         return tr("<b>qmake:</b> %1 %2").arg(program, project()->projectFilePath().fileName());
109     };
110     setSummaryUpdater(updateSummary);
111 
112     connect(target(), &Target::kitChanged, this, updateSummary);
113 }
114 
qmakeBuildConfiguration() const115 QmakeBuildConfiguration *QMakeStep::qmakeBuildConfiguration() const
116 {
117     return qobject_cast<QmakeBuildConfiguration *>(buildConfiguration());
118 }
119 
qmakeBuildSystem() const120 QmakeBuildSystem *QMakeStep::qmakeBuildSystem() const
121 {
122     return qmakeBuildConfiguration()->qmakeBuildSystem();
123 }
124 
125 ///
126 /// Returns all arguments
127 /// That is: possbile subpath
128 /// spec
129 /// config arguemnts
130 /// moreArguments
131 /// user arguments
allArguments(const BaseQtVersion * v,ArgumentFlags flags) const132 QString QMakeStep::allArguments(const BaseQtVersion *v, ArgumentFlags flags) const
133 {
134     QTC_ASSERT(v, return QString());
135     QmakeBuildConfiguration *bc = qmakeBuildConfiguration();
136     QStringList arguments;
137     if (bc->subNodeBuild())
138         arguments << bc->subNodeBuild()->filePath().toUserOutput();
139     else if (flags & ArgumentFlag::OmitProjectPath)
140         arguments << project()->projectFilePath().fileName();
141     else
142         arguments << project()->projectFilePath().toUserOutput();
143 
144     if (v->qtVersion() < QtVersionNumber(5, 0, 0))
145         arguments << "-r";
146     bool userProvidedMkspec = false;
147     for (ProcessArgs::ConstArgIterator ait(userArguments()); ait.next(); ) {
148         if (ait.value() == "-spec") {
149             if (ait.next()) {
150                 userProvidedMkspec = true;
151                 break;
152             }
153         }
154     }
155     const QString specArg = mkspec();
156     if (!userProvidedMkspec && !specArg.isEmpty())
157         arguments << "-spec" << QDir::toNativeSeparators(specArg);
158 
159     // Find out what flags we pass on to qmake
160     arguments << bc->configCommandLineArguments();
161 
162     arguments << deducedArguments().toArguments();
163 
164     QString args = ProcessArgs::joinArgs(arguments);
165     // User arguments
166     ProcessArgs::addArgs(&args, userArguments());
167     for (QString arg : qAsConst(m_extraArgs))
168         ProcessArgs::addArgs(&args, arg);
169     return (flags & ArgumentFlag::Expand) ? bc->macroExpander()->expand(args) : args;
170 }
171 
deducedArguments() const172 QMakeStepConfig QMakeStep::deducedArguments() const
173 {
174     Kit *kit = target()->kit();
175     QMakeStepConfig config;
176     Abi targetAbi;
177     if (ToolChain *tc = ToolChainKitAspect::cxxToolChain(kit)) {
178         targetAbi = tc->targetAbi();
179         if (HostOsInfo::isWindowsHost()
180             && tc->typeId() == ProjectExplorer::Constants::CLANG_TOOLCHAIN_TYPEID) {
181             config.sysRoot = SysRootKitAspect::sysRoot(kit).toString();
182             config.targetTriple = tc->originalTargetTriple();
183         }
184     }
185 
186     BaseQtVersion *version = QtKitAspect::qtVersion(kit);
187 
188     config.osType = QMakeStepConfig::osTypeFor(targetAbi, version);
189     config.separateDebugInfo = qmakeBuildConfiguration()->separateDebugInfo();
190     config.linkQmlDebuggingQQ2 = qmakeBuildConfiguration()->qmlDebugging();
191     config.useQtQuickCompiler = qmakeBuildConfiguration()->useQtQuickCompiler();
192 
193     return config;
194 }
195 
init()196 bool QMakeStep::init()
197 {
198     if (!AbstractProcessStep::init())
199         return false;
200 
201     m_wasSuccess = true;
202     QmakeBuildConfiguration *qmakeBc = qmakeBuildConfiguration();
203     const BaseQtVersion *qtVersion = QtKitAspect::qtVersion(kit());
204 
205     if (!qtVersion) {
206         emit addOutput(tr("No Qt version configured."), BuildStep::OutputFormat::ErrorMessage);
207         return false;
208     }
209 
210     FilePath workingDirectory;
211 
212     if (qmakeBc->subNodeBuild())
213         workingDirectory = qmakeBc->qmakeBuildSystem()->buildDir(qmakeBc->subNodeBuild()->filePath());
214     else
215         workingDirectory = qmakeBc->buildDirectory();
216 
217     m_qmakeCommand = CommandLine{qtVersion->qmakeFilePath(), allArguments(qtVersion), CommandLine::Raw};
218     m_runMakeQmake = (qtVersion->qtVersion() >= QtVersionNumber(5, 0 ,0));
219 
220     // The Makefile is used by qmake and make on the build device, from that
221     // perspective it is local.
222     QString makefile = workingDirectory.path() + '/';
223 
224     if (qmakeBc->subNodeBuild()) {
225         QmakeProFileNode *pro = qmakeBc->subNodeBuild();
226         if (pro && !pro->makefile().isEmpty())
227             makefile.append(pro->makefile());
228         else
229             makefile.append("Makefile");
230     } else if (!qmakeBc->makefile().isEmpty()) {
231         makefile.append(qmakeBc->makefile());
232     } else {
233         makefile.append("Makefile");
234     }
235 
236     if (m_runMakeQmake) {
237         const FilePath make = makeCommand();
238         if (make.isEmpty()) {
239             emit addOutput(tr("Could not determine which \"make\" command to run. "
240                               "Check the \"make\" step in the build configuration."),
241                            BuildStep::OutputFormat::ErrorMessage);
242             return false;
243         }
244         m_makeCommand = CommandLine{make, makeArguments(makefile), CommandLine::Raw};
245     } else {
246         m_makeCommand = {};
247     }
248 
249     // Check whether we need to run qmake
250     if (m_forced || QmakeSettings::alwaysRunQmake()
251             || qmakeBc->compareToImportFrom(makefile) != QmakeBuildConfiguration::MakefileMatches) {
252         m_needToRunQMake = true;
253     }
254     m_forced = false;
255 
256     processParameters()->setWorkingDirectory(workingDirectory);
257 
258     QmakeProFileNode *node = static_cast<QmakeProFileNode *>(qmakeBc->project()->rootProjectNode());
259     if (qmakeBc->subNodeBuild())
260         node = qmakeBc->subNodeBuild();
261     QTC_ASSERT(node, return false);
262     QString proFile = node->filePath().toString();
263 
264     Tasks tasks = qtVersion->reportIssues(proFile, workingDirectory.toString());
265     Utils::sort(tasks);
266 
267     if (!tasks.isEmpty()) {
268         bool canContinue = true;
269         for (const Task &t : qAsConst(tasks)) {
270             emit addTask(t);
271             if (t.type == Task::Error)
272                 canContinue = false;
273         }
274         if (!canContinue) {
275             emitFaultyConfigurationMessage();
276             return false;
277         }
278     }
279 
280     m_scriptTemplate = node->projectType() == ProjectType::ScriptTemplate;
281 
282     return true;
283 }
284 
setupOutputFormatter(OutputFormatter * formatter)285 void QMakeStep::setupOutputFormatter(OutputFormatter *formatter)
286 {
287     formatter->addLineParser(new QMakeParser);
288     m_outputFormatter = formatter;
289     AbstractProcessStep::setupOutputFormatter(formatter);
290 }
291 
doRun()292 void QMakeStep::doRun()
293 {
294     if (m_scriptTemplate) {
295         emit finished(true);
296         return;
297     }
298 
299     if (!m_needToRunQMake) {
300         emit addOutput(tr("Configuration unchanged, skipping qmake step."), BuildStep::OutputFormat::NormalMessage);
301         emit finished(true);
302         return;
303     }
304 
305     m_needToRunQMake = false;
306 
307     m_nextState = State::RUN_QMAKE;
308     runNextCommand();
309 }
310 
setForced(bool b)311 void QMakeStep::setForced(bool b)
312 {
313     m_forced = b;
314 }
315 
processStartupFailed()316 void QMakeStep::processStartupFailed()
317 {
318     m_needToRunQMake = true;
319     AbstractProcessStep::processStartupFailed();
320 }
321 
processSucceeded(int exitCode,QProcess::ExitStatus status)322 bool QMakeStep::processSucceeded(int exitCode, QProcess::ExitStatus status)
323 {
324     bool result = AbstractProcessStep::processSucceeded(exitCode, status);
325     if (!result)
326         m_needToRunQMake = true;
327     emit buildConfiguration()->buildDirectoryChanged();
328     return result;
329 }
330 
doCancel()331 void QMakeStep::doCancel()
332 {
333     AbstractProcessStep::doCancel();
334 }
335 
finish(bool success)336 void QMakeStep::finish(bool success)
337 {
338     m_wasSuccess = success;
339     runNextCommand();
340 }
341 
startOneCommand(const CommandLine & command)342 void QMakeStep::startOneCommand(const CommandLine &command)
343 {
344     ProcessParameters *pp = processParameters();
345     pp->setCommandLine(command);
346 
347     AbstractProcessStep::doRun();
348 }
349 
runNextCommand()350 void QMakeStep::runNextCommand()
351 {
352     if (isCanceled())
353         m_wasSuccess = false;
354 
355     if (!m_wasSuccess)
356         m_nextState = State::POST_PROCESS;
357 
358     emit progress(static_cast<int>(m_nextState) * 100 / static_cast<int>(State::POST_PROCESS),
359                   QString());
360 
361     switch (m_nextState) {
362     case State::IDLE:
363         return;
364     case State::RUN_QMAKE:
365         m_outputFormatter->setLineParsers({new QMakeParser});
366         m_nextState = (m_runMakeQmake ? State::RUN_MAKE_QMAKE_ALL : State::POST_PROCESS);
367         startOneCommand(m_qmakeCommand);
368         return;
369     case State::RUN_MAKE_QMAKE_ALL:
370         {
371             auto *parser = new GnuMakeParser;
372             parser->addSearchDir(processParameters()->workingDirectory());
373             m_outputFormatter->setLineParsers({parser});
374             m_nextState = State::POST_PROCESS;
375             startOneCommand(m_makeCommand);
376         }
377         return;
378     case State::POST_PROCESS:
379         m_nextState = State::IDLE;
380         emit finished(m_wasSuccess);
381         return;
382     }
383 }
384 
setUserArguments(const QString & arguments)385 void QMakeStep::setUserArguments(const QString &arguments)
386 {
387     m_userArgs->setArguments(arguments);
388 }
389 
extraArguments() const390 QStringList QMakeStep::extraArguments() const
391 {
392     return m_extraArgs;
393 }
394 
setExtraArguments(const QStringList & args)395 void QMakeStep::setExtraArguments(const QStringList &args)
396 {
397     if (m_extraArgs != args) {
398         m_extraArgs = args;
399         emit qmakeBuildConfiguration()->qmakeBuildConfigurationChanged();
400         qmakeBuildSystem()->scheduleUpdateAllNowOrLater();
401     }
402 }
403 
extraParserArguments() const404 QStringList QMakeStep::extraParserArguments() const
405 {
406     return m_extraParserArgs;
407 }
408 
setExtraParserArguments(const QStringList & args)409 void QMakeStep::setExtraParserArguments(const QStringList &args)
410 {
411     m_extraParserArgs = args;
412 }
413 
makeCommand() const414 FilePath QMakeStep::makeCommand() const
415 {
416     if (auto ms = stepList()->firstOfType<MakeStep>())
417         return ms->makeExecutable();
418     return FilePath();
419 }
420 
makeArguments(const QString & makefile) const421 QString QMakeStep::makeArguments(const QString &makefile) const
422 {
423     QString args;
424     if (!makefile.isEmpty()) {
425         ProcessArgs::addArg(&args, "-f");
426         ProcessArgs::addArg(&args, makefile);
427     }
428     ProcessArgs::addArg(&args, "qmake_all");
429     return args;
430 }
431 
effectiveQMakeCall() const432 QString QMakeStep::effectiveQMakeCall() const
433 {
434     BaseQtVersion *qtVersion = QtKitAspect::qtVersion(kit());
435     QString qmake = qtVersion ? qtVersion->qmakeFilePath().toUserOutput() : QString();
436     if (qmake.isEmpty())
437         qmake = tr("<no Qt version>");
438     QString make = makeCommand().toUserOutput();
439     if (make.isEmpty())
440         make = tr("<no Make step found>");
441 
442     QString result = qmake;
443     if (qtVersion) {
444         QmakeBuildConfiguration *qmakeBc = qmakeBuildConfiguration();
445         const QString makefile = qmakeBc ? qmakeBc->makefile() : QString();
446         result += ' ' + allArguments(qtVersion, ArgumentFlag::Expand);
447         if (qtVersion->qtVersion() >= QtVersionNumber(5, 0, 0))
448             result.append(QString::fromLatin1(" && %1 %2").arg(make).arg(makeArguments(makefile)));
449     }
450     return result;
451 }
452 
parserArguments()453 QStringList QMakeStep::parserArguments()
454 {
455     // NOTE: extra parser args placed before the other args intentionally
456     QStringList result = m_extraParserArgs;
457     BaseQtVersion *qt = QtKitAspect::qtVersion(kit());
458     QTC_ASSERT(qt, return QStringList());
459     for (ProcessArgs::ConstArgIterator ait(allArguments(qt, ArgumentFlag::Expand)); ait.next(); ) {
460         if (ait.isSimple())
461             result << ait.value();
462     }
463     return result;
464 }
465 
userArguments() const466 QString QMakeStep::userArguments() const
467 {
468     return m_userArgs->arguments(macroExpander());
469 }
470 
mkspec() const471 QString QMakeStep::mkspec() const
472 {
473     QString additionalArguments = userArguments();
474     ProcessArgs::addArgs(&additionalArguments, m_extraArgs);
475     for (ProcessArgs::ArgIterator ait(&additionalArguments); ait.next(); ) {
476         if (ait.value() == "-spec") {
477             if (ait.next())
478                 return FilePath::fromUserInput(ait.value()).toString();
479         }
480     }
481 
482     return QmakeKitAspect::effectiveMkspec(target()->kit());
483 }
484 
toMap() const485 QVariantMap QMakeStep::toMap() const
486 {
487     QVariantMap map(AbstractProcessStep::toMap());
488     map.insert(QMAKE_FORCED_KEY, m_forced);
489     map.insert(QMAKE_SELECTED_ABIS_KEY, m_selectedAbis);
490     return map;
491 }
492 
fromMap(const QVariantMap & map)493 bool QMakeStep::fromMap(const QVariantMap &map)
494 {
495     m_forced = map.value(QMAKE_FORCED_KEY, false).toBool();
496     m_selectedAbis = map.value(QMAKE_SELECTED_ABIS_KEY).toStringList();
497 
498     // Backwards compatibility with < Creator 4.12.
499     const QVariant separateDebugInfo
500             = map.value("QtProjectManager.QMakeBuildStep.SeparateDebugInfo");
501     if (separateDebugInfo.isValid())
502         qmakeBuildConfiguration()->forceSeparateDebugInfo(separateDebugInfo.toBool());
503     const QVariant qmlDebugging
504             = map.value("QtProjectManager.QMakeBuildStep.LinkQmlDebuggingLibrary");
505     if (qmlDebugging.isValid())
506         qmakeBuildConfiguration()->forceQmlDebugging(qmlDebugging.toBool());
507     const QVariant useQtQuickCompiler
508             = map.value("QtProjectManager.QMakeBuildStep.UseQtQuickCompiler");
509     if (useQtQuickCompiler.isValid())
510         qmakeBuildConfiguration()->forceQtQuickCompiler(useQtQuickCompiler.toBool());
511 
512     return BuildStep::fromMap(map);
513 }
514 
createConfigWidget()515 QWidget *QMakeStep::createConfigWidget()
516 {
517     abisLabel = new QLabel(tr("ABIs:"));
518     abisLabel->setAlignment(Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop);
519 
520     abisListWidget = new QListWidget;
521 
522     Layouting::Form builder;
523     builder.addRow(m_buildType);
524     builder.addRow(m_userArgs);
525     builder.addRow(m_effectiveCall);
526     builder.addRow({abisLabel, abisListWidget});
527     auto widget = builder.emerge(false);
528 
529     qmakeBuildConfigChanged();
530 
531     updateSummary();
532     updateAbiWidgets();
533     updateEffectiveQMakeCall();
534 
535     connect(m_userArgs, &BaseAspect::changed, widget, [this] {
536         updateAbiWidgets();
537         updateEffectiveQMakeCall();
538 
539         emit qmakeBuildConfiguration()->qmakeBuildConfigurationChanged();
540         qmakeBuildSystem()->scheduleUpdateAllNowOrLater();
541     });
542 
543     connect(m_buildType, &BaseAspect::changed,
544             widget, [this] { buildConfigurationSelected(); });
545 
546     connect(qmakeBuildConfiguration(), &QmakeBuildConfiguration::qmlDebuggingChanged,
547             widget, [this] {
548         linkQmlDebuggingLibraryChanged();
549         askForRebuild(tr("QML Debugging"));
550     });
551 
552     connect(project(), &Project::projectLanguagesUpdated,
553             widget, [this] { linkQmlDebuggingLibraryChanged(); });
554     connect(target(), &Target::parsingFinished,
555             widget, [this] { updateEffectiveQMakeCall(); });
556     connect(qmakeBuildConfiguration(), &QmakeBuildConfiguration::useQtQuickCompilerChanged,
557             widget, [this] { useQtQuickCompilerChanged(); });
558     connect(qmakeBuildConfiguration(), &QmakeBuildConfiguration::separateDebugInfoChanged,
559             widget, [this] { separateDebugInfoChanged(); });
560     connect(qmakeBuildConfiguration(), &QmakeBuildConfiguration::qmakeBuildConfigurationChanged,
561             widget, [this] { qmakeBuildConfigChanged(); });
562     connect(target(), &Target::kitChanged,
563             widget, [this] { qtVersionChanged(); });
564 
565     connect(abisListWidget, &QListWidget::itemChanged, this, [this] {
566         abisChanged();
567         if (QmakeBuildConfiguration *bc = qmakeBuildConfiguration())
568             BuildManager::buildLists({bc->cleanSteps()});
569     });
570 
571     VariableChooser::addSupportForChildWidgets(widget, macroExpander());
572 
573     return widget;
574 }
575 
qtVersionChanged()576 void QMakeStep::qtVersionChanged()
577 {
578     updateAbiWidgets();
579     updateEffectiveQMakeCall();
580 }
581 
qmakeBuildConfigChanged()582 void QMakeStep::qmakeBuildConfigChanged()
583 {
584     QmakeBuildConfiguration *bc = qmakeBuildConfiguration();
585     bool debug = bc->qmakeBuildConfiguration() & BaseQtVersion::DebugBuild;
586     m_ignoreChange = true;
587     m_buildType->setValue(debug? 0 : 1);
588     m_ignoreChange = false;
589     updateAbiWidgets();
590     updateEffectiveQMakeCall();
591 }
592 
linkQmlDebuggingLibraryChanged()593 void QMakeStep::linkQmlDebuggingLibraryChanged()
594 {
595     updateAbiWidgets();
596     updateEffectiveQMakeCall();
597 }
598 
useQtQuickCompilerChanged()599 void QMakeStep::useQtQuickCompilerChanged()
600 {
601     updateAbiWidgets();
602     updateEffectiveQMakeCall();
603     askForRebuild(tr("Qt Quick Compiler"));
604 }
605 
separateDebugInfoChanged()606 void QMakeStep::separateDebugInfoChanged()
607 {
608     updateAbiWidgets();
609     updateEffectiveQMakeCall();
610     askForRebuild(tr("Separate Debug Information"));
611 }
612 
isIos(const Kit * k)613 static bool isIos(const Kit *k)
614 {
615     const Id deviceType = DeviceTypeKitAspect::deviceTypeId(k);
616     return deviceType == Ios::Constants::IOS_DEVICE_TYPE
617            || deviceType == Ios::Constants::IOS_SIMULATOR_TYPE;
618 }
619 
abisChanged()620 void QMakeStep::abisChanged()
621 {
622     m_selectedAbis.clear();
623     for (int i = 0; i < abisListWidget->count(); ++i) {
624         auto item = abisListWidget->item(i);
625         if (item->checkState() == Qt::CheckState::Checked)
626             m_selectedAbis << item->text();
627     }
628 
629     if (BaseQtVersion *qtVersion = QtKitAspect::qtVersion(target()->kit())) {
630         if (qtVersion->hasAbi(Abi::LinuxOS, Abi::AndroidLinuxFlavor)) {
631             const QString prefix = "ANDROID_ABIS=";
632             QStringList args = m_extraArgs;
633             for (auto it = args.begin(); it != args.end(); ++it) {
634                 if (it->startsWith(prefix)) {
635                     args.erase(it);
636                     break;
637                 }
638             }
639             if (!m_selectedAbis.isEmpty())
640                 args << prefix + '"' + m_selectedAbis.join(' ') + '"';
641             setExtraArguments(args);
642 
643             buildSystem()->setProperty(Android::Constants::ANDROID_ABIS, m_selectedAbis);
644         } else if (qtVersion->hasAbi(Abi::DarwinOS) && !isIos(target()->kit())) {
645             const QString prefix = "QMAKE_APPLE_DEVICE_ARCHS=";
646             QStringList args = m_extraArgs;
647             for (auto it = args.begin(); it != args.end(); ++it) {
648                 if (it->startsWith(prefix)) {
649                     args.erase(it);
650                     break;
651                 }
652             }
653             QStringList archs;
654             for (const QString &selectedAbi : qAsConst(m_selectedAbis)) {
655                 const auto abi = Abi::abiFromTargetTriplet(selectedAbi);
656                 if (abi.architecture() == Abi::X86Architecture)
657                     archs << "x86_64";
658                 else if (abi.architecture() == Abi::ArmArchitecture)
659                     archs << "arm64";
660             }
661             if (!archs.isEmpty())
662                 args << prefix + '"' + archs.join(' ') + '"';
663             setExtraArguments(args);
664         }
665     }
666 
667     updateAbiWidgets();
668     updateEffectiveQMakeCall();
669 }
670 
buildConfigurationSelected()671 void QMakeStep::buildConfigurationSelected()
672 {
673     if (m_ignoreChange)
674         return;
675     QmakeBuildConfiguration *bc = qmakeBuildConfiguration();
676     BaseQtVersion::QmakeBuildConfigs buildConfiguration = bc->qmakeBuildConfiguration();
677     if (m_buildType->value() == 0) { // debug
678         buildConfiguration = buildConfiguration | BaseQtVersion::DebugBuild;
679     } else {
680         buildConfiguration = buildConfiguration & ~BaseQtVersion::DebugBuild;
681     }
682     m_ignoreChange = true;
683     bc->setQMakeBuildConfiguration(buildConfiguration);
684     m_ignoreChange = false;
685 
686     updateAbiWidgets();
687     updateEffectiveQMakeCall();
688 }
689 
askForRebuild(const QString & title)690 void QMakeStep::askForRebuild(const QString &title)
691 {
692     auto *question = new QMessageBox(Core::ICore::dialogParent());
693     question->setWindowTitle(title);
694     question->setText(tr("The option will only take effect if the project is recompiled. Do you want to recompile now?"));
695     question->setStandardButtons(QMessageBox::Yes | QMessageBox::No);
696     question->setModal(true);
697     connect(question, &QDialog::finished, this, &QMakeStep::recompileMessageBoxFinished);
698     question->show();
699 }
700 
updateAbiWidgets()701 void QMakeStep::updateAbiWidgets()
702 {
703     if (!abisLabel)
704         return;
705 
706     BaseQtVersion *qtVersion = QtKitAspect::qtVersion(target()->kit());
707     if (!qtVersion)
708         return;
709 
710     const Abis abis = qtVersion->qtAbis();
711     const bool enableAbisSelect = abis.size() > 1;
712     abisLabel->setVisible(enableAbisSelect);
713     abisListWidget->setVisible(enableAbisSelect);
714 
715     if (enableAbisSelect && abisListWidget->count() != abis.size()) {
716         abisListWidget->clear();
717         QStringList selectedAbis = m_selectedAbis;
718 
719         if (selectedAbis.isEmpty()) {
720             if (qtVersion->hasAbi(Abi::LinuxOS, Abi::AndroidLinuxFlavor)) {
721                 // Prefer ARM for Android, prefer 32bit.
722                 for (const Abi &abi : abis) {
723                     if (abi.param() == ProjectExplorer::Constants::ANDROID_ABI_ARMEABI_V7A)
724                         selectedAbis.append(abi.param());
725                 }
726                 if (selectedAbis.isEmpty()) {
727                     for (const Abi &abi : abis) {
728                         if (abi.param() == ProjectExplorer::Constants::ANDROID_ABI_ARM64_V8A)
729                             selectedAbis.append(abi.param());
730                     }
731                 }
732             } else if (qtVersion->hasAbi(Abi::DarwinOS) && !isIos(target()->kit()) && HostOsInfo::isRunningUnderRosetta()) {
733                 // Automatically select arm64 when running under Rosetta
734                 for (const Abi &abi : abis) {
735                     if (abi.architecture() == Abi::ArmArchitecture)
736                         selectedAbis.append(abi.param());
737                 }
738             }
739         }
740 
741         for (const Abi &abi : abis) {
742             const QString param = abi.param();
743             auto item = new QListWidgetItem{param, abisListWidget};
744             item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
745             item->setCheckState(selectedAbis.contains(param) ? Qt::Checked : Qt::Unchecked);
746         }
747         abisChanged();
748     }
749 }
750 
updateEffectiveQMakeCall()751 void QMakeStep::updateEffectiveQMakeCall()
752 {
753     m_effectiveCall->setValue(effectiveQMakeCall());
754 }
755 
recompileMessageBoxFinished(int button)756 void QMakeStep::recompileMessageBoxFinished(int button)
757 {
758     if (button == QMessageBox::Yes) {
759         if (BuildConfiguration *bc = buildConfiguration())
760             BuildManager::buildLists({bc->cleanSteps(), bc->buildSteps()});
761     }
762 }
763 
764 ////
765 // QMakeStepFactory
766 ////
767 
QMakeStepFactory()768 QMakeStepFactory::QMakeStepFactory()
769 {
770     registerStep<QMakeStep>(Constants::QMAKE_BS_ID);
771     setSupportedConfiguration(Constants::QMAKE_BC_ID);
772     setSupportedStepList(ProjectExplorer::Constants::BUILDSTEPS_BUILD);
773     //: QMakeStep default display name
774     setDisplayName(::QmakeProjectManager::QMakeStep::tr("qmake"));
775     setFlags(BuildStepInfo::UniqueStep);
776 }
777 
targetArchFor(const Abi &,const BaseQtVersion *)778 QMakeStepConfig::TargetArchConfig QMakeStepConfig::targetArchFor(const Abi &, const BaseQtVersion *)
779 {
780     return NoArch;
781 }
782 
osTypeFor(const Abi & targetAbi,const BaseQtVersion * version)783 QMakeStepConfig::OsType QMakeStepConfig::osTypeFor(const Abi &targetAbi, const BaseQtVersion *version)
784 {
785     OsType os = NoOsType;
786     const char IOSQT[] = "Qt4ProjectManager.QtVersion.Ios";
787     if (!version || version->type() != IOSQT)
788         return os;
789     if (targetAbi.os() == Abi::DarwinOS && targetAbi.binaryFormat() == Abi::MachOFormat) {
790         if (targetAbi.architecture() == Abi::X86Architecture)
791             os = IphoneSimulator;
792         else if (targetAbi.architecture() == Abi::ArmArchitecture)
793             os = IphoneOS;
794     }
795     return os;
796 }
797 
toArguments() const798 QStringList QMakeStepConfig::toArguments() const
799 {
800     QStringList arguments;
801 
802     // TODO: make that depend on the actual Qt version that is used
803     if (osType == IphoneSimulator)
804         arguments << "CONFIG+=iphonesimulator" << "CONFIG+=simulator" /*since Qt 5.7*/;
805     else if (osType == IphoneOS)
806         arguments << "CONFIG+=iphoneos" << "CONFIG+=device" /*since Qt 5.7*/;
807 
808     if (linkQmlDebuggingQQ2 == TriState::Enabled)
809         arguments << "CONFIG+=qml_debug";
810     else if (linkQmlDebuggingQQ2 == TriState::Disabled)
811         arguments << "CONFIG-=qml_debug";
812 
813     if (useQtQuickCompiler == TriState::Enabled)
814         arguments << "CONFIG+=qtquickcompiler";
815     else if (useQtQuickCompiler == TriState::Disabled)
816         arguments << "CONFIG-=qtquickcompiler";
817 
818     if (separateDebugInfo == TriState::Enabled)
819         arguments << "CONFIG+=force_debug_info" << "CONFIG+=separate_debug_info";
820     else if (separateDebugInfo == TriState::Disabled)
821         arguments << "CONFIG-=separate_debug_info";
822 
823     if (!sysRoot.isEmpty()) {
824         arguments << ("QMAKE_CFLAGS+=--sysroot=\"" + sysRoot + "\"");
825         arguments << ("QMAKE_CXXFLAGS+=--sysroot=\"" + sysRoot + "\"");
826         arguments << ("QMAKE_LFLAGS+=--sysroot=\"" + sysRoot + "\"");
827         if (!targetTriple.isEmpty()) {
828             arguments << ("QMAKE_CFLAGS+=--target=" + targetTriple);
829             arguments << ("QMAKE_CXXFLAGS+=--target=" + targetTriple);
830             arguments << ("QMAKE_LFLAGS+=--target=" + targetTriple);
831         }
832     }
833 
834     return arguments;
835 }
836 
837 } // QmakeProjectManager
838