An error occurred while loading the file. Please try again.
-
Valentin Fokin authored
Skip build of linquist, assistant, designer, pixeltool, qdbusviewer and distancefieldgenerator on disabled features. This also fixes build with -no-feature-action configuration. Change-Id: I0b7de9bae9c33ddb47d83ed577ec0e404bde360d Reviewed-by:
Oswald Buddenhagen <oswald.buddenhagen@qt.io>
a9867972
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the tools applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** 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.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QCoreApplication>
#include <QStringList>
#include <QDir>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonValue>
#include <QDebug>
#include <QXmlStreamReader>
#include <QDateTime>
#include <QStandardPaths>
#include <QUuid>
#include <QDirIterator>
#include <algorithm>
static const bool mustReadOutputAnyway = true; // pclose seems to return the wrong error code unless we read the output
void deleteRecursively(const QString &dirName)
{
QDir dir(dirName);
if (!dir.exists())
return;
QFileInfoList entries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
foreach (QFileInfo entry, entries) {
if (entry.isDir())
deleteRecursively(entry.absoluteFilePath());
else
QFile::remove(entry.absoluteFilePath());
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
}
QDir().rmdir(dirName);
}
struct Options
{
Options()
: helpRequested(false)
, verbose(false)
, timing(false)
, generateAssetsFileList(true)
, minimumAndroidVersion(9)
, targetAndroidVersion(10)
, deploymentMechanism(Bundled)
, releasePackage(false)
, digestAlg(QLatin1String("SHA1"))
, sigAlg(QLatin1String("SHA1withRSA"))
, internalSf(false)
, sectionsOnly(false)
, protectedAuthenticationPath(false)
, installApk(false)
, fetchedRemoteModificationDates(false)
{}
~Options()
{
if (!temporaryDirectoryName.isEmpty())
deleteRecursively(temporaryDirectoryName);
}
enum DeploymentMechanism
{
Bundled,
Ministro,
Debug
};
bool helpRequested;
bool verbose;
bool timing;
bool generateAssetsFileList;
QTime timer;
// External tools
QString sdkPath;
QString ndkPath;
QString antTool;
QString jdkPath;
// Build paths
QString qtInstallDirectory;
QString androidSourceDirectory;
QString outputDirectory;
QString inputFileName;
QString applicationBinary;
// Build information
QString androidPlatform;
QString architecture;
QString toolchainVersion;
QString toolchainPrefix;
QString toolPrefix;
QString ndkHost;
// Package information
int minimumAndroidVersion;
int targetAndroidVersion;
DeploymentMechanism deploymentMechanism;
QString packageName;
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
QStringList extraLibs;
// Signing information
bool releasePackage;
QString keyStore;
QString keyStorePassword;
QString keyStoreAlias;
QString storeType;
QString keyPass;
QString sigFile;
QString signedJar;
QString digestAlg;
QString sigAlg;
QString tsaUrl;
QString tsaCert;
bool internalSf;
bool sectionsOnly;
bool protectedAuthenticationPath;
// Installation information
bool installApk;
QString installLocation;
// Collected information
typedef QPair<QString, QString> BundledFile;
QList<BundledFile> bundledFiles;
QStringList qtDependencies;
QStringList localLibs;
QStringList localJars;
QStringList initClasses;
QString temporaryDirectoryName;
bool fetchedRemoteModificationDates;
QDateTime remoteModificationDate;
QStringList permissions;
QStringList features;
};
// Copy-pasted from qmake/library/ioutil.cpp
inline static bool hasSpecialChars(const QString &arg, const uchar (&iqm)[16])
{
for (int x = arg.length() - 1; x >= 0; --x) {
ushort c = arg.unicode()[x].unicode();
if ((c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7))))
return true;
}
return false;
}
static QString shellQuoteUnix(const QString &arg)
{
// Chars that should be quoted (TM). This includes:
static const uchar iqm[] = {
0xff, 0xff, 0xff, 0xff, 0xdf, 0x07, 0x00, 0xd8,
0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x78
}; // 0-32 \'"$`<>|;&(){}*?#!~[]
if (!arg.length())
return QString::fromLatin1("\"\"");
QString ret(arg);
if (hasSpecialChars(ret, iqm)) {
ret.replace(QLatin1Char('\''), QLatin1String("'\\''"));
ret.prepend(QLatin1Char('\''));
ret.append(QLatin1Char('\''));
}
return ret;
}
static QString shellQuoteWin(const QString &arg)
{
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
// Chars that should be quoted (TM). This includes:
// - control chars & space
// - the shell meta chars "&()<>^|
// - the potential separators ,;=
static const uchar iqm[] = {
0xff, 0xff, 0xff, 0xff, 0x45, 0x13, 0x00, 0x78,
0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x10
};
if (!arg.length())
return QString::fromLatin1("\"\"");
QString ret(arg);
if (hasSpecialChars(ret, iqm)) {
// Quotes are escaped and their preceding backslashes are doubled.
// It's impossible to escape anything inside a quoted string on cmd
// level, so the outer quoting must be "suspended".
ret.replace(QRegExp(QLatin1String("(\\\\*)\"")), QLatin1String("\"\\1\\1\\^\"\""));
// The argument must not end with a \ since this would be interpreted
// as escaping the quote -- rather put the \ behind the quote: e.g.
// rather use "foo"\ than "foo\"
int i = ret.length();
while (i > 0 && ret.at(i - 1) == QLatin1Char('\\'))
--i;
ret.insert(i, QLatin1Char('"'));
ret.prepend(QLatin1Char('"'));
}
return ret;
}
static QString shellQuote(const QString &arg)
{
if (QDir::separator() == QLatin1Char('\\'))
return shellQuoteWin(arg);
else
return shellQuoteUnix(arg);
}
Options parseOptions()
{
Options options;
QStringList arguments = QCoreApplication::arguments();
for (int i=0; i<arguments.size(); ++i) {
const QString &argument = arguments.at(i);
if (argument.compare(QLatin1String("--output"), Qt::CaseInsensitive) == 0) {
if (i + 1 == arguments.size())
options.helpRequested = true;
else
options.outputDirectory = arguments.at(++i);
} else if (argument.compare(QLatin1String("--input"), Qt::CaseInsensitive) == 0) {
if (i + 1 == arguments.size())
options.helpRequested = true;
else
options.inputFileName = arguments.at(++i);
} else if (argument.compare(QLatin1String("--install"), Qt::CaseInsensitive) == 0) {
options.installApk = true;
} else if (argument.compare(QLatin1String("--android-platform"), Qt::CaseInsensitive) == 0) {
if (i + 1 == arguments.size())
options.helpRequested = true;
else
options.androidPlatform = arguments.at(++i);
} else if (argument.compare(QLatin1String("--help"), Qt::CaseInsensitive) == 0) {
options.helpRequested = true;
} else if (argument.compare(QLatin1String("--verbose"), Qt::CaseInsensitive) == 0) {
options.verbose = true;
} else if (argument.compare(QLatin1String("--ant"), Qt::CaseInsensitive) == 0) {
if (i + 1 == arguments.size())
options.helpRequested = true;
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
else
options.antTool = arguments.at(++i);
} else if (argument.compare(QLatin1String("--deployment"), Qt::CaseInsensitive) == 0) {
if (i + 1 == arguments.size()) {
options.helpRequested = true;
} else {
QString deploymentMechanism = arguments.at(++i);
if (deploymentMechanism.compare(QLatin1String("ministro"), Qt::CaseInsensitive) == 0) {
options.deploymentMechanism = Options::Ministro;
} else if (deploymentMechanism.compare(QLatin1String("debug"), Qt::CaseInsensitive) == 0) {
options.deploymentMechanism = Options::Debug;
QString temporaryDirectoryName = QStandardPaths::standardLocations(QStandardPaths::TempLocation).at(0)
+ QLatin1String("/android-build-")
+ QUuid::createUuid().toString();
if (QFileInfo(temporaryDirectoryName).exists()) {
fprintf(stderr, "Temporary directory '%s' already exists. Bailing out.\n",
qPrintable(temporaryDirectoryName));
options.helpRequested = true;
} else {
options.temporaryDirectoryName = temporaryDirectoryName;
}
} else if (deploymentMechanism.compare(QLatin1String("bundled"), Qt::CaseInsensitive) == 0) {
options.deploymentMechanism = Options::Bundled;
} else {
fprintf(stderr, "Unrecognized deployment mechanism: %s\n", qPrintable(deploymentMechanism));
options.helpRequested = true;
}
}
} else if (argument.compare(QLatin1String("--device"), Qt::CaseInsensitive) == 0) {
if (i + 1 == arguments.size())
options.helpRequested = true;
else
options.installLocation = arguments.at(++i);
} else if (argument.compare(QLatin1String("--release"), Qt::CaseInsensitive) == 0) {
options.releasePackage = true;
} else if (argument.compare(QLatin1String("--jdk"), Qt::CaseInsensitive) == 0) {
if (i + 1 == arguments.size())
options.helpRequested = true;
else
options.jdkPath = arguments.at(++i);
} else if (argument.compare(QLatin1String("--sign"), Qt::CaseInsensitive) == 0) {
if (i + 2 >= arguments.size()) {
options.helpRequested = true;
} else {
options.releasePackage = true;
options.keyStore = arguments.at(++i);
options.keyStoreAlias = arguments.at(++i);
}
} else if (argument.compare(QLatin1String("--storepass"), Qt::CaseInsensitive) == 0) {
if (i + 1 == arguments.size())
options.helpRequested = true;
else
options.keyStorePassword = arguments.at(++i);
} else if (argument.compare(QLatin1String("--storetype"), Qt::CaseInsensitive) == 0) {
if (i + 1 == arguments.size())
options.helpRequested = true;
else
options.storeType = arguments.at(++i);
} else if (argument.compare(QLatin1String("--keypass"), Qt::CaseInsensitive) == 0) {
if (i + 1 == arguments.size())
options.helpRequested = true;
else
options.keyPass = arguments.at(++i);
} else if (argument.compare(QLatin1String("--sigfile"), Qt::CaseInsensitive) == 0) {
if (i + 1 == arguments.size())
options.helpRequested = true;
else
options.sigFile = arguments.at(++i);
351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
} else if (argument.compare(QLatin1String("--digestalg"), Qt::CaseInsensitive) == 0) {
if (i + 1 == arguments.size())
options.helpRequested = true;
else
options.digestAlg = arguments.at(++i);
} else if (argument.compare(QLatin1String("--sigalg"), Qt::CaseInsensitive) == 0) {
if (i + 1 == arguments.size())
options.helpRequested = true;
else
options.sigAlg = arguments.at(++i);
} else if (argument.compare(QLatin1String("--tsa"), Qt::CaseInsensitive) == 0) {
if (i + 1 == arguments.size())
options.helpRequested = true;
else
options.tsaUrl = arguments.at(++i);
} else if (argument.compare(QLatin1String("--tsacert"), Qt::CaseInsensitive) == 0) {
if (i + 1 == arguments.size())
options.helpRequested = true;
else
options.tsaCert = arguments.at(++i);
} else if (argument.compare(QLatin1String("--internalsf"), Qt::CaseInsensitive) == 0) {
options.internalSf = true;
} else if (argument.compare(QLatin1String("--sectionsonly"), Qt::CaseInsensitive) == 0) {
options.sectionsOnly = true;
} else if (argument.compare(QLatin1String("--protected"), Qt::CaseInsensitive) == 0) {
options.protectedAuthenticationPath = true;
} else if (argument.compare(QLatin1String("--no-generated-assets-cache"), Qt::CaseInsensitive) == 0) {
options.generateAssetsFileList = false;
}
}
if (options.inputFileName.isEmpty())
options.inputFileName = QString::fromLatin1("android-lib%1.so-deployment-settings.json").arg(QDir::current().dirName());
options.timing = qEnvironmentVariableIsSet("ANDROIDDEPLOYQT_TIMING_OUTPUT");
return options;
}
void printHelp()
{// "012345678901234567890123456789012345678901234567890123456789012345678901"
fprintf(stderr, "Syntax: %s --output <destination> [options]\n"
"\n"
" Creates an Android package in the build directory <destination> and\n"
" builds it into an .apk file.\n\n"
" Optional arguments:\n"
" --input <inputfile>: Reads <inputfile> for options generated by\n"
" qmake. A default file name based on the current working\n"
" directory will be used if nothing else is specified.\n"
" --deployment <mechanism>: Supported deployment mechanisms:\n"
" bundled (default): Include Qt files in stand-alone package.\n"
" ministro: Use the Ministro service to manage Qt files.\n"
" debug: Copy Qt files to device for quick debugging.\n"
" --install: Installs apk to device/emulator. By default this step is\n"
" not taken.\n"
" --device [device ID]: Use specified device for deployment. Default\n"
" is the device selected by default by adb.\n"
" --android-platform <platform>: Builds against the given android\n"
" platform. By default, the highest available version will be\n"
" used.\n"
" --ant <path/to/ant>: If unspecified, ant from the PATH will be\n"
" used.\n"
" --release: Builds a package ready for release. By default, the\n"
" package will be signed with a debug key.\n"
" --sign <url/to/keystore> <alias>: Signs the package with the\n"
" specified keystore, alias and store password. Also implies the\n"
" --release option.\n"
" Optional arguments for use with signing:\n"
" --storepass <password>: Keystore password.\n"
" --storetype <type>: Keystore type.\n"
421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
" --keypass <password>: Password for private key (if different\n"
" from keystore password.)\n"
" --sigfile <file>: Name of .SF/.DSA file.\n"
" --digestalg <name>: Name of digest algorithm. Default is\n"
" \"SHA1\".\n"
" --sigalg <name>: Name of signature algorithm. Default is\n"
" \"SHA1withRSA\".\n"
" --tsa <url>: Location of the Time Stamping Authority.\n"
" --tsacert <alias>: Public key certificate for TSA.\n"
" --internalsf: Include the .SF file inside the signature block.\n"
" --sectionsonly: Don't compute hash of entire manifest.\n"
" --protected: Keystore has protected authentication path.\n"
" --jdk <path/to/jdk>: Used to find the jarsigner tool when used\n"
" in combination with the --release argument. By default,\n"
" an attempt is made to detect the tool using the JAVA_HOME and\n"
" PATH environment variables, in that order.\n"
" --verbose: Prints out information during processing.\n"
" --no-generated-assets-cache: Do not pregenerate the entry list for\n"
" the assets file engine.\n"
" --help: Displays this information.\n\n",
qPrintable(QCoreApplication::arguments().at(0))
);
}
// Since strings compared will all start with the same letters,
// sorting by length and then alphabetically within each length
// gives the natural order.
bool quasiLexicographicalReverseLessThan(const QFileInfo &fi1, const QFileInfo &fi2)
{
QString s1 = fi1.baseName();
QString s2 = fi2.baseName();
if (s1.length() == s2.length())
return s1 > s2;
else
return s1.length() > s2.length();
}
// Files which contain templates that need to be overwritten by build data should be overwritten every
// time.
bool alwaysOverwritableFile(const QString &fileName)
{
return (fileName.endsWith(QLatin1String("/res/values/libs.xml"))
|| fileName.endsWith(QLatin1String("/AndroidManifest.xml"))
|| fileName.endsWith(QLatin1String("/res/values/strings.xml"))
|| fileName.endsWith(QLatin1String("/src/org/qtproject/qt5/android/bindings/QtActivity.java")));
}
bool copyFileIfNewer(const QString &sourceFileName,
const QString &destinationFileName,
bool verbose,
bool forceOverwrite = false)
{
if (QFile::exists(destinationFileName)) {
QFileInfo destinationFileInfo(destinationFileName);
QFileInfo sourceFileInfo(sourceFileName);
if (!forceOverwrite
&& sourceFileInfo.lastModified() <= destinationFileInfo.lastModified()
&& !alwaysOverwritableFile(destinationFileName)) {
if (verbose)
fprintf(stdout, " -- Skipping file %s. Same or newer file already in place.\n", qPrintable(sourceFileName));
return true;
} else {
if (!QFile(destinationFileName).remove()) {
fprintf(stderr, "Can't remove old file: %s\n", qPrintable(destinationFileName));
return false;
}
}
}
491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
if (!QDir().mkpath(QFileInfo(destinationFileName).path())) {
fprintf(stderr, "Cannot make output directory for %s.\n", qPrintable(destinationFileName));
return false;
}
if (!QFile::exists(destinationFileName) && !QFile::copy(sourceFileName, destinationFileName)) {
fprintf(stderr, "Failed to copy %s to %s.\n", qPrintable(sourceFileName), qPrintable(destinationFileName));
return false;
} else if (verbose) {
fprintf(stdout, " -- Copied %s\n", qPrintable(destinationFileName));
}
return true;
}
QString cleanPackageName(QString packageName)
{
QRegExp legalChars(QLatin1String("[a-zA-Z0-9_\\.]"));
for (int i = 0; i < packageName.length(); ++i) {
if (!legalChars.exactMatch(packageName.mid(i, 1)))
packageName[i] = QLatin1Char('_');
}
static QStringList keywords;
if (keywords.isEmpty()) {
keywords << QLatin1String("abstract") << QLatin1String("continue") << QLatin1String("for")
<< QLatin1String("new") << QLatin1String("switch") << QLatin1String("assert")
<< QLatin1String("default") << QLatin1String("if") << QLatin1String("package")
<< QLatin1String("synchronized") << QLatin1String("boolean") << QLatin1String("do")
<< QLatin1String("goto") << QLatin1String("private") << QLatin1String("this")
<< QLatin1String("break") << QLatin1String("double") << QLatin1String("implements")
<< QLatin1String("protected") << QLatin1String("throw") << QLatin1String("byte")
<< QLatin1String("else") << QLatin1String("import") << QLatin1String("public")
<< QLatin1String("throws") << QLatin1String("case") << QLatin1String("enum")
<< QLatin1String("instanceof") << QLatin1String("return") << QLatin1String("transient")
<< QLatin1String("catch") << QLatin1String("extends") << QLatin1String("int")
<< QLatin1String("short") << QLatin1String("try") << QLatin1String("char")
<< QLatin1String("final") << QLatin1String("interface") << QLatin1String("static")
<< QLatin1String("void") << QLatin1String("class") << QLatin1String("finally")
<< QLatin1String("long") << QLatin1String("strictfp") << QLatin1String("volatile")
<< QLatin1String("const") << QLatin1String("float") << QLatin1String("native")
<< QLatin1String("super") << QLatin1String("while");
}
// No keywords
int index = -1;
while (index < packageName.length()) {
int next = packageName.indexOf(QLatin1Char('.'), index + 1);
if (next == -1)
next = packageName.length();
QString word = packageName.mid(index + 1, next - index - 1);
if (!word.isEmpty()) {
QChar c = word[0];
if ((c >= QChar(QLatin1Char('0')) && c<= QChar(QLatin1Char('9')))
|| c == QLatin1Char('_')) {
packageName.insert(index + 1, QLatin1Char('a'));
index = next + 1;
continue;
}
}
if (keywords.contains(word)) {
packageName.insert(next, QLatin1String("_"));
index = next + 1;
} else {
index = next;
}
}
561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
return packageName;
}
QString detectLatestAndroidPlatform(const QString &sdkPath)
{
QDir dir(sdkPath + QLatin1String("/platforms"));
if (!dir.exists()) {
fprintf(stderr, "Directory %s does not exist\n", qPrintable(dir.absolutePath()));
return QString();
}
QFileInfoList fileInfos = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
if (fileInfos.isEmpty()) {
fprintf(stderr, "No platforms found in %s", qPrintable(dir.absolutePath()));
return QString();
}
std::sort(fileInfos.begin(), fileInfos.end(), quasiLexicographicalReverseLessThan);
QFileInfo latestPlatform = fileInfos.first();
return latestPlatform.baseName();
}
bool readInputFile(Options *options)
{
QFile file(options->inputFileName);
if (!file.open(QIODevice::ReadOnly)) {
fprintf(stderr, "Cannot read from input file: %s\n", qPrintable(options->inputFileName));
return false;
}
QJsonDocument jsonDocument = QJsonDocument::fromJson(file.readAll());
if (jsonDocument.isNull()) {
fprintf(stderr, "Invalid json file: %s\n", qPrintable(options->inputFileName));
return false;
}
QJsonObject jsonObject = jsonDocument.object();
{
QJsonValue sdkPath = jsonObject.value(QLatin1String("sdk"));
if (sdkPath.isUndefined()) {
fprintf(stderr, "No SDK path in json file %s\n", qPrintable(options->inputFileName));
return false;
}
options->sdkPath = sdkPath.toString();
if (options->androidPlatform.isEmpty()) {
options->androidPlatform = detectLatestAndroidPlatform(options->sdkPath);
if (options->androidPlatform.isEmpty())
return false;
} else {
if (!QDir(options->sdkPath + QLatin1String("/platforms/") + options->androidPlatform).exists()) {
fprintf(stderr, "Warning: Android platform '%s' does not exist in NDK.\n",
qPrintable(options->androidPlatform));
}
}
}
{
QJsonValue qtInstallDirectory = jsonObject.value("qt");
if (qtInstallDirectory.isUndefined()) {
fprintf(stderr, "No Qt directory in json file %s\n", qPrintable(options->inputFileName));
return false;
}
options->qtInstallDirectory = qtInstallDirectory.toString();
}
{
631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
QJsonValue androidSourcesDirectory = jsonObject.value("android-package-source-directory");
if (!androidSourcesDirectory.isUndefined())
options->androidSourceDirectory = androidSourcesDirectory.toString();
}
{
QJsonValue applicationBinary = jsonObject.value("application-binary");
if (applicationBinary.isUndefined()) {
fprintf(stderr, "No application binary defined in json file.\n");
return false;
}
options->applicationBinary = applicationBinary.toString();
if (!QFile::exists(options->applicationBinary)) {
fprintf(stderr, "Cannot find application binary %s.\n", qPrintable(options->applicationBinary));
return false;
}
}
{
QJsonValue deploymentDependencies = jsonObject.value("deployment-dependencies");
if (!deploymentDependencies.isUndefined())
options->qtDependencies = deploymentDependencies.toString().split(QLatin1Char(','));
}
{
QJsonValue targetArchitecture = jsonObject.value("target-architecture");
if (targetArchitecture.isUndefined()) {
fprintf(stderr, "No target architecture defined in json file.\n");
return false;
}
options->architecture = targetArchitecture.toString();
}
{
QJsonValue ndk = jsonObject.value("ndk");
if (ndk.isUndefined()) {
fprintf(stderr, "No NDK path defined in json file.\n");
return false;
}
options->ndkPath = ndk.toString();
}
{
QJsonValue toolchainPrefix = jsonObject.value("toolchain-prefix");
if (toolchainPrefix.isUndefined()) {
fprintf(stderr, "No toolchain prefix defined in json file.\n");
return false;
}
options->toolchainPrefix = toolchainPrefix.toString();
}
{
QJsonValue toolPrefix = jsonObject.value("tool-prefix");
if (toolPrefix.isUndefined()) {
fprintf(stderr, "Warning: No tool prefix defined in json file.\n");
options->toolPrefix = options->toolchainPrefix;
} else {
options->toolPrefix = toolPrefix.toString();
}
}
{
QJsonValue toolchainVersion = jsonObject.value("toolchain-version");
if (toolchainVersion.isUndefined()) {
fprintf(stderr, "No toolchain version defined in json file.\n");
return false;
}
options->toolchainVersion = toolchainVersion.toString();
701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770
}
{
QJsonValue ndkHost = jsonObject.value("ndk-host");
if (ndkHost.isUndefined()) {
fprintf(stderr, "No NDK host defined in json file.\n");
return false;
}
options->ndkHost = ndkHost.toString();
}
options->packageName = cleanPackageName(QString::fromLatin1("org.qtproject.example.%1").arg(QFileInfo(options->applicationBinary).baseName().mid(sizeof("lib") - 1)));
{
QJsonValue extraLibs = jsonObject.value("android-extra-libs");
if (!extraLibs.isUndefined())
options->extraLibs = extraLibs.toString().split(QLatin1Char(','));
}
return true;
}
bool copyFiles(const QDir &sourceDirectory, const QDir &destinationDirectory, bool verbose, bool forceOverwrite = false)
{
QFileInfoList entries = sourceDirectory.entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
foreach (QFileInfo entry, entries) {
if (entry.isDir()) {
QDir dir(entry.absoluteFilePath());
if (!destinationDirectory.mkpath(dir.dirName())) {
fprintf(stderr, "Cannot make directory %s in %s\n", qPrintable(dir.dirName()), qPrintable(destinationDirectory.path()));
return false;
}
if (!copyFiles(dir, QDir(destinationDirectory.path() + QLatin1String("/") + dir.dirName()), verbose, forceOverwrite))
return false;
} else {
QString destination = destinationDirectory.absoluteFilePath(entry.fileName());
if (!copyFileIfNewer(entry.absoluteFilePath(), destination, verbose, forceOverwrite))
return false;
}
}
return true;
}
bool copyAndroidTemplate(const Options &options)
{
if (options.verbose)
fprintf(stdout, "Copying Android package template.\n");
QDir sourceDirectory(options.qtInstallDirectory + QLatin1String("/src/android/java"));
if (!sourceDirectory.exists()) {
fprintf(stderr, "Cannot find template directory %s\n", qPrintable(sourceDirectory.absolutePath()));
return false;
}
if (!QDir::current().mkpath(options.outputDirectory)) {
fprintf(stderr, "Cannot create output directory %s\n", qPrintable(options.outputDirectory));
return false;
}
return copyFiles(sourceDirectory, QDir(options.outputDirectory), options.verbose);
}
bool copyAndroidSources(const Options &options)
{
if (options.androidSourceDirectory.isEmpty())
return true;
if (options.verbose)
771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840
fprintf(stdout, "Copying Android sources from project.\n");
QDir sourceDirectory(options.androidSourceDirectory);
if (!sourceDirectory.exists()) {
fprintf(stderr, "Cannot find android sources in %s", qPrintable(options.androidSourceDirectory));
return false;
}
return copyFiles(sourceDirectory, QDir(options.outputDirectory), options.verbose, true);
}
bool copyAndroidExtraLibs(const Options &options)
{
if (options.extraLibs.isEmpty())
return true;
if (options.verbose)
fprintf(stdout, "Copying %d external libraries to package.\n", options.extraLibs.size());
foreach (QString extraLib, options.extraLibs) {
QFileInfo extraLibInfo(extraLib);
if (!extraLibInfo.exists()) {
fprintf(stderr, "External library %s does not exist!\n", qPrintable(extraLib));
return false;
}
if (!extraLibInfo.fileName().startsWith(QLatin1String("lib")) || extraLibInfo.suffix() != QLatin1String("so")) {
fprintf(stderr, "The file name of external library %s must begin with \"lib\" and end with the suffix \".so\".\n",
qPrintable(extraLib));
return false;
}
QString destinationFile(options.outputDirectory
+ QLatin1String("/libs/")
+ options.architecture
+ QLatin1Char('/')
+ extraLibInfo.fileName());
if (!copyFileIfNewer(extraLib, destinationFile, options.verbose))
return false;
}
return true;
}
bool updateFile(const QString &fileName, const QHash<QString, QString> &replacements)
{
QFile inputFile(fileName);
if (!inputFile.open(QIODevice::ReadOnly)) {
fprintf(stderr, "Cannot open %s for reading.\n", qPrintable(fileName));
return false;
}
// All the files we are doing substitutes in are quite small. If this
// ever changes, this code should be updated to be more conservative.
QByteArray contents = inputFile.readAll();
bool hasReplacements = false;
QHash<QString, QString>::const_iterator it;
for (it = replacements.constBegin(); it != replacements.constEnd(); ++it) {
int index = contents.indexOf(it.key().toUtf8());
if (index >= 0) {
contents.replace(index, it.key().length(), it.value().toUtf8());
hasReplacements = true;
}
}
if (hasReplacements) {
inputFile.close();
841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910
if (!inputFile.open(QIODevice::WriteOnly)) {
fprintf(stderr, "Cannot open %s for writing.\n", qPrintable(fileName));
return false;
}
inputFile.write(contents);
}
return true;
}
bool updateLibsXml(const Options &options)
{
if (options.verbose)
fprintf(stdout, " -- res/values/libs.xml\n");
QString fileName = options.outputDirectory + QLatin1String("/res/values/libs.xml");
if (!QFile::exists(fileName)) {
fprintf(stderr, "Cannot find %s in prepared packaged. This file is required.\n", qPrintable(fileName));
return false;
}
QString libsPath = QLatin1String("libs/") + options.architecture + QLatin1Char('/');
QString qtLibs = QLatin1String("<item>gnustl_shared</item>\n");
QString bundledInLibs;
QString bundledInAssets;
foreach (Options::BundledFile bundledFile, options.bundledFiles) {
if (bundledFile.second.startsWith(QLatin1String("lib/"))) {
QString s = bundledFile.second.mid(sizeof("lib/lib") - 1);
s.chop(sizeof(".so") - 1);
qtLibs += QString::fromLatin1("<item>%1</item>\n").arg(s);
} else if (bundledFile.first.startsWith(libsPath)) {
QString s = bundledFile.first.mid(libsPath.length());
bundledInLibs += QString::fromLatin1("<item>%1:%2</item>\n")
.arg(s).arg(bundledFile.second);
} else if (bundledFile.first.startsWith(QLatin1String("assets/"))) {
QString s = bundledFile.first.mid(sizeof("assets/") - 1);
bundledInAssets += QString::fromLatin1("<item>%1:%2</item>\n")
.arg(s).arg(bundledFile.second);
}
}
QHash<QString, QString> replacements;
replacements[QLatin1String("<!-- %%INSERT_QT_LIBS%% -->")] = qtLibs;
if (options.deploymentMechanism == Options::Bundled) {
replacements[QLatin1String("<!-- %%INSERT_BUNDLED_IN_LIB%% -->")] = bundledInLibs;
replacements[QLatin1String("<!-- %%INSERT_BUNDLED_IN_ASSETS%% -->")] = bundledInAssets;
}
QString extraLibs;
if (!options.extraLibs.isEmpty()) {
foreach (QString extraLib, options.extraLibs) {
QFileInfo extraLibInfo(extraLib);
QString name = extraLibInfo.fileName().mid(sizeof("lib") - 1);
name.chop(sizeof(".so") - 1);
extraLibs += QLatin1String("<item>") + name + QLatin1String("</item>\n");
}
}
replacements[QLatin1String("<!-- %%INSERT_EXTRA_LIBS%% -->")] = extraLibs;
if (!updateFile(fileName, replacements))
return false;
return true;
}
911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980
bool updateAndroidManifest(Options &options)
{
if (options.verbose)
fprintf(stdout, " -- AndroidManifest.xml \n");
QStringList localLibs = options.localLibs;
// If .pro file overrides dependency detection, we need to see which platform plugin they picked
if (localLibs.isEmpty()) {
QString plugin;
foreach (QString qtDependency, options.qtDependencies) {
if (qtDependency.endsWith(QLatin1String("libqtforandroid.so"))
|| qtDependency.endsWith(QLatin1String("libqtforandroidGL.so"))) {
if (!plugin.isEmpty() && plugin != qtDependency) {
fprintf(stderr, "Both platform plugins libqtforandroid.so and libqtforandroidGL.so included in package. Please include only one.\n");
return false;
}
plugin = qtDependency;
}
}
if (plugin.isEmpty()) {
fprintf(stderr, "No platform plugin, neither libqtforandroid.so or libqtforandroidGL.so, included in package. Please include one.\n");
return false;
}
localLibs.append(plugin);
if (options.verbose)
fprintf(stdout, " -- Using platform plugin %s\n", qPrintable(plugin));
}
bool usesGL = false;
foreach (QString qtDependency, options.qtDependencies) {
if (qtDependency.endsWith(QLatin1String("libQt5OpenGL.so"))
|| qtDependency.endsWith(QLatin1String("libQt5Quick.so"))) {
usesGL = true;
break;
}
}
QHash<QString, QString> replacements;
replacements[QLatin1String("-- %%INSERT_APP_LIB_NAME%% --")] = QFileInfo(options.applicationBinary).baseName().mid(sizeof("lib") - 1);
replacements[QLatin1String("-- %%INSERT_LOCAL_LIBS%% --")] = localLibs.join(QLatin1Char(':'));
replacements[QLatin1String("-- %%INSERT_LOCAL_JARS%% --")] = options.localJars.join(QLatin1Char(':'));
replacements[QLatin1String("-- %%INSERT_INIT_CLASSES%% --")] = options.initClasses.join(QLatin1Char(':'));
replacements[QLatin1String("package=\"org.qtproject.example\"")] = QString::fromLatin1("package=\"%1\"").arg(options.packageName);
replacements[QLatin1String("-- %%BUNDLE_LOCAL_QT_LIBS%% --")]
= (options.deploymentMechanism == Options::Bundled) ? QString::fromLatin1("1") : QString::fromLatin1("0");
replacements[QLatin1String("-- %%USE_LOCAL_QT_LIBS%% --")]
= (options.deploymentMechanism != Options::Ministro) ? QString::fromLatin1("1") : QString::fromLatin1("0");
QString permissions;
foreach (QString permission, options.permissions)
permissions += QString::fromLatin1(" <uses-permission android:name=\"%1\" />\n").arg(permission);
replacements[QLatin1String("<!-- %%INSERT_PERMISSIONS -->")] = permissions;
QString features;
foreach (QString feature, options.features)
features += QStringLiteral(" <uses-feature android:name=\"%1\" android:required=\"false\" />\n").arg(feature);
if (usesGL)
features += QStringLiteral(" <uses-feature android:glEsVersion=\"0x00020000\" android:required=\"true\" />");
replacements[QLatin1String("<!-- %%INSERT_FEATURES -->")] = features;
QString androidManifestPath = options.outputDirectory + QLatin1String("/AndroidManifest.xml");
if (!updateFile(androidManifestPath, replacements))
return false;
// read the package, min & target sdk API levels from manifest file.
981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050
QFile androidManifestXml(androidManifestPath);
if (androidManifestXml.exists()) {
if (!androidManifestXml.open(QIODevice::ReadOnly)) {
fprintf(stderr, "Cannot open %s for reading.\n", qPrintable(androidManifestPath));
return false;
}
QXmlStreamReader reader(&androidManifestXml);
while (!reader.atEnd()) {
reader.readNext();
if (reader.isStartElement()) {
if (reader.name() == QLatin1String("manifest")) {
if (!reader.attributes().hasAttribute(QLatin1String("package"))) {
fprintf(stderr, "Invalid android manifest file: %s\n", qPrintable(androidManifestPath));
return false;
}
options.packageName = reader.attributes().value(QLatin1String("package")).toString();
} else if (reader.name() == QLatin1String("uses-sdk")) {
if (reader.attributes().hasAttribute(QLatin1String("android:minSdkVersion")))
options.minimumAndroidVersion = reader.attributes().value(QLatin1String("android:minSdkVersion")).toInt();
if (reader.attributes().hasAttribute(QLatin1String("android:targetSdkVersion")))
options.targetAndroidVersion = reader.attributes().value(QLatin1String("android:targetSdkVersion")).toInt();
}
}
}
if (reader.hasError()) {
fprintf(stderr, "Error in %s: %s\n", qPrintable(androidManifestPath), qPrintable(reader.errorString()));
return false;
}
} else {
fprintf(stderr, "No android manifest file");
return false;
}
return true;
}
bool updateStringsXml(const Options &options)
{
if (options.verbose)
fprintf(stdout, " -- res/values/strings.xml\n");
QHash<QString, QString> replacements;
replacements["<!-- %%INSERT_APP_NAME%% -->"] = QFileInfo(options.applicationBinary).baseName().mid(sizeof("lib") - 1);
QString fileName = options.outputDirectory + QLatin1String("/res/values/strings.xml");
if (!QFile::exists(fileName)) {
if (options.verbose)
fprintf(stdout, " -- Skipping update of strings.xml since it's missing.\n");
return true;
}
if (!updateFile(fileName, replacements))
return false;
return true;
}
bool updateJavaFiles(const Options &options)
{
if (options.verbose)
fprintf(stdout, " -- /src/org/qtproject/qt5/android/bindings/QtActivity.java\n");
QString fileName(options.outputDirectory + QLatin1String("/src/org/qtproject/qt5/android/bindings/QtActivity.java"));
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly)) {
fprintf(stderr, "Cannot open %s.\n", qPrintable(fileName));
return false;
1051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120
}
QByteArray contents;
while (!file.atEnd()) {
QByteArray line = file.readLine();
if (line.startsWith("//@ANDROID-")) {
bool ok;
int requiredSdkVersion = line.mid(sizeof("//@ANDROID-") - 1).trimmed().toInt(&ok);
if (requiredSdkVersion <= options.minimumAndroidVersion)
contents += line;
if (ok) {
while (!file.atEnd()) {
line = file.readLine();
if (requiredSdkVersion <= options.minimumAndroidVersion)
contents += line;
if (line.startsWith(QByteArray("//@ANDROID-") + QByteArray::number(requiredSdkVersion)))
break;
}
if (!line.startsWith(QByteArray("//@ANDROID-") + QByteArray::number(requiredSdkVersion))) {
fprintf(stderr, "Mismatched tag ANDROID-%d in %s\n", requiredSdkVersion, qPrintable(fileName));
return false;
}
} else {
fprintf(stderr, "Warning: Misformatted tag in %s: %s\n", qPrintable(fileName), line.constData());
}
} else {
contents += line;
}
}
file.close();
if (!file.open(QIODevice::WriteOnly)) {
fprintf(stderr, "Can't open %s for writing.\n", qPrintable(fileName));
return false;
}
if (file.write(contents) < contents.size()) {
fprintf(stderr, "Failed to write contents to %s.\n", qPrintable(fileName));
return false;
}
return true;
}
bool updateAndroidFiles(Options &options)
{
if (options.verbose)
fprintf(stdout, "Updating Android package files with project settings.\n");
if (!updateLibsXml(options))
return false;
if (!updateAndroidManifest(options))
return false;
if (!updateStringsXml(options))
return false;
if (!updateJavaFiles(options))
return false;
return true;
}
QStringList findFilesRecursively(const Options &options, const QString &fileName)
1121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190
{
QFileInfo info(options.qtInstallDirectory + QLatin1Char('/') + fileName);
if (!info.exists()) {
fprintf(stderr, "Warning: Dependency not found: %s\n", qPrintable(info.filePath()));
return QStringList();
}
if (info.isDir()) {
QStringList ret;
QDir dir(info.filePath());
QStringList entries = dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
foreach (QString entry, entries) {
QString s = fileName + QLatin1Char('/') + entry;
ret += findFilesRecursively(options, s);
}
return ret;
} else {
return QStringList() << fileName;
}
}
bool readAndroidDependencyXml(Options *options,
const QString &moduleName,
QSet<QString> *usedDependencies,
QSet<QString> *remainingDependencies)
{
QString androidDependencyName = options->qtInstallDirectory + QString::fromLatin1("/lib/%1-android-dependencies.xml").arg(moduleName);
QFile androidDependencyFile(androidDependencyName);
if (androidDependencyFile.exists()) {
if (options->verbose)
fprintf(stdout, "Reading Android dependencies for %s\n", qPrintable(moduleName));
if (!androidDependencyFile.open(QIODevice::ReadOnly)) {
fprintf(stderr, "Cannot open %s for reading.\n", qPrintable(androidDependencyName));
return false;
}
QXmlStreamReader reader(&androidDependencyFile);
while (!reader.atEnd()) {
reader.readNext();
if (reader.isStartElement()) {
if (reader.name() == QLatin1String("bundled")) {
if (!reader.attributes().hasAttribute(QLatin1String("file"))) {
fprintf(stderr, "Invalid android dependency file: %s\n", qPrintable(androidDependencyName));
return false;
}
QString file = reader.attributes().value(QLatin1String("file")).toString();
QStringList fileNames = findFilesRecursively(*options, file);
foreach (QString fileName, fileNames) {
if (usedDependencies->contains(fileName))
continue;
usedDependencies->insert(fileName);
if (options->verbose)
fprintf(stdout, "Appending dependency from xml: %s\n", qPrintable(fileName));
options->qtDependencies.append(fileName);
}
} else if (reader.name() == QLatin1String("jar")) {
int bundling = reader.attributes().value(QLatin1String("bundling")).toInt();
QString fileName = reader.attributes().value(QLatin1String("file")).toString();
if (bundling == (options->deploymentMechanism == Options::Bundled)) {
if (!usedDependencies->contains(fileName)) {
1191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260
options->qtDependencies.append(fileName);
usedDependencies->insert(fileName);
}
}
if (!fileName.isEmpty())
options->localJars.append(fileName);
if (reader.attributes().hasAttribute(QLatin1String("initClass"))) {
options->initClasses.append(reader.attributes().value(QLatin1String("initClass")).toString());
}
} else if (reader.name() == QLatin1String("lib")) {
QString fileName = reader.attributes().value(QLatin1String("file")).toString();
if (reader.attributes().hasAttribute(QLatin1String("replaces"))) {
QString replaces = reader.attributes().value(QLatin1String("replaces")).toString();
for (int i=0; i<options->localLibs.size(); ++i) {
if (options->localLibs.at(i) == replaces) {
options->localLibs[i] = fileName;
break;
}
}
} else if (!fileName.isEmpty()) {
options->localLibs.append(fileName);
}
if (fileName.endsWith(QLatin1String(".so"))) {
remainingDependencies->insert(fileName);
}
} else if (reader.name() == QLatin1String("permission")) {
QString name = reader.attributes().value(QLatin1String("name")).toString();
options->permissions.append(name);
} else if (reader.name() == QLatin1String("feature")) {
QString name = reader.attributes().value(QLatin1String("name")).toString();
options->features.append(name);
}
}
}
if (reader.hasError()) {
fprintf(stderr, "Error in %s: %s\n", qPrintable(androidDependencyName), qPrintable(reader.errorString()));
return false;
}
} else if (options->verbose) {
fprintf(stdout, "No android dependencies for %s\n", qPrintable(moduleName));
}
return true;
}
QStringList getQtLibsFromElf(const Options &options, const QString &fileName)
{
QString readElf = options.ndkPath
+ QLatin1String("/toolchains/")
+ options.toolchainPrefix
+ QLatin1Char('-')
+ options.toolchainVersion
+ QLatin1String("/prebuilt/")
+ options.ndkHost
+ QLatin1String("/bin/")
+ options.toolPrefix
+ QLatin1String("-readelf");
#if defined(Q_OS_WIN32)
readElf += QLatin1String(".exe");
#endif
if (!QFile::exists(readElf)) {
fprintf(stderr, "Command does not exist: %s\n", qPrintable(readElf));
return QStringList();
}
readElf = QString::fromLatin1("%1 -d -W %2").arg(shellQuote(readElf)).arg(shellQuote(fileName));
1261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330
FILE *readElfCommand = popen(readElf.toLocal8Bit().constData(), "r");
if (readElfCommand == 0) {
fprintf(stderr, "Cannot execute command %s", qPrintable(readElf));
return QStringList();
}
QStringList ret;
char buffer[512];
while (fgets(buffer, sizeof(buffer), readElfCommand) != 0) {
QByteArray line = QByteArray::fromRawData(buffer, qstrlen(buffer));
if (line.contains("(NEEDED)") && line.contains("Shared library:") ) {
const int pos = line.lastIndexOf('[') + 1;
QString libraryName = QLatin1String("lib/") + QString::fromLatin1(line.mid(pos, line.length() - pos - 2));
if (QFile::exists(options.qtInstallDirectory + QLatin1Char('/') + libraryName)) {
ret += libraryName;
}
}
}
pclose(readElfCommand);
return ret;
}
bool readDependenciesFromElf(Options *options,
const QString &fileName,
QSet<QString> *usedDependencies,
QSet<QString> *remainingDependencies)
{
// Get dependencies on libraries in $QTDIR/lib
QStringList dependencies = getQtLibsFromElf(*options, fileName);
if (options->verbose) {
fprintf(stdout, "Reading dependencies from %s\n", qPrintable(fileName));
foreach (QString dep, dependencies)
fprintf(stdout, " %s\n", qPrintable(dep));
}
// Recursively add dependencies from ELF and supplementary XML information
foreach (QString dependency, dependencies) {
if (usedDependencies->contains(dependency))
continue;
usedDependencies->insert(dependency);
if (!readDependenciesFromElf(options,
options->qtInstallDirectory + QLatin1Char('/') + dependency,
usedDependencies,
remainingDependencies)) {
return false;
}
options->qtDependencies.append(dependency);
if (options->verbose)
fprintf(stdout, "Appending dependency: %s\n", qPrintable(dependency));
QString qtBaseName = dependency.mid(sizeof("lib/lib") - 1);
qtBaseName = qtBaseName.left(qtBaseName.size() - (sizeof(".so") - 1));
if (!readAndroidDependencyXml(options, qtBaseName, usedDependencies, remainingDependencies)) {
return false;
}
}
return true;
}
bool goodToCopy(const Options *options, const QString &file, QStringList *unmetDependencies);
bool readDependencies(Options *options)
{
if (options->verbose)
1331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400
fprintf(stdout, "Detecting dependencies of application.\n");
// Override set in .pro file
if (!options->qtDependencies.isEmpty()) {
if (options->verbose)
fprintf(stdout, "\tDependencies explicitly overridden in .pro file. No detection needed.\n");
return true;
}
QSet<QString> usedDependencies;
QSet<QString> remainingDependencies;
// Add dependencies of application binary first
if (!readDependenciesFromElf(options, options->applicationBinary, &usedDependencies, &remainingDependencies))
return false;
QString qtDir = options->qtInstallDirectory + QLatin1Char('/');
while (!remainingDependencies.isEmpty()) {
QSet<QString>::iterator start = remainingDependencies.begin();
QString fileName = qtDir + *start;
remainingDependencies.erase(start);
QStringList unmetDependencies;
if (goodToCopy(options, fileName, &unmetDependencies)) {
bool ok = readDependenciesFromElf(options, fileName, &usedDependencies, &remainingDependencies);
if (!ok)
return false;
} else if (options->verbose) {
fprintf(stdout, "Skipping %s due to unmet dependencies: %s\n",
qPrintable(fileName),
qPrintable(unmetDependencies.join(QLatin1Char(','))));
}
}
QStringList::iterator it = options->localLibs.begin();
while (it != options->localLibs.end()) {
QStringList unmetDependencies;
if (!goodToCopy(options, qtDir + *it, &unmetDependencies)) {
if (options->verbose) {
fprintf(stdout, "Skipping %s due to unmet dependencies: %s\n",
qPrintable(*it),
qPrintable(unmetDependencies.join(QLatin1Char(','))));
}
it = options->localLibs.erase(it);
} else {
++it;
}
}
return true;
}
bool stripFile(const Options &options, const QString &fileName)
{
QString strip = options.ndkPath
+ QLatin1String("/toolchains/")
+ options.toolchainPrefix
+ QLatin1Char('-')
+ options.toolchainVersion
+ QLatin1String("/prebuilt/")
+ options.ndkHost
+ QLatin1String("/bin/")
+ options.toolPrefix
+ QLatin1String("-strip");
#if defined(Q_OS_WIN32)
strip += QLatin1String(".exe");
#endif
if (!QFile::exists(strip)) {
fprintf(stderr, "Command does not exist: %s\n", qPrintable(strip));
1401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470
return false;
}
strip = QString::fromLatin1("%1 %2").arg(shellQuote(strip)).arg(shellQuote(fileName));
FILE *stripCommand = popen(strip.toLocal8Bit().constData(), "r");
if (stripCommand == 0) {
fprintf(stderr, "Cannot execute command %s", qPrintable(strip));
return false;
}
pclose(stripCommand);
return true;
}
bool stripLibraries(const Options &options)
{
if (options.verbose)
fprintf(stdout, "Stripping libraries to minimize size.\n");
QString libraryPath = options.outputDirectory
+ QLatin1String("/libs/")
+ options.architecture;
QStringList libraries = QDir(libraryPath).entryList(QDir::Files);
foreach (QString library, libraries) {
if (library.endsWith(".so")) {
if (!stripFile(options, libraryPath + QLatin1Char('/') + library))
return false;
}
}
return true;
}
bool containsApplicationBinary(const Options &options)
{
if (options.verbose)
fprintf(stdout, "Checking if application binary is in package.\n");
QFileInfo applicationBinary(options.applicationBinary);
QString destinationFileName = options.outputDirectory
+ QLatin1String("/libs/")
+ options.architecture
+ QLatin1Char('/')
+ applicationBinary.fileName();
if (!QFile::exists(destinationFileName)) {
#if defined(Q_OS_WIN32)
QLatin1String makeTool("mingw32-make"); // Only Mingw host builds supported on Windows currently
#else
QLatin1String makeTool("make");
#endif
fprintf(stderr, "Application binary is not in output directory: %s. Please run '%s install INSTALL_ROOT=%s' first.\n",
qPrintable(destinationFileName),
qPrintable(makeTool),
qPrintable(options.outputDirectory));
return false;
}
return true;
}
FILE *runAdb(const Options &options, const QString &arguments)
{
QString adb = options.sdkPath + QLatin1String("/platform-tools/adb");
#if defined(Q_OS_WIN32)
1471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540
adb += QLatin1String(".exe");
#endif
if (!QFile::exists(adb)) {
fprintf(stderr, "Cannot find adb tool: %s\n", qPrintable(adb));
return 0;
}
QString installOption;
if (!options.installLocation.isEmpty())
installOption = QLatin1String(" -s ") + shellQuote(options.installLocation);
adb = QString::fromLatin1("%1%2 %3").arg(shellQuote(adb)).arg(installOption).arg(arguments);
if (options.verbose)
fprintf(stdout, "Running command \"%s\"\n", adb.toLocal8Bit().constData());
FILE *adbCommand = popen(adb.toLocal8Bit().constData(), "r");
if (adbCommand == 0) {
fprintf(stderr, "Cannot start adb: %s\n", qPrintable(adb));
return 0;
}
return adbCommand;
}
bool fetchRemoteModifications(Options *options, const QString &directory)
{
options->fetchedRemoteModificationDates = true;
FILE *adbCommand = runAdb(*options, QLatin1String(" shell cat ") + shellQuote(directory + QLatin1String("/modification.txt")));
if (adbCommand == 0)
return false;
char buffer[512];
QString qtPath;
while (fgets(buffer, sizeof(buffer), adbCommand) != 0)
qtPath += QString::fromUtf8(buffer, qstrlen(buffer));
pclose(adbCommand);
if (options->qtInstallDirectory != qtPath) {
adbCommand = runAdb(*options, QLatin1String(" shell rm -r ") + shellQuote(directory));
if (options->verbose) {
fprintf(stdout, " -- Removing old Qt libs.\n");
while (fgets(buffer, sizeof(buffer), adbCommand) != 0)
fprintf(stdout, "%s", buffer);
}
pclose(adbCommand);
}
adbCommand = runAdb(*options, QLatin1String(" ls ") + shellQuote(directory));
if (adbCommand == 0)
return false;
while (fgets(buffer, sizeof(buffer), adbCommand) != 0) {
QByteArray line = QByteArray::fromRawData(buffer, qstrlen(buffer));
if (line.count() < (3 * 8 + 3))
continue;
if (line.at(8) != ' '
|| line.at(17) != ' '
|| line.at(26) != ' ') {
continue;
}
QString fileName = QString::fromLocal8Bit(line.mid(27)).trimmed();
if (fileName != QLatin1String("modification.txt"))
continue;
bool ok;
int time = line.mid(18, 8).toUInt(&ok, 16);
if (!ok)
continue;
1541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610
options->remoteModificationDate = QDateTime::fromTime_t(time);
break;
}
pclose(adbCommand);
{
QFile file(options->temporaryDirectoryName + QLatin1String("/modification.txt"));
if (!file.open(QIODevice::WriteOnly)) {
fprintf(stderr, "Cannot create modification timestamp.\n");
return false;
}
file.write(options->qtInstallDirectory.toUtf8());
}
return true;
}
bool goodToCopy(const Options *options, const QString &file, QStringList *unmetDependencies)
{
if (!file.endsWith(QLatin1String(".so")))
return true;
bool ret = true;
foreach (const QString &lib, getQtLibsFromElf(*options, file)) {
if (!options->qtDependencies.contains(lib)) {
ret = false;
unmetDependencies->append(lib);
}
}
return ret;
}
bool deployToLocalTmp(Options *options,
const QString &qtDependency)
{
if (!options->fetchedRemoteModificationDates)
fetchRemoteModifications(options, QLatin1String("/data/local/tmp/qt"));
QFileInfo fileInfo(options->qtInstallDirectory + QLatin1Char('/') + qtDependency);
// Make sure precision is the same as what we get from Android
QDateTime sourceModified = QDateTime::fromTime_t(fileInfo.lastModified().toTime_t());
if (options->remoteModificationDate.isNull() || options->remoteModificationDate < sourceModified) {
if (!copyFileIfNewer(options->qtInstallDirectory + QLatin1Char('/') + qtDependency,
options->temporaryDirectoryName + QLatin1Char('/') + qtDependency,
options->verbose)) {
return false;
}
if (qtDependency.endsWith(QLatin1String(".so"))
&& !stripFile(*options, options->temporaryDirectoryName + QLatin1Char('/') + qtDependency)) {
return false;
}
}
return true;
}
bool copyQtFiles(Options *options)
{
if (options->verbose) {
switch (options->deploymentMechanism) {
case Options::Bundled:
fprintf(stdout, "Copying %d dependencies from Qt into package.\n", options->qtDependencies.size());
break;
case Options::Ministro:
1611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680
fprintf(stdout, "Setting %d dependencies from Qt in package.\n", options->qtDependencies.size());
break;
case Options::Debug:
fprintf(stdout, "Copying %d dependencies from Qt to device.\n", options->qtDependencies.size());
break;
};
}
if (options->deploymentMechanism == Options::Debug) {
// For debug deployment, we copy all libraries and plugins
QDirIterator dirIterator(options->qtInstallDirectory, QDirIterator::Subdirectories);
while (dirIterator.hasNext()) {
dirIterator.next();
QFileInfo info = dirIterator.fileInfo();
if (!info.isDir()) {
QString relativePath = info.absoluteFilePath().mid(options->qtInstallDirectory.length());
if (relativePath.startsWith(QLatin1Char('/')))
relativePath.remove(0, 1);
if ((relativePath.startsWith("lib/") && relativePath.endsWith(".so"))
|| relativePath.startsWith("jar/")
|| relativePath.startsWith("plugins/")
|| relativePath.startsWith("imports/")
|| relativePath.startsWith("qml/")
|| relativePath.startsWith("plugins/")) {
if (!deployToLocalTmp(options, relativePath))
return false;
}
}
}
foreach (QString qtDependency, options->qtDependencies)
options->bundledFiles += qMakePair(qtDependency, qtDependency);
} else {
QString libsDirectory = QLatin1String("libs/");
// Copy other Qt dependencies
QString libDestinationDirectory = libsDirectory + options->architecture + QLatin1Char('/');
QString assetsDestinationDirectory = QLatin1String("assets/--Added-by-androiddeployqt--/");
foreach (QString qtDependency, options->qtDependencies) {
QString sourceFileName = options->qtInstallDirectory + QLatin1Char('/') + qtDependency;
QString destinationFileName;
if (qtDependency.endsWith(QLatin1String(".so"))) {
QString garbledFileName;
if (qtDependency.startsWith(QLatin1String("lib/"))) {
garbledFileName = qtDependency.mid(sizeof("lib/") - 1);
} else {
garbledFileName = QLatin1String("lib")
+ QString(qtDependency).replace(QLatin1Char('/'), QLatin1Char('_'));
}
destinationFileName = libDestinationDirectory + garbledFileName;
} else if (qtDependency.startsWith(QLatin1String("jar/"))) {
destinationFileName = libsDirectory + qtDependency.mid(sizeof("jar/") - 1);
} else {
destinationFileName = assetsDestinationDirectory + qtDependency;
}
if (!QFile::exists(sourceFileName)) {
fprintf(stderr, "Source Qt file does not exist: %s.\n", qPrintable(sourceFileName));
return false;
}
QStringList unmetDependencies;
if (!goodToCopy(options, sourceFileName, &unmetDependencies)) {
if (options->verbose) {
fprintf(stdout, " -- Skipping %s. It has unmet dependencies: %s.\n",
qPrintable(sourceFileName),
1681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750
qPrintable(unmetDependencies.join(QLatin1Char(','))));
}
continue;
}
if (options->deploymentMechanism == Options::Bundled
&& !copyFileIfNewer(sourceFileName,
options->outputDirectory + QLatin1Char('/') + destinationFileName,
options->verbose)) {
return false;
}
options->bundledFiles += qMakePair(destinationFileName, qtDependency);
}
}
return true;
}
QStringList getLibraryProjectsInOutputFolder(const Options &options)
{
QStringList ret;
QFile file(options.outputDirectory + QLatin1String("/project.properties"));
if (file.open(QIODevice::ReadOnly)) {
while (!file.atEnd()) {
QByteArray line = file.readLine().trimmed();
if (line.startsWith("android.library.reference")) {
int equalSignIndex = line.indexOf('=');
if (equalSignIndex >= 0) {
QString path = QString::fromLocal8Bit(line.mid(equalSignIndex + 1));
QFileInfo info(options.outputDirectory + QLatin1Char('/') + path);
if (QDir::isRelativePath(path)
&& info.exists()
&& info.isDir()
&& info.canonicalFilePath().startsWith(options.outputDirectory)) {
ret += info.canonicalFilePath();
}
}
}
}
}
return ret;
}
bool createAndroidProject(const Options &options)
{
if (options.verbose)
fprintf(stdout, "Running Android tool to create package definition.\n");
QString androidToolExecutable = options.sdkPath + QLatin1String("/tools/android");
#if defined(Q_OS_WIN32)
androidToolExecutable += QLatin1String(".bat");
#endif
if (!QFile::exists(androidToolExecutable)) {
fprintf(stderr, "Cannot find Android tool: %s\n", qPrintable(androidToolExecutable));
return false;
}
QString androidTool = QString::fromLatin1("%1 update project --path %2 --target %3 --name QtApp")
.arg(shellQuote(androidToolExecutable))
.arg(shellQuote(options.outputDirectory))
.arg(shellQuote(options.androidPlatform));
if (options.verbose)
fprintf(stdout, " -- Command: %s\n", qPrintable(androidTool));
1751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820
FILE *androidToolCommand = popen(androidTool.toLocal8Bit().constData(), "r");
if (androidToolCommand == 0) {
fprintf(stderr, "Cannot run command '%s'\n", qPrintable(androidTool));
return false;
}
pclose(androidToolCommand);
// If the project has subprojects inside the current folder, we need to also run android update on these.
QStringList libraryProjects = getLibraryProjectsInOutputFolder(options);
foreach (QString libraryProject, libraryProjects) {
if (options.verbose)
fprintf(stdout, "Updating subproject %s\n", qPrintable(libraryProject));
androidTool = QString::fromLatin1("%1 update lib-project --path %2 --target %3")
.arg(shellQuote(androidToolExecutable))
.arg(shellQuote(libraryProject))
.arg(shellQuote(options.androidPlatform));
if (options.verbose)
fprintf(stdout, " -- Command: %s\n", qPrintable(androidTool));
FILE *androidToolCommand = popen(androidTool.toLocal8Bit().constData(), "r");
if (androidToolCommand == 0) {
fprintf(stderr, "Cannot run command '%s'\n", qPrintable(androidTool));
return false;
}
pclose(androidToolCommand);
}
return true;
}
QString findInPath(const QString &fileName)
{
QString path = qgetenv("PATH");
#if defined(Q_OS_WIN32)
QLatin1Char separator(';');
#else
QLatin1Char separator(':');
#endif
QStringList paths = path.split(separator);
foreach (QString path, paths) {
QFileInfo fileInfo(path + QLatin1Char('/') + fileName);
if (fileInfo.exists() && fileInfo.isFile() && fileInfo.isExecutable())
return path + QLatin1Char('/') + fileName;
}
return QString();
}
bool buildAndroidProject(const Options &options)
{
if (options.verbose)
fprintf(stdout, "Building Android package.\n");
QString antTool = options.antTool;
if (antTool.isEmpty()) {
#if defined(Q_OS_WIN32)
antTool = findInPath(QLatin1String("ant.bat"));
#else
antTool = findInPath(QLatin1String("ant"));
#endif
}
if (antTool.isEmpty()) {
fprintf(stderr, "Cannot find ant in PATH. Please use --ant option to pass in the correct path.\n");
return false;
1821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890
}
if (options.verbose)
fprintf(stdout, "Using ant: %s\n", qPrintable(antTool));
QString oldPath = QDir::currentPath();
if (!QDir::setCurrent(options.outputDirectory)) {
fprintf(stderr, "Cannot current path to %s\n", qPrintable(options.outputDirectory));
return false;
}
QString ant = QString::fromLatin1("%1 %2").arg(shellQuote(antTool)).arg(options.releasePackage ? QLatin1String(" release") : QLatin1String(" debug"));
FILE *antCommand = popen(ant.toLocal8Bit().constData(), "r");
if (antCommand == 0) {
fprintf(stderr, "Cannot run ant command: %s\n.", qPrintable(ant));
return false;
}
char buffer[512];
while (fgets(buffer, sizeof(buffer), antCommand) != 0)
fprintf(stdout, "%s", buffer);
int errorCode = pclose(antCommand);
if (errorCode != 0) {
fprintf(stderr, "Building the android package failed!\n");
if (!options.verbose)
fprintf(stderr, " -- For more information, run this command with --verbose.\n");
return false;
}
if (!QDir::setCurrent(oldPath)) {
fprintf(stderr, "Cannot change back to old path: %s\n", qPrintable(oldPath));
return false;
}
return true;
}
bool uninstallApk(const Options &options)
{
if (options.verbose)
fprintf(stdout, "Uninstalling old Android package %s if present.\n", qPrintable(options.packageName));
FILE *adbCommand = runAdb(options, QLatin1String(" uninstall ") + shellQuote(options.packageName));
if (adbCommand == 0)
return false;
if (options.verbose || mustReadOutputAnyway) {
char buffer[512];
while (fgets(buffer, sizeof(buffer), adbCommand) != 0)
if (options.verbose)
fprintf(stdout, "%s", buffer);
}
int returnCode = pclose(adbCommand);
if (returnCode != 0) {
fprintf(stderr, "Warning: Uninstall failed!\n");
if (!options.verbose)
fprintf(stderr, " -- Run with --verbose for more information.\n");
return false;
}
return true;
}
QString apkName(const Options &options)
{
if (options.releasePackage && options.keyStore.isEmpty())
1891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960
return QLatin1String("QtApp-release-unsigned");
else if (options.releasePackage)
return QLatin1String("QtApp-release");
else
return QLatin1String("QtApp-debug");
}
bool installApk(const Options &options)
{
// Uninstall if necessary
uninstallApk(options);
if (options.verbose)
fprintf(stdout, "Installing Android package to device.\n");
FILE *adbCommand = runAdb(options,
QLatin1String(" install -r ")
+ shellQuote(options.outputDirectory)
+ shellQuote(QLatin1String("/bin/")
+ apkName(options)
+ QLatin1String(".apk")));
if (adbCommand == 0)
return false;
if (options.verbose || mustReadOutputAnyway) {
char buffer[512];
while (fgets(buffer, sizeof(buffer), adbCommand) != 0)
if (options.verbose)
fprintf(stdout, "%s", buffer);
}
int returnCode = pclose(adbCommand);
if (returnCode != 0) {
fprintf(stderr, "Installing to device failed!\n");
if (!options.verbose)
fprintf(stderr, " -- Run with --verbose for more information.\n");
return false;
}
return true;
}
bool copyGnuStl(Options *options)
{
if (options->verbose)
fprintf(stdout, "Copying GNU STL library\n");
QString filePath = options->ndkPath
+ QLatin1String("/sources/cxx-stl/gnu-libstdc++/")
+ options->toolchainVersion
+ QLatin1String("/libs/")
+ options->architecture
+ QLatin1String("/libgnustl_shared.so");
if (!QFile::exists(filePath)) {
fprintf(stderr, "GNU STL library does not exist at %s\n", qPrintable(filePath));
return false;
}
QString destinationDirectory =
options->deploymentMechanism == Options::Debug
? options->temporaryDirectoryName + QLatin1String("/lib")
: options->outputDirectory + QLatin1String("/libs/") + options->architecture;
if (!copyFileIfNewer(filePath, destinationDirectory
+ QLatin1String("/libgnustl_shared.so"), options->verbose)) {
return false;
}
if (options->deploymentMechanism == Options::Debug && !deployToLocalTmp(options, QLatin1String("/lib/libgnustl_shared.so")))
return false;
1961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030
return true;
}
bool signPackage(const Options &options)
{
if (options.verbose)
fprintf(stdout, "Signing Android package.\n");
QString jdkPath = options.jdkPath;
if (jdkPath.isEmpty())
jdkPath = qgetenv("JAVA_HOME");
#if defined(Q_OS_WIN32)
QString jarSignerTool = QString::fromLatin1("jarsigner.exe");
#else
QString jarSignerTool = QString::fromLatin1("jarsigner");
#endif
if (jdkPath.isEmpty() || !QFile::exists(jdkPath + QLatin1String("/bin/") + jarSignerTool))
jarSignerTool = findInPath(jarSignerTool);
else
jarSignerTool = jdkPath + QLatin1String("/bin/") + jarSignerTool;
if (!QFile::exists(jarSignerTool)) {
fprintf(stderr, "Cannot find jarsigner in JAVA_HOME or PATH. Please use --jdk option to pass in the correct path to JDK.\n");
return false;
}
jarSignerTool = QString::fromLatin1("%1 -sigalg %2 -digestalg %3 -keystore %4")
.arg(shellQuote(jarSignerTool)).arg(shellQuote(options.sigAlg)).arg(shellQuote(options.digestAlg)).arg(shellQuote(options.keyStore));
if (!options.keyStorePassword.isEmpty())
jarSignerTool += QString::fromLatin1(" -storepass %1").arg(shellQuote(options.keyStorePassword));
if (!options.storeType.isEmpty())
jarSignerTool += QString::fromLatin1(" -storetype %1").arg(shellQuote(options.storeType));
if (!options.keyPass.isEmpty())
jarSignerTool += QString::fromLatin1(" -keypass %1").arg(shellQuote(options.keyPass));
if (!options.sigFile.isEmpty())
jarSignerTool += QString::fromLatin1(" -sigfile %1").arg(shellQuote(options.sigFile));
if (!options.signedJar.isEmpty())
jarSignerTool += QString::fromLatin1(" -signedjar %1").arg(shellQuote(options.signedJar));
if (!options.tsaUrl.isEmpty())
jarSignerTool += QString::fromLatin1(" -tsa %1").arg(shellQuote(options.tsaUrl));
if (!options.tsaCert.isEmpty())
jarSignerTool += QString::fromLatin1(" -tsacert %1").arg(shellQuote(options.tsaCert));
if (options.internalSf)
jarSignerTool += QLatin1String(" -internalsf");
if (options.sectionsOnly)
jarSignerTool += QLatin1String(" -sectionsonly");
if (options.protectedAuthenticationPath)
jarSignerTool += QLatin1String(" -protected");
jarSignerTool += QString::fromLatin1(" %1 %2")
.arg(shellQuote(options.outputDirectory
+ QLatin1String("/bin/")
+ apkName(options)
+ QLatin1String("-unsigned.apk")))
.arg(shellQuote(options.keyStoreAlias));
2031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100
FILE *jarSignerCommand = popen(jarSignerTool.toLocal8Bit().constData(), "r");
if (jarSignerCommand == 0) {
fprintf(stderr, "Couldn't run jarsigner.\n");
return false;
}
if (options.verbose) {
char buffer[512];
while (fgets(buffer, sizeof(buffer), jarSignerCommand) != 0)
fprintf(stdout, "%s", buffer);
}
int errorCode = pclose(jarSignerCommand);
if (errorCode != 0) {
fprintf(stderr, "jarsigner command failed.\n");
if (!options.verbose)
fprintf(stderr, " -- Run with --verbose for more information.\n");
return false;
}
QString zipAlignTool = options.sdkPath + QLatin1String("/tools/zipalign");
#if defined(Q_OS_WIN32)
zipAlignTool += QLatin1String(".exe");
#endif
if (!QFile::exists(zipAlignTool)) {
fprintf(stderr, "zipalign tool not found: %s\n", qPrintable(zipAlignTool));
return false;
}
zipAlignTool = QString::fromLatin1("%1%2 -f 4 %3 %4")
.arg(shellQuote(zipAlignTool))
.arg(options.verbose ? QString::fromLatin1(" -v") : QString())
.arg(shellQuote(options.outputDirectory
+ QLatin1String("/bin/")
+ apkName(options)
+ QLatin1String("-unsigned.apk")))
.arg(shellQuote(options.outputDirectory
+ QLatin1String("/bin/")
+ apkName(options)
+ QLatin1String(".apk")));
FILE *zipAlignCommand = popen(zipAlignTool.toLocal8Bit(), "r");
if (zipAlignCommand == 0) {
fprintf(stderr, "Couldn't run zipalign.\n");
return false;
}
char buffer[512];
while (fgets(buffer, sizeof(buffer), zipAlignCommand) != 0)
fprintf(stdout, "%s", buffer);
errorCode = pclose(zipAlignCommand);
if (errorCode != 0) {
fprintf(stderr, "zipalign command failed.\n");
if (!options.verbose)
fprintf(stderr, " -- Run with --verbose for more information.\n");
return false;
}
return true;
}
bool copyGdbServer(const Options &options)
{
if (options.verbose)
fprintf(stdout, "Copying gdbserver into package.\n");
QString architectureSubDirectory;
if (options.architecture.startsWith(QLatin1String("arm")))
2101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170
architectureSubDirectory = QLatin1String("android-arm");
else
architectureSubDirectory = QLatin1String("android-") + options.architecture;
QString gdbServerBinary = options.ndkPath
+ QLatin1String("/prebuilt/")
+ architectureSubDirectory
+ QLatin1String("/gdbserver/gdbserver");
if (!QFile::exists(gdbServerBinary)) {
fprintf(stderr, "Cannot find gdbserver at %s.\n", qPrintable(gdbServerBinary));
return false;
}
if (!copyFileIfNewer(gdbServerBinary,
options.outputDirectory
+ QLatin1String("/libs/")
+ options.architecture
+ QLatin1String("/gdbserver"),
options.verbose)) {
return false;
}
return true;
}
bool deployAllToLocalTmp(const Options &options)
{
FILE *adbCommand = runAdb(options,
QString::fromLatin1(" push %1 /data/local/tmp/qt/")
.arg(shellQuote(options.temporaryDirectoryName)));
if (adbCommand == 0)
return false;
if (options.verbose) {
fprintf(stdout, " -- Deploying Qt files to device.\n");
char buffer[512];
while (fgets(buffer, sizeof(buffer), adbCommand) != 0)
fprintf(stdout, "%s", buffer);
}
int errorCode = pclose(adbCommand);
if (errorCode != 0) {
fprintf(stderr, "Copying files to device failed!\n");
return false;
}
return true;
}
bool generateAssetsFileList(const Options &options)
{
if (options.verbose)
fprintf(stdout, "Pregenerating entry list for assets file engine.\n");
QString assetsPath = options.outputDirectory + QLatin1String("/assets/");
QString addedByAndroidDeployQtPath = assetsPath + QLatin1String("--Added-by-androiddeployqt--/");
if (!QDir().mkpath(addedByAndroidDeployQtPath)) {
fprintf(stderr, "Failed to create directory '%s'", qPrintable(addedByAndroidDeployQtPath));
return false;
}
QFile file(addedByAndroidDeployQtPath + QLatin1String("/qt_cache_pregenerated_file_list"));
if (file.open(QIODevice::WriteOnly)) {
QDirIterator dirIterator(assetsPath,
QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot,
QDirIterator::Subdirectories);
QHash<QString, QStringList> directoryContents;
while (dirIterator.hasNext()) {
const QString name = dirIterator.next().mid(assetsPath.length());
2171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240
int slashIndex = name.lastIndexOf(QLatin1Char('/'));
QString pathName = slashIndex >= 0 ? name.left(slashIndex) : QString::fromLatin1("/");
QString fileName = slashIndex >= 0 ? name.mid(pathName.length() + 1) : name;
if (!fileName.isEmpty() && dirIterator.fileInfo().isDir() && !fileName.endsWith(QLatin1Char('/')))
fileName += QLatin1Char('/');
if (fileName.isEmpty() && !directoryContents.contains(pathName))
directoryContents[pathName] = QStringList();
else if (!fileName.isEmpty())
directoryContents[pathName].append(fileName);
}
QDataStream stream(&file);
stream.setVersion(QDataStream::Qt_5_3);
QList<QString> directories = directoryContents.keys();
foreach (const QString &directory, directories) {
QStringList entryList = directoryContents.value(directory);
stream << directory << entryList.size();
foreach (const QString &entry, entryList)
stream << entry;
}
} else {
fprintf(stderr, "Pregenerating entry list for assets file engine failed!\n");
return false;
}
return true;
}
enum ErrorCode
{
Success,
SyntaxErrorOrHelpRequested = 1,
CannotReadInputFile = 2,
CannotCopyAndroidTemplate = 3,
CannotReadDependencies = 4,
CannotCopyGnuStl = 5,
CannotCopyQtFiles = 6,
CannotFindApplicationBinary = 7,
CannotCopyGdbServer = 8,
CannotStripLibraries = 9,
CannotCopyAndroidExtraLibs = 10,
CannotCopyAndroidSources = 11,
CannotUpdateAndroidFiles = 12,
CannotCreateAndroidProject = 13,
CannotBuildAndroidProject = 14,
CannotSignPackage = 15,
CannotInstallApk = 16,
CannotDeployAllToLocalTmp = 17,
CannotGenerateAssetsFileList = 18
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Options options = parseOptions();
if (options.helpRequested || options.outputDirectory.isEmpty()) {
printHelp();
return SyntaxErrorOrHelpRequested;
}
if (Q_UNLIKELY(options.timing))
options.timer.start();
if (!readInputFile(&options))
return CannotReadInputFile;
2241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310
if (Q_UNLIKELY(options.timing))
fprintf(stdout, "[TIMING] %d ms: Read input file\n", options.timer.elapsed());
fprintf(stdout,
// "012345678901234567890123456789012345678901234567890123456789012345678901"
"Generating Android Package\n"
" Input file: %s\n"
" Output directory: %s\n"
" Application binary: %s\n"
" Android build platform: %s\n"
" Install to device: %s\n",
qPrintable(options.inputFileName),
qPrintable(options.outputDirectory),
qPrintable(options.applicationBinary),
qPrintable(options.androidPlatform),
options.installApk
? (options.installLocation.isEmpty() ? "Default device" : qPrintable(options.installLocation))
: "No"
);
if (!copyAndroidTemplate(options))
return CannotCopyAndroidTemplate;
if (Q_UNLIKELY(options.timing))
fprintf(stdout, "[TIMING] %d ms: Copied Android template\n", options.timer.elapsed());
if (!readDependencies(&options))
return CannotReadDependencies;
if (Q_UNLIKELY(options.timing))
fprintf(stdout, "[TIMING] %d ms: Read dependencies\n", options.timer.elapsed());
if (options.deploymentMechanism != Options::Ministro && !copyGnuStl(&options))
return CannotCopyGnuStl;
if (Q_UNLIKELY(options.timing))
fprintf(stdout, "[TIMING] %d ms: Copied GNU STL\n", options.timer.elapsed());
if (!copyQtFiles(&options))
return CannotCopyQtFiles;
if (options.deploymentMechanism == Options::Debug && !deployAllToLocalTmp(options))
return CannotDeployAllToLocalTmp;
if (Q_UNLIKELY(options.timing))
fprintf(stdout, "[TIMING] %d ms: Copied Qt files\n", options.timer.elapsed());
if (!containsApplicationBinary(options))
return CannotFindApplicationBinary;
if (Q_UNLIKELY(options.timing))
fprintf(stdout, "[TIMING] %d ms: Checked for application binary\n", options.timer.elapsed());
if (!options.releasePackage && !copyGdbServer(options))
return CannotCopyGdbServer;
if (Q_UNLIKELY(options.timing))
fprintf(stdout, "[TIMING] %d ms: Copied GDB server\n", options.timer.elapsed());
if (!stripLibraries(options))
return CannotStripLibraries;
if (Q_UNLIKELY(options.timing))
fprintf(stdout, "[TIMING] %d ms: Stripped libraries\n", options.timer.elapsed());
if (!copyAndroidExtraLibs(options))
return CannotCopyAndroidExtraLibs;
if (Q_UNLIKELY(options.timing))
fprintf(stdout, "[TIMING] %d ms: Copied extra libs\n", options.timer.elapsed());
231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361
if (!copyAndroidSources(options))
return CannotCopyAndroidSources;
if (Q_UNLIKELY(options.timing))
fprintf(stdout, "[TIMING] %d ms: Copied android sources\n", options.timer.elapsed());
if (!updateAndroidFiles(options))
return CannotUpdateAndroidFiles;
if (options.generateAssetsFileList && !generateAssetsFileList(options))
return CannotGenerateAssetsFileList;
if (Q_UNLIKELY(options.timing))
fprintf(stdout, "[TIMING] %d ms: Updated files\n", options.timer.elapsed());
if (!createAndroidProject(options))
return CannotCreateAndroidProject;
if (Q_UNLIKELY(options.timing))
fprintf(stdout, "[TIMING] %d ms: Created project\n", options.timer.elapsed());
if (!buildAndroidProject(options))
return CannotBuildAndroidProject;
if (Q_UNLIKELY(options.timing))
fprintf(stdout, "[TIMING] %d ms: Built project\n", options.timer.elapsed());
if (!options.keyStore.isEmpty() && !signPackage(options))
return CannotSignPackage;
if (Q_UNLIKELY(options.timing))
fprintf(stdout, "[TIMING] %d ms: Signed package\n", options.timer.elapsed());
if (options.installApk && !installApk(options))
return CannotInstallApk;
if (Q_UNLIKELY(options.timing))
fprintf(stdout, "[TIMING] %d ms: Installed APK\n", options.timer.elapsed());
fprintf(stdout, "Android package built successfully.\n");
if (options.installApk)
fprintf(stdout, " -- It can now be run from the selected device/emulator.\n");
QString outputFile = options.outputDirectory + QLatin1String("/bin/") + apkName(options);
fprintf(stdout, " -- File: %s\n", qPrintable(outputFile));
return 0;
}