Let androiddeployqt write a dependency file
Let androiddeployqt write a dependency file so that the build system knows when to re-run it. Fixes: QTBUG-94567 Change-Id: I5985d707f257b22789013a74f0a6f7c4de6e5e88 Reviewed-by: Joerg Bornemann <joerg.bornemann@qt.io>bb10
parent
85e25d93b3
commit
857be50b2e
|
|
@ -291,10 +291,13 @@ function(qt6_android_add_apk_target target)
|
|||
set(apk_final_dir "$<TARGET_PROPERTY:${target},BINARY_DIR>/android-build")
|
||||
set(apk_intermediate_dir "${CMAKE_CURRENT_BINARY_DIR}/android-build")
|
||||
set(apk_file_name "${target}.apk")
|
||||
set(dep_file_name "${target}.d")
|
||||
set(apk_final_file_path "${apk_final_dir}/${apk_file_name}")
|
||||
set(apk_intermediate_file_path "${apk_intermediate_dir}/${apk_file_name}")
|
||||
set(dep_intermediate_file_path "${apk_intermediate_dir}/${dep_file_name}")
|
||||
|
||||
# This target is used by Qt Creator's Android support.
|
||||
# This target is used by Qt Creator's Android support and by the ${target}_make_apk target
|
||||
# in case DEPFILEs are not supported.
|
||||
add_custom_target(${target}_prepare_apk_dir ALL
|
||||
DEPENDS ${target}
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
|
|
@ -303,26 +306,42 @@ function(qt6_android_add_apk_target target)
|
|||
COMMENT "Copying ${target} binary to apk folder"
|
||||
)
|
||||
|
||||
# Add custom command that creates the apk in an intermediate location.
|
||||
# We need the intermediate location, because we cannot have target-dependent generator
|
||||
# expressions in OUTPUT.
|
||||
add_custom_command(OUTPUT "${apk_intermediate_file_path}"
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-E copy "$<TARGET_FILE:${target}>"
|
||||
"${apk_intermediate_dir}/libs/${CMAKE_ANDROID_ARCH_ABI}/$<TARGET_FILE_NAME:${target}>"
|
||||
COMMAND "${deployment_tool}"
|
||||
--input "${deployment_file}"
|
||||
--output "${apk_intermediate_dir}"
|
||||
--apk "${apk_intermediate_file_path}"
|
||||
COMMENT "Creating APK for ${target}"
|
||||
DEPENDS "${target}" "${deployment_file}")
|
||||
# The DEPFILE argument to add_custom_command is only available with Ninja or CMake>=3.20 and make.
|
||||
if (CMAKE_GENERATOR MATCHES "Ninja" OR
|
||||
(CMAKE_VERSION VERSION_GREATER_EQUAL 3.20 AND CMAKE_GENERATOR MATCHES "Makefiles"))
|
||||
# Add custom command that creates the apk in an intermediate location.
|
||||
# We need the intermediate location, because we cannot have target-dependent generator
|
||||
# expressions in OUTPUT.
|
||||
add_custom_command(OUTPUT "${apk_intermediate_file_path}"
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-E copy "$<TARGET_FILE:${target}>"
|
||||
"${apk_intermediate_dir}/libs/${CMAKE_ANDROID_ARCH_ABI}/$<TARGET_FILE_NAME:${target}>"
|
||||
COMMAND "${deployment_tool}"
|
||||
--input "${deployment_file}"
|
||||
--output "${apk_intermediate_dir}"
|
||||
--apk "${apk_intermediate_file_path}"
|
||||
--depfile "${dep_intermediate_file_path}"
|
||||
--builddir "${CMAKE_BINARY_DIR}"
|
||||
COMMENT "Creating APK for ${target}"
|
||||
DEPENDS "${target}" "${deployment_file}"
|
||||
DEPFILE "${dep_intermediate_file_path}")
|
||||
|
||||
# Create a ${target}_make_apk target to copy the apk from the intermediate to its final
|
||||
# location. If the final and intermediate locations are identical, this is a no-op.
|
||||
add_custom_target(${target}_make_apk
|
||||
COMMAND "${CMAKE_COMMAND}"
|
||||
-E copy_if_different "${apk_intermediate_file_path}" "${apk_final_file_path}"
|
||||
DEPENDS "${apk_intermediate_file_path}")
|
||||
# Create a ${target}_make_apk target to copy the apk from the intermediate to its final
|
||||
# location. If the final and intermediate locations are identical, this is a no-op.
|
||||
add_custom_target(${target}_make_apk
|
||||
COMMAND "${CMAKE_COMMAND}"
|
||||
-E copy_if_different "${apk_intermediate_file_path}" "${apk_final_file_path}"
|
||||
DEPENDS "${apk_intermediate_file_path}")
|
||||
else()
|
||||
add_custom_target(${target}_make_apk
|
||||
DEPENDS ${target}_prepare_apk_dir
|
||||
COMMAND ${deployment_tool}
|
||||
--input ${deployment_file}
|
||||
--output ${apk_final_dir}
|
||||
--apk ${apk_final_file_path}
|
||||
COMMENT "Creating APK for ${target}"
|
||||
)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(_qt_internal_create_global_apk_target)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ qt_internal_add_app(androiddeployqt
|
|||
QT_NO_FOREACH
|
||||
PUBLIC_LIBRARIES
|
||||
Qt::Core # special case
|
||||
INCLUDE_DIRECTORIES
|
||||
../shared
|
||||
)
|
||||
set_target_properties(androiddeployqt PROPERTIES
|
||||
WIN32_EXECUTABLE FALSE
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@
|
|||
#include <QDirIterator>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include <depfile_shared.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#if defined(Q_OS_WIN32)
|
||||
|
|
@ -73,6 +75,8 @@ public:
|
|||
|
||||
static const bool mustReadOutputAnyway = true; // pclose seems to return the wrong error code unless we read the output
|
||||
|
||||
static QStringList dependenciesForDepfile;
|
||||
|
||||
void deleteRecursively(const QString &dirName)
|
||||
{
|
||||
QDir dir(dirName);
|
||||
|
|
@ -169,6 +173,8 @@ struct Options
|
|||
QString applicationArguments;
|
||||
QString rootPath;
|
||||
QString rccBinaryPath;
|
||||
QString depFilePath;
|
||||
QString buildDirectory;
|
||||
QStringList qmlImportPaths;
|
||||
QStringList qrcFiles;
|
||||
|
||||
|
|
@ -474,6 +480,16 @@ Options parseOptions()
|
|||
options.helpRequested = true;
|
||||
else
|
||||
options.apkPath = arguments.at(++i);
|
||||
} else if (argument.compare(QLatin1String("--depfile"), Qt::CaseInsensitive) == 0) {
|
||||
if (i + 1 == arguments.size())
|
||||
options.helpRequested = true;
|
||||
else
|
||||
options.depFilePath = arguments.at(++i);
|
||||
} else if (argument.compare(QLatin1String("--builddir"), Qt::CaseInsensitive) == 0) {
|
||||
if (i + 1 == arguments.size())
|
||||
options.helpRequested = true;
|
||||
else
|
||||
options.buildDirectory = arguments.at(++i);
|
||||
} else if (argument.compare(QLatin1String("--sign"), Qt::CaseInsensitive) == 0) {
|
||||
if (i + 2 >= arguments.size()) {
|
||||
const QString keyStore = qEnvironmentVariable("QT_ANDROID_KEYSTORE_PATH");
|
||||
|
|
@ -560,6 +576,9 @@ Options parseOptions()
|
|||
}
|
||||
}
|
||||
|
||||
if (options.buildDirectory.isEmpty() && !options.depFilePath.isEmpty())
|
||||
options.helpRequested = true;
|
||||
|
||||
if (options.inputFileName.isEmpty())
|
||||
options.inputFileName = QLatin1String("android-%1-deployment-settings.json").arg(QDir::current().dirName());
|
||||
|
||||
|
|
@ -668,6 +687,11 @@ void printHelp()
|
|||
" qmlimportscanner binary is located using the Qt directory\n"
|
||||
" specified in the input file.\n"
|
||||
"\n"
|
||||
" --depfile <path/to/depfile>: Output a dependency file.\n"
|
||||
"\n"
|
||||
" --builddir <path/to/build/directory>: build directory. Necessary when\n"
|
||||
" generating a depfile because ninja requires relative paths.\n"
|
||||
"\n"
|
||||
" --help: Displays this information.\n",
|
||||
qPrintable(QCoreApplication::arguments().at(0))
|
||||
);
|
||||
|
|
@ -703,6 +727,7 @@ bool copyFileIfNewer(const QString &sourceFileName,
|
|||
const Options &options,
|
||||
bool forceOverwrite = false)
|
||||
{
|
||||
dependenciesForDepfile << sourceFileName;
|
||||
if (QFile::exists(destinationFileName)) {
|
||||
QFileInfo destinationFileInfo(destinationFileName);
|
||||
QFileInfo sourceFileInfo(sourceFileName);
|
||||
|
|
@ -840,6 +865,7 @@ bool readInputFile(Options *options)
|
|||
fprintf(stderr, "Cannot read from input file: %s\n", qPrintable(options->inputFileName));
|
||||
return false;
|
||||
}
|
||||
dependenciesForDepfile << options->inputFileName;
|
||||
|
||||
QJsonDocument jsonDocument = QJsonDocument::fromJson(file.readAll());
|
||||
if (jsonDocument.isNull()) {
|
||||
|
|
@ -2950,6 +2976,29 @@ enum ErrorCode
|
|||
CannotCreateRcc = 21
|
||||
};
|
||||
|
||||
bool writeDependencyFile(const Options &options)
|
||||
{
|
||||
if (options.verbose)
|
||||
fprintf(stdout, "Writing dependency file.\n");
|
||||
|
||||
QFile depFile(options.depFilePath);
|
||||
|
||||
QString relativeApkPath = QDir(options.buildDirectory).relativeFilePath(options.apkPath);
|
||||
|
||||
if (depFile.open(QIODevice::WriteOnly)) {
|
||||
depFile.write(escapeAndEncodeDependencyPath(relativeApkPath));
|
||||
depFile.write(u8": ");
|
||||
|
||||
for (const auto &file : dependenciesForDepfile) {
|
||||
depFile.write(u8" \\\n ");
|
||||
depFile.write(escapeAndEncodeDependencyPath(file));
|
||||
}
|
||||
|
||||
depFile.write(u8"\n");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication a(argc, argv);
|
||||
|
|
@ -3090,6 +3139,9 @@ int main(int argc, char *argv[])
|
|||
if (Q_UNLIKELY(options.timing))
|
||||
fprintf(stdout, "[TIMING] %d ms: Installed APK\n", options.timer.elapsed());
|
||||
|
||||
if (!options.depFilePath.isEmpty())
|
||||
writeDependencyFile(options);
|
||||
|
||||
fprintf(stdout, "Android package built successfully in %.3f ms.\n", options.timer.elapsed() / 1000.);
|
||||
|
||||
if (options.installApk)
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ qt_internal_add_tool(${target_name}
|
|||
INCLUDE_DIRECTORIES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
../../3rdparty/tinycbor/src
|
||||
../shared
|
||||
)
|
||||
|
||||
#### Keys ignored in scope 1:.:.:moc.pro:<TRUE>:
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include <depfile_shared.h>
|
||||
#include "preprocessor.h"
|
||||
#include "moc.h"
|
||||
#include "outputrevision.h"
|
||||
|
|
@ -177,50 +178,6 @@ static QStringList argumentsFromCommandLineAndFile(const QStringList &arguments,
|
|||
return allArguments;
|
||||
}
|
||||
|
||||
// Escape characters in given path. Dependency paths are Make-style, not NMake/Jom style.
|
||||
// The paths can also be consumed by Ninja.
|
||||
// "$" replaced by "$$"
|
||||
// "#" replaced by "\#"
|
||||
// " " replaced by "\ "
|
||||
// "\#" replaced by "\\#"
|
||||
// "\ " replaced by "\\\ "
|
||||
//
|
||||
// The escape rules are according to what clang / llvm escapes when generating a Make-style
|
||||
// dependency file.
|
||||
// Is a template function, because input param can be either a QString or a QByteArray.
|
||||
template <typename T> struct CharType;
|
||||
template <> struct CharType<QString> { using type = QLatin1Char; };
|
||||
template <> struct CharType<QByteArray> { using type = char; };
|
||||
template <typename StringType>
|
||||
StringType escapeDependencyPath(const StringType &path)
|
||||
{
|
||||
using CT = typename CharType<StringType>::type;
|
||||
StringType escapedPath;
|
||||
int size = path.size();
|
||||
escapedPath.reserve(size);
|
||||
for (int i = 0; i < size; ++i) {
|
||||
if (path[i] == CT('$')) {
|
||||
escapedPath.append(CT('$'));
|
||||
} else if (path[i] == CT('#')) {
|
||||
escapedPath.append(CT('\\'));
|
||||
} else if (path[i] == CT(' ')) {
|
||||
escapedPath.append(CT('\\'));
|
||||
int backwards_it = i - 1;
|
||||
while (backwards_it > 0 && path[backwards_it] == CT('\\')) {
|
||||
escapedPath.append(CT('\\'));
|
||||
--backwards_it;
|
||||
}
|
||||
}
|
||||
escapedPath.append(path[i]);
|
||||
}
|
||||
return escapedPath;
|
||||
}
|
||||
|
||||
QByteArray escapeAndEncodeDependencyPath(const QString &path)
|
||||
{
|
||||
return QFile::encodeName(escapeDependencyPath(path));
|
||||
}
|
||||
|
||||
int runMoc(int argc, char **argv)
|
||||
{
|
||||
QCoreApplication app(argc, argv);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2021 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the tools applications of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
|
||||
** 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 The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef QTBASE_DEPFILE_SHARED_H
|
||||
#define QTBASE_DEPFILE_SHARED_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QFile>
|
||||
|
||||
// Escape characters in given path. Dependency paths are Make-style, not NMake/Jom style.
|
||||
// The paths can also be consumed by Ninja.
|
||||
// "$" replaced by "$$"
|
||||
// "#" replaced by "\#"
|
||||
// " " replaced by "\ "
|
||||
// "\#" replaced by "\\#"
|
||||
// "\ " replaced by "\\\ "
|
||||
//
|
||||
// The escape rules are according to what clang / llvm escapes when generating a Make-style
|
||||
// dependency file.
|
||||
// Is a template function, because input param can be either a QString or a QByteArray.
|
||||
template <typename T> struct CharType;
|
||||
template <> struct CharType<QString> { using type = QLatin1Char; };
|
||||
template <> struct CharType<QByteArray> { using type = char; };
|
||||
template <typename StringType>
|
||||
StringType escapeDependencyPath(const StringType &path)
|
||||
{
|
||||
using CT = typename CharType<StringType>::type;
|
||||
StringType escapedPath;
|
||||
int size = path.size();
|
||||
escapedPath.reserve(size);
|
||||
for (int i = 0; i < size; ++i) {
|
||||
if (path[i] == CT('$')) {
|
||||
escapedPath.append(CT('$'));
|
||||
} else if (path[i] == CT('#')) {
|
||||
escapedPath.append(CT('\\'));
|
||||
} else if (path[i] == CT(' ')) {
|
||||
escapedPath.append(CT('\\'));
|
||||
int backwards_it = i - 1;
|
||||
while (backwards_it > 0 && path[backwards_it] == CT('\\')) {
|
||||
escapedPath.append(CT('\\'));
|
||||
--backwards_it;
|
||||
}
|
||||
}
|
||||
escapedPath.append(path[i]);
|
||||
}
|
||||
return escapedPath;
|
||||
}
|
||||
|
||||
static inline QByteArray escapeAndEncodeDependencyPath(const QString &path)
|
||||
{
|
||||
return QFile::encodeName(escapeDependencyPath(path));
|
||||
}
|
||||
|
||||
#endif // QTBASE_DEPFILE_SHARED_H
|
||||
Loading…
Reference in New Issue