Edit property based on library variant gradle android - android-ndk

I have an android project with native part in it. I would like to build my native code with different flags, depending on buildType and productFlavor, for instance:
android {
project.ext.buildFlags = ['-j16', 'all']
buildTypes {
debug { project.ext.buildFlags.add('NDK_DEBUG=1')}
}
productFlavors {
pretty {project.ext.buildFlags.add('PRETTY')}
ugly {project.ext.buildFlags.add('UGLY')}
}
task buildNativeCode(type: Exec) {
commandLine 'ndk-build', project.ext.buildFlags
}
tasks.withType(JavaCompile) {
compileTask -> compileTask.dependsOn buildNative
}
}
So, for uglyDebug I would like to build my native library with 'UGLY', and 'NDK_DEBUG' flags, prettyDebug with 'PRETTY' and 'NDK_DEBUG' flags and so on. However, they are always added, regardless of the chosen configuration. From what I gather, this is done during project evaluation and I don't know how to set it up properly. For now I ended up creating many buildNativeXXX tasks that have pretty much the same content. I would love to avoid code repetition.

You can use the following commands to modify any tasks afterwards:
afterEvaluate {
productFlavors.each { productFlavorName ->
buildTypes.each { buildTypeName ->
project.ext.buildFlags.add('NDK_DEBUG=1')
if (productFlavorName.equals("pretty")) {
//enter code here
project.ext.buildFlags.add('PRETTY')
} else {
project.ext.buildFlags.add('UGLY')
}
}
}
}
Now you have full control of how gradle adds the buildFlags for you. :)
If you want to use the buildConfig variables, you can set them using:
applicationVariants.all { variant ->
variant.buildConfigField "String", "COMMIT_HASH", "HASH123456789"
}

Related

Android Studio Gradle: how to get variant/flavour in custom assets processor task

I have simple assets processing gradle task prepare_assets in Android Studio 3.1.3. It is linked to preBuild task:
preBuild.dependsOn(prepare_assets)
Now I have several flavors for different resolution versions and I want to let prepare_assets know what assets to process. Maybe I don't get the idea of Gradle, but I can't understand how to achieve this: I tried to set variable in config phase
applicationVariants.all { variant ->
buildType = variant.buildType.name // sets the current build type
}
but when I read variable in my task it always the same.
In flavours section declaration:
all { flavor ->
task("${flavor.name.capitalize()}_prepare_assets") {
println "*** conf TEST for ${flavor.name.capitalize()}***"
doLast {
println "*** action TEST ${flavor.name.capitalize()}***"
if (flavor.name.equals("fullhd"))
{
//WARNING: to call copy, javaexec closures here use project.copy and project.javaexec!
}
else
{
...
}
}
}
}
In the bottom of file adding dependenses for runtime created android tasks for all flavors:
tasks.whenTaskAdded { theTask ->
if (theTask.class.name.contains("AppPreBuildTask_Decorated"))
{
for (Iterator iterator = android.productFlavors.iterator(); iterator.hasNext();) {
String flv_name = iterator.next().name.capitalize();
if (theTask.name.contains(flv_name+"Debug") || theTask.name.contains(flv_name+"Release"))
theTask.dependsOn "${flv_name}_prepare_assets";
}
}
}

How to properly add `mock` package to Android Studio project?

I want Android Studio to recognize my mock folder by putting mock at the end of the package name while in the Android pane.
How can I achieve this?
I've tried different combinations of:
right-clicking -> new package
right-clicking -> new Java Folder
declaring sourceSets in Gradle like so:
sourceSets {
mock {
java.srcDir 'src/mock'
}
}
add Mock as a flavor
productFlavors {
mock {
applicationIdSuffix = ".mock"
dimension "default"
}
prod {
dimension "default"
}
}
// Remove mockRelease as it's not needed.
android.variantFilter { variant ->
if (variant.buildType.name == 'release'
&& variant.flavors[0].name == 'mock') {
variant.ignore = true
}
}

Why does Android Studio not run my generateConfig but command line does

