Best way to add a code generation step in the build process? - android

I have a python script that modifies a part of a java class (add static fields) according to another file.
What is the best way to add it in the Gradle build process (My script requires pyton3 (so it would be better to tell the user if he doesn't have it) and need to be executed before the java compilation) ?

I'm not certain what the best way to run python from gradle is, but one candidate is to use the exec task:
task runPython(type:Exec) {
workingDir 'path_to_script'
commandLine /*'cmd', '/c'*/ 'python', 'my_script.py'
}
You might have to add cmd and /c on windows. You can also parse/assert on stdout for success conditions. See here for more about Exec.
As for ensuring that this task always runs before compileJava, you have to add this task as a dependsOn for the compileJava task:
compileJava.dependsOn runPython
alternate syntax:
compileJava{
dependsOn runPython
}
When compileJava dependsOn runPython, runPython is executed first everytime compileJava is invoked.

Related

Gradle Custom Task With Input

I have to create a gradle custom task in KOTLIN with different args as input.
So based on the args, the custom task should run other tasks.
e.g: I want to run:
./gradlew ci type=release distribution=true version=1.2.2
OR
./gradlew ci type=debug distribution=true version=1.2.2
This command should run tasks: clean, assembleRelease OR assembleDebug (based on type param) and also another task to distribute the artifact (already have this one) if the distribute param is true.
Question 1: Is there any way to create a custom task that runs other tasks based on external params?
Question 2: Is there any way to inject the args? (the above commands are not valid I think)
You can pass properties with -P command and here is an example.
task passP() {
if (customProp.equals("myProp")) {
println customProp
}
}
this will only print if you execute the following command Gradle -PcustomProp=myProp passP
Now that you can pass parameters, You can clean, assembleRelease OR assembleDebug according to the passed parameters.
There Are two ways you can achieve that.
First way :
Make a gradle custome task to execute another gradle command which do not look neat. but the code will look like this
task passP(type: Exec) {
commandLine("cmd", "/c")
if (customProp.equals("clean")) {
args "gradle clean"
}
}
This will execute a normal clean if you passed clean as a parameter.
The second way :
You would be using finializeBy keyword
You can call the 3 tasks according to the passed parameters.
The code will be something like this (not tested) :
task passP() {
if (customProp.equals("clean")) {
tasks.named("clean") { finalizedBy("passP") }
if (customProp.equals("debug")) {
tasks.named("assembleDebug ") { finalizedBy("passP") }
}
}
The first way work 100%, But am not sure about the second one, As am only used to use finalizedBy out side of the custom task scope.

How to integrate Flutter app's build process with Rust code? i.e. when building Flutter code, how to automatically build its Rust code?

I have a Flutter application that also has some Rust code, and they communicate via flutter_rust_bridge. There are already some tutorials talking about how to build such an app. However, they require me to manually trigger cargo build (or similar commands) and flutter run separately. You know, this is quite cumbersome, and if I forget to execute cargo, or that command fails, I run the risk of building a Flutter app with an old binary. Therefore, I wonder how I can automate that process. Thanks!
Android
Add the following to your build.gradle
[
new Tuple2('Debug', 'debug'),
new Tuple2('Profile', 'release'),
new Tuple2('Release', 'release')
].each {
def taskPostfix = it.first
def profileMode = it.second
tasks.whenTaskAdded { task ->
if (task.name == "javaPreCompile$taskPostfix") {
println "hello let ${task.name} dependsOn cargoBuild$taskPostfix"
task.dependsOn "cargoBuild$taskPostfix"
}
}
tasks.register("cargoBuild$taskPostfix", Exec) {
println "hello executing doLast for task cargoBuild$taskPostfix"
commandLine 'sh', '-c', "build-your-rust-code"
}
}
where build-your-rust-code is the command you use to build your Rust code. (For example, I use fastlane to wrap it (of course you can use others) and I put that in a rust folder, so I just call cd $projectDir/../rust/fastlane && bundle exec fastlane build_android profile:$profileMode.)
iOS
Method 1
The most manual way is as follows. Go to Build Phases, click + button and choose New Run Script Phase. Then enter the script you want to run to build your Rust code. (For example, for myself, I use Shell /bin/sh and call some fastlane commands which executes the actual commands.)
Method 2
A more automated approach is to use the CocoaPod files, which will automatically help you set up everything. For example, I add:
s.script_phase = {
:name => 'Compile Rust',
:script => your-script-to-compile-it,
:execution_position => :before_compile,
:shell_path => '/bin/sh'
}
(For example, my script is %q{env -i HOME="$HOME" PROJECT_DIR="$PROJECT_DIR" CONFIGURATION="$CONFIGURATION" /bin/bash -c 'source ~/.bashrc && cd $PROJECT_DIR/../.symlinks/plugins/vision_utils/rust/fastlane && LC_ALL= LANG=en_US.UTF-8 bundle exec fastlane build_ios profile:$CONFIGURATION'}. Notice that, at least in my case, I have to create a brand new shell. Otherwise the opencv in my Rust complains and fails to compile. But you may not have this problem if your Rust code does not contain c++ things.)
(Q&A style answer in order to help people who face the same situation as me, especially when not familiar with Android and iOS)

Declaring the inputs of a gradle task as multiple files

We have a gradle task that will automatically generate codes for us before building. See the following as an example,
task djinniTask(type: org.gradle.api.tasks.Exec) {
commandLine 'sh', './Djinni/run_djinni.sh'
}
assembleDebug.dependsOn djinniTask
Basically, the above run_djinni.sh is using a library djinni to generate JNI codes. The above works fine except that it will run this script every time we build even if we didn't update the script file, which is obviously not very efficient. We did a bit of research and found 17.9. Skipping tasks that are up-to-date. And as a result, the following works fine. It will skip this task if we didn't modify run_djinni.sh.
task transform {
ext.srcFile = file('./Djinni/run_djinni.sh')
ext.destDir = new File(buildDir, 'generated')
doLast {
commandLine 'sh', './Djinni/run_djinni.sh'
}
}
Now the problem is, the run_djinni.sh is not the only script file that we have. The project is big and we multiple scripts files like: run_foo_djinni.sh, run_bar_djinni.sh and etc. run_djinni.sh will call each of the other scripts. So is there a way to declare the inputs of a gradle task as multiple files, for example, in our case, every files that is under the Djinni folder?
Ok, according to gradle DSL you can define multiple inputs:
task transform {
inputs.files('file path', 'another file path')
}

How to change Gradle install tasks

I want to edit gradle task named installDebug. Where is the task (or script) located? Maybe this script is located in binary code and I'm not change that?
Really, I want run edit something option for adb.
Example: My task must contain:
Run adb like "adb connect 192.168.1.2:5555"
Run "debugInstall" gradles task, directly.
Do something, like - adb then open apk on my adb server..
What I should do:
Edit debugTask if possible?
Or edit build.grade and make own task script?
All the tasks are located in build.gradle script itself or in the plugin that is applied at the beginning of the script.
installDebug task is provided by as far as I remember android plugin. Every single task consists of actions that are executed sequentially. Here's the place to start.
You can extend a task adding action to the beginning of at the end of internal actions list.
So:
//this piece of code will run *adb connect* in the background
installDebug.doFirst {
def processBuilder = new ProcessBuilder(['adb', 'connnect', '192.168.1.2:5555'])
processBuilder.start()
}
installDebug.doLast {
//Do something, like - adb then open apk on my adb server..
}
Here, two actions were added to installDebug task. If you run gradle installDebug, first action will be run, then the task itself and finally the second action that is defined. That's all in general.
You can add a task to your build.gradle, and call it in command line.
This is what I have done :
task adbConnect(type: Exec) {
commandLine 'adb', 'connect', '192.168.200.92'
}
then I call gradle adbConnect connectedCheck, but you can use gradle adbConnect debugInstall

Android + gradle: injecting a task into build

I have a custom script which runs on the source (.java) and AndroidManifest.xml.
I'd like to execute this script as part of the gradle build process at the start of the assembleRelease task (maybe just after app:preBuild).
Any idea of how I can go about doing this?
I know I can do something like this to exec the script:
task DoStuff(type:Exec) {
workingDir 'path/to/script'
commandLine 'python3', 'do_stuff.py'
}
But I'm not sure where to put that, etc...
You can add a custom action to assembleRelease with doFirst method:
assembleRelease.doFirst {
//invoke python script but there's no access to `workingDir` or `commandLine`
//because it's an action not a task
}
or define dependency:
assembleRelease.dependsOn DoStuff
assembleRelease.mustRunAfter DoStuff //this might be redundant

Categories

Resources