gradle set a global property for all projects from gradle - gradle

I would like to set a property from within gradle (for example from settings.gradle) in a way similar to gradle.properties or -D. Is it possible?
The following code demonstrates what I am trying to do but its not working:
import org.gradle.internal.os.OperatingSystem
def getArchitecture() {
return System.getProperty("os.arch")
}
def getName() {
if(OperatingSystem.current().isMacOsX()) {
return "darwin"
} else if(OperatingSystem.current().isLinux()) {
return "linux"
} else {
throw Exception("The operating system you use is not supported.")
}
}
allprojects {
ext {
// this variable has to be visible from all the projects
// and .gradle files in the same way as if it was set
// from gradle.properties file
buildMachine = getName() + "_" + getArchitecture()
}
}
Edit
I would like this property to be defined in settings.gradle and be visible in all other .gradle file
Edit2
I have a number of build machines and I don't want to specify the value of the variable buildMachine (in settings and in other .gradle files) and I wouldn't like it to be passed with -P or with gradle.properties, because it seems its possible to figure out this property purely within gradle

You could simply set this property for the rootProject and in all other gradle scripts or subprojects, read from it..
For clarity sake..
in your root build.gradle
ext {
buildMachine = getName() + "_" + getArchitecture()
}
and in your subprojects..
ext {
println rootProject.buildMachine
}

Related

Gradle processResources expand on different projects modules

i'm trying to get a project.version so i wrote project2 gradle before codes
processResources {
def props = ['version': project.properties.get('version')]
filesMatching("**/MyConfiguration.kt") {
expand(props)
}
}
the MyConfiguration.kt file located project1 but the code return null
so i fixed codes like below
processResources {
def props = ['version': project.properties.get('version')]
expand(props)
}
then that codes return right value
maybe the problem is filesMatching input another module file?
i missed filesMatching input
need to setup .properties or .yaml (get project.properties this scope)

In a custom task in my buildSrc folder: How do I determine the file path of the subproject it is called in?

If I define a custom gradle task in buildSrc: How do I find out the relative path to the project from which the task is called?
In my buildSrc folder, I have a custom task that creates a Enum our of my message.properties file:
open class GenerateEnumTask : DefaultTask() {
#get:Input
open var inputFolder: String = "src/main/resources"
#get:Input
open val targetFilePath: String = "src/generated/kotlin/MessageCode.kt"
#get:OutputFile
val enumFile = File(targetFilePath)
#TaskAction
fun generateEnum() {
...
}
#Internal
override fun getDescription() = "This task uses downloaded property files and creates an enum kotlin file"
}
I then want to make sure the enum is generated before code compilation.
So I put this in the subproject "core", where I need the Enum.
build.gradle.kts:
tasks {
val generateEnumTask by registering(GenerateEnumTask::class)
withType<KotlinCompile> {
kotlinOptions.jvmTarget = Versions.jvmTarget
dependsOn(generateEnumTask)
dependsOn(formatKotlin)
doFirst{
println("compile kotlin in core project")
}
}
}
This does indeed work if I run gradle compileKotlin directly from the subfolder of the core project.
However, if I run the same command from the root project, the code searches for a src folder in the root directory.
Ah, the answer was simple: DefaultTask inherits from AbstractTask, which has a reference to the project that the task was called in (getProject)
This works nicely:
open var targetFolder: String = this.project.file("src/main/resources").absolutePath

Gradle how to change version number in source code