I'm trying to generate identical assets for each of the subprojects in a project. Since exactly the same thing has to happen for each subproject, I'm trying to avoid having to add the generation code to each subproject.
What I've got so far is:
// in my outer build.gradle
allprojects { proj ->
gradle.projectsEvaluated {
def configTemplate = project.file('src/main/templates/configuration.json')
def android = proj.getProperties()['android']
if (configTemplate.exists() && android != null) {
task generateConfigFiles(type: ConfigureTask) {
template configTemplate
environments proj.rootProject.file('scripts/environments.json')
environmentName host
destination new File(project.buildDir, 'templates/configuration.json')
}
android.applicationVariants.all { variant ->
variant.mergeAssets.dependsOn generateConfigFiles
}
}
}
}
// each application build.gradle
android {
sourceSets {
main {
assets.srcDir 'build/templates'
}
}
}
This seems to work exactly as expected when I run gradle from the command line ./gradlew mergeQaDebugAssets runs the generateConfigFiles task before running mergeQaDebugAssets
If I attempt to run this from Android Studio using the gradle window, it never runs generateConfigFiles
I've also gotten it to work by just including the projectsEvaluated step in each of the project build.gradle files, but, as noted, I'm trying to avoid the code duplication and maintenance headaches that brings.

Gradle plugin best practices for tasks that depend on extension objects

I would like feedback on the best practices for defining plugin tasks that depend on external state (i.e. defined in the build.gradle that referenced the plugin). I'm using extension objects and closures to defer accessing those settings until they're needed and available. I'm also interested in sharing state between tasks, e.g. configuring the outputs of one task to be the inputs of another.
The code uses "project.afterEvaluate" to define the tasks when the required settings have been configured through the extension object. This seems more complex than should be needed. If I move the code out of the "afterEvaluate", it gets compileFlag == null which isn't the external setting. If the code is changed again to use the << or doLast syntax, then it will get the external flag... but then it fails to work with type:Exec and other similarly helpful types.
I feel that I'm fighting Gradle in some ways, which means I don't understand better how to work well with it. The following is a simplified pseudo-code of what I'm using. This works but I'm looking to see if this can be simplified, or indeed what the best practices are. Also, the exception shouldn't be thrown unless the tasks are being executed.
apply plugin: MyPlugin
class MyPluginExtension {
String compileFlag = null
}
class MyPlugin implements Plugin<Project> {
void apply(Project project) {
project.extensions.create("myPluginConfig", MyPluginExtension)
project.afterEvaluate {
// Closure delays getting and checking flag until strictly needed
def compileFlag = {
if (project.myPluginConfig.compileFlag == null) {
throw new InvalidUserDataException(
"Must set compileFlag: myPluginConfig { compileFlag = '-flag' }")
}
return project.myPluginConfig.compileFlag
}
// Inputs for translateTask
def javaInputs = {
project.files(project.fileTree(
dir: project.projectDir, includes: ['**/*.java']))
}
// This is the output of the first task and input to the second
def translatedOutputs = {
project.files(javaInputs().collect { file ->
return file.path.replace('src/', 'build/dir/')
})
}
// Translates all java files into 'translatedOutputs'
project.tasks.create(name: 'translateTask', type:Exec) {
inputs.files javaInputs()
outputs.files translatedOutputs()
executable '/bin/echo'
inputs.files.each { file ->
args file.path
}
}
// Compiles 'translatedOutputs' to binary
project.tasks.create(name: 'compileTask', type:Exec, dependsOn: 'translateTask') {
inputs.files translatedOutputs()
outputs.file project.file(project.buildDir.path + '/compiledBinary')
executable '/bin/echo'
args compileFlag()
translatedOutputs().each { file ->
args file.path
}
}
}
}
}
I'd look at this problem another way. It seems like what you want to put in your extension is really owned by each of your tasks. If you had something that was a "global" plugin configuration option, would it be treated as an input necessarily?
Another way of doing this would have been to use your own SourceSets and wire those into your custom tasks. That's not quite easy enough yet, IMO. We're still pulling together the JVM and native representations of sources.
I'd recommend extracting your Exec tasks as custom tasks with a #TaskAction that does the heavy lifting (even if it just calls project.exec {}). You can then annotate your inputs with #Input, #InputFiles, etc and your outputs with #OutputFiles, #OutputDirectory, etc. Those annotations will help auto-wire your dependencies and inputs/outputs (I think that's where some of the fighting is coming from).
Another thing that you're missing is if the compileFlag effects the final output, you'd want to detect changes to it and force a rebuild (but not a re-translate).
I simplified the body of the plugin class by using the Groovy .with method.
I'm not completely happy with this (I think the translatedFiles could be done differently), but I hope it shows you some of the best practices. I made this a working example (as long as you have a src/something.java) by implementing the translate as a copy/rename and the compile as something that just creates an 'executable' file (contents is just the list of the inputs). I've also left your extension class in place to demonstrate the "global" plug-in config. Also take a look at what happens with compileFlag is not set (I wish the error was a little better).
The translateTask isn't going to be incremental (although, I think you could probably figure out a way to do that). So you'd probably need to delete the output directory each time. I wouldn't mix other output into that directory if you want to keep that simple.
HTH
apply plugin: 'base'
apply plugin: MyPlugin
class MyTranslateTask extends DefaultTask {
#InputFiles FileCollection srcFiles
#OutputDirectory File translatedDir
#TaskAction
public void translate() {
// println "toolhome is ${project.myPluginConfig.toolHome}"
// translate java files by renaming them
project.copy {
includeEmptyDirs = false
from(srcFiles)
into(translatedDir)
rename '(.+).java', '$1.m'
}
}
}
class MyCompileTask extends DefaultTask {
#Input String compileFlag
#InputFiles FileCollection translatedFiles
#OutputDirectory File outputDir
#TaskAction
public void compile() {
// write inputs to the executable file
project.file("$outputDir/executable") << "${project.myPluginConfig.toolHome} $compileFlag ${translatedFiles.collect { it.path }}"
}
}
class MyPluginExtension {
File toolHome = new File("/some/sane/default")
}
class MyPlugin implements Plugin<Project> {
void apply(Project project) {
project.with {
extensions.create("myPluginConfig", MyPluginExtension)
tasks.create(name: 'translateTask', type: MyTranslateTask) {
description = "Translates all java files into translatedDir"
srcFiles = fileTree(dir: projectDir, includes: [ '**/*.java' ])
translatedDir = file("${buildDir}/dir")
}
tasks.create(name: 'compileTask', type: MyCompileTask) {
description = "Compiles translated files into outputDir"
translatedFiles = fileTree(tasks.translateTask.outputs.files.singleFile) {
includes [ '**/*.m' ]
builtBy tasks.translateTask
}
outputDir = file("${buildDir}/compiledBinary")
}
}
}
}
myPluginConfig {
toolHome = file("/some/custom/path")
}
compileTask {
compileFlag = '-flag'
}

