/************************************************************************** ** ** Copyright (c) 2013 BogDan Vatra ** 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 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 > 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(target()->activeBuildConfiguration()); if (!bc) { raiseError(tr("Cannot create Android package: current build configuration is not Qt 4.")); return false; } Qt4Project *project = static_cast(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(); m_environment = bc->environment(); 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 &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 lines = buffer.trimmed().split('\r'); #else QList 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 &dependencies, QMap *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 availableLibraries, const QStringList &checkedLibs, const QStringList &dependencies) { QMap neededLibraries; QVector::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(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(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 *deployList, QList *pluginsAndImportsList) { Q_ASSERT(deployList != 0); QtSupport::BaseQtVersion *version = QtSupport::QtKitInformation::qtVersion(target()->kit()); if (!version) return; Qt4Project *project = static_cast(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(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 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 &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 &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(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 deployFiles; QList 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; buildProc->setProcessEnvironment(m_environment.toProcessEnvironment()); connect(buildProc, SIGNAL(readyReadStandardOutput()), this, SLOT(handleBuildStdOutOutput()), Qt::DirectConnection); connect(buildProc, SIGNAL(readyReadStandardError()), this, SLOT(handleBuildStdErrOutput()), Qt::DirectConnection); 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()<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(sender()); if (!process) return; handleProcessOutput(process, false); } void AndroidPackageCreationStep::handleBuildStdErrOutput() { QProcess *const process = qobject_cast(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