Java code:
public static String VERSION = "version_number";
Gradle build.gradle
version = '1.0'
How to set the version in java code from grade? The version must be in source code.
Is there a convenient way? A not-so-nice way:
copy the java file to another location, e.g. build/changed-source
change the version in the source, by replacing token
add the build/changed-source in main source set.
I'd do similar to Michael Easter but with these differences
Store generated sources separately from main sources (src/main/java and $buildDir/generated/java). This has the added benefit of not needing custom gitignore
Generate in a subdirectory of $buildDir so that clean task will delete the generated sources
Use a separate task for code generation with proper up-to-date & skip support
Use Copy.expand(Map) to do the token replacement
Since its directory based, everything in src/template/java will have tokens replaced. You can easily add more templates in future
src/template/java/com/foo/BuildInfo.java
package com.foo;
public class BuildInfo {
public static String getVersion() {
return "${version}";
}
}
build.gradle
task generateJava(type:Copy) {
def templateContext = [version: project.version]
inputs.properties templateContext // for gradle up-to-date check
from 'src/template/java'
into "$buildDir/generated/java"
expand templateContext
}
sourceSets.main.java.srcDir "$buildDir/generated/java" // add the extra source dir
compileJava.dependsOn generateJava // wire the generateJava task into the DAG
One method is to similar to your not-so-nice way, but slightly easier. Consider a file in templates/BuildInfo.java:
package __PACKAGE;
public class BuildInfo {
private static final String version = "__VERSION";
private static final String buildTimestamp = "__BUILD_TIMESTAMP";
public String toString() {
return "version : " + version + "\n" +
"build timestamp : " + buildTimestamp + "\n";
}
}
This file can then be "stamped" with information as first thing in the compileJava task and written to src/main/java/your/package/BuildInfo.java:
def targetPackage = 'net/codetojoy/util'
def targetPackageJava = 'net.codetojoy.util'
def appVersion = project.appVersion // from gradle.properties
def buildTimeStamp = new Date().toString()
compileJava {
doFirst {
ant.mkdir(dir: "${projectDir}/src/main/java/${targetPackage}")
def newBuildInfo = new File("${projectDir}/src/main/java/${targetPackage}/BuildInfo.java")
def templateBuildInfo = new File("${projectDir}/templates/TemplateBuildInfo.java")
newBuildInfo.withWriter { def writer ->
templateBuildInfo.eachLine { def line ->
def newLine = line.replace("__PACKAGE", targetPackageJava)
.replace("__VERSION", appVersion)
.replace("__BUILD_TIMESTAMP", buildTimeStamp)
writer.write(newLine + "\n");
}
}
}
}
A working example is provided here. Everything would be stored in source-control except the src/main/java/your/package/BuildInfo.java file. Note the version would be stored in gradle.properties.

How to access variant.outputFileName in Kotlin

We've been using a snippet like this one to rename the APK file generated by our Gradle build:
android.applicationVariants.all { variant ->
variant.outputs.all {
outputFileName = "${variant.name}-${variant.versionName}.apk"
}
}
Source: https://developer.android.com/studio/build/gradle-plugin-3-0-0-migration#variant_output
I am now in the process of converting my build.gradle to build.gradle.kts, i. e. to the Gradle Kotlin DSL. This is one of the last missing pieces: I can't figure out how to access outputFileName.
According to the API docs it does not even seem to exist:
BaseVariant.getOutputs() returns a DomainObjectCollection<BaseVariantOutput> which provides the all method used in the snippet.
BaseVariantOutput extends OutputFile which extends VariantOutput but none of these has an outputFileName or any getters or setters of a matching name.
So, I suspect there is some advanced Groovy magic at work to make this work - but how do I get there in Kotlin?
A little simplified version of #david.mihola answer:
android {
/**
* Notes Impl: Use DomainObjectCollection#all
*/
applicationVariants.all {
val variant = this
variant.outputs
.map { it as com.android.build.gradle.internal.api.BaseVariantOutputImpl }
.forEach { output ->
val outputFileName = "YourAppName - ${variant.baseName} - ${variant.versionName} ${variant.versionCode}.apk"
println("OutputFileName: $outputFileName")
output.outputFileName = outputFileName
}
}
}
Browsing through the source code of the Android Gradle plugin, I think I found the answer - here we go:
We are actually dealing with objects of type BaseVariantOutputImpl and this class does have both these methods:
public String getOutputFileName() {
return apkData.getOutputFileName();
}
public void setOutputFileName(String outputFileName) {
if (new File(outputFileName).isAbsolute()) {
throw new GradleException("Absolute path are not supported when setting " +
"an output file name");
}
apkData.setOutputFileName(outputFileName);
}
Using this knowledge we can now:
import com.android.build.gradle.internal.api.BaseVariantOutputImpl
and then cast our target objects like so:
applicationVariants.all(object : Action<ApplicationVariant> {
override fun execute(variant: ApplicationVariant) {
println("variant: ${variant}")
variant.outputs.all(object : Action<BaseVariantOutput> {
override fun execute(output: BaseVariantOutput) {
val outputImpl = output as BaseVariantOutputImpl
val fileName = output.outputFileName
.replace("-release", "-release-v${defaultConfig.versionName}-vc${defaultConfig.versionCode}-$gitHash")
.replace("-debug", "-debug-v${defaultConfig.versionName}-vc${defaultConfig.versionCode}-$gitHash")
println("output file name: ${fileName}")
outputImpl.outputFileName = fileName
}
})
}
})
So, I guess: Yes, there is some Groovy magic at work, namely that Groovy's dynamic type system allows you to just access getOutputFileName and setOutputFileName (by way of the abbreviated outputImpl.outputFileName syntax, as in Kotlin) from your code, hoping they will be there at runtime, even if the compile time interfaces that you know about don't have them.
Shorter version using lambdas:
applicationVariants.all{
outputs.all {
if(name.contains("release"))
(this as BaseVariantOutputImpl).outputFileName = "../../apk/$name-$versionName.apk"
}
}
This will place APK into app/apk folder with name made of variant name and version code.
You can change the format of filename as you wish.
Important: it must be done only on release builds, because ".." in path corrupts debug build process with strange errors.
For libraryVariants it is possible to change output file name without accessing internal api:
libraryVariants.all {
outputs.all {
packageLibraryProvider {
archiveFileName.set("yourlibrary-${buildType.name}.aar")
}
}
}
For Kotlin KTS.
NOTE: This is considered a temporal soluciĆ³n, until a proper way to do it in KTS is released by Android team.
Working in AGP v7.1.2 it might work also in lower versions of AGP.
:app build.gradle
android {
// ...
this.buildOutputs.all {
val variantOutputImpl = this as com.android.build.gradle.internal.api.BaseVariantOutputImpl
val variantName: String = variantOutputImpl.name
val outputFileName = "custom-name-${variantName}.apk"
variantOutputImpl.outputFileName = outputFileName
}
}

