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
39
40
41
42
43
/****************************************************************************
**
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of Qt Creator.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** 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.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "maemopackagecreationstep.h"
#include "maemodeployables.h"
#include "maemodeploystep.h"
#include "maemoglobal.h"
#include "maemopackagecreationwidget.h"
#include "maemoprofilewrapper.h"
#include "maemotemplatesmanager.h"
#include <projectexplorer/projectexplorerconstants.h>
#include <qt4buildconfiguration.h>
#include <qt4project.h>
#include <qt4target.h>
#include <QtCore/QProcess>
#include <QtCore/QProcessEnvironment>
#include <QtCore/QRegExp>
namespace {
const QLatin1String PackagingEnabledKey("Packaging Enabled");
}
using namespace ProjectExplorer::Constants;
using ProjectExplorer::BuildStepConfigWidget;
namespace Qt4ProjectManager {
namespace Internal {
const QLatin1String MaemoPackageCreationStep::DefaultVersionNumber("0.0.1");
MaemoPackageCreationStep::MaemoPackageCreationStep(BuildStepList *bsl)
: ProjectExplorer::BuildStep(bsl, CreatePackageId),
MaemoPackageCreationStep::MaemoPackageCreationStep(BuildStepList *bsl,
MaemoPackageCreationStep *other)
m_packagingEnabled(other->m_packagingEnabled)
MaemoPackageCreationStep::~MaemoPackageCreationStep()
{
}
setDefaultDisplayName(tr("Packaging for Maemo"));
m_lastBuildConfig = qt4BuildConfiguration();
connect(target(),
SIGNAL(activeBuildConfigurationChanged(ProjectExplorer::BuildConfiguration*)),
this, SLOT(handleBuildConfigChanged()));
handleBuildConfigChanged();
bool MaemoPackageCreationStep::init()
{
return true;
}
QVariantMap MaemoPackageCreationStep::toMap() const
{
QVariantMap map(ProjectExplorer::BuildStep::toMap());
map.insert(PackagingEnabledKey, m_packagingEnabled);
}
bool MaemoPackageCreationStep::fromMap(const QVariantMap &map)
{
m_packagingEnabled = map.value(PackagingEnabledKey, true).toBool();
return ProjectExplorer::BuildStep::fromMap(map);
}
void MaemoPackageCreationStep::run(QFutureInterface<bool> &fi)
{
bool success;
if (m_packagingEnabled) {
QProcess * const buildProc = new QProcess;
connect(buildProc, SIGNAL(readyReadStandardOutput()), this,
SLOT(handleBuildOutput()));
connect(buildProc, SIGNAL(readyReadStandardError()), this,
SLOT(handleBuildOutput()));
success = createPackage(buildProc);
disconnect(buildProc, 0, this, 0);
buildProc->deleteLater();
} else {
success = true;
}
fi.reportResult(success);
}
BuildStepConfigWidget *MaemoPackageCreationStep::createConfigWidget()
{
return new MaemoPackageCreationWidget(this);
}
bool MaemoPackageCreationStep::createPackage(QProcess *buildProc)
if (!packagingNeeded()) {
emit addOutput(tr("Package up to date."), MessageOutput);
emit addOutput(tr("Creating package file ..."), MessageOutput);
checkProjectName();
if (!preparePackagingProcess(buildProc, maemoToolChain(), buildDirectory(),
&error)) {
const QString projectDir
= buildConfiguration()->target()->project()->projectDirectory();
const bool inSourceBuild
= QFileInfo(buildDirectory()) == QFileInfo(projectDir);
if (!inSourceBuild && !copyDebianFiles())
if (!runCommand(buildProc, QLatin1String("dpkg-buildpackage -nc -uc -us")))
// Workaround for non-working dh_builddeb --destdir=.
if (!QDir(buildDirectory()).isRoot()) {
const ProjectExplorer::Project * const project
= buildConfiguration()->target()->project();
QString error;
const QString pkgFileName = packageFileName(project,
MaemoTemplatesManager::instance()->version(project, &error));
if (!error.isEmpty())
raiseError(tr("Packaging failed."), error);
const QString changesFileName = QFileInfo(pkgFileName)
.completeBaseName() + QLatin1String(".changes");
const QString packageSourceDir = buildDirectory() + QLatin1String("/../");
= packageSourceDir + pkgFileName;
const QString changesSourceFilePath
= packageSourceDir + changesFileName;
const QString changesTargetFilePath
= buildDirectory() + QLatin1Char('/') + changesFileName;
QFile::remove(packageFilePath());
if (!QFile::rename(packageSourceFilePath, packageFilePath())
|| !QFile::rename(changesSourceFilePath, changesTargetFilePath)) {
raiseError(tr("Packaging failed."),
tr("Could not move package files from %1 to %2.")
.arg(packageSourceDir, buildDirectory()));
emit addOutput(tr("Package created."), BuildStep::MessageOutput);
deployStep()->deployables()->setUnmodified();
if (inSourceBuild) {
buildProc->start(packagingCommand(maemoToolChain(),
QLatin1String("dh_clean")));
buildProc->waitForFinished();
}
return true;
}
bool MaemoPackageCreationStep::copyDebianFiles()
{
const QString debianDirPath = buildDirectory() + QLatin1String("/debian");
if (!removeDirectory(debianDirPath)) {
raiseError(tr("Packaging failed."),
tr("Could not remove directory '%1'.").arg(debianDirPath));
return false;
}
QDir buildDir(buildDirectory());
if (!buildDir.mkdir("debian")) {
raiseError(tr("Could not create Debian directory '%1'.")
.arg(debianDirPath));
return false;
}
const QString templatesDirPath = MaemoTemplatesManager::instance()
->debianDirPath(buildConfiguration()->target()->project());
QDir templatesDir(templatesDirPath);
const QStringList &files = templatesDir.entryList(QDir::Files);
const bool harmattanWorkaroundNeeded
= maemoToolChain()->version() == MaemoToolChain::Maemo6
&& !qt4BuildConfiguration()->qt4Target()->qt4Project()
->applicationProFiles().isEmpty();
foreach (const QString &fileName, files) {
const QString srcFile
= templatesDirPath + QLatin1Char('/') + fileName;
const QString destFile
= debianDirPath + QLatin1Char('/') + fileName;
if (!QFile::copy(srcFile, destFile)) {
raiseError(tr("Could not copy file '%1' to '%2'")
.arg(QDir::toNativeSeparators(srcFile),
QDir::toNativeSeparators(destFile)));
return false;
}
// Workaround for Harmattan icon bug
if (harmattanWorkaroundNeeded && fileName == QLatin1String("rules"))
addWorkaroundForHarmattanBug(destFile);
bool MaemoPackageCreationStep::removeDirectory(const QString &dirPath)
{
QDir dir(dirPath);
if (!dir.exists())
return true;
const QStringList &files
= dir.entryList(QDir::Files | QDir::Hidden | QDir::System);
foreach (const QString &fileName, files) {
if (!dir.remove(fileName))
return false;
}
const QStringList &subDirs
= dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
foreach (const QString &subDirName, subDirs) {
if (!removeDirectory(dirPath + QLatin1Char('/') + subDirName))
return false;
}
return dir.rmdir(dirPath);
}
bool MaemoPackageCreationStep::runCommand(QProcess *buildProc,
const QString &command)
emit addOutput(tr("Package Creation: Running command '%1'.").arg(command), BuildStep::MessageOutput);
buildProc->start(packagingCommand(maemoToolChain(), command));
if (!buildProc->waitForStarted()) {
raiseError(tr("Packaging failed."),
tr("Packaging error: Could not start command '%1'. Reason: %2")
.arg(command).arg(buildProc->errorString()));
buildProc->waitForFinished(-1);
if (buildProc->error() != QProcess::UnknownError
|| buildProc->exitCode() != 0) {
QString mainMessage = tr("Packaging Error: Command '%1' failed.")
.arg(command);
if (buildProc->error() != QProcess::UnknownError)
mainMessage += tr(" Reason: %1").arg(buildProc->errorString());
mainMessage += tr("Exit code: %1").arg(buildProc->exitCode());
void MaemoPackageCreationStep::handleBuildOutput()
{
QProcess * const buildProc = qobject_cast<QProcess *>(sender());
const QByteArray &stdOut = buildProc->readAllStandardOutput();
emit addOutput(QString::fromLocal8Bit(stdOut), BuildStep::NormalOutput);
const QByteArray &errorOut = buildProc->readAllStandardError();
emit addOutput(QString::fromLocal8Bit(errorOut), BuildStep::ErrorOutput);
void MaemoPackageCreationStep::handleBuildConfigChanged()
{
if (m_lastBuildConfig)
disconnect(m_lastBuildConfig, 0, this, 0);
m_lastBuildConfig = qt4BuildConfiguration();
connect(m_lastBuildConfig, SIGNAL(qtVersionChanged()), this,
SIGNAL(qtVersionChanged()));
connect(m_lastBuildConfig, SIGNAL(buildDirectoryChanged()), this,
SIGNAL(packageFilePathChanged()));
emit qtVersionChanged();
emit packageFilePathChanged();
}
const Qt4BuildConfiguration *MaemoPackageCreationStep::qt4BuildConfiguration() const
{
return static_cast<Qt4BuildConfiguration *>(buildConfiguration());
}
QString MaemoPackageCreationStep::buildDirectory() const
{
return qt4BuildConfiguration()->buildDirectory();
QString MaemoPackageCreationStep::projectName() const
return qt4BuildConfiguration()->qt4Target()->qt4Project()
->rootProjectNode()->displayName().toLower();
}
const MaemoToolChain *MaemoPackageCreationStep::maemoToolChain() const
{
return static_cast<MaemoToolChain *>(qt4BuildConfiguration()->toolChain());
}
MaemoDeployStep *MaemoPackageCreationStep::deployStep() const
{
MaemoDeployStep * const deployStep
= MaemoGlobal::buildStep<MaemoDeployStep>(target()->activeDeployConfiguration());
Q_ASSERT(deployStep &&
"Fatal error: Maemo build configuration without deploy step.");
return deployStep;
}
QString MaemoPackageCreationStep::maddeRoot() const
{
return maemoToolChain()->maddeRoot();
}
QString MaemoPackageCreationStep::targetRoot() const
{
return maemoToolChain()->targetRoot();
}
bool MaemoPackageCreationStep::packagingNeeded() const
{
const MaemoDeployables * const deployables = deployStep()->deployables();
QFileInfo packageInfo(packageFilePath());
if (!packageInfo.exists() || deployables->isModified())
const int deployableCount = deployables->deployableCount();
for (int i = 0; i < deployableCount; ++i) {
<= QFileInfo(deployables->deployableAt(i).localFilePath)
.lastModified())
return true;
}
const ProjectExplorer::Project * const project = target()->project();
const MaemoTemplatesManager * const templatesManager
= MaemoTemplatesManager::instance();
const QString debianPath = templatesManager->debianDirPath(project);
if (packageInfo.lastModified() <= QFileInfo(debianPath).lastModified())
return true;
const QStringList debianFiles = templatesManager->debianFiles(project);
foreach (const QString &debianFile, debianFiles) {
const QString absFilePath = debianPath + QLatin1Char('/') + debianFile;
if (packageInfo.lastModified() <= QFileInfo(absFilePath).lastModified())
return true;
}
QString MaemoPackageCreationStep::packageFilePath() const
{
QString error;
const QString &version = versionString(&error);
if (version.isEmpty())
return QString();
return buildDirectory() % '/'
% packageFileName(buildConfiguration()->target()->project(), version);
bool MaemoPackageCreationStep::isPackagingEnabled() const
{
return m_packagingEnabled || !maemoToolChain()->allowsPackagingDisabling();
}
QString MaemoPackageCreationStep::versionString(QString *error) const
return MaemoTemplatesManager::instance()
->version(buildConfiguration()->target()->project(), error);
bool MaemoPackageCreationStep::setVersionString(const QString &version,
QString *error)
const bool success = MaemoTemplatesManager::instance()
->setVersion(buildConfiguration()->target()->project(), version, error);
if (success)
emit packageFilePathChanged();
return success;
QString MaemoPackageCreationStep::nativePath(const QFile &file)
{
return QDir::toNativeSeparators(QFileInfo(file).filePath());
}
void MaemoPackageCreationStep::raiseError(const QString &shortMsg,
const QString &detailedMsg)
{
emit addOutput(detailedMsg.isNull() ? shortMsg : detailedMsg, BuildStep::ErrorOutput);
emit addTask(Task(Task::Error, shortMsg, QString(), -1,
TASK_CATEGORY_BUILDSYSTEM));
}
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
bool MaemoPackageCreationStep::preparePackagingProcess(QProcess *proc,
const MaemoToolChain *tc, const QString &workingDir, QString *error)
{
QFile configFile(tc->targetRoot() % QLatin1String("/config.sh"));
if (!configFile.open(QIODevice::ReadOnly)) {
*error = tr("Cannot open MADDE config file '%1'.")
.arg(nativePath(configFile));
return false;
}
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
const QString &path
= QDir::toNativeSeparators(tc->maddeRoot() + QLatin1Char('/'));
const QLatin1String key("PATH");
QString colon = QLatin1String(":");
#ifdef Q_OS_WIN
colon = QLatin1String(";");
env.insert(key, path % QLatin1String("bin") % colon % env.value(key));
#endif
env.insert(key, tc->targetRoot() % "/bin" % colon % env.value(key));
env.insert(key, path % QLatin1String("madbin") % colon % env.value(key));
QString perlLib
= QDir::fromNativeSeparators(path % QLatin1String("madlib/perl5"));
#ifdef Q_OS_WIN
perlLib = perlLib.remove(QLatin1Char(':'));
perlLib = perlLib.prepend(QLatin1Char('/'));
#endif
env.insert(QLatin1String("PERL5LIB"), perlLib);
env.insert(QLatin1String("PWD"), workingDir);
const QRegExp envPattern(QLatin1String("([^=]+)=[\"']?([^;\"']+)[\"']? ;.*"));
QByteArray line;
do {
line = configFile.readLine(200);
if (envPattern.exactMatch(line))
env.insert(envPattern.cap(1), envPattern.cap(2));
} while (!line.isEmpty());
proc->setProcessEnvironment(env);
proc->setWorkingDirectory(workingDir);
proc->start("cd " + workingDir);
proc->waitForFinished();
return true;
}
QString MaemoPackageCreationStep::packagingCommand(const MaemoToolChain *tc,
const QString &commandName)
{
QString perl;
#ifdef Q_OS_WIN
perl = tc->maddeRoot() + QLatin1String("/bin/perl.exe ");
#endif
return perl + tc->maddeRoot() % QLatin1String("/madbin/") % commandName;
}
void MaemoPackageCreationStep::checkProjectName()
{
const QRegExp legalName(QLatin1String("[0-9-+a-z\\.]+"));
if (!legalName.exactMatch(buildConfiguration()->target()->project()->displayName())) {
emit addTask(Task(Task::Warning,
tr("Your project name contains characters not allowed in Debian packages.\n"
"They must only use lower-case letters, numbers, '-', '+' and '.'.\n"
"We will try to work around that, but you may experience problems."),
QString(), -1, TASK_CATEGORY_BUILDSYSTEM));
}
}
QString MaemoPackageCreationStep::packageName(const ProjectExplorer::Project *project)
{
QString packageName = project->displayName().toLower();
const QRegExp legalLetter(QLatin1String("[a-z0-9+-.]"), Qt::CaseSensitive,
QRegExp::WildcardUnix);
for (int i = 0; i < packageName.length(); ++i) {
if (!legalLetter.exactMatch(packageName.mid(i, 1)))
packageName[i] = QLatin1Char('-');
}
return packageName;
}
QString MaemoPackageCreationStep::packageFileName(const ProjectExplorer::Project *project,
const QString &version)
{
return packageName(project) % QLatin1Char('_') % version
% QLatin1String("_armel.deb");
}
void MaemoPackageCreationStep::addWorkaroundForHarmattanBug(const QString &rulesFilePath)
{
QFile rulesFile(rulesFilePath);
if (!rulesFile.open(QIODevice::ReadWrite)) {
qWarning("Cannot open rules file for Maemo6 icon path adaptation.");
return;
}
QByteArray content = rulesFile.readAll();
const int makeInstallLine = content.indexOf("\t$(MAKE) INSTALL_ROOT");
if (makeInstallLine == -1)
return;
const int makeInstallEol = content.indexOf('\n', makeInstallLine);
if (makeInstallEol == -1)
return;
const QByteArray lineBefore("Icon=" + projectName().toUtf8());
const QByteArray lineAfter("Icon=/usr/share/icons/hicolor/64x64/apps/"
+ projectName().toUtf8() + ".png");
QString desktopFileDir = QFileInfo(rulesFile).dir().path()
+ QLatin1Char('/') + projectName()
+ QLatin1String("/usr/share/applications/");
#ifdef Q_OS_WIN
desktopFileDir.remove(QLatin1Char(':'));
desktopFileDir.prepend(QLatin1Char('/'));
const QList<Qt4ProFileNode *> &proFiles = qt4BuildConfiguration()
->qt4Target()->qt4Project()->applicationProFiles();
int insertPos = makeInstallEol + 1;
foreach (const Qt4ProFileNode * const proFile, proFiles) {
const QString appName = proFile->targetInformation().target;
const QString desktopFilePath
= desktopFileDir + appName + QLatin1String(".desktop");
const QString tmpFile
= desktopFileDir + appName + QLatin1String(".sed");
const QByteArray sedCmd = "\tsed 's:" + lineBefore + ':' + lineAfter
+ ":' " + desktopFilePath.toLocal8Bit() + " > " + tmpFile.toUtf8()
+ '\n';
const QByteArray mvCmd = "\tmv " + tmpFile.toUtf8() + ' '
+ desktopFilePath.toUtf8() + '\n';
content.insert(insertPos, sedCmd);
insertPos += sedCmd.length();
content.insert(insertPos, mvCmd);
insertPos += mvCmd.length();
}
rulesFile.resize(0);
rulesFile.write(content);
}