Qt QBS and android - android

I'm trying to find a simple tutorial: how to make a simple application for android using gbps. The following links were found:
Stack oferflow. The answer to this question has not been
received, although the version of the cbs has already been updated
to 1.11 and the support of android is included.
AndroidApk Item in QBS Documentation. In this case I get warning: '../Application/src/main/AndroidManifest.xml' does not exist.
I unfortunately could not find any new information. I ask for help.
Update:
For Qmake I just create standard widget project like this one:
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = androidtest
TEMPLATE = app
DEFINES += QT_DEPRECATED_WARNINGS
SOURCES += \
main.cpp \
mainwindow.cpp
HEADERS += \
mainwindow.h
FORMS += \
mainwindow.ui
CONFIG += mobility
MOBILITY =
And this is works and builds fine. QtCreator automatically create all necessary files and than run app on my phone
In Qbs I try to make same application. For this reason I have QBS-file:
import qbs
Project {
CppApplication {
name: "helloworld"
Depends {
name: "Qt"
submodules: [
"core",
"widgets"
]
}
Depends { name: "Android.ndk" }
Android.ndk.appStl: "gnustl_shared"
Group {
name: "src"
files: [
"main*.*"
]
}
}
AndroidApk {
name: "helloworld_android"
Depends {name: "helloworld" }
packageName: "com.example.android.helloworld"
}
}
At the end I have Done with HelloWorld product (libhelloworld.so). But first error of "helloworld_android" is a fail at android manifest. This file is undefined. What I should do next?

qmake has some built-in magic when building for Android, like using resources provided by Qt (including a manifest template) and running the android-deployqt tool. None of this is currently done by qbs.

Ok, I think I made it. It's not a good solution but this is work. Resulting APK you can find in "\install-root\$productName$\build\outputs\apk\$productName$-debug.apk
import qbs
import qbs.TextFile
import qbs.Process
import qbs.File
Project {
//Main Application
CppApplication {
name: "helloworld";
Depends {
name: "Qt"
submodules: [
"core",
"widgets"
]
}
Depends { name: "Android.ndk" }
Android.ndk.appStl: "gnustl_shared"
Group {
name: "src"
files: [
"main*.*"
]
}
Group {
qbs.install: true
fileTagsFilter: "dynamiclibrary"
qbs.installPrefix : product.name+"/libs/"+Android.ndk.abi+"/"
}
}
//Preparation
Product {
name: "Prepared2Deploy"
type: "prepared2deploy"
Depends { name: "helloworld" }
Depends { name: "Qt.core" }
Depends { name: "Android.ndk" }
Depends { name: "Android.sdk" }
Rule {
inputsFromDependencies: "installable"
Artifact {
filePath: input.fileName+".json"
fileTags: "prepared2deploy"
}
prepare: {
var cmd = new JavaScriptCommand();
cmd.description = "prepare for androidDeployQt";
cmd.highlight = "install";
cmd.sourceCode = function() {
var outputFile = new TextFile(output.filePath, TextFile.WriteOnly);
outputFile.writeLine("{");
outputFile.writeLine(" \"qt\": \"" + product.Qt.core.binPath.replace(/\/bin$/,"") + "\",");
outputFile.writeLine(" \"sdk\": \"" + product.Android.sdk.sdkDir + "\",");
outputFile.writeLine(" \"sdkBuildToolsRevision\": \"" + product.Android.sdk.buildToolsVersion + "\",");
var ndkDir = product.Android.ndk.ndkDir.replace(/\\/g,"/"); //why sdk ndk get wrong slashes?
outputFile.writeLine(" \"ndk\": \""+ndkDir+"\",");
var toolchain = product.cpp.toolchainPrefix.replace(/-$/,"");
outputFile.writeLine(" \"toolchain-prefix\": \"" + toolchain + "\",");
outputFile.writeLine(" \"tool-prefix\": \"" + toolchain + "\",");
outputFile.writeLine(" \"toolchain-version\": \"4.9\","); //how I can get it ???
outputFile.writeLine(" \"ndk-host\": \"windows-x86_64\","); //how I can get it ???
var abi = product.Android.ndk.abi
outputFile.writeLine(" \"target-architecture\": \""+abi+"\",");
outputFile.writeLine(" \"stdcpp-path\": \""+ndkDir+"/sources/cxx-stl/gnu-libstdc++/4.9/libs/" + //how I can get it ???
abi+"/lib"+product.Android.ndk.appStl+".so\",");
outputFile.writeLine(" \"application-binary\": \""+ input.filePath+"\"");
outputFile.writeLine("}");
outputFile.close();
}
return cmd;
}
}
}
//Deployer
Product {
name: "AndroidDeployQt"
Depends { name: "helloworld" }
id: androidDeployQt
type: "androidDeployQt"
Depends {name: "Qt.core" }
Rule {
inputsFromDependencies: "prepared2deploy"
alwaysRun: true
Artifact {
filePath: "log.txt"
fileTags: "androidDeployQt"
}
prepare: {
var cmd = new JavaScriptCommand();
cmd.description = "androidDeployQt";
cmd.highlight = "install";
cmd.sourceCode = function() {
var logFile = new TextFile(output.filePath, TextFile.WriteOnly);
logFile.writeLine(input.fileName);
var productName = input.fileName.replace(/.so.json$/,"").replace(/^(lib)/,"");
var androidDeployProcess = new Process();
var exitCode = androidDeployProcess.exec(product.Qt.core.binPath+"/androiddeployqt.exe",
[
"--input", input.filePath,
"--output", project.buildDirectory+"/install-root/"+productName,
"--android-platform", "android-25", //???
"--gradle"
])
if (exitCode) {
console.error("Error at androidDeployProcess. Error code: "+exitCode);
console.error(androidDeployProcess.readStdErr());
console.error("FULL_LOG: ");
console.error(androidDeployProcess.readStdOut());
}
logFile.close();
}
return cmd;
}
}
}
}

