Android: Handle dotted URI QML Modules in Java code gen

A QML module can have URI that include dot character.
In this change, we refactor the code generator to create
the same hierarchies as a Java package structure. Each
QML module URI will be appended to the base application
package name to form a package that represents the QML
module as a Java package. Then the generated code of each
QML component will be placed in a single file.

This change, also refactor how to generated code should
be written in the file, by buffering the generated code
to a QByteArray first, and then flushing it into the
target file. We also create a marker file inside the
directories of each module, so that we can entirely
remove the directory and all its files before generating
new code during the next build.

Fixes: QTBUG-125891
Fixes: QTBUG-125970
Fixes: QTBUG-125971
Change-Id: Iebce6495d9d29af32c3f1f97274c252444d2864e
Reviewed-by: Assam Boudjelthia <assam.boudjelthia@qt.io>
(cherry picked from commit 68785b3e59c8a8f4a383051123b87af3d930ff18)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
(cherry picked from commit 8d97598e9c2a53a49b1fc053ab8e23bb8d16d8ee)
This commit is contained in:
Soheil Armin 2024-11-08 00:09:33 +02:00 committed by Qt Cherry-pick Bot
parent ad98ad04bf
commit c5ac6e08fb

View File

@ -3459,6 +3459,30 @@ bool writeDependencyFile(const Options &options)
int generateJavaQmlComponents(const Options &options)
{
const auto firstCharToUpper = [](const QString &str) -> QString {
if (str.isEmpty())
return str;
return str.left(1).toUpper() + str.mid(1);
};
const auto upperFirstAndAfterDot = [](QString str) -> QString {
if (str.isEmpty())
return str;
str[0] = str[0].toUpper();
for (int i = 0; i < str.size(); ++i) {
if (str[i] == "."_L1) {
// Move to the next character after the dot
int j = i + 1;
if (j < str.size()) {
str[j] = str[j].toUpper();
}
}
}
return str;
};
const auto getImportPaths = [options](const QString &buildPath, const QString &libName,
QStringList &appImports, QStringList &externalImports) -> bool {
QFile confRspFile("%1/.qt/qml_imports/%2_conf.rsp"_L1.arg(buildPath, libName));
@ -3635,24 +3659,13 @@ int generateJavaQmlComponents(const Options &options)
<< "import org.qtproject.qt.android.QtQuickViewContent;\n\n";
};
const auto beginLibraryBlock = [](QTextStream &stream, const QString &libName) {
stream << QLatin1StringView("public final class %1 {\n").arg(libName);
};
const auto beginModuleBlock = [](QTextStream &stream, const QString &moduleName,
bool topLevel = false, int indentWidth = 4) {
const QString indent(indentWidth, u' ');
stream << indent
<< "public final%1 class %2 {\n"_L1.arg(topLevel ? ""_L1 : " static"_L1, moduleName);
};
const auto beginComponentBlock = [](QTextStream &stream, const QString &libName,
const QString &moduleName, const QString &preferPath,
const ComponentInfo &componentInfo, int indentWidth = 8) {
const QString indent(indentWidth, u' ');
stream << indent
<< "public final static class %1 extends QtQuickViewContent {\n"_L1
<< "public final class %1 extends QtQuickViewContent {\n"_L1
.arg(componentInfo.name)
<< indent << " @Override public String getLibraryName() {\n"_L1
<< indent << " return \"%1\";\n"_L1.arg(libName)
@ -3666,14 +3679,14 @@ int generateJavaQmlComponents(const Options &options)
<< indent << " }\n"_L1;
};
const auto beginPropertyBlock = [](QTextStream &stream, const QJsonObject &propertyData,
int indentWidth = 8) {
const auto beginPropertyBlock = [firstCharToUpper](QTextStream &stream,
const QJsonObject &propertyData,
int indentWidth = 8) {
const QString indent(indentWidth, u' ');
const QString propertyName = propertyData["name"_L1].toString();
if (propertyName.isEmpty())
return;
const QString upperPropertyName =
propertyName[0].toUpper() + propertyName.last(propertyName.size() - 1);
const QString upperPropertyName = firstCharToUpper(propertyName);
const QString typeName = propertyData["typeName"_L1].toString();
const bool isReadyonly = propertyData["isReadonly"_L1].toBool();
@ -3697,8 +3710,9 @@ int generateJavaQmlComponents(const Options &options)
<< indent << "}\n";
};
const auto beginSignalBlock = [](QTextStream &stream, const QJsonObject &methodData,
int indentWidth = 8) {
const auto beginSignalBlock = [firstCharToUpper](QTextStream &stream,
const QJsonObject &methodData,
int indentWidth = 8) {
const QString indent(indentWidth, u' ');
if (methodData["methodType"_L1] != 0)
return;
@ -3709,8 +3723,7 @@ int generateJavaQmlComponents(const Options &options)
const QString methodName = methodData["name"_L1].toString();
if (methodName.isEmpty())
return;
const QString upperMethodName =
methodName[0].toUpper() + methodName.last(methodName.size() - 1);
const QString upperMethodName = firstCharToUpper(methodName);
const QString typeName = !parameters.isEmpty()
? parameters[0].toObject()["typeName"_L1].toString()
: "void"_L1;
@ -3725,18 +3738,20 @@ int generateJavaQmlComponents(const Options &options)
<< indent << "}\n";
};
constexpr static auto markerFileName = "qml_java_contents"_L1;
const QString libName(options.applicationBinary);
const QString libClassname = libName[0].toUpper() + libName.last(libName.size() - 1);
const QString javaPackage = options.packageName;
const QString outputDir = "%1/src/%2"_L1.arg(options.outputDirectory,
QString(javaPackage).replace(u'.', u'/'));
QString javaPackageBase = options.packageName;
const QString expectedBaseLeaf = ".%1"_L1.arg(libName);
if (!javaPackageBase.endsWith(expectedBaseLeaf))
javaPackageBase += expectedBaseLeaf;
const QString baseSourceDir = "%1/src/%2"_L1.arg(options.outputDirectory,
QString(javaPackageBase).replace(u'.', u'/'));
const QString buildPath(QDir(options.buildDirectory).absolutePath());
const QString domBinaryPath(options.qmlDomBinaryPath);
const bool leafEqualsLibname = javaPackage.endsWith(".%1"_L1.arg(libName));
fprintf(stdout, "Generating Java QML Components in %s directory.\n", qPrintable(outputDir));
if (!QDir().current().mkpath(outputDir)) {
fprintf(stderr, "Cannot create %s directory\n", qPrintable(outputDir));
fprintf(stdout, "Generating Java QML Components in %s directory.\n", qPrintable(baseSourceDir));
if (!QDir().current().mkpath(baseSourceDir)) {
fprintf(stderr, "Cannot create %s directory\n", qPrintable(baseSourceDir));
return false;
}
@ -3745,20 +3760,12 @@ int generateJavaQmlComponents(const Options &options)
if (!getImportPaths(buildPath, libName, appImports, externalImports))
return false;
QTextStream outputStream;
QFile outputFile;
if (!leafEqualsLibname) {
outputFile.setFileName("%1/%2.java"_L1.arg(outputDir, libClassname));
if (outputFile.exists())
outputFile.remove();
if (!outputFile.open(QFile::WriteOnly)) {
fprintf(stderr, "Cannot open %s file to write.\n", qPrintable(outputFile.fileName()));
return false;
}
outputStream.setDevice(&outputFile);
createHeaderBlock(outputStream, javaPackage);
beginLibraryBlock(outputStream, libClassname);
// Remove previous directories generated by this code generator
{
const QString srcDir = "%1/src"_L1.arg(options.outputDirectory);
QDirIterator iter(srcDir, { markerFileName }, QDir::Files, QDirIterator::Subdirectories);
while (iter.hasNext())
iter.nextFileInfo().dir().removeRecursively();
}
int generatedComponents = 0;
@ -3767,9 +3774,7 @@ int generateJavaQmlComponents(const Options &options)
if (!moduleInfo.isValid())
continue;
const QString moduleClassname = moduleInfo.moduleName[0].toUpper()
+ moduleInfo.moduleName.last(moduleInfo.moduleName.size() - 1);
const QString modulePackageSuffix = upperFirstAndAfterDot(moduleInfo.moduleName);
if (moduleInfo.moduleName == libName) {
fprintf(stderr,
"A QML module name (%s) cannot be the same as the target name when building "
@ -3778,30 +3783,24 @@ int generateJavaQmlComponents(const Options &options)
return false;
}
int indentBase = 4;
if (leafEqualsLibname) {
indentBase = 0;
QIODevice *outputStreamDevice = outputStream.device();
if (outputStreamDevice) {
outputStream.flush();
outputStream.reset();
outputStreamDevice->close();
}
outputFile.setFileName("%1/%2.java"_L1.arg(outputDir,moduleClassname));
if (outputFile.exists() && !outputFile.remove())
return false;
if (!outputFile.open(QFile::WriteOnly)) {
fprintf(stderr, "Cannot open %s file to write.\n", qPrintable(outputFile.fileName()));
return false;
}
outputStream.setDevice(&outputFile);
createHeaderBlock(outputStream, javaPackage);
const QString javaPackage = "%1.%2"_L1.arg(javaPackageBase, modulePackageSuffix);
const QString outputDir =
"%1/%2"_L1.arg(baseSourceDir, QString(modulePackageSuffix).replace(u'.', u'/'));
if (!QDir().current().mkpath(outputDir)) {
fprintf(stderr, "Cannot create %s directory\n", qPrintable(outputDir));
return false;
}
beginModuleBlock(outputStream, moduleClassname, leafEqualsLibname, indentBase);
indentBase += 4;
// Add a marker file to indicate this as a module package source directory
{
QFile markerFile("%1/%2"_L1.arg(outputDir, markerFileName));
if (!markerFile.open(QFile::WriteOnly)) {
fprintf(stderr, "Cannot create %s file\n", qPrintable(markerFile.fileName()));
return false;
}
}
int indentBase = 0;
for (const auto &qmlComponent : moduleInfo.qmlComponents) {
const bool isSelected = options.selectedJavaQmlComponents.contains(
@ -3815,6 +3814,11 @@ int generateJavaQmlComponents(const Options &options)
if (component.isEmpty())
continue;
QByteArray componentClassBody;
QTextStream outputStream(&componentClassBody, QTextStream::ReadWrite);
createHeaderBlock(outputStream, javaPackage);
beginComponentBlock(outputStream, libName, moduleInfo.moduleName, moduleInfo.preferPath,
qmlComponent, indentBase);
indentBase += 4;
@ -3829,16 +3833,23 @@ int generateJavaQmlComponents(const Options &options)
indentBase -= 4;
endBlock(outputStream, indentBase);
outputStream.flush();
// Write component class body to file
QFile outputFile("%1/%2.java"_L1.arg(outputDir, qmlComponent.name));
if (outputFile.exists())
outputFile.remove();
if (!outputFile.open(QFile::WriteOnly)) {
fprintf(stderr, "Cannot open %s file to write.\n",
qPrintable(outputFile.fileName()));
return false;
}
outputFile.write(componentClassBody);
outputFile.close();
generatedComponents++;
}
indentBase -= 4;
endBlock(outputStream, indentBase);
}
if (!leafEqualsLibname)
endBlock(outputStream, 0);
outputStream.flush();
outputStream.device()->close();
return generatedComponents;
}