Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at qt-sales@nokia.com.
**
**************************************************************************/
#include "maemotemplatesmanager.h"
#include "maemopackagecreationstep.h"
#include "maemotoolchain.h"
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/session.h>
#include <projectexplorer/target.h>
#include <qt4projectmanager/qt4nodes.h>
#include <qt4projectmanager/qt4project.h>
#include <qt4projectmanager/qt4projectmanagerconstants.h>
#include <qt4projectmanager/qt4target.h>
#include <QtCore/QFileSystemWatcher>
#include <QtCore/QProcess>
#include <QtGui/QMessageBox>
using namespace ProjectExplorer;
namespace Qt4ProjectManager {
namespace Internal {
namespace {
const QByteArray IconFieldName("XB-Maemo-Icon-26:");
const QString MaemoDirName("maemo");
const QString PackagingDirName = MaemoDirName + QLatin1Char('/') + ("packaging");
MaemoTemplatesManager *MaemoTemplatesManager::m_instance = 0;
MaemoTemplatesManager *MaemoTemplatesManager::instance(QObject *parent)
{
Q_ASSERT(!m_instance != !parent);
if (!m_instance)
m_instance = new MaemoTemplatesManager(parent);
return m_instance;
}
MaemoTemplatesManager::MaemoTemplatesManager(QObject *parent) : QObject(parent)
{
SessionManager * const session
= ProjectExplorerPlugin::instance()->session();
connect(session, SIGNAL(startupProjectChanged(ProjectExplorer::Project*)),
this, SLOT(handleActiveProjectChanged(ProjectExplorer::Project*)));
connect(session, SIGNAL(aboutToRemoveProject(ProjectExplorer::Project*)),
this, SLOT(handleProjectToBeRemoved(ProjectExplorer::Project*)));
handleActiveProjectChanged(session->startupProject());
}
void MaemoTemplatesManager::handleActiveProjectChanged(ProjectExplorer::Project *project)
{
if (project && !m_maemoProjects.contains(project)) {
connect(project, SIGNAL(addedTarget(ProjectExplorer::Target*)),
this, SLOT(handleTarget(ProjectExplorer::Target*)));
connect(project, SIGNAL(activeTargetChanged(ProjectExplorer::Target*)),
this, SLOT(handleTarget(ProjectExplorer::Target*)));
const QList<Target *> &targets = project->targets();
foreach (Target * const target, targets) {
if (handleTarget(target)) {
QFileSystemWatcher * const fsWatcher
= new QFileSystemWatcher(this);
fsWatcher->addPath(debianDirPath(project));
fsWatcher->addPath(changeLogFilePath(project));
fsWatcher->addPath(controlFilePath(project));
connect(fsWatcher, SIGNAL(directoryChanged(QString)), this,
SLOT(handleDebianDirContentsChanged()));
connect(fsWatcher, SIGNAL(fileChanged(QString)), this,
SLOT(handleDebianFileChanged(QString)));
handleDebianDirContentsChanged();
handleDebianFileChanged(changeLogFilePath(project));
handleDebianFileChanged(controlFilePath(project));
connect(qobject_cast<Qt4Project *>(project),
SIGNAL(proFileUpdated(Qt4ProjectManager::Internal::Qt4ProFileNode*)),
this, SLOT(handleProFileUpdated(Qt4ProjectManager::Internal::Qt4ProFileNode*)),
Qt::QueuedConnection);
m_maemoProjects.insert(project, fsWatcher);
}
}
bool MaemoTemplatesManager::handleTarget(ProjectExplorer::Target *target)
{
if (!target
|| target->id() != QLatin1String(Constants::MAEMO_DEVICE_TARGET_ID))
return false;
if (!createDebianTemplatesIfNecessary(target))
return false;
if (!syncDesktopFiles(target))
return false;
return true;
bool MaemoTemplatesManager::createDebianTemplatesIfNecessary(const ProjectExplorer::Target *target)
{
Project * const project = target->project();
QDir projectDir(project->projectDirectory());
if (projectDir.exists(MaemoDirName))
const QString packagingTemplatesDir
= projectDir.path() + QLatin1Char('/') + PackagingDirName;
if (!projectDir.mkpath(PackagingDirName)) {
raiseError(tr("Could not create directory '%1'.")
.arg(QDir::toNativeSeparators(packagingTemplatesDir)));
}
QProcess dh_makeProc;
QString error;
const Qt4Target * const qt4Target = qobject_cast<const Qt4Target *>(target);
Q_ASSERT_X(qt4Target, Q_FUNC_INFO, "Target ID does not match actual type.");
const MaemoToolChain * const tc
= dynamic_cast<MaemoToolChain *>(qt4Target->activeBuildConfiguration()->toolChain());
Q_ASSERT_X(tc, Q_FUNC_INFO, "Maemo target has no maemo toolchain.");
if (!MaemoPackageCreationStep::preparePackagingProcess(&dh_makeProc, tc,
packagingTemplatesDir, &error)) {
raiseError(error);
}
const QString command = QLatin1String("dh_make -s -n -p ")
+ MaemoPackageCreationStep::packageName(project) + QLatin1Char('_')
+ MaemoPackageCreationStep::DefaultVersionNumber;
dh_makeProc.start(MaemoPackageCreationStep::packagingCommand(tc, command));
if (!dh_makeProc.waitForStarted()) {
raiseError(tr("Unable to create debian templates: dh_make failed (%1)")
.arg(dh_makeProc.errorString()));
}
dh_makeProc.write("\n"); // Needs user input.
dh_makeProc.waitForFinished(-1);
if (dh_makeProc.error() != QProcess::UnknownError
|| dh_makeProc.exitCode() != 0) {
raiseError(tr("Unable to create debian templates: dh_make failed (%1)")
.arg(dh_makeProc.errorString()));
}
QDir debianDir(packagingTemplatesDir + QLatin1String("/debian"));
const QStringList &files = debianDir.entryList(QDir::Files);
QStringList filesToAddToProject;
foreach (const QString &fileName, files) {
if (fileName.endsWith(QLatin1String(".ex"), Qt::CaseInsensitive)
|| fileName.compare(QLatin1String("README.debian"), Qt::CaseInsensitive) == 0
|| fileName.compare(QLatin1String("dirs"), Qt::CaseInsensitive) == 0
|| fileName.compare(QLatin1String("docs"), Qt::CaseInsensitive) == 0) {
debianDir.remove(fileName);
} else
filesToAddToProject << debianDir.absolutePath()
+ QLatin1Char('/') + fileName;
}
qobject_cast<Qt4Project *>(project)->rootProjectNode()
->addFiles(UnknownFileType, filesToAddToProject);
const QString rulesFilePath
= packagingTemplatesDir + QLatin1String("/debian/rules");
QFile rulesFile(rulesFilePath);
if (!rulesFile.open(QIODevice::ReadWrite)) {
raiseError(tr("Packaging Error: Cannot open file '%1'.")
.arg(QDir::toNativeSeparators(rulesFilePath)));
}
QByteArray rulesContents = rulesFile.readAll();
rulesContents.replace("DESTDIR", "INSTALL_ROOT");
rulesContents.replace("dh_shlibdeps", "# dh_shlibdeps");
// Would be the right solution, but does not work (on Windows),
// because dpkg-genchanges doesn't know about it (and can't be told).
// rulesContents.replace("dh_builddeb", "dh_builddeb --destdir=.");
rulesFile.resize(0);
rulesFile.write(rulesContents);
if (rulesFile.error() != QFile::NoError) {
raiseError(tr("Packaging Error: Cannot write file '%1'.")
.arg(QDir::toNativeSeparators(rulesFilePath)));
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
bool MaemoTemplatesManager::syncDesktopFiles(const ProjectExplorer::Target *target)
{
const QByteArray desktopTemplate("[Desktop Entry]\nEncoding=UTF-8\n"
"Version=1.0\nType=Application\nTerminal=false\nName=topx\nExec=\n"
"Icon=\nX-Window-Icon=\nX-HildonDesk-ShowInToolbar=true\n"
"X-Osso-Type=application/x-executable\n");
const Qt4Target * const qt4Target = qobject_cast<const Qt4Target *>(target);
Q_ASSERT_X(qt4Target, Q_FUNC_INFO,
"Impossible: Target has Maemo id, but could not be cast to Qt4Target.");
const QList<Qt4ProFileNode *> &applicationProjects
= qt4Target->qt4Project()->applicationProFiles();
QList<QString> applicationProjectNames;
foreach (const Qt4ProFileNode * const subProject, applicationProjects)
applicationProjectNames << subProject->displayName();
const QString maemoDirPath = target->project()->projectDirectory()
+ QLatin1Char('/') + MaemoDirName;
QDir maemoDir(maemoDirPath);
const QStringList &desktopFiles
= maemoDir.entryList(QStringList() << QLatin1String("*.desktop"),
QDir::Files);
QStringList filesToAdd;
QStringList filesToRemove;
// Step 1: Remove all desktop files that refer to projects that don't exist anymore.
foreach (const QString &desktopFile, desktopFiles) {
const QString projectName = QFileInfo(desktopFile).completeBaseName();
if (!applicationProjectNames.contains(projectName)) {
const QString &absFilePath
= maemoDirPath + QLatin1Char('/') + desktopFile;
if (!QFile::remove(absFilePath)) {
qWarning("Failed to remove outdated project file %s.",
qPrintable(absFilePath));
}
filesToRemove << absFilePath;
}
}
// Step 2: Create a desktop file for every project that doesn't have one.
foreach (const QString &projectName, applicationProjectNames) {
const QString &desktopFileName
= projectName + QLatin1String(".desktop");
if (!desktopFiles.contains(desktopFileName)) {
const QString &absFilePath
= maemoDirPath + QLatin1Char('/') + desktopFileName;
QFile desktopFile(absFilePath);
if (!desktopFile.open(QIODevice::WriteOnly)) {
qWarning("Failed to create desktop file %s",
qPrintable(absFilePath));
continue;
}
filesToAdd << absFilePath;
if (desktopFile.write(desktopTemplate) != desktopTemplate.length()) {
qWarning("Failed to write to desktop file %s",
qPrintable(absFilePath));
continue;
}
}
}
if (!filesToAdd.isEmpty()) {
qt4Target->qt4Project()->rootProjectNode()
->addFiles(UnknownFileType, filesToAdd);
}
if (!filesToRemove.isEmpty()) {
qt4Target->qt4Project()->rootProjectNode()
->removeFiles(UnknownFileType, filesToRemove);
}
// TODO: Step 3: update the "Name" and "Exec" entries of all desktop files (name = subproject displayname, path = rhs of deployable)
return true;
}
void MaemoTemplatesManager::handleProjectToBeRemoved(ProjectExplorer::Project *project)
{
MaemoProjectMap::Iterator it = m_maemoProjects.find(project);
if (it != m_maemoProjects.end()) {
delete it.value();
m_maemoProjects.erase(it);
}
void MaemoTemplatesManager::handleProFileUpdated(Qt4ProFileNode *proFileNode)
{
for (MaemoProjectMap::ConstIterator it = m_maemoProjects.begin();
it != m_maemoProjects.end(); ++it) {
const Qt4Project * const project = qobject_cast<Qt4Project *>(it.key());
if (isParent(project->rootProjectNode(), proFileNode)) {
// We assume here that the project configuratiion is identical for
// all Maemo targets. If that is not the case (e.g. the user has
// configured different deployment paths for Maemo5 vs. Maemo6,
// we can do things slightly wrong.
syncDesktopFiles(project->activeTarget());
break;
}
}
}
QString MaemoTemplatesManager::version(const Project *project,
QString *error) const
{
QSharedPointer<QFile> changeLog
= openFile(changeLogFilePath(project), QIODevice::ReadOnly, error);
if (!changeLog)
const QByteArray &firstLine = changeLog->readLine();
const int openParenPos = firstLine.indexOf('(');
if (openParenPos == -1) {
*error = tr("Debian changelog file '%1' has unexpected format.")
.arg(QDir::toNativeSeparators(changeLog->fileName()));
return QString();
}
const int closeParenPos = firstLine.indexOf(')', openParenPos);
if (closeParenPos == -1) {
*error = tr("Debian changelog file '%1' has unexpected format.")
.arg(QDir::toNativeSeparators(changeLog->fileName()));
return QString();
}
return QString::fromUtf8(firstLine.mid(openParenPos + 1,
closeParenPos - openParenPos - 1).data());
}
bool MaemoTemplatesManager::setVersion(const Project *project,
const QString &version, QString *error) const
{
QSharedPointer<QFile> changeLog
= openFile(changeLogFilePath(project), QIODevice::ReadWrite, error);
if (!changeLog)
QString content = QString::fromUtf8(changeLog->readAll());
content.replace(QRegExp(QLatin1String("\\([a-zA-Z0-9_\\.]+\\)")),
changeLog->resize(0);
changeLog->write(content.toUtf8());
changeLog->close();
if (changeLog->error() != QFile::NoError) {
*error = tr("Error writing Debian changelog file '%1': %2")
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
.arg(QDir::toNativeSeparators(changeLog->fileName()),
changeLog->errorString());
return false;
}
return true;
}
QIcon MaemoTemplatesManager::packageManagerIcon(const Project *project,
QString *error) const
{
QSharedPointer<QFile> controlFile
= openFile(controlFilePath(project), QIODevice::ReadOnly, error);
if (!controlFile)
return QIcon();
bool iconFieldFound = false;
QByteArray currentLine;
while (!iconFieldFound && !controlFile->atEnd()) {
currentLine = controlFile->readLine();
iconFieldFound = currentLine.startsWith(IconFieldName);
}
if (!iconFieldFound)
return QIcon();
int pos = IconFieldName.length();
currentLine = currentLine.trimmed();
QByteArray base64Icon;
do {
while (pos < currentLine.length())
base64Icon += currentLine.at(pos++);
do
currentLine = controlFile->readLine();
while (currentLine.startsWith('#'));
if (currentLine.isEmpty() || !isspace(currentLine.at(0)))
break;
currentLine = currentLine.trimmed();
if (currentLine.isEmpty())
break;
pos = 0;
} while (true);
QPixmap pixmap;
if (!pixmap.loadFromData(QByteArray::fromBase64(base64Icon))) {
*error = tr("Invalid icon data in Debian control file.");
return QIcon();
}
return QIcon(pixmap);
}
bool MaemoTemplatesManager::setPackageManagerIcon(const Project *project,
const QString &iconFilePath, QString *error) const
{
const QSharedPointer<QFile> controlFile
= openFile(controlFilePath(project), QIODevice::ReadWrite, error);
if (!controlFile)
return false;
const QPixmap pixmap(iconFilePath);
if (pixmap.isNull()) {
*error = tr("Could not read image file '%1'.").arg(iconFilePath);
return false;
}
QByteArray iconAsBase64;
QBuffer buffer(&iconAsBase64);
buffer.open(QIODevice::WriteOnly);
if (!pixmap.scaled(48, 48).save(&buffer,
QFileInfo(iconFilePath).suffix().toAscii())) {
*error = tr("Could not export image file '%1'.").arg(iconFilePath);
return false;
}
buffer.close();
iconAsBase64 = iconAsBase64.toBase64();
QByteArray contents = controlFile->readAll();
const int iconFieldPos = contents.startsWith(IconFieldName)
? 0 : contents.indexOf('\n' + IconFieldName);
if (iconFieldPos == -1) {
if (!contents.endsWith('\n'))
contents += '\n';
contents.append(IconFieldName).append(' ').append(iconAsBase64)
.append('\n');
} else {
const int oldIconStartPos
= (iconFieldPos != 0) + iconFieldPos + IconFieldName.length();
int nextEolPos = contents.indexOf('\n', oldIconStartPos);
while (nextEolPos != -1 && nextEolPos != contents.length() - 1
&& contents.at(nextEolPos + 1) != '\n'
&& (contents.at(nextEolPos + 1) == '#'
|| std::isspace(contents.at(nextEolPos + 1))))
nextEolPos = contents.indexOf('\n', nextEolPos + 1);
if (nextEolPos == -1)
nextEolPos = contents.length();
contents.replace(oldIconStartPos, nextEolPos - oldIconStartPos,
' ' + iconAsBase64);
}
controlFile->resize(0);
controlFile->write(contents);
if (controlFile->error() != QFile::NoError) {
*error = tr("Error writing file '%1': %2")
.arg(QDir::toNativeSeparators(controlFile->fileName()),
controlFile->errorString());
return false;
}
return true;
}
QStringList MaemoTemplatesManager::debianFiles(const Project *project) const
{
return QDir(debianDirPath(project))
.entryList(QDir::Files, QDir::Name | QDir::IgnoreCase);
}
QString MaemoTemplatesManager::debianDirPath(const Project *project) const
{
return project->projectDirectory() + QLatin1Char('/')
+ PackagingDirName + QLatin1String("/debian");
}
QString MaemoTemplatesManager::changeLogFilePath(const Project *project) const
{
return debianDirPath(project) + QLatin1String("/changelog");
}
QString MaemoTemplatesManager::controlFilePath(const Project *project) const
{
return debianDirPath(project) + QLatin1String("/control");
}
void MaemoTemplatesManager::raiseError(const QString &reason)
{
QMessageBox::critical(0, tr("Error creating Maemo templates"), reason);
}
void MaemoTemplatesManager::handleDebianFileChanged(const QString &filePath)
const Project * const project
= findProject(qobject_cast<QFileSystemWatcher *>(sender()));
if (project) {
if (filePath == changeLogFilePath(project))
emit changeLogChanged(project);
else if (filePath == controlFilePath(project))
emit controlChanged(project);
}
}
void MaemoTemplatesManager::handleDebianDirContentsChanged()
{
const Project * const project
= findProject(qobject_cast<QFileSystemWatcher *>(sender()));
if (project)
emit debianDirContentsChanged(project);
QSharedPointer<QFile> MaemoTemplatesManager::openFile(const QString &filePath,
QIODevice::OpenMode mode, QString *error) const
{
const QString nativePath = QDir::toNativeSeparators(filePath);
QSharedPointer<QFile> file(new QFile(filePath));
if (!file->exists()) {
*error = tr("File '%1' does not exist").arg(nativePath);
} else if (!file->open(mode)) {
*error = tr("Cannot open file '%1': %2")
.arg(nativePath, file->errorString());
}
return file;
}
Project *MaemoTemplatesManager::findProject(const QFileSystemWatcher *fsWatcher) const
{
for (MaemoProjectMap::ConstIterator it = m_maemoProjects.constBegin();
it != m_maemoProjects.constEnd(); ++it) {
if (it.value() == fsWatcher)
return it.key();
}
return 0;
}
bool MaemoTemplatesManager::isParent(const ProjectNode *parent,
const ProjectNode *child) const
{
if (parent == child)
return true;
const QList<ProjectNode *> &directChildren = parent->subProjectNodes();
foreach (const ProjectNode * directChild, directChildren) {
if (isParent(directChild, child))
return true;
}
return false;
}
} // namespace Internal
} // namespace Qt4ProjectManager