QBS 1.13, released on February this year (2019) makes deploying an Android application as simple as with qmake. In practice, you don't need to do anything special. For example, I took the contactlist application from the Qt examples and added this QBS file:
import qbs 1.0
Project {
QtGuiApplication {
name: "contactlist"
install: true
files: [
"contactmodel.h",
"contactmodel.cpp",
"main.cpp",
]
Group {
files: [
"*.qml",
"designer/Backend/*.qml",
"designer/Backend/qmldir",
]
fileTags: ["qt.core.resource_data"]
}
Depends { name: "Qt.quick" }
}
}
As you can see, there's nothing specific to Android here. The only trick I'm using is to assign the tag qt.core.resource_data to the QML files in order to have them compiled as resource files — but it's not even required.
With qbs run the application will be run in your connected Android device.

Related

Add Custom values to build.gradle file via build-extras.gradle in Cordova

I had updated cordova-android version to 6.4.0 and before that I had 5.1.1 installed. Here the problem was that when updated to 6.4.0 version, while building the project I was getting error. So to overcome that issue I had to add the below code
configurations.all {
resolutionStrategy {
force 'com.android.support:support-v4:27.1.0'
}
}
Now the problem is every time I build the project I have to edit build.gradle file, which is generated while adding the platform to the project in Cordova. As this is not part of Source Control.
To overcome this I have used the solution from this post. Here I am adding the Javascript file and adding the hook in the config.xml
Java script file
var fs = require('fs');
var rootdir = process.argv[2];
var android_dir = rootdir + '/platforms/android';
var gradle_file = rootdir + '/build-extras.gradle';
var dest_gradle_file = android_dir + '/build-extras.gradle';
if (fs.existsSync(android_dir) && fs.existsSync(gradle_file)) {
console.log('Copy ' + gradle_file + ' to ' + android_dir);
fs.createReadStream(gradle_file).pipe(fs.createWriteStream (dest_gradle_file));
} else {
console.log(gradle_file + ' not found. Skipping');
}
Build-extras.gradle
ext.postBuildExtras = {
android {
configurations.all {
resolutionStrategy {
force 'com.android.support:support-v4:27.1.0'
}
}
}
}
Hooks in Config.xml
<platform name="android">
<hook src="scripts/buildGradleHook.js" type="before_build" />
</platform>
The hooks added is not reflecting in the generated android folder. That is build-extras.gradle file is not reflected in android folder.
I tried your solution and found that the vars declared to define the different paths are wrong.
I changed your hook code for this:
module.exports = function(ctx) {
var fs = ctx.requireCordovaModule('fs'),
path = ctx.requireCordovaModule('path'),
rootdir = ctx.opts.projectRoot,
android_dir = path.join(ctx.opts.projectRoot, 'platforms/android');
gradle_file = rootdir + '/build-extras.gradle';
dest_gradle_file = android_dir + '/build-extras.gradle';
/*
console.log("Before-Build Hook - rootdir", rootdir);
console.log("Before-Build Hook - android_dir", android_dir);
console.log("Before-Build Hook - gradle_file", gradle_file);
console.log("Before-Build Hook - dest_gradle_file", dest_gradle_file);
*/
if(!fs.existsSync(gradle_file)){
console.log(gradle_file + ' not found. Skipping');
return;
}else if(!fs.existsSync(android_dir)){
console.log(android_dir + ' not found. Skipping');
return;
}
console.log('Copy ' + gradle_file + ' to ' + android_dir);
fs.createReadStream(gradle_file).pipe(fs.createWriteStream(dest_gradle_file));
}
Also, in the Hook doc says it must be executable, so it needs to be wrapped by " module.exports = function(ctx) { }".

