1 /****************************************************************************
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of Qt Creator.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
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
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file. Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights. These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
40 ****************************************************************************/
42 #include "maemopackagecreationstep.h"
44 #include "maemoconstants.h"
45 #include "maemodeployables.h"
46 #include "maemodeploystep.h"
47 #include "maemoglobal.h"
48 #include "maemopackagecreationwidget.h"
49 #include "maemoprofilewrapper.h"
50 #include "maemotemplatesmanager.h"
51 #include "maemotoolchain.h"
53 #include <projectexplorer/buildsteplist.h>
54 #include <projectexplorer/projectexplorerconstants.h>
55 #include <qt4buildconfiguration.h>
56 #include <qt4project.h>
57 #include <qt4target.h>
59 #include <QtCore/QProcess>
60 #include <QtCore/QProcessEnvironment>
61 #include <QtCore/QRegExp>
62 #include <QtCore/QStringBuilder>
63 #include <QtGui/QWidget>
66 const QLatin1String PackagingEnabledKey("Packaging Enabled");
69 using namespace ProjectExplorer::Constants;
70 using ProjectExplorer::BuildStepList;
71 using ProjectExplorer::BuildStepConfigWidget;
72 using ProjectExplorer::Task;
74 namespace Qt4ProjectManager {
77 const QLatin1String MaemoPackageCreationStep::DefaultVersionNumber("0.0.1");
79 MaemoPackageCreationStep::MaemoPackageCreationStep(BuildStepList *bsl)
80 : ProjectExplorer::BuildStep(bsl, CreatePackageId),
81 m_packagingEnabled(true)
86 MaemoPackageCreationStep::MaemoPackageCreationStep(BuildStepList *bsl,
87 MaemoPackageCreationStep *other)
88 : BuildStep(bsl, other),
89 m_packagingEnabled(other->m_packagingEnabled)
94 MaemoPackageCreationStep::~MaemoPackageCreationStep()
98 void MaemoPackageCreationStep::ctor()
100 setDefaultDisplayName(tr("Packaging for Maemo"));
102 m_lastBuildConfig = qt4BuildConfiguration();
104 SIGNAL(activeBuildConfigurationChanged(ProjectExplorer::BuildConfiguration*)),
105 this, SLOT(handleBuildConfigChanged()));
106 handleBuildConfigChanged();
109 bool MaemoPackageCreationStep::init()
114 QVariantMap MaemoPackageCreationStep::toMap() const
116 QVariantMap map(ProjectExplorer::BuildStep::toMap());
117 map.insert(PackagingEnabledKey, m_packagingEnabled);
121 bool MaemoPackageCreationStep::fromMap(const QVariantMap &map)
123 m_packagingEnabled = map.value(PackagingEnabledKey, true).toBool();
124 return ProjectExplorer::BuildStep::fromMap(map);
127 void MaemoPackageCreationStep::run(QFutureInterface<bool> &fi)
130 if (m_packagingEnabled) {
131 QProcess * const buildProc = new QProcess;
132 connect(buildProc, SIGNAL(readyReadStandardOutput()), this,
133 SLOT(handleBuildOutput()));
134 connect(buildProc, SIGNAL(readyReadStandardError()), this,
135 SLOT(handleBuildOutput()));
136 success = createPackage(buildProc);
137 disconnect(buildProc, 0, this, 0);
138 buildProc->deleteLater();
142 fi.reportResult(success);
145 BuildStepConfigWidget *MaemoPackageCreationStep::createConfigWidget()
147 return new MaemoPackageCreationWidget(this);
150 bool MaemoPackageCreationStep::createPackage(QProcess *buildProc)
152 if (!packagingNeeded()) {
153 emit addOutput(tr("Package up to date."), MessageOutput);
157 emit addOutput(tr("Creating package file ..."), MessageOutput);
160 if (!preparePackagingProcess(buildProc, maemoToolChain(), buildDirectory(),
166 const QString projectDir
167 = buildConfiguration()->target()->project()->projectDirectory();
168 const bool inSourceBuild
169 = QFileInfo(buildDirectory()) == QFileInfo(projectDir);
170 if (!inSourceBuild && !copyDebianFiles())
173 if (!runCommand(buildProc, QLatin1String("dpkg-buildpackage -nc -uc -us")))
176 // Workaround for non-working dh_builddeb --destdir=.
177 if (!QDir(buildDirectory()).isRoot()) {
178 const ProjectExplorer::Project * const project
179 = buildConfiguration()->target()->project();
181 const QString pkgFileName = packageFileName(project,
182 MaemoTemplatesManager::instance()->version(project, &error));
183 if (!error.isEmpty())
184 raiseError(tr("Packaging failed."), error);
185 const QString changesFileName = QFileInfo(pkgFileName)
186 .completeBaseName() + QLatin1String(".changes");
187 const QString packageSourceDir = buildDirectory() + QLatin1String("/../");
188 const QString packageSourceFilePath
189 = packageSourceDir + pkgFileName;
190 const QString changesSourceFilePath
191 = packageSourceDir + changesFileName;
192 const QString changesTargetFilePath
193 = buildDirectory() + QLatin1Char('/') + changesFileName;
194 QFile::remove(packageFilePath());
195 QFile::remove(changesTargetFilePath);
196 if (!QFile::rename(packageSourceFilePath, packageFilePath())
197 || !QFile::rename(changesSourceFilePath, changesTargetFilePath)) {
198 raiseError(tr("Packaging failed."),
199 tr("Could not move package files from %1 to %2.")
200 .arg(packageSourceDir, buildDirectory()));
205 emit addOutput(tr("Package created."), BuildStep::MessageOutput);
206 deployStep()->deployables()->setUnmodified();
208 buildProc->start(packagingCommand(maemoToolChain(),
209 QLatin1String("dh_clean")));
210 buildProc->waitForFinished();
215 bool MaemoPackageCreationStep::copyDebianFiles()
217 const QString debianDirPath = buildDirectory() + QLatin1String("/debian");
218 if (!removeDirectory(debianDirPath)) {
219 raiseError(tr("Packaging failed."),
220 tr("Could not remove directory '%1'.").arg(debianDirPath));
223 QDir buildDir(buildDirectory());
224 if (!buildDir.mkdir("debian")) {
225 raiseError(tr("Could not create debian directory '%1'.")
226 .arg(debianDirPath));
229 const QString templatesDirPath = MaemoTemplatesManager::instance()
230 ->debianDirPath(buildConfiguration()->target()->project());
231 QDir templatesDir(templatesDirPath);
232 const QStringList &files = templatesDir.entryList(QDir::Files);
233 foreach (const QString &fileName, files) {
234 const QString srcFile
235 = templatesDirPath + QLatin1Char('/') + fileName;
236 const QString destFile
237 = debianDirPath + QLatin1Char('/') + fileName;
238 if (!QFile::copy(srcFile, destFile)) {
239 raiseError(tr("Could not copy file '%1' to '%2'")
240 .arg(QDir::toNativeSeparators(srcFile),
241 QDir::toNativeSeparators(destFile)));
248 bool MaemoPackageCreationStep::removeDirectory(const QString &dirPath)
254 const QStringList &files
255 = dir.entryList(QDir::Files | QDir::Hidden | QDir::System);
256 foreach (const QString &fileName, files) {
257 if (!dir.remove(fileName))
261 const QStringList &subDirs
262 = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
263 foreach (const QString &subDirName, subDirs) {
264 if (!removeDirectory(dirPath + QLatin1Char('/') + subDirName))
268 return dir.rmdir(dirPath);
271 bool MaemoPackageCreationStep::runCommand(QProcess *buildProc,
272 const QString &command)
274 emit addOutput(tr("Package Creation: Running command '%1'.").arg(command), BuildStep::MessageOutput);
275 buildProc->start(packagingCommand(maemoToolChain(), command));
276 if (!buildProc->waitForStarted()) {
277 raiseError(tr("Packaging failed."),
278 tr("Packaging error: Could not start command '%1'. Reason: %2")
279 .arg(command).arg(buildProc->errorString()));
282 buildProc->waitForFinished(-1);
283 if (buildProc->error() != QProcess::UnknownError
284 || buildProc->exitCode() != 0) {
285 QString mainMessage = tr("Packaging Error: Command '%1' failed.")
287 if (buildProc->error() != QProcess::UnknownError)
288 mainMessage += tr(" Reason: %1").arg(buildProc->errorString());
290 mainMessage += tr("Exit code: %1").arg(buildProc->exitCode());
291 raiseError(mainMessage);
297 void MaemoPackageCreationStep::handleBuildOutput()
299 QProcess * const buildProc = qobject_cast<QProcess *>(sender());
301 const QByteArray &stdOut = buildProc->readAllStandardOutput();
302 if (!stdOut.isEmpty())
303 emit addOutput(QString::fromLocal8Bit(stdOut), BuildStep::NormalOutput);
304 const QByteArray &errorOut = buildProc->readAllStandardError();
305 if (!errorOut.isEmpty()) {
306 emit addOutput(QString::fromLocal8Bit(errorOut), BuildStep::ErrorOutput);
310 void MaemoPackageCreationStep::handleBuildConfigChanged()
312 if (m_lastBuildConfig)
313 disconnect(m_lastBuildConfig, 0, this, 0);
314 m_lastBuildConfig = qt4BuildConfiguration();
315 connect(m_lastBuildConfig, SIGNAL(qtVersionChanged()), this,
316 SIGNAL(qtVersionChanged()));
317 connect(m_lastBuildConfig, SIGNAL(buildDirectoryChanged()), this,
318 SIGNAL(packageFilePathChanged()));
319 emit qtVersionChanged();
320 emit packageFilePathChanged();
323 const Qt4BuildConfiguration *MaemoPackageCreationStep::qt4BuildConfiguration() const
325 return static_cast<Qt4BuildConfiguration *>(buildConfiguration());
328 QString MaemoPackageCreationStep::buildDirectory() const
330 return qt4BuildConfiguration()->buildDirectory();
333 QString MaemoPackageCreationStep::projectName() const
335 return qt4BuildConfiguration()->qt4Target()->qt4Project()
336 ->rootProjectNode()->displayName().toLower();
339 const MaemoToolChain *MaemoPackageCreationStep::maemoToolChain() const
341 return static_cast<MaemoToolChain *>(qt4BuildConfiguration()->toolChain());
344 MaemoDeployStep *MaemoPackageCreationStep::deployStep() const
346 MaemoDeployStep * const deployStep
347 = MaemoGlobal::buildStep<MaemoDeployStep>(target()->activeDeployConfiguration());
348 Q_ASSERT(deployStep &&
349 "Fatal error: Maemo build configuration without deploy step.");
353 QString MaemoPackageCreationStep::maddeRoot() const
355 return maemoToolChain()->maddeRoot();
358 QString MaemoPackageCreationStep::targetRoot() const
360 return maemoToolChain()->targetRoot();
363 bool MaemoPackageCreationStep::packagingNeeded() const
365 const MaemoDeployables * const deployables = deployStep()->deployables();
366 QFileInfo packageInfo(packageFilePath());
367 if (!packageInfo.exists() || deployables->isModified())
370 const int deployableCount = deployables->deployableCount();
371 for (int i = 0; i < deployableCount; ++i) {
372 if (packageInfo.lastModified()
373 <= QFileInfo(deployables->deployableAt(i).localFilePath)
378 const ProjectExplorer::Project * const project = target()->project();
379 const MaemoTemplatesManager * const templatesManager
380 = MaemoTemplatesManager::instance();
381 const QString debianPath = templatesManager->debianDirPath(project);
382 if (packageInfo.lastModified() <= QFileInfo(debianPath).lastModified())
384 const QStringList debianFiles = templatesManager->debianFiles(project);
385 foreach (const QString &debianFile, debianFiles) {
386 const QString absFilePath = debianPath + QLatin1Char('/') + debianFile;
387 if (packageInfo.lastModified() <= QFileInfo(absFilePath).lastModified())
394 QString MaemoPackageCreationStep::packageFilePath() const
397 const QString &version = versionString(&error);
398 if (version.isEmpty())
400 return buildDirectory() % '/'
401 % packageFileName(buildConfiguration()->target()->project(), version);
404 bool MaemoPackageCreationStep::isPackagingEnabled() const
406 return m_packagingEnabled || !maemoToolChain()->allowsPackagingDisabling();
409 QString MaemoPackageCreationStep::versionString(QString *error) const
411 return MaemoTemplatesManager::instance()
412 ->version(buildConfiguration()->target()->project(), error);
416 bool MaemoPackageCreationStep::setVersionString(const QString &version,
419 const bool success = MaemoTemplatesManager::instance()
420 ->setVersion(buildConfiguration()->target()->project(), version, error);
422 emit packageFilePathChanged();
426 QString MaemoPackageCreationStep::nativePath(const QFile &file)
428 return QDir::toNativeSeparators(QFileInfo(file).filePath());
431 void MaemoPackageCreationStep::raiseError(const QString &shortMsg,
432 const QString &detailedMsg)
434 emit addOutput(detailedMsg.isNull() ? shortMsg : detailedMsg, BuildStep::ErrorOutput);
435 emit addTask(Task(Task::Error, shortMsg, QString(), -1,
436 TASK_CATEGORY_BUILDSYSTEM));
439 bool MaemoPackageCreationStep::preparePackagingProcess(QProcess *proc,
440 const MaemoToolChain *tc, const QString &workingDir, QString *error)
442 QFile configFile(tc->targetRoot() % QLatin1String("/config.sh"));
443 if (!configFile.open(QIODevice::ReadOnly)) {
444 *error = tr("Cannot open MADDE config file '%1'.")
445 .arg(nativePath(configFile));
449 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
451 = QDir::toNativeSeparators(tc->maddeRoot() + QLatin1Char('/'));
453 const QLatin1String key("PATH");
454 QString colon = QLatin1String(":");
456 colon = QLatin1String(";");
457 env.insert(key, path % QLatin1String("bin") % colon % env.value(key));
460 env.insert(key, tc->targetRoot() % "/bin" % colon % env.value(key));
461 env.insert(key, path % QLatin1String("madbin") % colon % env.value(key));
464 = QDir::fromNativeSeparators(path % QLatin1String("madlib/perl5"));
466 perlLib = perlLib.remove(QLatin1Char(':'));
467 perlLib = perlLib.prepend(QLatin1Char('/'));
469 env.insert(QLatin1String("PERL5LIB"), perlLib);
470 env.insert(QLatin1String("PWD"), workingDir);
472 const QRegExp envPattern(QLatin1String("([^=]+)=[\"']?([^;\"']+)[\"']? ;.*"));
475 line = configFile.readLine(200);
476 if (envPattern.exactMatch(line))
477 env.insert(envPattern.cap(1), envPattern.cap(2));
478 } while (!line.isEmpty());
480 proc->setProcessEnvironment(env);
481 proc->setWorkingDirectory(workingDir);
482 proc->start("cd " + workingDir);
483 proc->waitForFinished();
487 QString MaemoPackageCreationStep::packagingCommand(const MaemoToolChain *tc,
488 const QString &commandName)
492 perl = tc->maddeRoot() + QLatin1String("/bin/perl.exe ");
494 return perl + tc->maddeRoot() % QLatin1String("/madbin/") % commandName;
497 void MaemoPackageCreationStep::checkProjectName()
499 const QRegExp legalName(QLatin1String("[0-9-+a-z\\.]+"));
500 if (!legalName.exactMatch(buildConfiguration()->target()->project()->displayName())) {
501 emit addTask(Task(Task::Warning,
502 tr("Your project name contains characters not allowed in Debian packages.\n"
503 "They must only use lower-case letters, numbers, '-', '+' and '.'.\n"
504 "We will try to work around that, but you may experience problems."),
505 QString(), -1, TASK_CATEGORY_BUILDSYSTEM));
509 QString MaemoPackageCreationStep::packageName(const ProjectExplorer::Project *project)
511 QString packageName = project->displayName().toLower();
512 const QRegExp legalLetter(QLatin1String("[a-z0-9+-.]"), Qt::CaseSensitive,
513 QRegExp::WildcardUnix);
514 for (int i = 0; i < packageName.length(); ++i) {
515 if (!legalLetter.exactMatch(packageName.mid(i, 1)))
516 packageName[i] = QLatin1Char('-');
521 QString MaemoPackageCreationStep::packageFileName(const ProjectExplorer::Project *project,
522 const QString &version)
524 return packageName(project) % QLatin1Char('_') % version
525 % QLatin1String("_armel.deb");
528 const QLatin1String MaemoPackageCreationStep::CreatePackageId("Qt4ProjectManager.MaemoPackageCreationStep");
530 } // namespace Internal
531 } // namespace Qt4ProjectManager