OSDN Git Service

52e44b91b4beeda49c1ac73fb2f863940c9ebb67
[qt-creator-jp/qt-creator-jp.git] / src / plugins / qt4projectmanager / qt-maemo / qt4maemotarget.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
8 **
9 ** No Commercial Usage
10 **
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 **
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file.  Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights.  These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
31 **
32 **************************************************************************/
33
34 #include "qt4maemotarget.h"
35
36 #include "maemoglobal.h"
37 #include "maemopackagecreationstep.h"
38 #include "maemopertargetdeviceconfigurationlistmodel.h"
39 #include "maemorunconfiguration.h"
40 #include "maemotoolchain.h"
41 #include "qt4maemodeployconfiguration.h"
42
43 #include <coreplugin/icore.h>
44 #include <coreplugin/iversioncontrol.h>
45 #include <coreplugin/vcsmanager.h>
46 #include <projectexplorer/customexecutablerunconfiguration.h>
47 #include <projectexplorer/projectexplorer.h>
48 #include <projectexplorer/projectnodes.h>
49 #include <qt4projectmanager/qt4project.h>
50
51 #include <QtGui/QApplication>
52 #include <QtGui/QMainWindow>
53 #include <QtCore/QBuffer>
54 #include <QtCore/QRegExp>
55 #include <QtCore/QDir>
56 #include <QtCore/QFile>
57 #include <QtCore/QFileInfo>
58 #include <QtCore/QProcess>
59 #include <QtCore/QStringList>
60 #include <QtGui/QIcon>
61 #include <QtGui/QMessageBox>
62
63 #include <cctype>
64
65 using namespace Qt4ProjectManager;
66 using namespace Qt4ProjectManager::Internal;
67
68 namespace {
69 const QByteArray NameFieldName("Package");
70 const QByteArray IconFieldName("XB-Maemo-Icon-26");
71 const QByteArray ShortDescriptionFieldName("Description");
72 const QByteArray PackageFieldName("Package");
73 const QLatin1String PackagingDirName("qtc_packaging");
74 const QByteArray NameTag("Name");
75 const QByteArray SummaryTag("Summary");
76 const QByteArray VersionTag("Version");
77 const QByteArray ReleaseTag("Release");
78
79 bool adaptTagValue(QByteArray &document, const QByteArray &fieldName,
80     const QByteArray &newFieldValue, bool caseSensitive)
81 {
82     QByteArray adaptedLine = fieldName + ": " + newFieldValue;
83     const QByteArray completeTag = fieldName + ":";
84     const int lineOffset = caseSensitive ? document.indexOf(completeTag)
85         : document.toLower().indexOf(completeTag.toLower());
86     if (lineOffset == -1) {
87         document.append(adaptedLine).append('\n');
88         return true;
89     }
90
91     int newlineOffset = document.indexOf('\n', lineOffset);
92     bool updated = false;
93     if (newlineOffset == -1) {
94         newlineOffset = document.length();
95         adaptedLine += '\n';
96         updated = true;
97     }
98     const int replaceCount = newlineOffset - lineOffset;
99     if (!updated && document.mid(lineOffset, replaceCount) != adaptedLine)
100         updated = true;
101     if (updated)
102         document.replace(lineOffset, replaceCount, adaptedLine);
103     return updated;
104 }
105
106
107 } // anonymous namespace
108
109
110 AbstractQt4MaemoTarget::AbstractQt4MaemoTarget(Qt4Project *parent, const QString &id) :
111     Qt4BaseTarget(parent, id),
112     m_filesWatcher(new QFileSystemWatcher(this)),
113     m_buildConfigurationFactory(new Qt4BuildConfigurationFactory(this)),
114     m_deployConfigurationFactory(new Qt4MaemoDeployConfigurationFactory(this)),
115     m_isInitialized(false)
116 {
117     setIcon(QIcon(":/projectexplorer/images/MaemoDevice.png"));
118     connect(parent, SIGNAL(addedTarget(ProjectExplorer::Target*)),
119         this, SLOT(handleTargetAdded(ProjectExplorer::Target*)));
120     connect(parent, SIGNAL(fromMapFinished()),
121         this, SLOT(handleFromMapFinished()));
122 }
123
124 AbstractQt4MaemoTarget::~AbstractQt4MaemoTarget()
125 { }
126
127 AbstractQt4MaemoTarget::DebugArchitecture AbstractQt4MaemoTarget::debugArchitecture() const
128 {
129     const QString arch
130         = MaemoGlobal::architecture(activeBuildConfiguration()->qtVersion());
131     if (arch.startsWith(QLatin1String("arm"))) {
132         return DebugArchitecture(QLatin1String("arm"),
133             QLatin1String("arm-none-linux-gnueabi"));
134     } else if (arch.startsWith(QLatin1String("x86_64"))) {
135         return DebugArchitecture(QLatin1String("i386:x86-64"),
136             QLatin1String("x86_64-unknown-linux-gnu "));
137     } else {
138         return DebugArchitecture(QLatin1String("x86"),
139             QLatin1String("i386-unknown-linux-gnu "));
140     }
141 }
142
143 QList<ProjectExplorer::ToolChain *> AbstractQt4MaemoTarget::possibleToolChains(ProjectExplorer::BuildConfiguration *bc) const
144 {
145     QList<ProjectExplorer::ToolChain *> result;
146
147     Qt4BuildConfiguration *qt4Bc = qobject_cast<Qt4BuildConfiguration *>(bc);
148     if (!qt4Bc)
149         return result;
150
151     QList<ProjectExplorer::ToolChain *> candidates = Qt4BaseTarget::possibleToolChains(bc);
152     foreach (ProjectExplorer::ToolChain *i, candidates) {
153         MaemoToolChain *tc = dynamic_cast<MaemoToolChain *>(i);
154         if (!tc)
155             continue;
156         if (tc->qtVersionId() == qt4Bc->qtVersion()->uniqueId())
157             result.append(tc);
158     }
159
160     return result;
161 }
162
163
164 Qt4BuildConfigurationFactory *AbstractQt4MaemoTarget::buildConfigurationFactory() const
165 {
166     return m_buildConfigurationFactory;
167 }
168
169 ProjectExplorer::DeployConfigurationFactory *AbstractQt4MaemoTarget::deployConfigurationFactory() const
170 {
171     return m_deployConfigurationFactory;
172 }
173
174 QString AbstractQt4MaemoTarget::defaultBuildDirectory() const
175 {
176     //TODO why?
177 #if defined(Q_OS_WIN)
178     return project()->projectDirectory();
179 #endif
180     return Qt4BaseTarget::defaultBuildDirectory();
181 }
182
183 void AbstractQt4MaemoTarget::createApplicationProFiles()
184 {
185     removeUnconfiguredCustomExectutableRunConfigurations();
186
187     QList<Qt4ProFileNode *> profiles = qt4Project()->applicationProFiles();
188     QSet<QString> paths;
189     foreach (Qt4ProFileNode *pro, profiles)
190         paths << pro->path();
191
192     foreach (ProjectExplorer::RunConfiguration *rc, runConfigurations())
193         if (MaemoRunConfiguration *qt4rc = qobject_cast<MaemoRunConfiguration *>(rc))
194             paths.remove(qt4rc->proFilePath());
195
196     // Only add new runconfigurations if there are none.
197     foreach (const QString &path, paths)
198         addRunConfiguration(new MaemoRunConfiguration(this, path));
199
200     // Oh still none? Add a custom executable runconfiguration
201     if (runConfigurations().isEmpty()) {
202         addRunConfiguration(new ProjectExplorer::CustomExecutableRunConfiguration(this));
203     }
204 }
205
206 QList<ProjectExplorer::RunConfiguration *> AbstractQt4MaemoTarget::runConfigurationsForNode(ProjectExplorer::Node *n)
207 {
208     QList<ProjectExplorer::RunConfiguration *> result;
209     foreach (ProjectExplorer::RunConfiguration *rc, runConfigurations())
210         if (MaemoRunConfiguration *mrc = qobject_cast<MaemoRunConfiguration *>(rc))
211             if (mrc->proFilePath() == n->path())
212                 result << rc;
213     return result;
214 }
215
216 bool AbstractQt4MaemoTarget::setProjectVersion(const QString &version,
217     QString *error)
218 {
219     bool success = true;
220     foreach (Target * const target, project()->targets()) {
221         AbstractQt4MaemoTarget * const maemoTarget
222             = qobject_cast<AbstractQt4MaemoTarget *>(target);
223         if (maemoTarget) {
224             if (!maemoTarget->setProjectVersionInternal(version, error))
225                 success = false;
226         }
227     }
228     return success;
229 }
230
231 bool AbstractQt4MaemoTarget::setPackageName(const QString &name)
232 {
233     bool success = true;
234     foreach (Target * const target, project()->targets()) {
235         AbstractQt4MaemoTarget * const maemoTarget
236             = qobject_cast<AbstractQt4MaemoTarget *>(target);
237         if (maemoTarget) {
238             if (!maemoTarget->setPackageNameInternal(name))
239                 success = false;
240         }
241     }
242     return success;
243 }
244
245 bool AbstractQt4MaemoTarget::setShortDescription(const QString &description)
246 {
247     bool success = true;
248     foreach (Target * const target, project()->targets()) {
249         AbstractQt4MaemoTarget * const maemoTarget
250             = qobject_cast<AbstractQt4MaemoTarget *>(target);
251         if (maemoTarget) {
252             if (!maemoTarget->setShortDescriptionInternal(description))
253                 success = false;
254         }
255     }
256     return success;
257 }
258
259 QSharedPointer<QFile> AbstractQt4MaemoTarget::openFile(const QString &filePath,
260     QIODevice::OpenMode mode, QString *error) const
261 {
262     const QString nativePath = QDir::toNativeSeparators(filePath);
263     QSharedPointer<QFile> file(new QFile(filePath));
264     if (mode == QIODevice::ReadOnly && !file->exists()) {
265         if (error)
266             *error = tr("File '%1' does not exist").arg(nativePath);
267         file.clear();
268     } else if (!file->open(mode)) {
269         if (error) {
270             *error = tr("Cannot open file '%1': %2")
271                 .arg(nativePath, file->errorString());
272         }
273         file.clear();
274     }
275     return file;
276 }
277
278 void AbstractQt4MaemoTarget::handleFromMapFinished()
279 {
280     handleTargetAdded(this);
281 }
282
283 void AbstractQt4MaemoTarget::handleTargetAdded(ProjectExplorer::Target *target)
284 {
285     if (target != this)
286         return;
287
288     if (!project()->rootProjectNode()) {
289         // Project is not fully setup yet, happens on new project
290         // we wait for the fromMapFinished that comes afterwards
291         return;
292     }
293
294     disconnect(project(), SIGNAL(fromMapFinished()),
295         this, SLOT(handleFromMapFinished()));
296     disconnect(project(), SIGNAL(addedTarget(ProjectExplorer::Target*)),
297         this, SLOT(handleTargetAdded(ProjectExplorer::Target*)));
298     connect(project(), SIGNAL(aboutToRemoveTarget(ProjectExplorer::Target*)),
299         SLOT(handleTargetToBeRemoved(ProjectExplorer::Target*)));
300     if (createTemplates() == ActionFailed)
301         return;
302     initPackagingSettingsFromOtherTarget();
303     handleTargetAddedSpecial();
304     m_isInitialized = true;
305 }
306
307 void AbstractQt4MaemoTarget::handleTargetToBeRemoved(ProjectExplorer::Target *target)
308 {
309     if (target != this)
310         return;
311     if (!targetCanBeRemoved())
312         return;
313
314     Core::ICore * const core = Core::ICore::instance();
315     const int answer = QMessageBox::warning(core->mainWindow(),
316         tr("Qt Creator"), tr("Do you want to remove the packaging file(s) "
317            "associated with the target '%1'?").arg(displayName()),
318         QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
319     if (answer == QMessageBox::No)
320         return;
321     const QStringList pkgFilePaths = packagingFilePaths();
322     project()->rootProjectNode()->removeFiles(ProjectExplorer::UnknownFileType,
323         pkgFilePaths);
324     Core::IVersionControl * const vcs = core->vcsManager()
325         ->findVersionControlForDirectory(QFileInfo(packagingFilePaths().first()).dir().path());
326     if (vcs && vcs->supportsOperation(Core::IVersionControl::DeleteOperation)) {
327         foreach (const QString &filePath, pkgFilePaths)
328             vcs->vcsDelete(filePath);
329     }
330     removeTarget();
331     QString error;
332     const QString packagingPath = project()->projectDirectory()
333         + QLatin1Char('/') + PackagingDirName;
334     const QStringList otherContents = QDir(packagingPath).entryList(QDir::Dirs
335         | QDir::Files | QDir::Hidden | QDir::NoDotAndDotDot);
336     if (otherContents.isEmpty()) {
337         if (!MaemoGlobal::removeRecursively(packagingPath, error))
338             qDebug("%s", qPrintable(error));
339     }
340 }
341
342 AbstractQt4MaemoTarget::ActionStatus AbstractQt4MaemoTarget::createTemplates()
343 {
344     QDir projectDir(project()->projectDirectory());
345     if (!projectDir.exists(PackagingDirName)
346             && !projectDir.mkdir(PackagingDirName)) {
347         raiseError(tr("Error creating Maemo packaging directory '%1'.")
348             .arg(PackagingDirName));
349         return ActionFailed;
350     }
351
352     const ActionStatus actionStatus = createSpecialTemplates();
353     if (actionStatus == ActionFailed)
354         return ActionFailed;
355     if (actionStatus == ActionSuccessful) {
356         const QStringList &files = packagingFilePaths();
357         QMessageBox::StandardButton button
358             = QMessageBox::question(Core::ICore::instance()->mainWindow(),
359                   tr("Add Packaging Files to Project"),
360                   tr("Qt Creator has set up the following files to enable "
361                      "packaging:\n   %1\nDo you want to add them to the project?")
362                       .arg(files.join(QLatin1String("\n   "))),
363                   QMessageBox::Yes | QMessageBox::No);
364         if (button == QMessageBox::Yes)
365             ProjectExplorer::ProjectExplorerPlugin::instance()->addExistingFiles(project()->rootProjectNode(), files);
366     }
367     return actionStatus;
368 }
369
370 bool AbstractQt4MaemoTarget::initPackagingSettingsFromOtherTarget()
371 {
372     bool success = true;
373     foreach (const Target * const target, project()->targets()) {
374         const AbstractQt4MaemoTarget * const maemoTarget
375             = qobject_cast<const AbstractQt4MaemoTarget *>(target);
376         if (maemoTarget && maemoTarget != this && maemoTarget->m_isInitialized) {
377             if (!setProjectVersionInternal(maemoTarget->projectVersion()))
378                 success = false;
379             if (!setPackageNameInternal(maemoTarget->packageName()))
380                 success = false;
381             if (!setShortDescriptionInternal(maemoTarget->shortDescription()))
382                 success = false;
383             break;
384         }
385     }
386     return initAdditionalPackagingSettingsFromOtherTarget();
387 }
388
389 void AbstractQt4MaemoTarget::initDeviceConfigurationsModel()
390 {
391     m_deviceConfigurationsListModel
392         = new MaemoPerTargetDeviceConfigurationListModel(this);
393 }
394
395 void AbstractQt4MaemoTarget::raiseError(const QString &reason)
396 {
397     QMessageBox::critical(0, tr("Error creating Maemo templates"), reason);
398 }
399
400 AbstractDebBasedQt4MaemoTarget::AbstractDebBasedQt4MaemoTarget(Qt4Project *parent,
401     const QString &id) : AbstractQt4MaemoTarget(parent, id)
402 {
403 }
404
405 AbstractDebBasedQt4MaemoTarget::~AbstractDebBasedQt4MaemoTarget() {}
406
407 QString AbstractDebBasedQt4MaemoTarget::projectVersion(QString *error) const
408 {
409     QSharedPointer<QFile> changeLog = openFile(changeLogFilePath(),
410         QIODevice::ReadOnly, error);
411     if (!changeLog)
412         return QString();
413     const QByteArray &firstLine = changeLog->readLine();
414     const int openParenPos = firstLine.indexOf('(');
415     if (openParenPos == -1) {
416         if (error) {
417             *error = tr("Debian changelog file '%1' has unexpected format.")
418                 .arg(QDir::toNativeSeparators(changeLog->fileName()));
419         }
420         return QString();
421     }
422     const int closeParenPos = firstLine.indexOf(')', openParenPos);
423     if (closeParenPos == -1) {
424         if (error) {
425             *error = tr("Debian changelog file '%1' has unexpected format.")
426                 .arg(QDir::toNativeSeparators(changeLog->fileName()));
427         }
428         return QString();
429     }
430     return QString::fromUtf8(firstLine.mid(openParenPos + 1,
431         closeParenPos - openParenPos - 1).data());
432 }
433
434 bool AbstractDebBasedQt4MaemoTarget::setProjectVersionInternal(const QString &version,
435     QString *error)
436 {
437     const QString filePath = changeLogFilePath();
438     MaemoGlobal::FileUpdate update(filePath);
439     QSharedPointer<QFile> changeLog
440         = openFile(filePath, QIODevice::ReadWrite, error);
441     if (!changeLog)
442         return false;
443
444     QString content = QString::fromUtf8(changeLog->readAll());
445     content.replace(QRegExp(QLatin1String("\\([a-zA-Z0-9_\\.]+\\)")),
446         QLatin1Char('(') + version + QLatin1Char(')'));
447     changeLog->resize(0);
448     changeLog->write(content.toUtf8());
449     changeLog->close();
450     if (changeLog->error() != QFile::NoError) {
451         if (error) {
452             *error = tr("Error writing Debian changelog file '%1': %2")
453                 .arg(QDir::toNativeSeparators(changeLog->fileName()),
454                      changeLog->errorString());
455         }
456         return false;
457     }
458     return true;
459 }
460
461 QIcon AbstractDebBasedQt4MaemoTarget::packageManagerIcon(QString *error) const
462 {
463     const QByteArray &base64Icon = controlFileFieldValue(IconFieldName, true);
464     if (base64Icon.isEmpty())
465         return QIcon();
466     QPixmap pixmap;
467     if (!pixmap.loadFromData(QByteArray::fromBase64(base64Icon))) {
468         if (error)
469             *error = tr("Invalid icon data in Debian control file.");
470         return QIcon();
471     }
472     return QIcon(pixmap);
473 }
474
475 bool AbstractDebBasedQt4MaemoTarget::setPackageManagerIconInternal(const QString &iconFilePath,
476     QString *error)
477 {
478     const QString filePath = controlFilePath();
479     MaemoGlobal::FileUpdate update(filePath);
480     const QSharedPointer<QFile> controlFile
481         = openFile(filePath, QIODevice::ReadWrite, error);
482     if (!controlFile)
483         return false;
484     const QPixmap pixmap(iconFilePath);
485     if (pixmap.isNull()) {
486         if (error)
487             *error = tr("Could not read image file '%1'.").arg(iconFilePath);
488         return false;
489     }
490
491     QByteArray iconAsBase64;
492     QBuffer buffer(&iconAsBase64);
493     buffer.open(QIODevice::WriteOnly);
494     if (!pixmap.scaled(48, 48).save(&buffer,
495         QFileInfo(iconFilePath).suffix().toAscii())) {
496         if (error)
497             *error = tr("Could not export image file '%1'.").arg(iconFilePath);
498         return false;
499     }
500     buffer.close();
501     iconAsBase64 = iconAsBase64.toBase64();
502     QByteArray contents = controlFile->readAll();
503     const QByteArray iconFieldNameWithColon = IconFieldName + ':';
504     const int iconFieldPos = contents.startsWith(iconFieldNameWithColon)
505         ? 0 : contents.indexOf('\n' + iconFieldNameWithColon);
506     if (iconFieldPos == -1) {
507         if (!contents.endsWith('\n'))
508             contents += '\n';
509         contents.append(iconFieldNameWithColon).append(' ').append(iconAsBase64)
510             .append('\n');
511     } else {
512         const int oldIconStartPos = (iconFieldPos != 0) + iconFieldPos
513             + iconFieldNameWithColon.length();
514         int nextEolPos = contents.indexOf('\n', oldIconStartPos);
515         while (nextEolPos != -1 && nextEolPos != contents.length() - 1
516             && contents.at(nextEolPos + 1) != '\n'
517             && (contents.at(nextEolPos + 1) == '#'
518                 || std::isspace(contents.at(nextEolPos + 1))))
519             nextEolPos = contents.indexOf('\n', nextEolPos + 1);
520         if (nextEolPos == -1)
521             nextEolPos = contents.length();
522         contents.replace(oldIconStartPos, nextEolPos - oldIconStartPos,
523             ' ' + iconAsBase64);
524     }
525     controlFile->resize(0);
526     controlFile->write(contents);
527     if (controlFile->error() != QFile::NoError) {
528         if (error) {
529             *error = tr("Error writing file '%1': %2")
530                 .arg(QDir::toNativeSeparators(controlFile->fileName()),
531                     controlFile->errorString());
532         }
533         return false;
534     }
535     return true;
536 }
537
538 QString AbstractDebBasedQt4MaemoTarget::packageName() const
539 {
540     return QString::fromUtf8(controlFileFieldValue(NameFieldName, false));
541 }
542
543 bool AbstractDebBasedQt4MaemoTarget::setPackageNameInternal(const QString &packageName)
544 {
545     const QString oldPackageName = this->packageName();
546
547     if (!setControlFieldValue(NameFieldName, packageName.toUtf8()))
548         return false;
549     if (!setControlFieldValue("Source", packageName.toUtf8()))
550         return false;
551
552     QSharedPointer<QFile> changelogFile
553         = openFile(changeLogFilePath(), QIODevice::ReadWrite, 0);
554     if (!changelogFile)
555         return false;
556     QString changelogContents = QString::fromUtf8(changelogFile->readAll());
557     QRegExp pattern(QLatin1String("[^\\s]+( \\(\\d\\.\\d\\.\\d\\))"));
558     changelogContents.replace(pattern, packageName + QLatin1String("\\1"));
559     if (!changelogFile->resize(0))
560         return false;
561     changelogFile->write(changelogContents.toUtf8());
562
563     QSharedPointer<QFile> rulesFile
564         = openFile(rulesFilePath(), QIODevice::ReadWrite, 0);
565     if (!rulesFile)
566         return false;
567     QByteArray rulesContents = rulesFile->readAll();
568     const QString oldString = QLatin1String("debian/") + oldPackageName;
569     const QString newString = QLatin1String("debian/") + packageName;
570     rulesContents.replace(oldString.toUtf8(), newString.toUtf8());
571     rulesFile->resize(0);
572     rulesFile->write(rulesContents);
573     if (rulesFile->error() != QFile::NoError
574             || changelogFile->error() != QFile::NoError) {
575         return false;
576     }
577     return true;
578 }
579
580 QString AbstractDebBasedQt4MaemoTarget::packageManagerName() const
581 {
582     return QString::fromUtf8(controlFileFieldValue(packageManagerNameFieldName(), false));
583 }
584
585 bool AbstractDebBasedQt4MaemoTarget::setPackageManagerName(const QString &name,
586     QString *error)
587 {
588     bool success = true;
589     foreach (Target * const t, project()->targets()) {
590         AbstractDebBasedQt4MaemoTarget * const target
591             = qobject_cast<AbstractDebBasedQt4MaemoTarget *>(t);
592         if (target) {
593             if (!target->setPackageManagerNameInternal(name, error))
594                 success = false;
595         }
596     }
597     return success;
598 }
599
600 bool AbstractDebBasedQt4MaemoTarget::setPackageManagerNameInternal(const QString &name,
601     QString *error)
602 {
603     Q_UNUSED(error);
604     return setControlFieldValue(packageManagerNameFieldName(), name.toUtf8());
605 }
606
607 QString AbstractDebBasedQt4MaemoTarget::shortDescription() const
608 {
609     return QString::fromUtf8(controlFileFieldValue(ShortDescriptionFieldName, false));
610 }
611
612 QString AbstractDebBasedQt4MaemoTarget::packageFileName() const
613 {
614     return QString::fromUtf8(controlFileFieldValue(PackageFieldName, false))
615         + QLatin1Char('_') + projectVersion() + QLatin1String("_armel.deb");
616 }
617
618 bool AbstractDebBasedQt4MaemoTarget::setShortDescriptionInternal(const QString &description)
619 {
620     return setControlFieldValue(ShortDescriptionFieldName, description.toUtf8());
621 }
622
623 QString AbstractDebBasedQt4MaemoTarget::debianDirPath() const
624 {
625     return project()->projectDirectory() + QLatin1Char('/') + PackagingDirName
626         + QLatin1Char('/') + debianDirName();
627 }
628
629 QStringList AbstractDebBasedQt4MaemoTarget::debianFiles() const
630 {
631     return QDir(debianDirPath())
632         .entryList(QDir::Files, QDir::Name | QDir::IgnoreCase);
633 }
634
635 QString AbstractDebBasedQt4MaemoTarget::changeLogFilePath() const
636 {
637     return debianDirPath() + QLatin1String("/changelog");
638 }
639
640 QString AbstractDebBasedQt4MaemoTarget::controlFilePath() const
641 {
642     return debianDirPath() + QLatin1String("/control");
643 }
644
645 QString AbstractDebBasedQt4MaemoTarget::rulesFilePath() const
646 {
647     return debianDirPath() + QLatin1String("/rules");
648 }
649
650 QByteArray AbstractDebBasedQt4MaemoTarget::controlFileFieldValue(const QString &key,
651     bool multiLine) const
652 {
653     QByteArray value;
654     QFile controlFile(controlFilePath());
655     if (!controlFile.open(QIODevice::ReadOnly))
656         return value;
657     const QByteArray &contents = controlFile.readAll();
658     const int keyPos = contents.indexOf(key.toUtf8() + ':');
659     if (keyPos == -1)
660         return value;
661     int valueStartPos = keyPos + key.length() + 1;
662     int valueEndPos = contents.indexOf('\n', keyPos);
663     if (valueEndPos == -1)
664         valueEndPos = contents.count();
665     value = contents.mid(valueStartPos, valueEndPos - valueStartPos).trimmed();
666     if (multiLine) {
667         Q_FOREVER {
668             valueStartPos = valueEndPos + 1;
669             if (valueStartPos >= contents.count())
670                 break;
671             const char firstChar = contents.at(valueStartPos);
672             if (firstChar == '#' || isspace(firstChar)) {
673                 valueEndPos = contents.indexOf('\n', valueStartPos);
674                 if (valueEndPos == -1)
675                     valueEndPos = contents.count();
676                 if (firstChar != '#') {
677                     value += contents.mid(valueStartPos,
678                         valueEndPos - valueStartPos).trimmed();
679                 }
680             } else {
681                 break;
682             }
683         }
684     }
685     return value;
686 }
687
688 bool AbstractDebBasedQt4MaemoTarget::setControlFieldValue(const QByteArray &fieldName,
689     const QByteArray &fieldValue)
690 {
691     QFile controlFile(controlFilePath());
692     MaemoGlobal::FileUpdate update(controlFile.fileName());
693     if (!controlFile.open(QIODevice::ReadWrite))
694         return false;
695     QByteArray contents = controlFile.readAll();
696     if (adaptControlFileField(contents, fieldName, fieldValue)) {
697         controlFile.resize(0);
698         controlFile.write(contents);
699     }
700     return true;
701 }
702
703 bool AbstractDebBasedQt4MaemoTarget::adaptControlFileField(QByteArray &document,
704     const QByteArray &fieldName, const QByteArray &newFieldValue)
705 {
706     return adaptTagValue(document, fieldName, newFieldValue, true);
707 }
708
709 void AbstractDebBasedQt4MaemoTarget::handleTargetAddedSpecial()
710 {
711     if (controlFileFieldValue(IconFieldName, true).isEmpty()) {
712         // Such a file is created by the mobile wizards.
713         const QString iconPath = project()->projectDirectory()
714             + QLatin1Char('/') + project()->displayName()
715             + QLatin1String(".png");
716         if (QFileInfo(iconPath).exists())
717             setPackageManagerIcon(iconPath);
718     }
719     m_filesWatcher->addPath(debianDirPath());
720     m_filesWatcher->addPath(changeLogFilePath());
721     m_filesWatcher->addPath(controlFilePath());
722     connect(m_filesWatcher, SIGNAL(directoryChanged(QString)), this,
723         SLOT(handleDebianDirContentsChanged()));
724     connect(m_filesWatcher, SIGNAL(fileChanged(QString)), this,
725         SLOT(handleDebianFileChanged(QString)));
726     handleDebianDirContentsChanged();
727     handleDebianFileChanged(changeLogFilePath());
728     handleDebianFileChanged(controlFilePath());
729 }
730
731 bool AbstractDebBasedQt4MaemoTarget::targetCanBeRemoved() const
732 {
733     return QFileInfo(debianDirPath()).exists();
734 }
735
736 void AbstractDebBasedQt4MaemoTarget::removeTarget()
737 {
738     QString error;
739     MaemoGlobal::removeRecursively(debianDirPath(), error);
740 }
741
742 void AbstractDebBasedQt4MaemoTarget::handleDebianFileChanged(const QString &filePath)
743 {
744     if (filePath == changeLogFilePath())
745         emit changeLogChanged();
746     else if (filePath == controlFilePath())
747         emit controlChanged();
748 }
749
750 void AbstractDebBasedQt4MaemoTarget::handleDebianDirContentsChanged()
751 {
752     emit debianDirContentsChanged();
753 }
754
755 AbstractQt4MaemoTarget::ActionStatus AbstractDebBasedQt4MaemoTarget::createSpecialTemplates()
756 {
757     if (QFileInfo(debianDirPath()).exists())
758         return NoActionRequired;
759     QDir projectDir(project()->projectDirectory());
760     QProcess dh_makeProc;
761     QString error;
762     const Qt4BuildConfiguration * const bc = activeBuildConfiguration();
763     MaemoPackageCreationStep::preparePackagingProcess(&dh_makeProc, bc,
764         projectDir.path() + QLatin1Char('/') + PackagingDirName);
765     const QString dhMakeDebianDir = projectDir.path() + QLatin1Char('/')
766         + PackagingDirName + QLatin1String("/debian");
767     MaemoGlobal::removeRecursively(dhMakeDebianDir, error);
768     const QStringList dh_makeArgs = QStringList() << QLatin1String("dh_make")
769         << QLatin1String("-s") << QLatin1String("-n") << QLatin1String("-p")
770         << (defaultPackageFileName() + QLatin1Char('_')
771             + MaemoPackageCreationStep::DefaultVersionNumber);
772     if (!MaemoGlobal::callMad(dh_makeProc, dh_makeArgs, activeBuildConfiguration()->qtVersion(), true)
773             || !dh_makeProc.waitForStarted()) {
774         raiseError(tr("Unable to create Debian templates: dh_make failed (%1)")
775             .arg(dh_makeProc.errorString()));
776         return ActionFailed;
777     }
778     dh_makeProc.write("\n"); // Needs user input.
779     dh_makeProc.waitForFinished(-1);
780     if (dh_makeProc.error() != QProcess::UnknownError
781         || dh_makeProc.exitCode() != 0) {
782         raiseError(tr("Unable to create debian templates: dh_make failed (%1)")
783             .arg(dh_makeProc.errorString()));
784         return ActionFailed;
785     }
786
787     if (!QFile::rename(dhMakeDebianDir, debianDirPath())) {
788         raiseError(tr("Unable to move new debian directory to '%1'.")
789             .arg(QDir::toNativeSeparators(debianDirPath())));
790         MaemoGlobal::removeRecursively(dhMakeDebianDir, error);
791         return ActionFailed;
792     }
793
794     QDir debianDir(debianDirPath());
795     const QStringList &files = debianDir.entryList(QDir::Files);
796     foreach (const QString &fileName, files) {
797         if (fileName.endsWith(QLatin1String(".ex"), Qt::CaseInsensitive)
798             || fileName.compare(QLatin1String("README.debian"), Qt::CaseInsensitive) == 0
799             || fileName.compare(QLatin1String("dirs"), Qt::CaseInsensitive) == 0
800             || fileName.compare(QLatin1String("docs"), Qt::CaseInsensitive) == 0) {
801             debianDir.remove(fileName);
802         }
803     }
804
805     return adaptRulesFile() && adaptControlFile()
806         ? ActionSuccessful : ActionFailed;
807 }
808
809 bool AbstractDebBasedQt4MaemoTarget::adaptRulesFile()
810 {
811     QFile rulesFile(rulesFilePath());
812     if (!rulesFile.open(QIODevice::ReadWrite)) {
813         raiseError(tr("Packaging Error: Cannot open file '%1'.")
814                    .arg(QDir::toNativeSeparators(rulesFilePath())));
815         return false;
816     }
817     QByteArray rulesContents = rulesFile.readAll();
818     const QByteArray comment("# Uncomment this line for use without Qt Creator");
819     rulesContents.replace("DESTDIR", "INSTALL_ROOT");
820     rulesContents.replace("dh_shlibdeps", "# dh_shlibdeps " + comment);
821     rulesContents.replace("# Add here commands to configure the package.",
822         "# qmake PREFIX=/usr" + comment);
823     rulesContents.replace("$(MAKE)\n", "# $(MAKE) " + comment + '\n');
824
825     // Would be the right solution, but does not work (on Windows),
826     // because dpkg-genchanges doesn't know about it (and can't be told).
827     // rulesContents.replace("dh_builddeb", "dh_builddeb --destdir=.");
828
829     rulesFile.resize(0);
830     rulesFile.write(rulesContents);
831     rulesFile.close();
832     if (rulesFile.error() != QFile::NoError) {
833         raiseError(tr("Packaging Error: Cannot write file '%1'.")
834                    .arg(QDir::toNativeSeparators(rulesFilePath())));
835         return false;
836     }
837     return true;
838 }
839
840 bool AbstractDebBasedQt4MaemoTarget::adaptControlFile()
841 {
842     QFile controlFile(controlFilePath());
843     if (!controlFile.open(QIODevice::ReadWrite)) {
844         raiseError(tr("Packaging Error: Cannot open file '%1'.")
845                    .arg(QDir::toNativeSeparators(controlFilePath())));
846         return false;
847     }
848
849     QByteArray controlContents = controlFile.readAll();
850
851     adaptControlFileField(controlContents, "Section", "user/hidden");
852     adaptControlFileField(controlContents, "Priority", "optional");
853     adaptControlFileField(controlContents, packageManagerNameFieldName(),
854         project()->displayName().toUtf8());
855     const int buildDependsOffset = controlContents.indexOf("Build-Depends:");
856     if (buildDependsOffset == -1) {
857         qDebug("Unexpected: no Build-Depends field in debian control file.");
858     } else {
859         int buildDependsNewlineOffset
860             = controlContents.indexOf('\n', buildDependsOffset);
861         if (buildDependsNewlineOffset == -1) {
862             controlContents += '\n';
863             buildDependsNewlineOffset = controlContents.length() - 1;
864         }
865         controlContents.insert(buildDependsNewlineOffset,
866             ", libqt4-dev");
867     }
868
869     addAdditionalControlFileFields(controlContents);
870     controlFile.resize(0);
871     controlFile.write(controlContents);
872     controlFile.close();
873     if (controlFile.error() != QFile::NoError) {
874         raiseError(tr("Packaging Error: Cannot write file '%1'.")
875                    .arg(QDir::toNativeSeparators(controlFilePath())));
876         return false;
877     }
878     return true;
879 }
880
881 bool AbstractDebBasedQt4MaemoTarget::initAdditionalPackagingSettingsFromOtherTarget()
882 {
883     foreach (const Target * const t, project()->targets()) {
884         const AbstractDebBasedQt4MaemoTarget *target
885             = qobject_cast<const AbstractDebBasedQt4MaemoTarget *>(t);
886         if (target && target != this) {
887             return setControlFieldValue(IconFieldName,
888                 target->controlFileFieldValue(IconFieldName, true));
889         }
890     }
891     return true;
892 }
893
894 QStringList AbstractDebBasedQt4MaemoTarget::packagingFilePaths() const
895 {
896     QStringList filePaths;
897     const QString parentDir = debianDirPath();
898     foreach (const QString &fileName, debianFiles())
899         filePaths << parentDir + QLatin1Char('/') + fileName;
900     return filePaths;
901 }
902
903 QString AbstractDebBasedQt4MaemoTarget::defaultPackageFileName() const
904 {
905     QString packageName = project()->displayName().toLower();
906
907     // We also replace dots, because OVI store chokes on them.
908     const QRegExp legalLetter(QLatin1String("[a-z0-9+-]"), Qt::CaseSensitive,
909         QRegExp::WildcardUnix);
910
911     for (int i = 0; i < packageName.length(); ++i) {
912         if (!legalLetter.exactMatch(packageName.mid(i, 1)))
913             packageName[i] = QLatin1Char('-');
914     }
915     return packageName;
916 }
917
918 bool AbstractDebBasedQt4MaemoTarget::setPackageManagerIcon(const QString &iconFilePath,
919     QString *error)
920 {
921     bool success = true;
922     foreach (Target * const target, project()->targets()) {
923         AbstractDebBasedQt4MaemoTarget* const maemoTarget
924             = qobject_cast<AbstractDebBasedQt4MaemoTarget*>(target);
925         if (maemoTarget) {
926             if (!maemoTarget->setPackageManagerIconInternal(iconFilePath, error))
927                 success = false;
928         }
929     }
930     return success;
931 }
932
933
934 AbstractRpmBasedQt4MaemoTarget::AbstractRpmBasedQt4MaemoTarget(Qt4Project *parent,
935     const QString &id) : AbstractQt4MaemoTarget(parent, id)
936 {
937 }
938
939 AbstractRpmBasedQt4MaemoTarget::~AbstractRpmBasedQt4MaemoTarget()
940 {
941 }
942
943 QString AbstractRpmBasedQt4MaemoTarget::specFilePath() const
944 {
945     const QLatin1Char sep('/');
946     return project()->projectDirectory() + sep + PackagingDirName + sep
947         + specFileName();
948 }
949
950 QString AbstractRpmBasedQt4MaemoTarget::projectVersion(QString *error) const
951 {
952     return QString::fromUtf8(getValueForTag(VersionTag, error));
953 }
954
955 bool AbstractRpmBasedQt4MaemoTarget::setProjectVersionInternal(const QString &version,
956     QString *error)
957 {
958     return setValueForTag(VersionTag, version.toUtf8(), error);
959 }
960
961 QString AbstractRpmBasedQt4MaemoTarget::packageName() const
962 {
963     return QString::fromUtf8(getValueForTag(NameTag, 0));
964 }
965
966 bool AbstractRpmBasedQt4MaemoTarget::setPackageNameInternal(const QString &name)
967 {
968     return setValueForTag(NameTag, name.toUtf8(), 0);
969 }
970
971 QString AbstractRpmBasedQt4MaemoTarget::shortDescription() const
972 {
973     return QString::fromUtf8(getValueForTag(SummaryTag, 0));
974 }
975
976 QString AbstractRpmBasedQt4MaemoTarget::packageFileName() const
977 {
978     return packageName() + QLatin1Char('-') + projectVersion() + QLatin1Char('-')
979         + QString::fromUtf8(getValueForTag(ReleaseTag, 0)) + QLatin1Char('.')
980         + MaemoGlobal::architecture(activeBuildConfiguration()->qtVersion())
981         + QLatin1String(".rpm");
982 }
983
984 bool AbstractRpmBasedQt4MaemoTarget::setShortDescriptionInternal(const QString &description)
985 {
986     return setValueForTag(SummaryTag, description.toUtf8(), 0);
987 }
988
989 AbstractQt4MaemoTarget::ActionStatus AbstractRpmBasedQt4MaemoTarget::createSpecialTemplates()
990 {
991     if (QFileInfo(specFilePath()).exists())
992         return NoActionRequired;
993     QSharedPointer<QFile> specFile
994         = openFile(specFilePath(), QIODevice::WriteOnly, 0);
995     if (!specFile)
996         return ActionFailed;
997     QByteArray initialContent(
998         "Name: %%name%%\n"
999         "Summary: <insert short description here>\n"
1000         "Version: 0.0.1\n"
1001         "Release: 1\n"
1002         "License: <Enter your application's license here>\n"
1003         "Group: <Set your application's group here>\n"
1004         "%description\n"
1005         "<Insert longer, multi-line description\n"
1006         "here.>\n"
1007         "\n"
1008         "%prep\n"
1009         "%setup -q\n"
1010         "\n"
1011         "%build\n"
1012         "# You can leave this empty for use with Qt Creator."
1013         "\n"
1014         "%install\n"
1015         "rm -rf %{buildroot}\n"
1016         "make INSTALL_ROOT=%{buildroot} install\n"
1017         "\n"
1018         "%clean\n"
1019         "rm -rf %{buildroot}\n"
1020         "\n"
1021         "BuildRequires: \n"
1022         "# %define _unpackaged_files_terminate_build 0\n"
1023         "%files\n"
1024         "%defattr(-,root,root,-)"
1025         "/usr\n"
1026         "/opt\n"
1027         "# Add additional files to be included in the package here.\n"
1028         "%pre\n"
1029         "# Add pre-install scripts here."
1030         "%post\n"
1031         "/sbin/ldconfig # For shared libraries\n"
1032         "%preun\n"
1033         "# Add pre-uninstall scripts here."
1034         "%postun\n"
1035         "# Add post-uninstall scripts here."
1036         );
1037     initialContent.replace("%%name%%", project()->displayName().toUtf8());
1038     return specFile->write(initialContent) == initialContent.count()
1039         ? ActionSuccessful : ActionFailed;
1040 }
1041
1042 void AbstractRpmBasedQt4MaemoTarget::handleTargetAddedSpecial()
1043 {
1044     m_filesWatcher->addPath(specFilePath());
1045     connect(m_filesWatcher, SIGNAL(fileChanged(QString)), this,
1046         SIGNAL(specFileChanged()));
1047 }
1048
1049 bool AbstractRpmBasedQt4MaemoTarget::targetCanBeRemoved() const
1050 {
1051     return QFileInfo(specFilePath()).exists();
1052 }
1053
1054 void AbstractRpmBasedQt4MaemoTarget::removeTarget()
1055 {
1056     QFile::remove(specFilePath());
1057 }
1058
1059 bool AbstractRpmBasedQt4MaemoTarget::initAdditionalPackagingSettingsFromOtherTarget()
1060 {
1061     // Nothing to do here for now.
1062     return true;
1063 }
1064
1065 QByteArray AbstractRpmBasedQt4MaemoTarget::getValueForTag(const QByteArray &tag,
1066     QString *error) const
1067 {
1068     QSharedPointer<QFile> specFile
1069         = openFile(specFilePath(), QIODevice::ReadOnly, error);
1070     if (!specFile)
1071         return QByteArray();
1072     const QByteArray &content = specFile->readAll();
1073     const QByteArray completeTag = tag.toLower() + ':';
1074     int index = content.toLower().indexOf(completeTag);
1075     if (index == -1)
1076         return QByteArray();
1077     index += completeTag.count();
1078     int endIndex = content.indexOf('\n', index);
1079     if (endIndex == -1)
1080         endIndex = content.count();
1081     return content.mid(index, endIndex - index).trimmed();
1082 }
1083
1084 bool AbstractRpmBasedQt4MaemoTarget::setValueForTag(const QByteArray &tag,
1085     const QByteArray &value, QString *error)
1086 {
1087     QSharedPointer<QFile> specFile
1088         = openFile(specFilePath(), QIODevice::ReadWrite, error);
1089     if (!specFile)
1090         return false;
1091     QByteArray content = specFile->readAll();
1092     if (adaptTagValue(content, tag, value, false)) {
1093         specFile->resize(0);
1094         specFile->write(content);
1095     }
1096     return true;
1097 }
1098
1099 Qt4Maemo5Target::Qt4Maemo5Target(Qt4Project *parent, const QString &id)
1100         : AbstractDebBasedQt4MaemoTarget(parent, id)
1101 {
1102     setDisplayName(defaultDisplayName());
1103     initDeviceConfigurationsModel();
1104 }
1105
1106 Qt4Maemo5Target::~Qt4Maemo5Target() {}
1107
1108 QString Qt4Maemo5Target::defaultDisplayName()
1109 {
1110     return QApplication::translate("Qt4ProjectManager::Qt4Target", "Maemo5",
1111         "Qt4 Maemo5 target display name");
1112 }
1113
1114 void Qt4Maemo5Target::addAdditionalControlFileFields(QByteArray &controlContents)
1115 {
1116     Q_UNUSED(controlContents);
1117 }
1118
1119 QString Qt4Maemo5Target::debianDirName() const
1120 {
1121     return QLatin1String("debian_fremantle");
1122 }
1123
1124 QByteArray Qt4Maemo5Target::packageManagerNameFieldName() const
1125 {
1126     return "XB-Maemo-Display-Name";
1127 }
1128
1129 Qt4HarmattanTarget::Qt4HarmattanTarget(Qt4Project *parent, const QString &id)
1130         : AbstractDebBasedQt4MaemoTarget(parent, id)
1131 {
1132     setDisplayName(defaultDisplayName());
1133     initDeviceConfigurationsModel();
1134 }
1135
1136 Qt4HarmattanTarget::~Qt4HarmattanTarget() {}
1137
1138 QString Qt4HarmattanTarget::defaultDisplayName()
1139 {
1140     return QApplication::translate("Qt4ProjectManager::Qt4Target", "Harmattan",
1141         "Qt4 Harmattan target display name");
1142 }
1143
1144 void Qt4HarmattanTarget::addAdditionalControlFileFields(QByteArray &controlContents)
1145 {
1146     adaptControlFileField(controlContents, "XB-Meego-Desktop-Entry", "");
1147     adaptControlFileField(controlContents, "XB-MeeGo-Desktop-Entry-Filename", "");
1148 }
1149
1150 QString Qt4HarmattanTarget::debianDirName() const
1151 {
1152     return QLatin1String("debian_harmattan");
1153 }
1154
1155 QByteArray Qt4HarmattanTarget::packageManagerNameFieldName() const
1156 {
1157     return "XSBC-Maemo-Display-Name";
1158 }
1159
1160
1161 Qt4MeegoTarget::Qt4MeegoTarget(Qt4Project *parent, const QString &id)
1162        : AbstractRpmBasedQt4MaemoTarget(parent, id)
1163 {
1164     setDisplayName(defaultDisplayName());
1165     initDeviceConfigurationsModel();
1166 }
1167
1168 Qt4MeegoTarget::~Qt4MeegoTarget() {}
1169
1170 QString Qt4MeegoTarget::defaultDisplayName()
1171 {
1172     return QApplication::translate("Qt4ProjectManager::Qt4Target",
1173         "Meego", "Qt4 Meego target display name");
1174 }
1175
1176 QString Qt4MeegoTarget::specFileName() const
1177 {
1178     return QLatin1String("meego.spec");
1179 }