OSDN Git Service

308fd1476b56cb7e32a838d74aa52fb0374498ce
[qt-creator-jp/qt-creator-jp.git] / src / plugins / qt4projectmanager / qt-s60 / s60createpackagestep.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
8 **
9 **
10 ** GNU Lesser General Public License Usage
11 **
12 ** This file may be used under the terms of the GNU Lesser General Public
13 ** License version 2.1 as published by the Free Software Foundation and
14 ** appearing in the file LICENSE.LGPL included in the packaging of this file.
15 ** Please review the following information to ensure the GNU Lesser General
16 ** Public License version 2.1 requirements will be met:
17 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 **
19 ** In addition, as a special exception, Nokia gives you certain additional
20 ** rights. These rights are described in the Nokia Qt LGPL Exception
21 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 **
23 ** Other Usage
24 **
25 ** Alternatively, this file may be used in accordance with the terms and
26 ** conditions contained in a signed written agreement between you and Nokia.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at info@qt.nokia.com.
30 **
31 **************************************************************************/
32
33 #include "s60createpackagestep.h"
34
35 #include "qt4projectmanagerconstants.h"
36 #include "qt4buildconfiguration.h"
37 #include "qt4nodes.h"
38 #include "qt4project.h"
39 #include "s60createpackageparser.h"
40 #include "abldparser.h"
41 #include "sbsv2parser.h"
42 #include "passphraseforkeydialog.h"
43 #include "s60certificateinfo.h"
44 #include "s60certificatedetailsdialog.h"
45 #include "symbianqtversion.h"
46
47 #include <app/app_version.h>
48
49 #include <utils/checkablemessagebox.h>
50 #include <utils/fileutils.h>
51
52 #include <projectexplorer/buildconfiguration.h>
53 #include <projectexplorer/buildsteplist.h>
54 #include <projectexplorer/projectexplorerconstants.h>
55 #include <projectexplorer/target.h>
56 #include <projectexplorer/project.h>
57 #include <projectexplorer/gnumakeparser.h>
58 #include <projectexplorer/task.h>
59
60 #include <QtCore/QDir>
61 #include <QtCore/QTimer>
62 #include <QtCore/QCryptographicHash>
63
64 #include <QtCore/QSettings>
65 #include <QtGui/QMessageBox>
66
67 using namespace Qt4ProjectManager;
68 using namespace Qt4ProjectManager::Internal;
69
70 namespace {
71     const char * const SIGN_BS_ID = "Qt4ProjectManager.S60SignBuildStep";
72     const char * const SIGNMODE_KEY("Qt4ProjectManager.S60CreatePackageStep.SignMode");
73     const char * const CERTIFICATE_KEY("Qt4ProjectManager.S60CreatePackageStep.Certificate");
74     const char * const KEYFILE_KEY("Qt4ProjectManager.S60CreatePackageStep.Keyfile");
75     const char * const SMART_INSTALLER_KEY("Qt4ProjectManager.S60CreatorPackageStep.SmartInstaller");
76     const char * const PATCH_WARNING_SHOWN_KEY("Qt4ProjectManager.S60CreatorPackageStep.PatchWarningShown");
77     const char * const SUPPRESS_PATCH_WARNING_DIALOG_KEY("Qt4ProjectManager.S60CreatorPackageStep.SuppressPatchWarningDialog");
78
79     const char * const MAKE_PASSPHRASE_ARGUMENT("QT_SIS_PASSPHRASE=");
80     const char * const MAKE_KEY_ARGUMENT("QT_SIS_KEY=");
81     const char * const MAKE_CERTIFICATE_ARGUMENT("QT_SIS_CERTIFICATE=");
82 }
83
84 S60CreatePackageStep::S60CreatePackageStep(ProjectExplorer::BuildStepList *bsl) :
85     BuildStep(bsl, QLatin1String(SIGN_BS_ID)),
86     m_signingMode(SignSelf),
87     m_createSmartInstaller(false),
88     m_outputParserChain(0),
89     m_process(0),
90     m_timer(0),
91     m_eventLoop(0),
92     m_futureInterface(0),
93     m_passphrases(0),
94     m_parser(0),
95     m_suppressPatchWarningDialog(false),
96     m_patchWarningDialog(0)
97 {
98     ctor_package();
99 }
100
101 S60CreatePackageStep::S60CreatePackageStep(ProjectExplorer::BuildStepList *bsl, S60CreatePackageStep *bs) :
102     BuildStep(bsl, bs),
103     m_signingMode(bs->m_signingMode),
104     m_customSignaturePath(bs->m_customSignaturePath),
105     m_customKeyPath(bs->m_customKeyPath),
106     m_passphrase(bs->m_passphrase),
107     m_createSmartInstaller(bs->m_createSmartInstaller),
108     m_outputParserChain(0),
109     m_timer(0),
110     m_eventLoop(0),
111     m_futureInterface(0),
112     m_passphrases(0),
113     m_parser(0),
114     m_suppressPatchWarningDialog(false),
115     m_patchWarningDialog(0)
116 {
117     ctor_package();
118 }
119
120 S60CreatePackageStep::S60CreatePackageStep(ProjectExplorer::BuildStepList *bsl, const QString &id) :
121     BuildStep(bsl, id),
122     m_signingMode(SignSelf),
123     m_createSmartInstaller(false),
124     m_outputParserChain(0),
125     m_timer(0),
126     m_eventLoop(0),
127     m_futureInterface(0),
128     m_passphrases(0),
129     m_parser(0),
130     m_suppressPatchWarningDialog(false),
131     m_patchWarningDialog(0)
132 {
133     ctor_package();
134 }
135
136 void S60CreatePackageStep::ctor_package()
137 {
138     //: default create SIS package build step display name
139     setDefaultDisplayName(tr("Create SIS Package"));
140     connect(this, SIGNAL(badPassphrase()),
141             this, SLOT(definePassphrase()), Qt::QueuedConnection);
142     connect(this, SIGNAL(warnAboutPatching()),
143             this, SLOT(handleWarnAboutPatching()), Qt::QueuedConnection);
144
145     m_passphrases = new QSettings(QSettings::IniFormat, QSettings::UserScope,
146                                  QLatin1String("Nokia"), QLatin1String("QtCreatorKeys"), this);
147 }
148
149 S60CreatePackageStep::~S60CreatePackageStep()
150 {
151     delete m_patchWarningDialog;
152 }
153
154 QVariantMap S60CreatePackageStep::toMap() const
155 {
156     QVariantMap map(BuildStep::toMap());
157     map.insert(QLatin1String(SIGNMODE_KEY), static_cast<int>(m_signingMode));
158     map.insert(QLatin1String(CERTIFICATE_KEY), m_customSignaturePath);
159     map.insert(QLatin1String(KEYFILE_KEY), m_customKeyPath);
160     map.insert(QLatin1String(SMART_INSTALLER_KEY), m_createSmartInstaller);
161     map.insert(QLatin1String(SUPPRESS_PATCH_WARNING_DIALOG_KEY), m_suppressPatchWarningDialog);
162     return map;
163 }
164
165 bool S60CreatePackageStep::fromMap(const QVariantMap &map)
166 {
167     m_signingMode = static_cast<SigningMode>(map.value(QLatin1String(SIGNMODE_KEY), static_cast<int>(SignSelf)).toInt());
168     m_customSignaturePath = map.value(QLatin1String(CERTIFICATE_KEY)).toString();
169     setCustomKeyPath(map.value(QLatin1String(KEYFILE_KEY)).toString());
170     m_createSmartInstaller = map.value(QLatin1String(SMART_INSTALLER_KEY), false).toBool();
171     m_suppressPatchWarningDialog = map.value(QLatin1String(SUPPRESS_PATCH_WARNING_DIALOG_KEY),
172                                              false).toBool();
173     return BuildStep::fromMap(map);
174 }
175
176 Qt4BuildConfiguration *S60CreatePackageStep::qt4BuildConfiguration() const
177 {
178     return static_cast<Qt4BuildConfiguration *>(buildConfiguration());
179 }
180
181 bool S60CreatePackageStep::init()
182 {
183     Qt4Project *pro = qobject_cast<Qt4Project *>(buildConfiguration()->target()->project());
184
185     QList<Qt4ProFileNode *> nodes = pro->allProFiles();
186
187     SymbianQtVersion *sqv = dynamic_cast<SymbianQtVersion *>(qt4BuildConfiguration()->qtVersion());
188     if (!sqv)
189         return false;
190     m_isBuildWithSymbianSbsV2 = sqv->isBuildWithSymbianSbsV2();
191
192     m_workingDirectories.clear();
193     QStringList projectCapabilities;
194     foreach (Qt4ProFileNode *node, nodes) {
195         projectCapabilities += node->symbianCapabilities();
196         m_workingDirectories << node->buildDir();
197     }
198     projectCapabilities.removeDuplicates();
199
200     m_makeCmd = qt4BuildConfiguration()->makeCommand();
201     if (!QFileInfo(m_makeCmd).isAbsolute()) {
202         // Try to detect command in environment
203         const QString tmp = buildConfiguration()->environment().searchInPath(m_makeCmd);
204         if (tmp.isEmpty()) {
205             emit addOutput(tr("Could not find make command '%1' in the build environment").arg(m_makeCmd), BuildStep::ErrorOutput);
206             return false;
207         }
208         m_makeCmd = tmp;
209     }
210
211     if (signingMode() == SignCustom && !validateCustomSigningResources(projectCapabilities))
212         return false;
213
214     m_environment = qt4BuildConfiguration()->environment();
215
216     m_cancel = false;
217
218     return true;
219 }
220
221 void S60CreatePackageStep::definePassphrase()
222 {
223     Q_ASSERT(!m_cancel);
224     PassphraseForKeyDialog *passwordDialog
225             = new PassphraseForKeyDialog(QFileInfo(customKeyPath()).fileName());
226     if (passwordDialog->exec()) {
227         QString newPassphrase = passwordDialog->passphrase();
228         setPassphrase(newPassphrase);
229         if (passwordDialog->savePassphrase())
230             savePassphraseForKey(m_keyId, newPassphrase);
231     } else {
232         m_cancel = true;
233     }
234     delete passwordDialog;
235
236     m_waitCondition.wakeAll();
237 }
238
239 void S60CreatePackageStep::packageWasPatched(const QString &package, const QStringList &changes)
240 {
241     m_packageChanges.append(qMakePair(package, changes));
242 }
243
244 void S60CreatePackageStep::handleWarnAboutPatching()
245 {
246     if (!m_suppressPatchWarningDialog && !m_packageChanges.isEmpty()) {
247         if (m_patchWarningDialog){
248             m_patchWarningDialog->raise();
249             return;
250         }
251
252         m_patchWarningDialog = new Utils::CheckableMessageBox(0);
253         connect(m_patchWarningDialog, SIGNAL(finished(int)), this, SLOT(packageWarningDialogDone()));
254
255         QString title;
256         QString changedText;
257         const QString url = QString::fromLatin1("qthelp://com.nokia.qtcreator.%1%2%3/doc/creator-run-settings.html#capabilities-and-signing").
258                 arg(IDE_VERSION_MAJOR).arg(IDE_VERSION_MINOR).arg(IDE_VERSION_RELEASE);
259         if (m_packageChanges.count() == 1) {
260             title = tr("Package Modified");
261             changedText = tr("<p>Qt modified your package <b>%1</b>.</p>").arg(m_packageChanges.at(0).first);
262         } else {
263             title = tr("Packages Modified");
264             changedText = tr("<p>Qt modified some of your packages.</p>");
265         }
266         const QString text =
267             tr("%1<p><em>These changes were not part of your build system</em> but are required to "
268                "make sure the <em>self-signed</em> package can be installed successfully on a device.</p>"
269                "<p>Check the Build Issues pane for more details on the modifications made.</p>"
270                "<p>Please see the <a href=\"%2\">documentation</a> for other signing options which "
271                "remove the need for this patching.</p>").arg(changedText, url);
272         m_patchWarningDialog->setWindowTitle(title);
273         m_patchWarningDialog->setText(text);
274         m_patchWarningDialog->setCheckBoxText(tr("Ignore patching for this packaging step."));
275         m_patchWarningDialog->setIconPixmap(QMessageBox::standardIcon(QMessageBox::Warning));
276         m_patchWarningDialog->setChecked(m_suppressPatchWarningDialog);
277         m_patchWarningDialog->setStandardButtons(QDialogButtonBox::Ok);
278         m_patchWarningDialog->open();
279     }
280 }
281
282 void S60CreatePackageStep::savePassphraseForKey(const QString &keyId, const QString &passphrase)
283 {
284     m_passphrases->beginGroup("keys");
285     if (passphrase.isEmpty())
286         m_passphrases->remove(keyId);
287     else
288         m_passphrases->setValue(keyId, obfuscatePassphrase(passphrase, keyId));
289     m_passphrases->endGroup();
290 }
291
292 QString S60CreatePackageStep::loadPassphraseForKey(const QString &keyId)
293 {
294     if (keyId.isEmpty())
295         return QString();
296     m_passphrases->beginGroup("keys");
297     QString passphrase = elucidatePassphrase(m_passphrases->value(keyId, QByteArray()).toByteArray(), keyId);
298     m_passphrases->endGroup();
299     return passphrase;
300 }
301
302 QByteArray S60CreatePackageStep::obfuscatePassphrase(const QString &passphrase, const QString &key) const
303 {
304     QByteArray byteArray = passphrase.toUtf8();
305     char *data = byteArray.data();
306     const QChar *keyData = key.data();
307     int keyDataSize = key.size();
308     for (int i = 0; i <byteArray.size(); ++i)
309         data[i] = data[i]^keyData[i%keyDataSize].toAscii();
310     return byteArray.toBase64();
311 }
312
313 QString S60CreatePackageStep::elucidatePassphrase(QByteArray obfuscatedPassphrase, const QString &key) const
314 {
315     QByteArray byteArray = QByteArray::fromBase64(obfuscatedPassphrase);
316     if (byteArray.isEmpty())
317         return QString();
318
319     char *data = byteArray.data();
320     const QChar *keyData = key.data();
321     int keyDataSize = key.size();
322     for (int i = 0; i < byteArray.size(); ++i)
323         data[i] = data[i]^keyData[i%keyDataSize].toAscii();
324     return QString::fromUtf8(byteArray.data());
325 }
326
327 void S60CreatePackageStep::run(QFutureInterface<bool> &fi)
328 {
329     if (m_workingDirectories.isEmpty()) {
330         fi.reportResult(true);
331         return;
332     }
333
334     m_timer = new QTimer();
335     connect(m_timer, SIGNAL(timeout()), this, SLOT(checkForCancel()), Qt::DirectConnection);
336     m_timer->start(500);
337     m_eventLoop = new QEventLoop;
338
339     bool returnValue = false;
340     if (!createOnePackage()) {
341         fi.reportResult(false);
342         return;
343     }
344
345     Q_ASSERT(!m_futureInterface);
346     m_futureInterface = &fi;
347     returnValue = m_eventLoop->exec();
348
349     // Finished
350     m_timer->stop();
351     delete m_timer;
352     m_timer = 0;
353
354     delete m_process;
355     m_process = 0;
356     delete m_eventLoop;
357     m_eventLoop = 0;
358
359     m_futureInterface = 0;
360
361     if (returnValue)
362         emit warnAboutPatching();
363     fi.reportResult(returnValue);
364 }
365
366 bool S60CreatePackageStep::createOnePackage()
367 {
368     // Setup arguments:
369     m_args.clear();
370     if (m_createSmartInstaller) {
371         if (signingMode() == NotSigned)
372             m_args << QLatin1String("unsigned_installer_sis");
373         else
374             m_args << QLatin1String("installer_sis");
375     } else if (signingMode() == NotSigned)
376         m_args << QLatin1String("unsigned_sis");
377     else
378         m_args << QLatin1String("sis");
379
380     if (signingMode() == SignCustom) {
381         m_args << QLatin1String(MAKE_CERTIFICATE_ARGUMENT) + QDir::toNativeSeparators(customSignaturePath())
382                << QLatin1String(MAKE_KEY_ARGUMENT) + QDir::toNativeSeparators(customKeyPath());
383
384         setPassphrase(loadPassphraseForKey(m_keyId));
385
386         if (!passphrase().isEmpty())
387             m_args << QLatin1String(MAKE_PASSPHRASE_ARGUMENT) + passphrase();
388     }
389
390     // Setup working directory:
391     QString workingDirectory = m_workingDirectories.first();
392     QDir wd(workingDirectory);
393     if (!wd.exists())
394         wd.mkpath(wd.absolutePath());
395
396
397     // Setup process...
398     Q_ASSERT(!m_process);
399     m_process = new QProcess();
400     m_process->setEnvironment(m_environment.toStringList());
401
402     connect(m_process, SIGNAL(readyReadStandardOutput()),
403             this, SLOT(processReadyReadStdOutput()),
404             Qt::DirectConnection);
405     connect(m_process, SIGNAL(readyReadStandardError()),
406             this, SLOT(processReadyReadStdError()),
407             Qt::DirectConnection);
408
409     connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)),
410             this, SLOT(packageDone(int, QProcess::ExitStatus)),
411             Qt::DirectConnection);
412
413     m_process->setWorkingDirectory(wd.absolutePath());
414
415     // Setup parsers:
416     Q_ASSERT(!m_outputParserChain);
417     if (!m_isBuildWithSymbianSbsV2) {
418         m_outputParserChain = new Qt4ProjectManager::AbldParser;
419         m_outputParserChain->appendOutputParser(new ProjectExplorer::GnuMakeParser);
420     } else {
421         m_outputParserChain = new ProjectExplorer::GnuMakeParser();
422     }
423     Q_ASSERT(!m_parser);
424     m_parser = new S60CreatePackageParser(wd.absolutePath());
425     m_outputParserChain->appendOutputParser(m_parser);
426     m_outputParserChain->setWorkingDirectory(wd.absolutePath());
427
428     connect(m_outputParserChain, SIGNAL(addOutput(QString,ProjectExplorer::BuildStep::OutputFormat)),
429             this, SIGNAL(addOutput(QString,ProjectExplorer::BuildStep::OutputFormat)));
430     connect(m_outputParserChain, SIGNAL(addTask(ProjectExplorer::Task)),
431             this, SIGNAL(addTask(ProjectExplorer::Task)), Qt::DirectConnection);
432
433     connect(m_parser, SIGNAL(packageWasPatched(QString,QStringList)),
434             this, SLOT(packageWasPatched(QString,QStringList)), Qt::DirectConnection);
435
436     // Go for it!
437     m_process->start(m_makeCmd, m_args);
438     if (!m_process->waitForStarted()) {
439         emit addOutput(tr("Could not start process \"%1\" in %2")
440                        .arg(QDir::toNativeSeparators(m_makeCmd),
441                             workingDirectory),
442                        BuildStep::ErrorMessageOutput);
443         return false;
444     }
445     emit addOutput(tr("Starting: \"%1\" %2 in %3\n")
446                    .arg(QDir::toNativeSeparators(m_makeCmd),
447                         m_args.join(" "),
448                         workingDirectory),
449                    BuildStep::MessageOutput);
450     return true;
451 }
452
453 bool S60CreatePackageStep::validateCustomSigningResources(const QStringList &capabilitiesInPro)
454 {
455     Q_ASSERT(signingMode() == SignCustom);
456
457     QString errorString;
458     if (customSignaturePath().isEmpty())
459         errorString = tr("No certificate file specified. Please specify one in the project settings.");
460     else if (!QFileInfo(customSignaturePath()).exists())
461         errorString = tr("Certificate file \"%1\" does not exist. "
462                          "Please specify an existing certificate file in the project settings.").arg(customSignaturePath());
463
464     if (customKeyPath().isEmpty())
465         errorString = tr("No key file specified. Please specify one in the project settings.");
466     else if (!QFileInfo(customKeyPath()).exists())
467         errorString = tr("Key file \"%1\" does not exist. "
468                          "Please specify an existing key file in the project settings.").arg(customKeyPath());
469
470     if (!errorString.isEmpty()) {
471         reportPackageStepIssue(errorString, true);
472         return false;
473     }
474     QScopedPointer<S60CertificateInfo> certInfoPtr(new S60CertificateInfo(customSignaturePath()));
475     S60CertificateInfo::CertificateState certState = certInfoPtr.data()->validateCertificate();
476     switch (certState) {
477     case S60CertificateInfo::CertificateError:
478         reportPackageStepIssue(certInfoPtr.data()->errorString(), true);
479         return false;
480     case S60CertificateInfo::CertificateWarning:
481         reportPackageStepIssue(certInfoPtr.data()->errorString(), false);
482         break;
483     default:
484         break;
485     }
486
487     QStringList unsupportedCaps;
488     if (certInfoPtr.data()->compareCapabilities(capabilitiesInPro, unsupportedCaps)) {
489         if (!unsupportedCaps.isEmpty()) {
490             QString message = tr("The package created will not install on a "
491                                  "device as some of the defined capabilities "
492                                  "are not supported by the certificate: %1")
493                     .arg(unsupportedCaps.join(" "));
494             reportPackageStepIssue(message, true);
495             return false;
496         }
497
498     } else
499         reportPackageStepIssue(certInfoPtr.data()->errorString(), false);
500     return true;
501 }
502
503 void S60CreatePackageStep::reportPackageStepIssue(const QString &message, bool isError )
504 {
505     emit addOutput(message, isError?
506                        BuildStep::ErrorMessageOutput:
507                        BuildStep::MessageOutput);
508     emit addTask(ProjectExplorer::Task(isError?
509                                            ProjectExplorer::Task::Error:
510                                            ProjectExplorer::Task::Warning,
511                                        message,
512                                        QString(), -1,
513                                        ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM));
514 }
515
516 void S60CreatePackageStep::packageWarningDialogDone()
517 {
518     if (m_patchWarningDialog)
519         m_suppressPatchWarningDialog = m_patchWarningDialog->isChecked();
520     if (m_suppressPatchWarningDialog) {
521         m_patchWarningDialog->deleteLater();
522         m_patchWarningDialog = 0;
523     }
524 }
525
526 void S60CreatePackageStep::packageDone(int exitCode, QProcess::ExitStatus status)
527 {
528     QString line = QString::fromLocal8Bit(m_process->readAllStandardError());
529     if (!line.isEmpty())
530         stdError(line);
531
532     line = QString::fromLocal8Bit(m_process->readAllStandardOutput());
533     if (!line.isEmpty())
534         stdOutput(line);
535
536     if (status == QProcess::NormalExit && exitCode == 0) {
537         emit addOutput(tr("The process \"%1\" exited normally.")
538                        .arg(QDir::toNativeSeparators(m_makeCmd)),
539                        BuildStep::MessageOutput);
540     } else if (status == QProcess::NormalExit) {
541         emit addOutput(tr("The process \"%1\" exited with code %2.")
542                        .arg(QDir::toNativeSeparators(m_makeCmd), QString::number(exitCode)),
543                        BuildStep::ErrorMessageOutput);
544     } else {
545         emit addOutput(tr("The process \"%1\" crashed.").arg(QDir::toNativeSeparators(m_makeCmd)), BuildStep::ErrorMessageOutput);
546     }
547
548     bool needPassphrase = m_parser->needPassphrase();
549
550     // Clean up:
551     delete m_outputParserChain;
552     m_outputParserChain = 0;
553     m_parser = 0;
554     delete m_process;
555     m_process = 0;
556
557     // Process next directories:
558     if (needPassphrase) {
559         emit badPassphrase();
560         QMutexLocker locker(&m_mutex);
561         m_waitCondition.wait(&m_mutex);
562     } else {
563         if (status != QProcess::NormalExit || exitCode != 0) {
564             m_eventLoop->exit(false);
565             return;
566         }
567
568         m_workingDirectories.removeFirst();
569         if (m_workingDirectories.isEmpty()) {
570             m_eventLoop->exit(true);
571             return;
572         }
573     }
574
575     if (m_cancel || !createOnePackage())
576         m_eventLoop->exit(false);
577 }
578
579 void S60CreatePackageStep::processReadyReadStdOutput()
580 {
581     m_process->setReadChannel(QProcess::StandardOutput);
582     while (m_process->canReadLine()) {
583         QString line = QString::fromLocal8Bit(m_process->readLine());
584         stdOutput(line);
585     }
586 }
587
588 void S60CreatePackageStep::stdOutput(const QString &line)
589 {
590     if (m_outputParserChain)
591         m_outputParserChain->stdOutput(line);
592     emit addOutput(line, BuildStep::NormalOutput, BuildStep::DontAppendNewline);
593 }
594
595 void S60CreatePackageStep::processReadyReadStdError()
596 {
597     m_process->setReadChannel(QProcess::StandardError);
598     while (m_process->canReadLine()) {
599         QString line = QString::fromLocal8Bit(m_process->readLine());
600         stdError(line);
601     }
602 }
603
604 void S60CreatePackageStep::stdError(const QString &line)
605 {
606     if (m_outputParserChain)
607         m_outputParserChain->stdError(line);
608     emit addOutput(line, BuildStep::ErrorOutput, BuildStep::DontAppendNewline);
609 }
610
611 void S60CreatePackageStep::checkForCancel()
612 {
613     if (m_futureInterface->isCanceled()
614          && m_timer && m_timer->isActive()) {
615         m_timer->stop();
616         if (m_process) {
617             m_process->terminate();
618             m_process->waitForFinished(5000); //while waiting, the process can be killed
619             if (m_process)
620                 m_process->kill();
621         }
622         if (m_eventLoop)
623             m_eventLoop->exit(false);
624     }
625 }
626
627 QString S60CreatePackageStep::generateKeyId(const QString &keyPath) const
628 {
629     if (keyPath.isEmpty())
630         return QString();
631
632     Utils::FileReader reader;
633     if (!reader.fetch(keyPath, QIODevice::Text)) {
634         emit addOutput(reader.errorString(), BuildStep::ErrorOutput);
635         return QString();
636     }
637
638     //key file is quite small in size
639     return QCryptographicHash::hash(reader.data(),
640                                     QCryptographicHash::Md5).toHex();
641 }
642
643 bool S60CreatePackageStep::immutable() const
644 {
645     return false;
646 }
647
648 ProjectExplorer::BuildStepConfigWidget *S60CreatePackageStep::createConfigWidget()
649 {
650     return new S60CreatePackageStepConfigWidget(this);
651 }
652
653 S60CreatePackageStep::SigningMode S60CreatePackageStep::signingMode() const
654 {
655     return m_signingMode;
656 }
657
658 void S60CreatePackageStep::setSigningMode(SigningMode mode)
659 {
660     m_signingMode = mode;
661 }
662
663 QString S60CreatePackageStep::customSignaturePath() const
664 {
665     return m_customSignaturePath;
666 }
667
668 void S60CreatePackageStep::setCustomSignaturePath(const QString &path)
669 {
670     m_customSignaturePath = path;
671 }
672
673 QString S60CreatePackageStep::customKeyPath() const
674 {
675     return m_customKeyPath;
676 }
677
678 void S60CreatePackageStep::setCustomKeyPath(const QString &path)
679 {
680     m_customKeyPath = path;
681     m_keyId = generateKeyId(m_customKeyPath);
682 }
683
684 QString S60CreatePackageStep::passphrase() const
685 {
686     return m_passphrase;
687 }
688
689 void S60CreatePackageStep::setPassphrase(const QString &passphrase)
690 {
691     if (passphrase.isEmpty())
692         return;
693     m_passphrase = passphrase;
694 }
695
696 QString S60CreatePackageStep::keyId() const
697 {
698     return m_keyId;
699 }
700
701 void S60CreatePackageStep::setKeyId(const QString &keyId)
702 {
703     m_keyId = keyId;
704 }
705
706 bool S60CreatePackageStep::createsSmartInstaller() const
707 {
708     return m_createSmartInstaller;
709 }
710
711 void S60CreatePackageStep::setCreatesSmartInstaller(bool value)
712 {
713     m_createSmartInstaller = value;
714     static_cast<Qt4BuildConfiguration *>(buildConfiguration())->emitS60CreatesSmartInstallerChanged();
715 }
716
717 void S60CreatePackageStep::resetPassphrases()
718 {
719     m_passphrases->beginGroup("keys");
720     QStringList keys = m_passphrases->allKeys();
721     foreach (const QString &key, keys) {
722         m_passphrases->setValue(key, QString());
723     }
724     m_passphrases->remove(QString());
725     m_passphrases->endGroup();
726 }
727
728 // #pragma mark -- S60SignBuildStepFactory
729
730 S60CreatePackageStepFactory::S60CreatePackageStepFactory(QObject *parent) :
731     ProjectExplorer::IBuildStepFactory(parent)
732 {
733 }
734
735 S60CreatePackageStepFactory::~S60CreatePackageStepFactory()
736 {
737 }
738
739 bool S60CreatePackageStepFactory::canCreate(ProjectExplorer::BuildStepList *parent, const QString &id) const
740 {
741     if (parent->id() != QLatin1String(ProjectExplorer::Constants::BUILDSTEPS_DEPLOY))
742         return false;
743     if (parent->target()->id() != QLatin1String(Constants::S60_DEVICE_TARGET_ID))
744         return false;
745     return (id == QLatin1String(SIGN_BS_ID));
746 }
747
748 ProjectExplorer::BuildStep *S60CreatePackageStepFactory::create(ProjectExplorer::BuildStepList *parent, const QString &id)
749 {
750     if (!canCreate(parent, id))
751         return 0;
752     return new S60CreatePackageStep(parent);
753 }
754
755 bool S60CreatePackageStepFactory::canClone(ProjectExplorer::BuildStepList *parent, ProjectExplorer::BuildStep *source) const
756 {
757     return canCreate(parent, source->id());
758 }
759
760 ProjectExplorer::BuildStep *S60CreatePackageStepFactory::clone(ProjectExplorer::BuildStepList *parent, ProjectExplorer::BuildStep *source)
761 {
762     if (!canClone(parent, source))
763         return 0;
764     return new S60CreatePackageStep(parent, static_cast<S60CreatePackageStep *>(source));
765 }
766
767 bool S60CreatePackageStepFactory::canRestore(ProjectExplorer::BuildStepList *parent, const QVariantMap &map) const
768 {
769     QString id(ProjectExplorer::idFromMap(map));
770     return canCreate(parent, id);
771 }
772
773 ProjectExplorer::BuildStep *S60CreatePackageStepFactory::restore(ProjectExplorer::BuildStepList *parent, const QVariantMap &map)
774 {
775     if (!canRestore(parent, map))
776         return 0;
777     S60CreatePackageStep *bs(new S60CreatePackageStep(parent));
778     if (bs->fromMap(map))
779         return bs;
780     delete bs;
781     return 0;
782 }
783
784 QStringList S60CreatePackageStepFactory::availableCreationIds(ProjectExplorer::BuildStepList *parent) const
785 {
786     if (parent->id() != QLatin1String(ProjectExplorer::Constants::BUILDSTEPS_DEPLOY))
787         return QStringList();
788     if (parent->target()->id() == QLatin1String(Constants::S60_DEVICE_TARGET_ID))
789         return QStringList() << QLatin1String(SIGN_BS_ID);
790     return QStringList();
791 }
792
793 QString S60CreatePackageStepFactory::displayNameForId(const QString &id) const
794 {
795     if (id == QLatin1String(SIGN_BS_ID))
796         return tr("Create SIS Package");
797     return QString();
798 }
799
800 // #pragma mark -- S60SignBuildStepConfigWidget
801
802 S60CreatePackageStepConfigWidget::S60CreatePackageStepConfigWidget(S60CreatePackageStep *signStep)
803     : BuildStepConfigWidget(), m_signStep(signStep)
804 {
805     m_ui.setupUi(this);
806     m_ui.signaturePath->setExpectedKind(Utils::PathChooser::File);
807     m_ui.signaturePath->setPromptDialogFilter(QLatin1String("*.cer *.crt *.der *.pem"));
808     m_ui.keyFilePath->setExpectedKind(Utils::PathChooser::File);
809     updateUi();
810
811     bool enableCertDetails = m_signStep->signingMode() == S60CreatePackageStep::SignCustom
812             && m_ui.signaturePath->isValid();
813     m_ui.certificateDetails->setEnabled(enableCertDetails);
814
815     connect(m_ui.certificateDetails, SIGNAL(clicked()),
816             this, SLOT(displayCertificateDetails()));
817     connect(m_ui.customCertificateButton, SIGNAL(clicked()),
818             this, SLOT(updateFromUi()));
819     connect(m_ui.selfSignedButton, SIGNAL(clicked()),
820             this, SLOT(updateFromUi()));
821     connect(m_ui.notSignedButton, SIGNAL(clicked()),
822             this, SLOT(updateFromUi()));
823     connect(m_ui.signaturePath, SIGNAL(changed(QString)),
824             this, SLOT(signatureChanged(QString)));
825     connect(m_ui.keyFilePath, SIGNAL(changed(QString)),
826             this, SLOT(updateFromUi()));
827     connect(m_ui.smartInstaller, SIGNAL(clicked()),
828             this, SLOT(updateFromUi()));
829     connect(m_ui.resetPassphrasesButton, SIGNAL(clicked()),
830             this, SLOT(resetPassphrases()));
831 }
832
833 void S60CreatePackageStepConfigWidget::signatureChanged(QString certFile)
834 {
835     m_ui.certificateDetails->setEnabled(m_ui.signaturePath->isValid());
836
837     if (!certFile.isEmpty() && m_ui.keyFilePath->path().isEmpty()) {
838         /*  If a cert file is selected and there is not key file inserted,
839             then we check if there is a .key or .pem file in the folder with
840             the same base name as the cert file. This file is probably a key
841             file for this cert and the key field is then populated automatically
842         */
843         QFileInfo certFileInfo(certFile);
844         QDir directory = QDir(certFileInfo.absolutePath());
845         QString keyFile(certFileInfo.baseName() + QLatin1String(".key"));
846         QString pemFile(certFileInfo.baseName() + QLatin1String(".pem"));
847         QStringList files;
848         QStringList keys;
849         keys << keyFile << pemFile;
850         files = directory.entryList(QStringList(keys),
851                                     QDir::Files | QDir::NoSymLinks);
852
853         if (files.isEmpty())
854             m_ui.keyFilePath->setInitialBrowsePathBackup(certFileInfo.path());
855         else
856             m_ui.keyFilePath->setPath(directory.filePath(files[0]));
857     }
858     updateFromUi();
859 }
860
861 void S60CreatePackageStepConfigWidget::updateUi()
862 {
863     switch(m_signStep->signingMode()) {
864     case S60CreatePackageStep::SignCustom:
865         m_ui.selfSignedButton->setChecked(false);
866         m_ui.customCertificateButton->setChecked(true);
867         m_ui.notSignedButton->setChecked(false);
868         m_ui.certificateDetails->setEnabled(m_ui.signaturePath->isValid());
869         break;
870     case S60CreatePackageStep::NotSigned:
871         m_ui.selfSignedButton->setChecked(false);
872         m_ui.customCertificateButton->setChecked(false);
873         m_ui.notSignedButton->setChecked(true);
874         m_ui.certificateDetails->setEnabled(false);
875         break;
876     default:
877         m_ui.selfSignedButton->setChecked(true);
878         m_ui.customCertificateButton->setChecked(false);
879         m_ui.notSignedButton->setChecked(false);
880         m_ui.certificateDetails->setEnabled(false);
881         break;
882     }
883     bool customSigned = m_signStep->signingMode() == S60CreatePackageStep::SignCustom;
884     m_ui.signaturePath->setEnabled(customSigned);
885     m_ui.keyFilePath->setEnabled(customSigned);
886     m_ui.signaturePath->setPath(m_signStep->customSignaturePath());
887     m_ui.keyFilePath->setPath(m_signStep->customKeyPath());
888     m_ui.smartInstaller->setChecked(m_signStep->createsSmartInstaller());
889     emit updateSummary();
890 }
891
892 void S60CreatePackageStepConfigWidget::updateFromUi()
893 {
894     S60CreatePackageStep::SigningMode signingMode(S60CreatePackageStep::SignSelf);
895     if (m_ui.selfSignedButton->isChecked())
896         signingMode = S60CreatePackageStep::SignSelf;
897     else if (m_ui.customCertificateButton->isChecked())
898         signingMode = S60CreatePackageStep::SignCustom;
899     else if (m_ui.notSignedButton->isChecked())
900         signingMode = S60CreatePackageStep::NotSigned;
901
902     m_signStep->setSigningMode(signingMode);
903     m_signStep->setCustomSignaturePath(m_ui.signaturePath->path());
904     m_signStep->setCustomKeyPath(m_ui.keyFilePath->path());
905     m_signStep->setCreatesSmartInstaller(m_ui.smartInstaller->isChecked());
906     updateUi();
907 }
908
909 void S60CreatePackageStepConfigWidget::displayCertificateDetails()
910 {
911     S60CertificateInfo *certificateInformation = new S60CertificateInfo(m_ui.signaturePath->path());
912     certificateInformation->devicesSupported().sort();
913
914     S60CertificateDetailsDialog dialog;
915     dialog.setText(certificateInformation->toHtml(false));
916     dialog.exec();
917     delete certificateInformation;
918 }
919
920 void S60CreatePackageStepConfigWidget::resetPassphrases()
921 {
922     QMessageBox msgBox(QMessageBox::Question, tr("Reset Passphrases"),
923                        tr("Do you want to reset all passphrases saved for keys used?"),
924                        QMessageBox::Reset|QMessageBox::Cancel, this);
925     if (msgBox.exec() == QMessageBox::Reset)
926         m_signStep->resetPassphrases();
927 }
928
929 QString S60CreatePackageStepConfigWidget::summaryText() const
930 {
931     QString text;
932     switch(m_signStep->signingMode()) {
933     case S60CreatePackageStep::SignCustom:
934         if (!m_signStep->customSignaturePath().isEmpty()
935                 && !m_signStep->customKeyPath().isEmpty())
936             text = tr("signed with the certificate \"%1\" using the key \"%2\"")
937                .arg(QFileInfo(m_signStep->customSignaturePath()).fileName(),
938                     QFileInfo(m_signStep->customKeyPath()).fileName());
939         else
940             text = tr("signed with a certificate and a key that need to be specified");
941         break;
942     case S60CreatePackageStep::NotSigned:
943         text = tr("not signed");
944         break;
945     default:
946         text = tr("self-signed");
947         break;
948     }
949     if (m_signStep->createsSmartInstaller())
950         return tr("<b>Create SIS Package:</b> %1, using Smart Installer").arg(text);
951     return tr("<b>Create SIS Package:</b> %1").arg(text);
952 }
953
954 QString S60CreatePackageStepConfigWidget::displayName() const
955 {
956     return m_signStep->displayName();
957 }