import qbs.File import qbs.FileInfo import qbs.ModUtils import qbs.TextFile import qbs.Utilities import qbs.Process import qbs.Xml Module { version: @version@ property bool useMinistro: false property string qmlRootDir: product.sourceDirectory property stringList extraPrefixDirs property stringList deploymentDependencies // qmake: ANDROID_DEPLOYMENT_DEPENDENCIES property stringList extraPlugins // qmake: ANDROID_EXTRA_PLUGINS property stringList extraLibs // qmake: ANDROID_EXTRA_LIBS property bool verboseAndroidDeployQt: false property string _androidDeployQtFilePath: FileInfo.joinPaths(_qtBinaryDir, "bin", "androiddeployqt") property string _qtBinaryDir property string _qtInstallDir // TODO: Remove in 1.20 // From 1.20 product property used from an export item will point to the // importingProduct property. So using the importingProduct property will be useless // and the change will be reverted property var _importingProduct: (typeof importingProduct !== "undefined") ? importingProduct : product property bool _enableSdkSupport: _importingProduct.type && _importingProduct.type.contains("android.package") && !consoleApplication property bool _enableNdkSupport: !_importingProduct.aggregate || _importingProduct.multiplexConfigurationId property string _templatesBaseDir: FileInfo.joinPaths(_qtInstallDir, "src", "android") property string _deployQtOutDir: FileInfo.joinPaths(product.buildDirectory, "deployqt_out") property bool _multiAbi: Utilities.versionCompare(version, "5.14") >= 0 // QTBUG-87288: correct QtNetwork jar dependencies for 5.15.0 < Qt < 5.15.3 property bool _correctQtNetworkDependencies: Utilities.versionCompare(version, "5.15.0") > 0 && Utilities.versionCompare(version, "5.15.3") < 0 Depends { name: "Android.sdk"; condition: _enableSdkSupport } Depends { name: "Android.ndk"; condition: _enableNdkSupport } Depends { name: "java"; condition: _enableSdkSupport } Depends { name: "cpp"; condition: _enableNdkSupport } Properties { condition: _enableNdkSupport && qbs.toolchain.contains("clang") Android.ndk.appStl: "c++_shared" } Properties { condition: _enableNdkSupport && !qbs.toolchain.contains("clang") Android.ndk.appStl: "gnustl_shared" } Properties { condition: _enableSdkSupport Android.sdk.customManifestProcessing: true java._tagJniHeaders: false // prevent rule cycle } Properties { condition: _enableSdkSupport && Utilities.versionCompare(version, "5.15") >= 0 && Utilities.versionCompare(version, "6.0") < 0 java.additionalClassPaths: [FileInfo.joinPaths(_qtInstallDir, "jar", "QtAndroid.jar")] } Properties { condition: _enableSdkSupport && Utilities.versionCompare(version, "6.0") >= 0 java.additionalClassPaths: [FileInfo.joinPaths(_qtInstallDir, "jar", "Qt6Android.jar")] } // "ANDROID_HAS_WSTRING" was removed from qtcore qstring.h in Qt 5.14.0 Properties { condition: _enableNdkSupport && Utilities.versionCompare(version, "5.14.0") < 0 && (Android.ndk.abi === "armeabi-v7a" || Android.ndk.abi === "x86") cpp.defines: "ANDROID_HAS_WSTRING" } Properties { condition: _enableSdkSupport Android.sdk._bundledInAssets: _multiAbi } Properties { condition: _enableSdkSupport && Utilities.versionCompare(version, "6.0") < 0 Android.sdk.minimumVersion: "21" } Properties { condition: _enableSdkSupport && Utilities.versionCompare(version, "6.0") >= 0 Android.sdk.minimumVersion: "23" } Properties { condition: _enableNdkSupport cpp.archSuffix: _multiAbi ? "_" + Android.ndk.abi : "" } Rule { condition: _enableSdkSupport multiplex: true property stringList inputTags: ["android.nativelibrary", "qrc"] inputsFromDependencies: inputTags inputs: product.aggregate ? [] : inputTags Artifact { filePath: "androiddeployqt.json" fileTags: "qt_androiddeployqt_input" } prepare: { var cmd = new JavaScriptCommand(); cmd.description = "creating " + output.fileName; cmd.sourceCode = function() { var theBinary; var nativeLibs = inputs["android.nativelibrary"]; var architectures = []; var triples = []; var hostArch; var targetArchitecture; if (nativeLibs.length === 1) { theBinary = nativeLibs[0]; hostArch = theBinary.Android.ndk.hostArch; targetArchitecture = theBinary.Android.ndk.abi; if (product.Qt.android_support._multiAbi) { architectures.push(theBinary.Android.ndk.abi); triples.push(theBinary.cpp.toolchainTriple); } } else { for (i = 0; i < nativeLibs.length; ++i) { var candidate = nativeLibs[i]; if (product.Qt.android_support._multiAbi) { if (candidate.product.name === product.name) { architectures.push(candidate.Android.ndk.abi); triples.push(candidate.cpp.toolchainTriple); hostArch = candidate.Android.ndk.hostArch; targetArchitecture = candidate.Android.ndk.abi; theBinary = candidate; } } else { if (!candidate.fileName.contains(candidate.product.targetName)) continue; if (!theBinary) { theBinary = candidate; hostArch = theBinary.Android.ndk.hostArch; targetArchitecture = theBinary.Android.ndk.abi; continue; } if (candidate.product.name !== product.name) { continue; // This is not going to be a match } if (candidate.product.name === product.name && theBinary.product.name !== product.name) { theBinary = candidate; // The new candidate is a better match. hostArch = theBinary.Android.ndk.hostArch; targetArchitecture = theBinary.Android.ndk.abi; continue; } throw "Qt applications for Android support only one native binary " + "per package.\n" + "In particular, you cannot build a Qt app for more than " + "one architecture at the same time."; } } } var f = new TextFile(output.filePath, TextFile.WriteOnly); f.writeLine("{"); f.writeLine('"description": "This file was generated by qbs to be read by ' + 'androiddeployqt and should not be modified by hand.",'); f.writeLine('"qt": "' + product.Qt.android_support._qtInstallDir + '",'); f.writeLine('"sdk": "' + product.Android.sdk.sdkDir + '",'); f.writeLine('"sdkBuildToolsRevision": "' + product.Android.sdk.buildToolsVersion + '",'); f.writeLine('"ndk": "' + product.Android.sdk.ndkDir + '",'); f.writeLine('"toolchain-prefix": "llvm",'); f.writeLine('"tool-prefix": "llvm",'); f.writeLine('"useLLVM": true,'); f.writeLine('"ndk-host": "' + hostArch + '",'); if (!product.Qt.android_support._multiAbi) { f.writeLine('"target-architecture": "' + targetArchitecture + '",'); } else { var line = '"architectures": {'; for (var i in architectures) { line = line + '"' + architectures[i] + '":"' + triples[i] + '"'; if (i < architectures.length-1) line = line + ','; } line = line + "},"; f.writeLine(line); } f.writeLine('"qml-root-path": "' + product.Qt.android_support.qmlRootDir + '",'); var deploymentDeps = product.Qt.android_support.deploymentDependencies; if (deploymentDeps && deploymentDeps.length > 0) f.writeLine('"deployment-dependencies": "' + deploymentDeps.join() + '",'); var extraPlugins = product.Qt.android_support.extraPlugins; if (extraPlugins && extraPlugins.length > 0) f.writeLine('"android-extra-plugins": "' + extraPlugins.join() + '",'); var extraLibs = product.Qt.android_support.extraLibs; if (extraLibs && extraLibs.length > 0) { for (var i = 0; i < extraLibs.length; ++i) { if (!FileInfo.isAbsolutePath(extraLibs[i])) { extraLibs[i] = FileInfo.joinPaths(product.sourceDirectory, extraLibs[i]); } } f.writeLine('"android-extra-libs": "' + extraLibs.join() + '",'); } var prefixDirs = product.Qt.android_support.extraPrefixDirs; if (prefixDirs && prefixDirs.length > 0) f.writeLine('"extraPrefixDirs": ' + JSON.stringify(prefixDirs) + ','); if ((product.qmlImportPaths instanceof Array) && product.qmlImportPaths.length > 0) f.writeLine('"qml-import-paths": "' + product.qmlImportPaths.join(',') + '",'); if (Utilities.versionCompare(product.Qt.android_support.version, "6.0") >= 0) { f.writeLine('"qml-importscanner-binary": "' + product.Qt.core.qmlImportScannerFilePath + '",'); f.writeLine('"rcc-binary": "' + product.Qt.core.binPath + '/rcc' + '",'); if (inputs["qrc"] && inputs["qrc"].length > 0) { var qrcFiles = []; var qrcInputs = inputs["qrc"]; for (i = 0; i < qrcInputs.length; ++i) { qrcFiles.push(qrcInputs[i].filePath); } f.writeLine('"qrcFiles": "' + qrcFiles.join(',') + '",'); } } // QBS-1429 if (!product.Qt.android_support._multiAbi) { f.writeLine('"stdcpp-path": "' + (product.cpp.sharedStlFilePath ? product.cpp.sharedStlFilePath : product.cpp.staticStlFilePath) + '",'); f.writeLine('"application-binary": "' + theBinary.filePath + '"'); } else { f.writeLine('"stdcpp-path": "' + product.Android.sdk.ndkDir + '/toolchains/llvm/prebuilt/' + hostArch + '/sysroot/usr/lib/",'); f.writeLine('"application-binary": "' + theBinary.product.targetName + '"'); } f.writeLine("}"); f.close(); }; return cmd; } } // We use the manifest template from the Qt installation if and only if the project // does not provide a manifest file. Rule { condition: _enableSdkSupport multiplex: true requiresInputs: false inputs: "android.manifest" excludedInputs: "qt.android_manifest" outputFileTags: ["android.manifest", "qt.android_manifest"] outputArtifacts: { if (inputs["android.manifest"]) return []; return [{ filePath: "qt_manifest/AndroidManifest.xml", fileTags: ["android.manifest", "qt.android_manifest"] }]; } prepare: { var cmd = new JavaScriptCommand(); cmd.description = "copying Qt Android manifest template"; cmd.sourceCode = function() { File.copy(FileInfo.joinPaths(product.Qt.android_support._templatesBaseDir, "templates", "AndroidManifest.xml"), output.filePath); }; return cmd; } } Rule { condition: _enableSdkSupport multiplex: true property stringList defaultInputs: ["qt_androiddeployqt_input", "android.manifest_processed"] property stringList allInputs: ["qt_androiddeployqt_input", "android.manifest_processed", "android.nativelibrary"] inputsFromDependencies: "android.nativelibrary" inputs: product.aggregate ? defaultInputs : allInputs outputFileTags: [ "android.manifest_final", "android.resources", "android.assets", "bundled_jar", "android.deployqt_list", ] outputArtifacts: { var artifacts = [ { filePath: "AndroidManifest.xml", fileTags: "android.manifest_final" }, { filePath: product.Qt.android_support._deployQtOutDir + "/res/values/libs.xml", fileTags: "android.resources" }, { filePath: product.Qt.android_support._deployQtOutDir + "/res/values/strings.xml", fileTags: "android.resources" }, { filePath: product.Qt.android_support._deployQtOutDir + "/assets/.dummy", fileTags: "android.assets" }, { filePath: "deployqt.list", fileTags: "android.deployqt_list" }, ]; if (!product.Qt.android_support.useMinistro) { artifacts.push({ filePath: FileInfo.joinPaths(product.java.classFilesDir, "QtAndroid.jar"), fileTags: ["bundled_jar"] }); } return artifacts; } prepare: { var copyCmd = new JavaScriptCommand(); copyCmd.description = "copying Qt resource templates"; copyCmd.sourceCode = function() { File.copy(inputs["android.manifest_processed"][0].filePath, product.Qt.android_support._deployQtOutDir + "/AndroidManifest.xml"); File.copy(product.Qt.android_support._templatesBaseDir + "/java/res", product.Qt.android_support._deployQtOutDir + "/res"); File.copy(product.Qt.android_support._templatesBaseDir + "/templates/res/values/libs.xml", product.Qt.android_support._deployQtOutDir + "/res/values/libs.xml"); if (!product.Qt.android_support._multiAbi) { try { File.remove(FileInfo.path(outputs["android.assets"][0].filePath)); } catch (e) { } } else { for (var i in inputs["android.nativelibrary"]) { var input = inputs["android.nativelibrary"][i]; File.copy(input.filePath, FileInfo.joinPaths(product.Qt.android_support._deployQtOutDir, "libs", input.Android.ndk.abi, input.fileName)); } } }; var androidDeployQtArgs = [ "--output", product.Qt.android_support._deployQtOutDir, "--input", inputs["qt_androiddeployqt_input"][0].filePath, "--aux-mode", "--deployment", product.Qt.android_support.useMinistro ? "ministro" : "bundled", "--android-platform", product.Android.sdk.platform, ]; if (product.Qt.android_support.verboseAndroidDeployQt) androidDeployQtArgs.push("--verbose"); var androidDeployQtCmd = new Command( product.Qt.android_support._androidDeployQtFilePath, androidDeployQtArgs); androidDeployQtCmd.description = "running androiddeployqt"; // We do not want androiddeployqt to write directly into our APK base dir, so // we ran it on an isolated directory and now we move stuff over. // We remember the files for which we do that, so if the next invocation // of androiddeployqt creates fewer files, the other ones are removed from // the APK base dir. var moveCmd = new JavaScriptCommand(); moveCmd.description = "processing androiddeployqt outout"; moveCmd.sourceCode = function() { var libsDir = product.Qt.android_support._deployQtOutDir + "/libs"; var libDir = product.Android.sdk.packageContentsDir + "/lib"; var listFilePath = outputs["android.deployqt_list"][0].filePath; var oldLibs = []; try { var listFile = new TextFile(listFilePath, TextFile.ReadOnly); var listFileLine = listFile.readLine(); while (listFileLine) { oldLibs.push(listFileLine); listFileLine = listFile.readLine(); } listFile.close(); } catch (e) { } listFile = new TextFile(listFilePath, TextFile.WriteOnly); var newLibs = []; var moveLibFiles = function(prefix) { var fullSrcPrefix = FileInfo.joinPaths(libsDir, prefix); var files = File.directoryEntries(fullSrcPrefix, File.Files); for (var i = 0; i < files.length; ++i) { var file = files[i]; var srcFilePath = FileInfo.joinPaths(fullSrcPrefix, file); var targetFilePath; if (file.endsWith(".jar")) targetFilePath = FileInfo.joinPaths(product.java.classFilesDir, file); else targetFilePath = FileInfo.joinPaths(libDir, prefix, file); File.move(srcFilePath, targetFilePath); listFile.writeLine(targetFilePath); newLibs.push(targetFilePath); } var dirs = File.directoryEntries(fullSrcPrefix, File.Dirs | File.NoDotAndDotDot); for (i = 0; i < dirs.length; ++i) moveLibFiles(FileInfo.joinPaths(prefix, dirs[i])); }; moveLibFiles(""); listFile.close(); for (i = 0; i < oldLibs.length; ++i) { if (!newLibs.contains(oldLibs[i])) File.remove(oldLibs[i]); } }; var correctingCmd = new JavaScriptCommand(); if (product.Qt.android_support._correctQtNetworkDependencies) { correctingCmd.description = "correcting network jar dependency"; correctingCmd.sourceCode = function() { var findNetworkLib = function() { var libsDir = product.Android.sdk.packageContentsDir + "/lib"; var dirList = File.directoryEntries(libsDir, File.Dirs | File.NoDotAndDotDot); for (var i = 0; i < dirList.length; ++i) { var archDir = FileInfo.joinPaths(libsDir, dirList[i]); var fileList = File.directoryEntries(archDir, File.Files); if (fileList) { for (var j = 0; j < fileList.length; ++j) { if (fileList[j].contains("libQt5Network")) { return true; } } } } return false; } if (findNetworkLib()) { var manifestData = new Xml.DomDocument(); var manifestFilePath = product.Qt.android_support._deployQtOutDir + "/AndroidManifest.xml" manifestData.load(manifestFilePath); var rootElem = manifestData.documentElement(); if (!rootElem || !rootElem.isElement() || rootElem.tagName() != "manifest") throw "No manifest tag found in '" + manifestFilePath + "'."; var appElem = rootElem.firstChild("application"); if (!appElem || !appElem.isElement() || appElem.tagName() != "application") throw "No application tag found in '" + manifestFilePath + "'."; var activityElem = appElem.firstChild("activity"); if (!activityElem || !activityElem.isElement() || activityElem.tagName() != "activity") throw "No activity tag found in '" + manifestFilePath + "'."; var metaDataElem = activityElem.firstChild("meta-data"); while (metaDataElem && metaDataElem.isElement()) { if (metaDataElem.attribute("android:name") == "android.app.load_local_jars" ) { var value = metaDataElem.attribute("android:value"); var fileName = "QtAndroidNetwork.jar"; metaDataElem.setAttribute("android:value", value + ":jar/" + fileName); var jarFilePath = FileInfo.joinPaths( product.Qt.android_support._qtInstallDir, "jar", fileName); var targetFilePath = FileInfo.joinPaths(product.java.classFilesDir, fileName); File.copy(jarFilePath, targetFilePath); break; } metaDataElem = metaDataElem.nextSibling("meta-data"); } manifestData.save(outputs["android.manifest_final"][0].filePath, 4); } else { File.move(product.Qt.android_support._deployQtOutDir + "/AndroidManifest.xml", outputs["android.manifest_final"][0].filePath); } }; } else { correctingCmd.description = "copying manifest"; correctingCmd.sourceCode = function() { File.move(product.Qt.android_support._deployQtOutDir + "/AndroidManifest.xml", outputs["android.manifest_final"][0].filePath); } } return [copyCmd, androidDeployQtCmd, moveCmd, correctingCmd]; } } Group { condition: Qt.android_support._enableSdkSupport name: "helper sources from qt" prefix: Qt.android_support._templatesBaseDir + "/java/" Android.sdk.aidlSearchPaths: prefix + "src" files: [ "**/*.java", "**/*.aidl", ] } validate: { if (Utilities.versionCompare(version, "5.12") < 0) throw ModUtils.ModuleError("Cannot use Qt " + version + " with Android. " + "Version 5.12 or later is required."); } }