Files
qt-creator/src/plugins/android/androidpackagecreationstep.cpp
BogDan Vatra bb0573a8aa Fix Android plugin.
Change-Id: I56533be94fc868d04bd1d289ff9d3c381391d41b
Reviewed-by: Tobias Hunger <tobias.hunger@nokia.com>
2012-06-23 11:53:33 +02:00

589 lines
21 KiB
C++

/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2012 BogDan Vatra <bog_dan_ro@yahoo.com>
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
**
** GNU Lesser General Public License Usage
**
** 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.
**
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**************************************************************************/
#include "androidpackagecreationstep.h"
#include "androidconstants.h"
#include "androiddeploystep.h"
#include "androidglobal.h"
#include "androidpackagecreationwidget.h"
#include "androidmanager.h"
#include <projectexplorer/buildsteplist.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/runconfiguration.h>
#include <projectexplorer/target.h>
#include <qt4projectmanager/qt4buildconfiguration.h>
#include <qt4projectmanager/qt4project.h>
#include <qt4projectmanager/qt4nodes.h>
#include <coreplugin/icore.h>
#include <coreplugin/fileutils.h>
#include <QAbstractListModel>
#include <QProcess>
#include <QVector>
#include <QPair>
#include <QWidget>
#include <QMessageBox>
#include <QInputDialog>
#include <QMainWindow>
using namespace ProjectExplorer;
using namespace ProjectExplorer::Constants;
using ProjectExplorer::BuildStepList;
using ProjectExplorer::BuildStepConfigWidget;
using ProjectExplorer::Task;
namespace Android {
namespace Internal {
namespace {
const QLatin1String KeystoreLocationKey("KeystoreLocation");
const QLatin1String AliasString("Alias name:");
const QLatin1String CertificateSeparator("*******************************************");
}
using namespace Qt4ProjectManager;
class CertificatesModel: public QAbstractListModel
{
public:
CertificatesModel(const QString &rowCertificates, QObject *parent)
: QAbstractListModel(parent)
{
int from = rowCertificates.indexOf(AliasString);
QPair<QString, QString> item;
while (from > -1) {
from += 11;// strlen(AliasString);
const int eol = rowCertificates.indexOf(QLatin1String("\n"), from);
item.first = rowCertificates.mid(from, eol - from).trimmed();
const int eoc = rowCertificates.indexOf(CertificateSeparator, eol);
item.second = rowCertificates.mid(eol + 1, eoc - eol - 2).trimmed();
from = rowCertificates.indexOf(AliasString, eoc);
m_certs.push_back(item);
}
}
protected:
int rowCount(const QModelIndex &parent = QModelIndex()) const
{
if (parent.isValid())
return 0;
return m_certs.size();
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const
{
if (!index.isValid() || (role != Qt::DisplayRole && role != Qt::ToolTipRole))
return QVariant();
if (role == Qt::DisplayRole)
return m_certs[index.row()].first;
return m_certs[index.row()].second;
}
private:
QVector<QPair<QString, QString> > m_certs;
};
AndroidPackageCreationStep::AndroidPackageCreationStep(BuildStepList *bsl)
: BuildStep(bsl, CreatePackageId)
{
ctor();
}
AndroidPackageCreationStep::AndroidPackageCreationStep(BuildStepList *bsl,
AndroidPackageCreationStep *other)
: BuildStep(bsl, other)
{
ctor();
}
AndroidPackageCreationStep::~AndroidPackageCreationStep()
{
}
void AndroidPackageCreationStep::ctor()
{
setDefaultDisplayName(tr("Packaging for Android"));
m_openPackageLocation = true;
connect(&m_outputParser, SIGNAL(addTask(ProjectExplorer::Task)), this, SIGNAL(addTask(ProjectExplorer::Task)));
}
bool AndroidPackageCreationStep::init()
{
const Qt4BuildConfiguration *bc = qobject_cast<Qt4BuildConfiguration *>(target()->activeBuildConfiguration());
if (!bc) {
raiseError(tr("Cannot create android package: current build configuration is not Qt4."));
return false;
}
Qt4Project *project = static_cast<Qt4Project *>(target()->project());
m_outputParser.setProjectFileList(project->files(Project::AllFiles));
// Copying
m_androidDir = AndroidManager::dirPath(target());
Utils::FileName path = m_androidDir;
Utils::FileName androidLibPath;
if (project->rootQt4ProjectNode()->variableValue(Qt4ProjectManager::ConfigVar).contains(QLatin1String("x86")))
androidLibPath = path.appendPath(QLatin1String("libs/x86"));
else if (project->rootQt4ProjectNode()
->variableValue(Qt4ProjectManager::ConfigVar).contains(QLatin1String("armeabi-v7a")))
androidLibPath = path.appendPath(QLatin1String("libs/armeabi-v7a"));
else
androidLibPath = path.appendPath(QLatin1String("libs/armeabi"));
path = m_androidDir;
m_gdbServerDestination = path.appendPath(QLatin1String("gdbserver"));
m_gdbServerSource = AndroidConfigurations::instance().gdbServerPath(target()->activeRunConfiguration()->abi().architecture());
m_debugBuild = bc->qmakeBuildConfiguration() & QtSupport::BaseQtVersion::DebugBuild;
if (!AndroidManager::createAndroidTemplatesIfNecessary(target()))
return false;
AndroidManager::updateTarget(target(), AndroidManager::targetSDK(target()), AndroidManager::applicationName(target()));
m_antToolPath = AndroidConfigurations::instance().antToolPath();
m_apkPathUnsigned = AndroidManager::apkPath(target(), AndroidManager::ReleaseBuildUnsigned);
m_apkPathSigned = AndroidManager::apkPath(target(), AndroidManager::ReleaseBuildSigned);
m_keystorePathForRun = m_keystorePath;
m_certificatePasswdForRun = m_certificatePasswd;
m_jarSigner = AndroidConfigurations::instance().jarsignerPath();
initCheckRequiredLibrariesForRun();
return true;
}
void AndroidPackageCreationStep::run(QFutureInterface<bool> &fi)
{
fi.reportResult(createPackage());
}
BuildStepConfigWidget *AndroidPackageCreationStep::createConfigWidget()
{
return new AndroidPackageCreationWidget(this);
}
void AndroidPackageCreationStep::checkRequiredLibraries()
{
QProcess readelfProc;
QString appPath = AndroidManager::targetApplicationPath(target());
if (!QFile::exists(appPath)) {
raiseError(tr("Cannot find read elf information"),
tr("Cannot find '%1'.\n"
"Please make sure your application is "
"built successfully and is selected in Application tab ('Run option') ").arg(appPath));
return;
}
readelfProc.start(AndroidConfigurations::instance().readelfPath(target()->activeRunConfiguration()->abi().architecture()).toString(),
QStringList() << QLatin1String("-d") << QLatin1String("-W") << appPath);
if (!readelfProc.waitForFinished(-1)) {
readelfProc.terminate();
return;
}
QStringList libs;
QList<QByteArray> lines = readelfProc.readAll().trimmed().split('\n');
foreach (const QByteArray &line, lines) {
if (line.contains("(NEEDED)") && line.contains("Shared library:") ) {
const int pos = line.lastIndexOf('[') + 1;
libs << QString::fromLatin1(line.mid(pos, line.length() - pos - 1));
}
}
QStringList checkedLibs = AndroidManager::qtLibs(target());
QStringList requiredLibraries;
foreach (const QString &qtLib, AndroidManager::availableQtLibs(target())) {
if (libs.contains(QLatin1String("lib") + qtLib + QLatin1String(".so")) || checkedLibs.contains(qtLib))
requiredLibraries << qtLib;
}
AndroidManager::setQtLibs(target(), requiredLibraries);
checkedLibs = AndroidManager::prebundledLibs(target());
requiredLibraries.clear();
foreach (const QString &qtLib, AndroidManager::availableQtLibs(target())) {
if (libs.contains(qtLib) || checkedLibs.contains(qtLib))
requiredLibraries << qtLib;
}
AndroidManager::setPrebundledLibs(target(), requiredLibraries);
emit updateRequiredLibrariesModels();
}
void AndroidPackageCreationStep::initCheckRequiredLibrariesForRun()
{
m_appPath = Utils::FileName::fromString(AndroidManager::targetApplicationPath(target()));
m_readElf = AndroidConfigurations::instance().readelfPath(target()->activeRunConfiguration()->abi().architecture());
m_qtLibs = AndroidManager::qtLibs(target());
m_availableQtLibs = AndroidManager::availableQtLibs(target());
m_prebundledLibs = AndroidManager::prebundledLibs(target());
}
void AndroidPackageCreationStep::checkRequiredLibrariesForRun()
{
QProcess readelfProc;
if (!m_appPath.toFileInfo().exists()) {
raiseError(tr("Can't find read elf information"),
tr("Can't find '%1'.\n"
"Please make sure your application is "
"built successfully and is selected in Application tab ('Run option') ").arg(m_appPath.toUserOutput()));
return;
}
readelfProc.start(m_readElf.toString(), QStringList() << QLatin1String("-d") << QLatin1String("-W") << m_appPath.toUserOutput());
if (!readelfProc.waitForFinished(-1)) {
readelfProc.terminate();
return;
}
QStringList libs;
QList<QByteArray> lines = readelfProc.readAll().trimmed().split('\n');
foreach (const QByteArray &line, lines) {
if (line.contains("(NEEDED)") && line.contains("Shared library:") ) {
const int pos = line.lastIndexOf('[') + 1;
libs << QString::fromLatin1(line.mid(pos, line.length() - pos - 1));
}
}
QStringList requiredLibraries;
foreach (const QString &qtLib, m_availableQtLibs) {
if (libs.contains(QLatin1String("lib") + qtLib + QLatin1String(".so")) || m_qtLibs.contains(qtLib))
requiredLibraries << qtLib;
}
QMetaObject::invokeMethod(this, "setQtLibs",Qt::BlockingQueuedConnection,
Q_ARG(QStringList, requiredLibraries));
requiredLibraries.clear();
foreach (const QString &qtLib, m_availableQtLibs) {
if (libs.contains(qtLib) || m_prebundledLibs.contains(qtLib))
requiredLibraries << qtLib;
}
QMetaObject::invokeMethod(this, "setPrebundledLibs", Qt::BlockingQueuedConnection,
Q_ARG(QStringList, requiredLibraries));
emit updateRequiredLibrariesModels();
}
void AndroidPackageCreationStep::setQtLibs(const QStringList &qtLibs)
{
AndroidManager::setQtLibs(target(), qtLibs);
}
void AndroidPackageCreationStep::setPrebundledLibs(const QStringList &prebundledLibs)
{
AndroidManager::setPrebundledLibs(target(), prebundledLibs);
}
Utils::FileName AndroidPackageCreationStep::keystorePath()
{
return m_keystorePath;
}
void AndroidPackageCreationStep::setKeystorePath(const Utils::FileName &path)
{
m_keystorePath = path;
m_certificatePasswd.clear();
m_keystorePasswd.clear();
}
void AndroidPackageCreationStep::setKeystorePassword(const QString &pwd)
{
m_keystorePasswd = pwd;
}
void AndroidPackageCreationStep::setCertificateAlias(const QString &alias)
{
m_certificateAlias = alias;
}
void AndroidPackageCreationStep::setCertificatePassword(const QString &pwd)
{
m_certificatePasswd = pwd;
}
void AndroidPackageCreationStep::setOpenPackageLocation(bool open)
{
m_openPackageLocation = open;
}
QAbstractItemModel *AndroidPackageCreationStep::keystoreCertificates()
{
QString rawCerts;
QProcess keytoolProc;
while (!rawCerts.length() || !m_keystorePasswd.length()) {
QStringList params;
params << QLatin1String("-list") << QLatin1String("-v") << QLatin1String("-keystore") << m_keystorePathForRun.toUserOutput() << QLatin1String("-storepass");
if (!m_keystorePasswd.length())
keystorePassword();
if (!m_keystorePasswd.length())
return 0;
params << m_keystorePasswd;
keytoolProc.start(AndroidConfigurations::instance().keytoolPath().toString(), params);
if (!keytoolProc.waitForStarted() || !keytoolProc.waitForFinished()) {
QMessageBox::critical(0, tr("Error"),
tr("Failed to run keytool"));
return 0;
}
if (keytoolProc.exitCode()) {
QMessageBox::critical(0, tr("Error"),
tr("Invalid password"));
m_keystorePasswd.clear();
}
rawCerts = QString::fromLatin1(keytoolProc.readAllStandardOutput());
}
return new CertificatesModel(rawCerts, this);
}
bool AndroidPackageCreationStep::fromMap(const QVariantMap &map)
{
if (!BuildStep::fromMap(map))
return false;
m_keystorePath = Utils::FileName::fromString(map.value(KeystoreLocationKey).toString());
return true;
}
QVariantMap AndroidPackageCreationStep::toMap() const
{
QVariantMap map(BuildStep::toMap());
map.insert(KeystoreLocationKey, m_keystorePath.toString());
return map;
}
bool AndroidPackageCreationStep::createPackage()
{
checkRequiredLibrariesForRun();
emit addOutput(tr("Copy Qt app & libs to Android package ..."), MessageOutput);
QStringList build;
build << QLatin1String("clean");
QFile::remove(m_gdbServerDestination.toString());
if (m_debugBuild || !m_certificateAlias.length()) {
build << QLatin1String("debug");
if (!QFile::copy(m_gdbServerSource.toString(), m_gdbServerDestination.toString())) {
raiseError(tr("Can't copy gdbserver from '%1' to '%2'").arg(m_gdbServerSource.toUserOutput())
.arg(m_gdbServerDestination.toUserOutput()));
return false;
}
} else {
build << QLatin1String("release");
}
emit addOutput(tr("Creating package file ..."), MessageOutput);
QProcess *const buildProc = new QProcess;
connect(buildProc, SIGNAL(readyReadStandardOutput()), this,
SLOT(handleBuildStdOutOutput()));
connect(buildProc, SIGNAL(readyReadStandardError()), this,
SLOT(handleBuildStdErrOutput()));
buildProc->setWorkingDirectory(m_androidDir.toString());
if (!runCommand(buildProc, m_antToolPath.toString(), build)) {
disconnect(buildProc, 0, this, 0);
buildProc->deleteLater();
return false;
}
if (!(m_debugBuild) && m_certificateAlias.length()) {
emit addOutput(tr("Signing package ..."), MessageOutput);
while (true) {
if (m_certificatePasswdForRun.isEmpty())
QMetaObject::invokeMethod(this, "certificatePassword", Qt::BlockingQueuedConnection);
if (m_certificatePasswdForRun.isEmpty()) {
disconnect(buildProc, 0, this, 0);
buildProc->deleteLater();
return false;
}
QByteArray keyPass = m_certificatePasswdForRun.toUtf8();
build.clear();
build << QLatin1String("-verbose") << QLatin1String("-keystore") << m_keystorePathForRun.toUserOutput()
<< QLatin1String("-storepass") << m_keystorePasswd
<< m_apkPathUnsigned.toUserOutput()
<< m_certificateAlias;
buildProc->start(m_jarSigner.toString(), build); //TODO
if (!buildProc->waitForStarted()) {
disconnect(buildProc, 0, this, 0);
buildProc->deleteLater();
return false;
}
keyPass += "\n";
buildProc->write(keyPass);
buildProc->waitForBytesWritten();
buildProc->waitForFinished();
if (!buildProc->exitCode())
break;
emit addOutput(tr("Failed, try again"), ErrorMessageOutput);
m_certificatePasswdForRun.clear();
}
if (QFile::rename(m_apkPathUnsigned.toString(), m_apkPathSigned.toString())) {
emit addOutput(tr("Release signed package created to %1")
.arg(m_apkPathSigned.toUserOutput())
, MessageOutput);
if (m_openPackageLocation)
QMetaObject::invokeMethod(this, "showInGraphicalShell", Qt::QueuedConnection);
}
}
emit addOutput(tr("Package created."), BuildStep::MessageOutput);
disconnect(buildProc, 0, this, 0);
buildProc->deleteLater();
return true;
}
void AndroidPackageCreationStep::stripAndroidLibs(const QStringList & files, Abi::Architecture architecture)
{
QProcess stripProcess;
foreach (const QString &file, files) {
stripProcess.start(AndroidConfigurations::instance().stripPath(architecture).toString(),
QStringList()<<QLatin1String("--strip-unneeded") << file);
stripProcess.waitForStarted();
if (!stripProcess.waitForFinished())
stripProcess.terminate();
}
}
bool AndroidPackageCreationStep::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 AndroidPackageCreationStep::runCommand(QProcess *buildProc
, const QString &program, const QStringList &arguments)
{
emit addOutput(tr("Package deploy: Running command '%1 %2'.").arg(program).arg(arguments.join(QLatin1String(" "))), BuildStep::MessageOutput);
buildProc->start(program, arguments);
if (!buildProc->waitForStarted()) {
raiseError(tr("Packaging failed."),
tr("Packaging error: Could not start command '%1 %2'. Reason: %3")
.arg(program).arg(arguments.join(QLatin1String(" "))).arg(buildProc->errorString()));
return false;
}
buildProc->waitForFinished(-1);
if (buildProc->error() != QProcess::UnknownError
|| buildProc->exitCode() != 0) {
QString mainMessage = tr("Packaging Error: Command '%1 %2' failed.")
.arg(program).arg(arguments.join(QLatin1String(" ")));
if (buildProc->error() != QProcess::UnknownError)
mainMessage += tr(" Reason: %1").arg(buildProc->errorString());
else
mainMessage += tr("Exit code: %1").arg(buildProc->exitCode());
raiseError(mainMessage);
return false;
}
return true;
}
void AndroidPackageCreationStep::handleBuildStdOutOutput()
{
QProcess *const process = qobject_cast<QProcess *>(sender());
if (!process)
return;
process->setReadChannel(QProcess::StandardOutput);
while (process->canReadLine()) {
QString line = QString::fromLocal8Bit(process->readLine());
m_outputParser.stdOutput(line);
emit addOutput(line, BuildStep::NormalOutput, BuildStep::DontAppendNewline);
}
}
void AndroidPackageCreationStep::handleBuildStdErrOutput()
{
QProcess *const process = qobject_cast<QProcess *>(sender());
if (!process)
return;
process->setReadChannel(QProcess::StandardError);
while (process->canReadLine()) {
QString line = QString::fromLocal8Bit(process->readLine());
m_outputParser.stdError(line);
emit addOutput(line, BuildStep::ErrorOutput, BuildStep::DontAppendNewline);
}
}
void AndroidPackageCreationStep::keystorePassword()
{
m_keystorePasswd.clear();
bool ok;
QString text = QInputDialog::getText(0, tr("Keystore"),
tr("Keystore password:"), QLineEdit::Password,
QString(), &ok);
if (ok && !text.isEmpty())
m_keystorePasswd = text;
}
void AndroidPackageCreationStep::certificatePassword()
{
m_certificatePasswdForRun.clear();
bool ok;
QString text = QInputDialog::getText(0, tr("Certificate"),
tr("Certificate password (%1):").arg(m_certificateAlias), QLineEdit::Password,
QString(), &ok);
if (ok && !text.isEmpty())
m_certificatePasswdForRun = text;
}
void AndroidPackageCreationStep::showInGraphicalShell()
{
Core::FileUtils::showInGraphicalShell(Core::ICore::instance()->mainWindow(), m_apkPathSigned.toString());
}
void AndroidPackageCreationStep::raiseError(const QString &shortMsg,
const QString &detailedMsg)
{
emit addOutput(detailedMsg.isNull() ? shortMsg : detailedMsg, BuildStep::ErrorOutput);
emit addTask(Task(Task::Error, shortMsg, Utils::FileName::fromString(QString()), -1,
TASK_CATEGORY_BUILDSYSTEM));
}
const Core::Id AndroidPackageCreationStep::CreatePackageId("Qt4ProjectManager.AndroidPackageCreationStep");
} // namespace Internal
} // namespace Qt4ProjectManager