How to set Cordova Android project to use NDK

I am trying to do what this question/answer has
java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader
but in cordova instead.
android {
....
defaultConfig {
....
ndk {
abiFilters "armeabi", "armeabi-v7a", "x86", "mips"
}
}
}
This works if I edit build.gradle manually in platforms/android/build.gradle Using Cordova Android 6.4.0 (7.1 seems to break almost every plugin, including some cordova plugins such as cordova-network-information, so I've been unable to upgrade so far, and am looking for other solutions).
Editing manually isn't ideal, is there a way to set this automatically? Possibly with a hook or config.xml change?
Thanks
(edit)
Updated to 7.1 successfully, 64 bit still broken.
I was able to do this using the build-extras option, combined with Android 7.1
In your project root, create a file called build-extras.gradle
Put this inside it and save
android {
defaultConfig {
ndk {
abiFilters "armeabi", "armeabi-v7a", "x86", "mips"
}
}
}
Next, in your scripts folder, make a new script called update_build_gradle.js
Put this inside it and save
module.exports = function (context) {
if (context.opts.cordova.platforms.indexOf('android') < 0) {
return;
}
console.log("Starting gradle modifications");
const path = require('path');
const fs = require('fs');
const gradlePath = path.join(context.opts.projectRoot, 'platforms/android/app/build-extras.gradle');
const gradleExtraPath = path.join(context.opts.projectRoot, 'build-extras.gradle');
return new Promise(function (resolve, reject) {
fs.copyFile(gradleExtraPath, gradlePath, function (err) {
if (err) {
console.error("Failed to copy to " + gradlePath + " from " + gradleExtraPath);
reject(err);
} else {
console.log("Copied to " + gradlePath + " successfully");
resolve();
}
});
});
};
Lastly, open up your config.xml, find your <platform name="android"> tree and enter this new hook
<hook src="scripts/update_build_gradle.js" type="before_build" />
Note that the documentation here https://cordova.apache.org/docs/en/latest/guide/platforms/android/index.html#extending-buildgradle is wrong.
. This file must be placed in the android platform directory (/platforms/android), so it is recommended that you copy it over via a script attached to the before_build hook.
It actually needs to be in /platforms/android/app
-Edit
As of cordova version 9, you can't use requireCordovaModule anymore. But you can safely replace with just require.

Allow user to specify Gradle dependency for Cordova plugin

I am writing a Cordova plugin for android that has a Gradle dependency.
I want the developers to be able to specify what version of the dependency they want without editing the plugin directly.
Right now I have this in my plugin.xml:
<hook type="before_plugin_install" src="modifyPluginXML.js" />
<framework src="resources/custom.gradle" custom="true" type="gradleReference"/>
<framework src="$TO_BE_DEFINED_PACKAGE" type="gradleReference"/>
and I have a hook that takes the command line argument and replaces $TO_BE_DEFINED_PACKAGE in the plugin.xml with the package path/name provided in the argument.
Here is the modifyPluginXML.js hook:
module.exports = function(context) {
var Q = context.requireCordovaModule('q');
var deferral = new Q.defer();
var fs = require('fs'),
xml2js = require('xml2js'),
path = require('path'),
util = require('util');
var parser = new xml2js.Parser({explicitArray:false});
var cb = function(data){
console.log("plugin.xml updated");
deferral.resolve();
}
fs.readFile(__dirname + '/plugin.xml', function(err, data) {
parser.parseString(data, function (err, result) {
//console.log(util.inspect(result, false, null));
var externalDep = "";
for (var i = 0; i < process.argv.length;i++){
if(process.argv[i].indexOf('EXTERNAL_DEP') >= 0){
externalDep = process.argv[i].replace("EXTERNAL_DEP=", "");
console.log(externalDep);
}
}
result.plugin.platform.framework[1]['$'].src = externalDep;
var builder = new xml2js.Builder();
var xml = builder.buildObject(result);
var filepath = path.normalize(path.join(__dirname, '/plugin.xml'));
fs.writeFile(filepath, xml, cb);
});
});
return deferral.promise;
}
As of right now if you add the plugin with cordova plugin add plugin-name EXTERNAL_DEP=5.0 it will correctly replace the framework src in the plugin.xml with the source specified in the command line argument.
The problem I am running into is that cordova doesn't seem to care about the new plugin.xml. It still uses the old plugin.xml's framework tags.
In the generate build.gradle file I still see this:
dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
// SUB-PROJECT DEPENDENCIES START
debugCompile project(path: "CordovaLib", configuration: "debug")
releaseCompile project(path: "CordovaLib", configuration: "release")
compile "$TO_BE_DEFINED_PACKAGE"
// SUB-PROJECT DEPENDENCIES END
}
So even though the plugin.xml is getting updated correctly with the before_plugin_install hook, cordova uses the old value to generate the build.gradle file.
Does anyone have any suggestions, or different routes I could take?
You don't have to write in the plugin.xml because that is only read on plugin install.
If you want to edit the gradle references you can write on the platforms/android/build.gradle file, that is where the references from the plugin.xml are copied.
Anyway, I don't think is a good idea to allow users to choose the version to use, you, as developer of the plugin, should choose the version that you tested and you are sure that works fine, using a different version might do the plugin to not work correctly or not work at all.
Took #jcesarmobile advice and instead wrote to the build.gradle file. I first check the cmd line for any arguments than I check the config.xml for any variables set for the specific plugin. Here is the script I came up with.
module.exports = function (context) {
var Q = context.requireCordovaModule('q');
var deferral = new Q.defer();
var result = null;
var externalDep = null;
var fs = require('fs'),
xml2js = require('xml2js'),
path = require('path'),
util = require('util');
var gradleLocation = process.cwd() + "/platforms/android/build.gradle";
var parser = new xml2js.Parser({explicitArray: false});
function findPluginVarInConfig() {
fs.readFile(process.cwd() + '/config.xml', function (err, data) {
parser.parseString(data, function (err, result) {
//console.log(util.inspect(result, false, null));
for (var i = 0; i < result.widget.plugin.length; i++) {
if (result.widget.plugin[i]['$'].name == 'plugin-name') {
externalDep = result.widget.plugin[i].variable['$'].value;
}
}
if (externalDep) {
console.log("found " + externalDep + " in config.xml");
replaceGradleReference();
} else {
console.log("plugin-name could not find defined dependency defined in config.xml or cmd line args defaulting to 0.0.1");
externalDep = "0.0.1";
replaceGradleReference();
}
});
});
}
function findPluginVarInCmdArgs() {
for (var i = 0; i < process.argv.length; i++) {
if (process.argv[i].indexOf('EXTERNAL_DEP') >= 0) {
externalDep = process.argv[i].replace("EXTERNAL_DEP=", "");
}
}
if (externalDep) {
console.log("found " + externalDep + " in command line args");
replaceGradleReference();
} else {
findPluginVarInConfig();
}
}
function replaceGradleReference() {
fs.readFile(gradleLocation, 'utf8', function (err, data) {
if (err) {
return console.log(err);
}
var replaced = false;
if (data.indexOf('$INITIAL_PBR_SOURCE' >= 0)) {
result = data.replace('$INITIAL_PBR_SOURCE', function (text) {
replaced = true;
return externalDep
});
if (!replaced) {
console.log("FAILED TO ADD " + externalDep + " TO BUILD SCRIPT");
}
}
if (result) {
fs.writeFile(gradleLocation, result, 'utf8', function (err) {
if (err) {
console.log(err);
} else {
console.log("Succesfully added " + externalDep + " to buildscript");
}
deferral.resolve();
});
} else {
console.log("PBR external dependency is already added to buildscript");
}
});
}
findPluginVarInCmdArgs();
return deferral.promise;
};