Creating a closure in ext

I am implementing the texturePacker task given in LibGDX's TexturePacker with gradle.
project.ext {
// ...
texturePacker = ["assets", "../android/assets", "texture"]
}
import com.badlogic.gdx.tools.texturepacker.TexturePacker
task texturePacker << {
if (project.ext.has('texturePacker')) {
logger.info "Calling TexturePacker: "+ texturePacker
TexturePacker.process(texturePacker[0], texturePacker[1], texturePacker[2])
}
}
I got it working with the suggested modifications for the classpath and added extension variable. Now I want to modify the textPacker extension variable to be a closure (Is that the right terminology?) with descriptive member names rather than an array. I tried doing this:
project.ext {
// ...
texturePacker {
inputDir = "assets"
outputDir = "../android/assets"
packFileName = "texture"
}
}
This gives the following error:
Error:Could not find method texturePacker() for arguments [build_4dusyb6n0t7j9dfuws8cc2jlu$_run_closure1$_closure7#6305684e] on project ':desktop' of type org.gradle.api.Project.
I am very new to gradle and groovy, so I have no idea what this error means. More importantly, what is the correct way to do what I want?
I suppose, closure is not the thing you need, since it's used not to store variables, but to store some executable code. By the way, if need to store it, you have to add = as follows:
project.ext {
texturePacker = {
inputDir = "assets"
outputDir = "../android/assets"
packFileName = "texture"
}
}
Anyway, if need to store variables within texturePacker variable, you rather have to use a Map type, then a Closure. This could be done like this:
project.ext {
texturePacker = [
inputDir : "assets",
outputDir : "../android/assets",
packFileName : "texture"
]
}
And then you can access this variable just by names, as:
println texturePacker.inputDir
Or, I think you can also go for implementing your own task with those properties. You can use DefaultTask which is a standard implementation of a regular task (and I'm sure it'd be enough for you);
class TexturePacker extends DefaultTask {
String inputDir; // a property - not a field!
String outputDir; // a property - not a field!
...
#TaskAction
void doSth(){
// do sth with properties above - that will be called automatically by gradle as a task-execution
}
}
task packer (type:TexturePacker) {
inputDir '<your-input-dir>'
outputDir '<your-output-dir>'
}
Syntax might not be super correct, but I think you get the idea.

Resources