Newer
Older
/**************************************************************************
**
** 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 "maemodeployablelistmodel.h"
#include "maemodeployables.h"
#include "maemodeploystep.h"
#include "maemoglobal.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 PackagingDirName = ("maemopackaging");
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))
return;
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)
handleTarget(target);
bool MaemoTemplatesManager::handleTarget(ProjectExplorer::Target *target)
{
if (!target
|| target->id() != QLatin1String(Constants::MAEMO_DEVICE_TARGET_ID))
return false;
if (!createDebianTemplatesIfNecessary(target))
return false;
const Qt4Target * const qt4Target = qobject_cast<Qt4Target *>(target);
const MaemoDeployStep * const deployStep
= MaemoGlobal::buildStep<MaemoDeployStep>(qt4Target->activeDeployConfiguration());
connect(deployStep->deployables(), SIGNAL(modelsCreated()), this,
SLOT(handleProFileUpdated()), Qt::QueuedConnection);
Project * const project = target->project();
if (m_maemoProjects.contains(project))
return true;
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));
m_maemoProjects.insert(project, fsWatcher);
bool MaemoTemplatesManager::createDebianTemplatesIfNecessary(const ProjectExplorer::Target *target)
{
Project * const project = target->project();
QDir projectDir(project->projectDirectory());
if (projectDir.exists(PackagingDirName))
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)));
bool MaemoTemplatesManager::updateDesktopFiles(const Qt4Target *target)
{
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();
bool success = true;
foreach (Qt4ProFileNode *proFileNode, applicationProjects)
success &= updateDesktopFile(qt4Target, proFileNode);
return success;
}
bool MaemoTemplatesManager::updateDesktopFile(const Qt4Target *target,
Qt4ProFileNode *proFileNode)
{
const QString appName = proFileNode->targetInformation().target;
const QString desktopFilePath = QFileInfo(proFileNode->path()).path()
+ QLatin1Char('/') + appName + QLatin1String(".desktop");
QFile desktopFile(desktopFilePath);
const bool existsAlready = desktopFile.exists();
if (!desktopFile.open(QIODevice::ReadWrite)) {
qWarning("Failed to open '%s': %s", qPrintable(desktopFilePath),
qPrintable(desktopFile.errorString()));
return false;
const QByteArray desktopTemplate("[Desktop Entry]\nEncoding=UTF-8\n"
"Version=1.0\nType=Application\nTerminal=false\nName=\nExec=\n"
"Icon=\nX-Window-Icon=\nX-HildonDesk-ShowInToolbar=true\n"
"X-Osso-Type=application/x-executable\n");
QByteArray contents
= existsAlready ? desktopFile.readAll() : desktopTemplate;
QString executable;
const MaemoDeployables * const deployables
= MaemoGlobal::buildStep<MaemoDeployStep>(target->activeDeployConfiguration())
->deployables();
for (int i = 0; i < deployables->modelCount(); ++i) {
const MaemoDeployableListModel * const model = deployables->modelAt(i);
if (model->proFileNode() == proFileNode) {
executable = model->remoteExecutableFilePath();
break;
if (executable.isEmpty()) {
qWarning("Strange: Project file node not managed by MaemoDeployables.");
} else {
int execNewLinePos, execValuePos;
findLine("Exec=", contents, execNewLinePos, execValuePos);
contents.replace(execValuePos, execNewLinePos - execValuePos,
executable.toUtf8());
int nameNewLinePos, nameValuePos;
findLine("Name=", contents, nameNewLinePos, nameValuePos);
if (nameNewLinePos == nameValuePos)
contents.insert(nameValuePos, appName.toUtf8());
desktopFile.resize(0);
desktopFile.write(contents);
desktopFile.close();
if (desktopFile.error() != QFile::NoError) {
qWarning("Could not write '%s': %s", qPrintable(desktopFilePath),
qPrintable(desktopFile.errorString()));
if (!existsAlready) {
proFileNode->addFiles(UnknownFileType,
QStringList() << desktopFilePath);
}
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()
const MaemoDeployables * const deployables
= qobject_cast<MaemoDeployables *>(sender());
if (!deployables)
return;
const Target * const target = deployables->buildStep()->target();
if (m_maemoProjects.contains(target->project()))
updateDesktopFiles(qobject_cast<const Qt4Target *>(target));
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")
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
458
459
.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;
}
void MaemoTemplatesManager::findLine(const QByteArray &string,
QByteArray &document, int &lineEndPos, int &valuePos)
int lineStartPos = document.indexOf(string);
if (lineStartPos == -1) {
lineStartPos = document.length();
document += string + '\n';
}
valuePos = lineStartPos + string.length();
lineEndPos = document.indexOf('\n', lineStartPos);
if (lineEndPos == -1) {
lineEndPos = document.length();
document += '\n';
} // namespace Internal
} // namespace Qt4ProjectManager