Error: Hook failed with error code 1:

I am working on an IONIC Project,
while I am adding android platform, it gives me an error like this.
Error: Hook failed with error code 1: D:\IONIC Workspace\risecx-app\hooks\before_prepare\01_jshint.js
at C:\Users\HP\AppData\Roaming\npm\node_modules\cordova\node_modules\cordova-lib\src\hooks\HooksRunner.js:195:23
at _rejected (C:\Users\HP\AppData\Roaming\npm\node_modules\cordova\node_modules\q\q.js:797:24)
at C:\Users\HP\AppData\Roaming\npm\node_modules\cordova\node_modules\q\q.js:823:30
at Promise.when (C:\Users\HP\AppData\Roaming\npm\node_modules\cordova\node_modules\q\q.js:1035:31)
at Promise.promise.promiseDispatch (C:\Users\HP\AppData\Roaming\npm\node_modules\cordova\node_modules\q\q.js:741:41)
at C:\Users\HP\AppData\Roaming\npm\node_modules\cordova\node_modules\q\q.js:557:44
at flush (C:\Users\HP\AppData\Roaming\npm\node_modules\cordova\node_modules\q\q.js:108:17)
at doNTCallback0 (node.js:417:9)
at process._tickCallback (node.js:346:13)
my hooks\before_prepare\01_jshint.js file is like...
#!/usr/bin/env node
var fs = require('fs');
var path = require('path');
var jshint = require('jshint').JSHINT;
var async = require('async');
var foldersToProcess = [ 'js', 'js/services' ];
foldersToProcess.forEach(function(folder) {
processFiles("www/" + folder);
});
function processFiles(dir, callback) {
var errorCount = 0;
fs.readdir(dir, function(err, list) {
if (err) {
console.log('processFiles err: ' + err);
return;
}
async.eachSeries(list, function(file, innercallback) {
file = dir + '/' + file;
fs.stat(file, function(err, stat) {
if(!stat.isDirectory()) {
if(path.extname(file) === ".js") {
lintFile(file, function(hasError) {
if(hasError) {
errorCount++;
}
innercallback();
});
} else {
innercallback();
}
} else {
innercallback();
}
});
}, function(error) {
if(errorCount > 0) {
process.exit(1);
}
});
});
}
function lintFile(file, callback) {
console.log("Linting " + file);
fs.readFile(file, function(err, data) {
if(err) {
console.log('Error: ' + err);
return;
}
if(jshint(data.toString())) {
console.log('File ' + file + ' has no errors.');
console.log('-----------------------------------------');
callback(false);
} else {
console.log('Errors in file ' + file);
var out = jshint.data(),
errors = out.errors;
for(var j = 0; j < errors.length; j++) {
console.log(errors[j].line + ':' + errors[j].character + ' -> ' + errors[j].reason + ' -> ' +
errors[j].evidence);
}
console.log('-----------------------------------------');
callback(true);
}
});
}
I have worked with following commands....
npm install jshint --save
npm install q --save
npm install async
so any idea, where I am making a mistake....
Just delete your hooks directory and type this command.
ionic hooks add
It will solve your problem.
Please check your hooks directory have execute permissions.Give all permission to hooks and it's child folders
It also fails with error code 1 if you have a mistake in any of your files. Check to make sure it hasn't told you about any errors in you files before that.
In my case, I created an after_prepare hook to override cordova build process to use the actual versionCode specified in config.xml. I had the same error.
I solved it by adding "#!/usr/bin/env node" in my hook file(without quotes) at the top.That resolved everything. Hope it may help you .
I had a similar issue with a Moodle Mobile 2 build returning this error - I was missing the 'node_modules' folder from the main directory for the build to complete.
Hope this helps

