-
Daniel Teske authored
Change-Id: I4964e5bf22a715375a7fa080604d16692c956fc2 Reviewed-by:
BogDan Vatra <bogdan@kde.org>
Daniel Teske authoredChange-Id: I4964e5bf22a715375a7fa080604d16692c956fc2 Reviewed-by:
BogDan Vatra <bogdan@kde.org>
androidpackagecreationstep.cpp 34.12 KiB
/**************************************************************************
**
** Copyright (c) 2013 BogDan Vatra <bog_dan_ro@yahoo.com>
** Contact: http://www.qt-project.org/legal
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** 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, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#include "androidpackagecreationstep.h"
#include "androidconstants.h"
#include "androiddeploystep.h"
#include "androidglobal.h"
#include "androidpackagecreationwidget.h"
#include "androidmanager.h"
#include "androidgdbserverkitinformation.h"
#include "androidtoolchain.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 <qtsupport/qtkitinformation.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;
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(QLatin1Char('\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();
}
void AndroidPackageCreationStep::ctor()
{
setDefaultDisplayName(tr("Packaging for Android"));
m_openPackageLocation = true;
m_bundleQt = false;
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 Qt 4."));
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;
QString androidTargetArch = project->rootQt4ProjectNode()->singleVariableValue(Qt4ProjectManager::AndroidArchVar);
if (androidTargetArch.isEmpty()) {
raiseError(tr("Cannot create Android package: No ANDROID_TARGET_ARCH set in make spec."));
return false;
}
Utils::FileName androidLibPath = path.appendPath(QLatin1String("libs/") + androidTargetArch);
m_gdbServerDestination = androidLibPath.appendPath(QLatin1String("gdbserver"));
m_gdbServerSource = AndroidGdbServerKitInformation::gdbServer(target()->kit());
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();
m_zipAligner = AndroidConfigurations::instance().zipalignPath();
ProjectExplorer::ToolChain *tc = ProjectExplorer::ToolChainKitInformation::toolChain(target()->kit());
if (tc->type() != QLatin1String(Constants::ANDROID_TOOLCHAIN_TYPE))
return false;
initCheckRequiredLibrariesForRun();
return true;
}
void AndroidPackageCreationStep::run(QFutureInterface<bool> &fi)
{
fi.reportResult(createPackage());
}
BuildStepConfigWidget *AndroidPackageCreationStep::createConfigWidget()
{
return new AndroidPackageCreationWidget(this);
}
static inline QString msgCannotFindElfInformation()
{
return AndroidPackageCreationStep::tr("Cannot find ELF information");
}
static inline QString msgCannotFindExecutable(const QString &appPath)
{
return AndroidPackageCreationStep::tr("Cannot find '%1'.\n"
"Please make sure your application is "
"built successfully and is selected in Application tab ('Run option').").arg(appPath);
}
static void parseSharedLibs(const QByteArray &buffer, QStringList *libs)
{
#if defined(_WIN32)
QList<QByteArray> lines = buffer.trimmed().split('\r');
#else
QList<QByteArray> lines = buffer.trimmed().split('\n');
#endif
foreach (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));
}
}
}
void markNeeded(const QString &library,
const QVector<AndroidManager::Library> &dependencies,
QMap<QString, bool> *neededMap)
{
if (!neededMap->contains(library))
return;
if (neededMap->value(library))
return;
neededMap->insert(library, true);
for (int i = 0; i < dependencies.size(); ++i) {
if (dependencies.at(i).name == library) {
foreach (const QString &dependency, dependencies.at(i).dependencies)
markNeeded(dependency, dependencies, neededMap);
break;
}
}
}
QStringList requiredLibraries(QVector<AndroidManager::Library> availableLibraries,
const QStringList &checkedLibs, const QStringList &dependencies)
{
QMap<QString, bool> neededLibraries;
QVector<AndroidManager::Library>::const_iterator it, end;
it = availableLibraries.constBegin();
end = availableLibraries.constEnd();
for (; it != end; ++it)
neededLibraries[(*it).name] = false;
// Checked items are always needed
foreach (const QString &lib, checkedLibs)
markNeeded(lib, availableLibraries, &neededLibraries);
foreach (const QString &lib, dependencies) {
if (lib.startsWith(QLatin1String("lib"))
&& lib.endsWith(QLatin1String(".so")))
markNeeded(lib.mid(3, lib.size() - 6), availableLibraries, &neededLibraries);
}
for (int i = availableLibraries.size() - 1; i>= 0; --i)
if (!neededLibraries.value(availableLibraries.at(i).name))
availableLibraries.remove(i);
QStringList requiredLibraries;
foreach (const AndroidManager::Library &lib, availableLibraries) {
if (neededLibraries.value(lib.name))
requiredLibraries << lib.name;
}
return requiredLibraries;
}
void AndroidPackageCreationStep::checkRequiredLibraries()
{
QProcess readelfProc;
QString appPath = AndroidManager::targetApplicationPath(target());
if (!QFile::exists(appPath)) {
raiseError(msgCannotFindElfInformation(), msgCannotFindExecutable(appPath));
return;
}
ProjectExplorer::ToolChain *tc = ProjectExplorer::ToolChainKitInformation::toolChain(target()->kit());
if (tc->type() != QLatin1String(Constants::ANDROID_TOOLCHAIN_TYPE))
return;
AndroidToolChain *atc = static_cast<AndroidToolChain *>(tc);
readelfProc.start(AndroidConfigurations::instance().readelfPath(target()->activeRunConfiguration()->abi().architecture(), atc->ndkToolChainVersion()).toString(),
QStringList() << QLatin1String("-d") << QLatin1String("-W") << appPath);
if (!readelfProc.waitForFinished(-1)) {
readelfProc.kill();
return;
}
QStringList libs;
parseSharedLibs(readelfProc.readAll(), &libs);
AndroidManager::setQtLibs(target(), requiredLibraries(AndroidManager::availableQtLibsWithDependencies(target()),
AndroidManager::qtLibs(target()), libs));
QStringList checkedLibs = AndroidManager::prebundledLibs(target());
QStringList prebundledLibraries;
foreach (const QString &qtLib, AndroidManager::availableQtLibs(target())) {
if (libs.contains(qtLib) || checkedLibs.contains(qtLib))
prebundledLibraries << qtLib;
}
AndroidManager::setPrebundledLibs(target(), prebundledLibraries);
emit updateRequiredLibrariesModels();
}
void AndroidPackageCreationStep::initCheckRequiredLibrariesForRun()
{
ProjectExplorer::ToolChain *tc = ProjectExplorer::ToolChainKitInformation::toolChain(target()->kit());
if (tc->type() != QLatin1String(Constants::ANDROID_TOOLCHAIN_TYPE))
return;
AndroidToolChain *atc = static_cast<AndroidToolChain *>(tc);
m_appPath = Utils::FileName::fromString(AndroidManager::targetApplicationPath(target()));
m_readElf = AndroidConfigurations::instance().readelfPath(target()->activeRunConfiguration()->abi().architecture(),
atc->ndkToolChainVersion());
m_qtLibs = AndroidManager::qtLibs(target());
m_availableQtLibs = AndroidManager::availableQtLibsWithDependencies(target());
m_prebundledLibs = AndroidManager::prebundledLibs(target());
}
void AndroidPackageCreationStep::getBundleInformation()
{
m_bundleQt = AndroidManager::bundleQt(target());
if (m_bundleQt) {
m_bundledJars = AndroidManager::loadLocalJars(target()).split(QLatin1Char(':'),
QString::SkipEmptyParts);
m_otherBundledFiles = AndroidManager::loadLocalBundledFiles(target()).split(QLatin1Char(':'),
QString::SkipEmptyParts);
}
}
void AndroidPackageCreationStep::checkRequiredLibrariesForRun()
{
QProcess readelfProc;
if (!m_appPath.toFileInfo().exists()) {
raiseError(msgCannotFindElfInformation(), msgCannotFindExecutable(m_appPath.toUserOutput()));
return;
}
readelfProc.start(m_readElf.toString(), QStringList() << QLatin1String("-d") << QLatin1String("-W") << m_appPath.toUserOutput());
if (!readelfProc.waitForFinished(-1)) {
readelfProc.kill();
return;
}
QStringList libs;
parseSharedLibs(readelfProc.readAll(), &libs);
m_qtLibsWithDependencies = requiredLibraries(m_availableQtLibs, m_qtLibs, libs);
QMetaObject::invokeMethod(this, "setQtLibs",Qt::BlockingQueuedConnection,
Q_ARG(QStringList, m_qtLibsWithDependencies));
QMetaObject::invokeMethod(this, "getBundleInformation", Qt::BlockingQueuedConnection);
QStringList prebundledLibraries;
foreach (const AndroidManager::Library &qtLib, m_availableQtLibs) {
if (libs.contains(qtLib.name) || m_prebundledLibs.contains(qtLib.name))
prebundledLibraries << qtLib.name;
}
QMetaObject::invokeMethod(this, "setPrebundledLibs", Qt::BlockingQueuedConnection,
Q_ARG(QStringList, prebundledLibraries));
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_keystorePath.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;
}
QStringList AndroidPackageCreationStep::collectRelativeFilePaths(const QString &parentPath)
{
QStringList relativeFilePaths;
QDirIterator libsIt(parentPath, QDir::NoFilter, QDirIterator::Subdirectories);
int pos = parentPath.size();
while (libsIt.hasNext()) {
libsIt.next();
if (!libsIt.fileInfo().isDir())
relativeFilePaths.append(libsIt.filePath().mid(pos));
}
return relativeFilePaths;
}
void AndroidPackageCreationStep::collectFiles(QList<DeployItem> *deployList,
QList<DeployItem> *pluginsAndImportsList)
{
Q_ASSERT(deployList != 0);
QtSupport::BaseQtVersion *version = QtSupport::QtKitInformation::qtVersion(target()->kit());
if (!version)
return;
Qt4Project *project = static_cast<Qt4Project *>(target()->project());
QString androidTargetArch = project->rootQt4ProjectNode()->singleVariableValue(Qt4ProjectManager::AndroidArchVar);
QString androidAssetsPath = m_androidDir.toString() + QLatin1String("/assets/");
QString androidJarPath = m_androidDir.toString() + QLatin1String("/libs/");
QString androidLibPath = m_androidDir.toString() + QLatin1String("/libs/") + androidTargetArch;
QString qtVersionSourcePath = version->sourcePath().toString();
foreach (QString qtLib, m_qtLibsWithDependencies) {
QString fullPath = qtVersionSourcePath
+ QLatin1String("/lib/lib")
+ qtLib
+ QLatin1String(".so");
QString destinationPath = androidLibPath
+ QLatin1String("/lib")
+ qtLib
+ QLatin1String(".so");
// If the Qt lib/ folder contains libgnustl_shared.so, don't deploy it from there, since
// it will be deployed directly from the NDK instead.
if (qtLib != QLatin1String("gnustl_shared")) {
DeployItem deployItem(fullPath, 0, destinationPath, true);
deployList->append(deployItem);
}
}
if (!androidTargetArch.isEmpty()) {
ProjectExplorer::ToolChain *tc = ProjectExplorer::ToolChainKitInformation::toolChain(target()->kit());
if (tc->type() != QLatin1String(Constants::ANDROID_TOOLCHAIN_TYPE))
return;
AndroidToolChain *atc = static_cast<AndroidToolChain *>(tc);
QString libgnustl = AndroidManager::libGnuStl(androidTargetArch, atc->ndkToolChainVersion());
DeployItem deployItem(libgnustl, 0, androidLibPath + QLatin1String("/libgnustl_shared.so"), false);
deployList->append(deployItem);
}
foreach (QString jar, m_bundledJars) {
QString fullPath = qtVersionSourcePath + QLatin1Char('/') + jar;
QFileInfo fileInfo(fullPath);
if (fileInfo.exists()) {
QString destinationPath = androidJarPath
+ AndroidManager::libraryPrefix()
+ fileInfo.fileName();
deployList->append(DeployItem(fullPath, 0, destinationPath, true));
}
}
QSet<QString> alreadyListed;
foreach (QString bundledFile, m_otherBundledFiles) {
QStringList allFiles;
if (QFileInfo(qtVersionSourcePath + QLatin1Char('/') + bundledFile).isDir()) {
if (!bundledFile.endsWith(QLatin1Char('/')))
bundledFile.append(QLatin1Char('/'));
allFiles = collectRelativeFilePaths(qtVersionSourcePath + QLatin1Char('/') + bundledFile);
} else {
// If we need to bundle a specific file, we just add an empty string and the file
// names and data will be prepared correctly in the loop below.
allFiles = QStringList(QString());
}
foreach (QString file, allFiles) {
QString fullPath = qtVersionSourcePath + QLatin1Char('/') + bundledFile + file;
if (alreadyListed.contains(fullPath))
continue;
alreadyListed.insert(fullPath);
QString garbledFileName;
QString destinationPath;
bool shouldStrip = false;
QString fullFileName = bundledFile + file;
if (fullFileName.endsWith(QLatin1String(".so"))) {
if (fullFileName.startsWith(QLatin1String("lib/"))) {
// Special case when the destination folder is lib/
// Since this is also the source folder, there is no need to garble the file
// name and copy it. We also won't have write access to this folder, so we
// couldn't if we wanted to.
garbledFileName = fullFileName.mid(sizeof("lib/") - 1);
} else {
garbledFileName = QLatin1String("lib")
+ AndroidManager::libraryPrefix()
+ QString(fullFileName).replace(QLatin1Char('/'), QLatin1Char('_'));
}
destinationPath = androidLibPath + QLatin1Char('/') + garbledFileName;
shouldStrip = true;
} else {
garbledFileName = AndroidManager::libraryPrefix() + QLatin1Char('/') + fullFileName;
destinationPath = androidAssetsPath + garbledFileName;
}
deployList->append(DeployItem(fullPath, 0, destinationPath, shouldStrip));
pluginsAndImportsList->append(DeployItem(garbledFileName,
0,
fullFileName,
shouldStrip));
}
}
}
void AndroidPackageCreationStep::removeManagedFilesFromPackage()
{
// Clean up all files managed by Qt Creator
{
QString androidLibPath = m_androidDir.toString() + QLatin1String("/libs/");
QDirIterator dirIt(m_androidDir.toString(), QDirIterator::Subdirectories);
while (dirIt.hasNext()) {
dirIt.next();
if (!dirIt.fileInfo().isDir()) {
bool isQtLibrary = dirIt.fileInfo().path().startsWith(androidLibPath)
&& dirIt.fileName().startsWith(QLatin1String("libQt5"))
&& dirIt.fileName().endsWith(QLatin1String(".so"));
if (dirIt.filePath().contains(AndroidManager::libraryPrefix()) || isQtLibrary)
QFile::remove(dirIt.filePath());
}
}
}
removeDirectory(m_androidDir.toString() + QLatin1String("/assets/") + AndroidManager::libraryPrefix());
}
void AndroidPackageCreationStep::copyFilesIntoPackage(const QList<DeployItem> &deployList)
{
foreach (DeployItem item, deployList) {
QFileInfo info(item.remoteFileName);
if (info.exists())
QFile::remove(item.remoteFileName);
else
QDir().mkpath(info.absolutePath());
QFile::copy(item.localFileName, item.remoteFileName);
}
}
void AndroidPackageCreationStep::stripFiles(const QList<DeployItem> &deployList)
{
QStringList fileList;
foreach (DeployItem item, deployList)
if (item.needsStrip)
fileList.append(item.remoteFileName);
ProjectExplorer::ToolChain *tc = ProjectExplorer::ToolChainKitInformation::toolChain(target()->kit());
if (tc->type() != QLatin1String(Constants::ANDROID_TOOLCHAIN_TYPE))
return;
AndroidToolChain *atc = static_cast<AndroidToolChain *>(tc);
stripAndroidLibs(fileList,
target()->activeRunConfiguration()->abi().architecture(),
atc->ndkToolChainVersion());
}
void AndroidPackageCreationStep::updateXmlForFiles(const QStringList &inLibList,
const QStringList &inAssetsList)
{
AndroidManager::setBundledInLib(target(), inLibList);
AndroidManager::setBundledInAssets(target(), inAssetsList);
}
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");
QDir dir;
dir.mkpath(m_gdbServerDestination.toFileInfo().absolutePath());
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");
}
QList<DeployItem> deployFiles;
QList<DeployItem> importsAndPlugins;
QtSupport::BaseQtVersion *version = QtSupport::QtKitInformation::qtVersion(target()->kit());
// Qt 5 supports bundling libraries inside the apk. We guard the code for Qt 5 to be sure we
// do not disrupt existing projects.
if (version && version->qtVersion() >= QtSupport::QtVersionNumber(5, 0, 0)) {
bool bundleQt = AndroidManager::bundleQt(target());
// Collect the files to bundle in the package
if (bundleQt)
collectFiles(&deployFiles, &importsAndPlugins);
// Remove files from package if they are not needed
removeManagedFilesFromPackage();
// Deploy files to package
if (bundleQt) {
copyFilesIntoPackage(deployFiles);
stripFiles(deployFiles);
QStringList inLibList;
QStringList inAssetsList;
foreach (DeployItem deployItem, importsAndPlugins) {
QString conversionInfo = deployItem.localFileName
+ QLatin1Char(':')
+ deployItem.remoteFileName;
if (deployItem.localFileName.endsWith(QLatin1String(".so")))
inLibList.append(conversionInfo);
else
inAssetsList.append(conversionInfo);
}
QMetaObject::invokeMethod(this,
"updateXmlForFiles",
Qt::BlockingQueuedConnection,
Q_ARG(QStringList, inLibList),
Q_ARG(QStringList, inAssetsList));
}
}
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);
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();
}
build.clear();
build << QLatin1String("-f") << QLatin1String("-v") << QLatin1String("4") << m_apkPathUnsigned.toString() << m_apkPathSigned.toString();
buildProc->start(m_zipAligner.toString(), build);
buildProc->waitForFinished();
if (!buildProc->exitCode()) {
QFile::remove(m_apkPathUnsigned.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, const QString &ndkToolchainVersion)
{
QProcess stripProcess;
foreach (const QString &file, files) {
stripProcess.start(AndroidConfigurations::instance().stripPath(architecture, ndkToolchainVersion).toString(),
QStringList()<<QLatin1String("--strip-unneeded") << file);
stripProcess.waitForStarted();
if (!stripProcess.waitForFinished())
stripProcess.kill();
}
}
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);
handleProcessOutput(buildProc, false);
handleProcessOutput(buildProc, true);
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;
handleProcessOutput(process, false);
}
void AndroidPackageCreationStep::handleBuildStdErrOutput()
{
QProcess *const process = qobject_cast<QProcess *>(sender());
if (!process)
return;
handleProcessOutput(process, true);
}
void AndroidPackageCreationStep::handleProcessOutput(QProcess *process, bool stdErr)
{
process->setReadChannel(stdErr ? QProcess::StandardError : QProcess::StandardOutput);
while (process->canReadLine()) {
QString line = QString::fromLocal8Bit(process->readLine());
if (stdErr)
m_outputParser.stdError(line);
else
m_outputParser.stdOutput(line);
emit addOutput(line, stdErr ? BuildStep::ErrorOutput
: BuildStep::NormalOutput,
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