OSDN Git Service

Maemo: Fix object deletion issue in packaging step.
[qt-creator-jp/qt-creator-jp.git] / src / plugins / qt4projectmanager / qt-maemo / maemopackagecreationstep.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of Qt Creator.
8 **
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
14 ** this package.
15 **
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.
23 **
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.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "maemopackagecreationstep.h"
43
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"
52
53 #include <projectexplorer/buildsteplist.h>
54 #include <projectexplorer/projectexplorerconstants.h>
55 #include <qt4buildconfiguration.h>
56 #include <qt4project.h>
57 #include <qt4target.h>
58
59 #include <QtCore/QProcess>
60 #include <QtCore/QProcessEnvironment>
61 #include <QtCore/QRegExp>
62 #include <QtCore/QStringBuilder>
63 #include <QtGui/QWidget>
64
65 namespace {
66     const QLatin1String PackagingEnabledKey("Packaging Enabled");
67 }
68
69 using namespace ProjectExplorer::Constants;
70 using ProjectExplorer::BuildStepList;
71 using ProjectExplorer::BuildStepConfigWidget;
72 using ProjectExplorer::Task;
73
74 namespace Qt4ProjectManager {
75 namespace Internal {
76
77 const QLatin1String MaemoPackageCreationStep::DefaultVersionNumber("0.0.1");
78
79 MaemoPackageCreationStep::MaemoPackageCreationStep(BuildStepList *bsl)
80     : ProjectExplorer::BuildStep(bsl, CreatePackageId),
81       m_packagingEnabled(true)
82 {
83     ctor();
84 }
85
86 MaemoPackageCreationStep::MaemoPackageCreationStep(BuildStepList *bsl,
87     MaemoPackageCreationStep *other)
88     : BuildStep(bsl, other),
89       m_packagingEnabled(other->m_packagingEnabled)
90 {
91     ctor();
92 }
93
94 MaemoPackageCreationStep::~MaemoPackageCreationStep()
95 {
96 }
97
98 void MaemoPackageCreationStep::ctor()
99 {
100     setDefaultDisplayName(tr("Packaging for Maemo"));
101
102     m_lastBuildConfig = qt4BuildConfiguration();
103     connect(target(),
104         SIGNAL(activeBuildConfigurationChanged(ProjectExplorer::BuildConfiguration*)),
105         this, SLOT(handleBuildConfigChanged()));
106     handleBuildConfigChanged();
107 }
108
109 bool MaemoPackageCreationStep::init()
110 {
111     return true;
112 }
113
114 QVariantMap MaemoPackageCreationStep::toMap() const
115 {
116     QVariantMap map(ProjectExplorer::BuildStep::toMap());
117     map.insert(PackagingEnabledKey, m_packagingEnabled);
118     return map;
119 }
120
121 bool MaemoPackageCreationStep::fromMap(const QVariantMap &map)
122 {
123     m_packagingEnabled = map.value(PackagingEnabledKey, true).toBool();
124     return ProjectExplorer::BuildStep::fromMap(map);
125 }
126
127 void MaemoPackageCreationStep::run(QFutureInterface<bool> &fi)
128 {
129     bool success;
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();
139     } else  {
140         success = true;
141     }
142     fi.reportResult(success);
143 }
144
145 BuildStepConfigWidget *MaemoPackageCreationStep::createConfigWidget()
146 {
147     return new MaemoPackageCreationWidget(this);
148 }
149
150 bool MaemoPackageCreationStep::createPackage(QProcess *buildProc)
151 {
152     if (!packagingNeeded()) {
153         emit addOutput(tr("Package up to date."), MessageOutput);
154         return true;
155     }
156
157     emit addOutput(tr("Creating package file ..."), MessageOutput);
158     checkProjectName();
159     QString error;
160     if (!preparePackagingProcess(buildProc, maemoToolChain(), buildDirectory(),
161         &error)) {
162         raiseError(error);
163         return false;
164     }
165
166     const QString projectDir
167         = buildConfiguration()->target()->project()->projectDirectory();
168     const bool inSourceBuild
169         = QFileInfo(buildDirectory()) == QFileInfo(projectDir);
170     if (!inSourceBuild && !copyDebianFiles())
171         return false;
172
173     if (!runCommand(buildProc, QLatin1String("dpkg-buildpackage -nc -uc -us")))
174         return false;
175
176     // Workaround for non-working dh_builddeb --destdir=.
177     if (!QDir(buildDirectory()).isRoot()) {
178         const ProjectExplorer::Project * const project
179             = buildConfiguration()->target()->project();
180         QString error;
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()));
201             return false;
202         }
203     }
204
205     emit addOutput(tr("Package created."), BuildStep::MessageOutput);
206     deployStep()->deployables()->setUnmodified();
207     if (inSourceBuild) {
208         buildProc->start(packagingCommand(maemoToolChain(),
209             QLatin1String("dh_clean")));
210         buildProc->waitForFinished();
211     }
212     return true;
213 }
214
215 bool MaemoPackageCreationStep::copyDebianFiles()
216 {
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));
221         return false;
222     }
223     QDir buildDir(buildDirectory());
224     if (!buildDir.mkdir("debian")) {
225         raiseError(tr("Could not create debian directory '%1'.")
226                    .arg(debianDirPath));
227         return false;
228     }
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)));
242             return false;
243         }
244     }
245     return true;
246 }
247
248 bool MaemoPackageCreationStep::removeDirectory(const QString &dirPath)
249 {
250     QDir dir(dirPath);
251     if (!dir.exists())
252         return true;
253
254     const QStringList &files
255         = dir.entryList(QDir::Files | QDir::Hidden | QDir::System);
256     foreach (const QString &fileName, files) {
257         if (!dir.remove(fileName))
258             return false;
259     }
260
261     const QStringList &subDirs
262         = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
263     foreach (const QString &subDirName, subDirs) {
264         if (!removeDirectory(dirPath + QLatin1Char('/') + subDirName))
265             return false;
266     }
267
268     return dir.rmdir(dirPath);
269 }
270
271 bool MaemoPackageCreationStep::runCommand(QProcess *buildProc,
272     const QString &command)
273 {
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()));
280         return false;
281     }
282     buildProc->waitForFinished(-1);
283     if (buildProc->error() != QProcess::UnknownError
284         || buildProc->exitCode() != 0) {
285         QString mainMessage = tr("Packaging Error: Command '%1' failed.")
286             .arg(command);
287         if (buildProc->error() != QProcess::UnknownError)
288             mainMessage += tr(" Reason: %1").arg(buildProc->errorString());
289         else
290             mainMessage += tr("Exit code: %1").arg(buildProc->exitCode());
291         raiseError(mainMessage);
292         return false;
293     }
294     return true;
295 }
296
297 void MaemoPackageCreationStep::handleBuildOutput()
298 {
299     QProcess * const buildProc = qobject_cast<QProcess *>(sender());
300     Q_ASSERT(buildProc);
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);
307     }
308 }
309
310 void MaemoPackageCreationStep::handleBuildConfigChanged()
311 {
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();
321 }
322
323 const Qt4BuildConfiguration *MaemoPackageCreationStep::qt4BuildConfiguration() const
324 {
325     return static_cast<Qt4BuildConfiguration *>(buildConfiguration());
326 }
327
328 QString MaemoPackageCreationStep::buildDirectory() const
329 {
330     return qt4BuildConfiguration()->buildDirectory();
331 }
332
333 QString MaemoPackageCreationStep::projectName() const
334 {
335     return qt4BuildConfiguration()->qt4Target()->qt4Project()
336         ->rootProjectNode()->displayName().toLower();
337 }
338
339 const MaemoToolChain *MaemoPackageCreationStep::maemoToolChain() const
340 {
341     return static_cast<MaemoToolChain *>(qt4BuildConfiguration()->toolChain());
342 }
343
344 MaemoDeployStep *MaemoPackageCreationStep::deployStep() const
345 {
346     MaemoDeployStep * const deployStep
347         = MaemoGlobal::buildStep<MaemoDeployStep>(target()->activeDeployConfiguration());
348     Q_ASSERT(deployStep &&
349         "Fatal error: Maemo build configuration without deploy step.");
350     return deployStep;
351 }
352
353 QString MaemoPackageCreationStep::maddeRoot() const
354 {
355     return maemoToolChain()->maddeRoot();
356 }
357
358 QString MaemoPackageCreationStep::targetRoot() const
359 {
360     return maemoToolChain()->targetRoot();
361 }
362
363 bool MaemoPackageCreationStep::packagingNeeded() const
364 {
365     const MaemoDeployables * const deployables = deployStep()->deployables();
366     QFileInfo packageInfo(packageFilePath());
367     if (!packageInfo.exists() || deployables->isModified())
368         return true;
369
370     const int deployableCount = deployables->deployableCount();
371     for (int i = 0; i < deployableCount; ++i) {
372         if (packageInfo.lastModified()
373             <= QFileInfo(deployables->deployableAt(i).localFilePath)
374                .lastModified())
375             return true;
376     }
377
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())
383         return true;
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())
388             return true;
389     }
390
391     return false;
392 }
393
394 QString MaemoPackageCreationStep::packageFilePath() const
395 {
396     QString error;
397     const QString &version = versionString(&error);
398     if (version.isEmpty())
399         return QString();
400     return buildDirectory() % '/'
401         % packageFileName(buildConfiguration()->target()->project(), version);
402 }
403
404 bool MaemoPackageCreationStep::isPackagingEnabled() const
405 {
406     return m_packagingEnabled || !maemoToolChain()->allowsPackagingDisabling();
407 }
408
409 QString MaemoPackageCreationStep::versionString(QString *error) const
410 {
411     return MaemoTemplatesManager::instance()
412         ->version(buildConfiguration()->target()->project(), error);
413
414 }
415
416 bool MaemoPackageCreationStep::setVersionString(const QString &version,
417     QString *error)
418 {
419     const bool success = MaemoTemplatesManager::instance()
420         ->setVersion(buildConfiguration()->target()->project(), version, error);
421     if (success)
422         emit packageFilePathChanged();
423     return success;
424 }
425
426 QString MaemoPackageCreationStep::nativePath(const QFile &file)
427 {
428     return QDir::toNativeSeparators(QFileInfo(file).filePath());
429 }
430
431 void MaemoPackageCreationStep::raiseError(const QString &shortMsg,
432                                           const QString &detailedMsg)
433 {
434     emit addOutput(detailedMsg.isNull() ? shortMsg : detailedMsg, BuildStep::ErrorOutput);
435     emit addTask(Task(Task::Error, shortMsg, QString(), -1,
436                       TASK_CATEGORY_BUILDSYSTEM));
437 }
438
439 bool MaemoPackageCreationStep::preparePackagingProcess(QProcess *proc,
440     const MaemoToolChain *tc, const QString &workingDir, QString *error)
441 {
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));
446         return false;
447     }
448
449     QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
450     const QString &path
451         = QDir::toNativeSeparators(tc->maddeRoot() + QLatin1Char('/'));
452
453     const QLatin1String key("PATH");
454     QString colon = QLatin1String(":");
455 #ifdef Q_OS_WIN
456     colon = QLatin1String(";");
457     env.insert(key, path % QLatin1String("bin") % colon % env.value(key));
458 #endif
459
460     env.insert(key, tc->targetRoot() % "/bin" % colon % env.value(key));
461     env.insert(key, path % QLatin1String("madbin") % colon % env.value(key));
462
463     QString perlLib
464         = QDir::fromNativeSeparators(path % QLatin1String("madlib/perl5"));
465 #ifdef Q_OS_WIN
466     perlLib = perlLib.remove(QLatin1Char(':'));
467     perlLib = perlLib.prepend(QLatin1Char('/'));
468 #endif
469     env.insert(QLatin1String("PERL5LIB"), perlLib);
470     env.insert(QLatin1String("PWD"), workingDir);
471
472     const QRegExp envPattern(QLatin1String("([^=]+)=[\"']?([^;\"']+)[\"']? ;.*"));
473     QByteArray line;
474     do {
475         line = configFile.readLine(200);
476         if (envPattern.exactMatch(line))
477             env.insert(envPattern.cap(1), envPattern.cap(2));
478     } while (!line.isEmpty());
479
480     proc->setProcessEnvironment(env);
481     proc->setWorkingDirectory(workingDir);
482     proc->start("cd " + workingDir);
483     proc->waitForFinished();
484     return true;
485 }
486
487 QString MaemoPackageCreationStep::packagingCommand(const MaemoToolChain *tc,
488     const QString &commandName)
489 {
490     QString perl;
491 #ifdef Q_OS_WIN
492     perl = tc->maddeRoot() + QLatin1String("/bin/perl.exe ");
493 #endif
494     return perl + tc->maddeRoot() % QLatin1String("/madbin/") % commandName;
495 }
496
497 void MaemoPackageCreationStep::checkProjectName()
498 {
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));
506     }
507 }
508
509 QString MaemoPackageCreationStep::packageName(const ProjectExplorer::Project *project)
510 {
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('-');
517     }
518     return packageName;
519 }
520
521 QString MaemoPackageCreationStep::packageFileName(const ProjectExplorer::Project *project,
522     const QString &version)
523 {
524     return packageName(project) % QLatin1Char('_') % version
525         % QLatin1String("_armel.deb");
526 }
527
528 const QLatin1String MaemoPackageCreationStep::CreatePackageId("Qt4ProjectManager.MaemoPackageCreationStep");
529
530 } // namespace Internal
531 } // namespace Qt4ProjectManager