Package name does not correspond to the file path - Gradle configuration

I'm trying migrating a normal Android Studio (IntelliJ) project to Gradle project recently. And currently I'm encounter a problem: IntelliJ gives me a warning on the beginning of every file says that my 'package name does not correspond to the file path'. e.g.
The first line of my some/prefixes/a/b/c/d/E.java is:
package a.b.c.d;
....
IntelliJ thinks the package name should be 'c.d' instead of 'a.b.c.d'. Because I set
SourceSets {
main.java.srcDirs = ["some/prefixes/a/b"]
}
in the module's build.gradle.
I know I could do the change below to make IntelliJ happy:
SourceSets {
main.java.srcDirs = ['some/prefixes']
}
But I can't do that because there're huge numbers of projects under 'some/prefixes' and I definitely don't want to introduce all of them into this module.
I used to add a packagePrefix="a.b" in my 'module.iml' in my original Android studio project and it works well:
https://www.jetbrains.com/idea/help/configuring-content-roots.html#d2814695e312
But I don't know how to accomplish similar fix after migrating to Gradle project.
I end up to write a task for gradle.
The task add the famous packagePrefix to the *.iml file.
This solution only work for intelliJ, I hope someone have a better solution.
task addPackagePrefix << {
println 'addPackagePrefix'
def imlFile = file(MODULE_NAME+".iml")
if (!imlFile.exists()) {
println 'no module find '
return
}
def parsedXml = (new XmlParser()).parse(imlFile)
if(parsedXml.component[1] && parsedXml.component[1].content){
parsedXml.component[1].content.findAll { Node node ->
node.sourceFolder.findAll { Node s ->
def url = s.attribute("url").toString()
if (url.endsWith(SRC_DIR)) {
println 'Node founded '
def attr = s.attribute('packagePrefix')
if (attr == null) {
// add prefix
println 'Adding package prefix'
s.attributes().put('packagePrefix', PACKAGE_NAME)
println s.toString()
// writing
def writer = new StringWriter()
new XmlNodePrinter(new PrintWriter(writer)).print(parsedXml)
imlFile.text = writer.toString()
}
}
}
}
}

Categories

Resources