Apply configuration to specific projects in gradle build script

I have a gradle project that has java applications as well as android applications.
root/
build.gradle
settings.gradle
java1/
java2/
android1/
android2/
java3/
etc.
What is the best practice for structuring my build script? I am a total gradle novice and am migrating the project from maven to gradle.
I wanted to do something instead of
configure(subprojects) {}
to apply plugins and other specific things.
such as
configure(java1, java2, java3) { // java specifics }
configure(android1, android2) { // android specifics }
I am probably approaching this from the wrong way.
More explicitly I need to apply the plugin java only for the java projects and the android plugin for the android projects.
configure(subprojects.findAll {it.name == "java1" || it.name == "java2"}) {
Under the filtering section in the guide
Hope this helps someone else out.
There are multiple ways, depending on what you want... Some examples:
// Configures just project java1
project(":java1") { ... }
// Configures projects java1 and java2
["java1","java2"].each { name ->
project(":$name") { ... }
}
You can use normal groovy to find/iterate over all the projects.
Another option:
configure([ project(':sub1'), project(':sub2'), ... ]) {
...
}
The shortest and easiest option:
configure(subprojects.findAll()) {
if (it.name.equals('MY_PROJECT')) {
...
} else {
...
}
}
Another approach...
In the settings.gradle you do define your projects like this:
gradle.ext.javaProjects=[]
gradle.ext.androidProjects=[]
javaProject('java1')
javaProject('java2')
javaProject('java3')
androidProject('android1')
androidProject('android2')
def javaProject(String name) {
gradle.ext.javaProjects.add(name)
include ":$name"
}
def androidProject(String name) {
gradle.ext.androidProjects.add(name)
include ":$name"
}
Now you can reference those in your root build.gradle:
def javaProjects = subprojects.findAll {gradle.ext.javaProjects.contains(it.name)};
def androidProjects = subprojects.findAll {gradle.ext.javaProjects.contains(it.name)};
configure(javaProjects) {
...
}
configure(androidProjects) {
...
}
Maybe thats overkill... but i usually have the project definition method in my settings.gradle anyway. E.g. if you want to put all java projects under a java folder you could define things like this:
def javaProject(String name) {
gradle.ext.javaProjects.add(name)
include ":$name"
project(":$name").projectDir = file('java/'+ name)
}

Resources