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.
Related
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
}
}
I am trying to create a Grails plugin that creates a custom Gradle Task which can be depended on by bootRun. I would like to do something like this:
#CompileStatic
static void configureProcessConfig(Project project) {
TaskContainer taskContainer = project.tasks
if(taskContainer.findByName('processConfig') == null) {
taskContainer.create("processConfig") {
List<File> testResources = [project.file("src/test/resources")]
for (t in testResources) {
if (t.name.contains('.properties') || t.name.contains('.groovy')) {
Path originFile = t.toPath()
Path destFile = Paths.get('build/classes/main/' + t.name)
Files.copy(originFile, destFile)
}
}
}
def processConfigTask = taskContainer.findByName('processConfig')
taskContainer.findByName("bootRun")?.dependsOn(processConfigTask)
}
}
However, I can't seem to get it to work in my xxxGrailsPlugin.groovy file. I don't know where to get the Project file to call this. It doesn't create the task. I am happy to do something different, but I can't figure out how to do it. I would prefer not to write to every build.gradle file where this plugin is used, but if that's the best option, I guess I will.
Any help is appreciated. Thanks!
EDIT : I rephrased my question in taken the propositon of David M. Karr into account.
I am writing a gradle plugin. This plugin is launching a task extending GradleBuild. The external gradle build file needs some info as parameters. These parameters are given in project extension.
Plugin code
class MyPlugin implements Plugin<Project> {
def mExt
void apply(Project project) {
mExt = project.extensions.create('myext',MyExt)
project.task('myTask', type:GradleBuild){
def param = new StartParameter()
param.setProjectProperties([target:getTarget()])
// Problem here
startParameter = param
buildFile = getMyBuildPath()
tasks = [
'build',
'generateDebugJavadocJar'
]
}
}
def getMyBuildPath(){
...
}
// Problem here
def getTarget(){
return {mExt.target}
}
}
class MyExt {
def String target = "uninitialised"
}
Gradle build file :
apply plugin : 'com.example.myplugin'
ext{
target = "myTarget"
}
External Gradle build file :
task build(){
println project.target
}
If I put a closure in getTarget(), println project.target shows the closure and not the string.
If I don't put the closure :
// Problem here
def getTarget(){
return mExt.target
}
Then I got "uninitialised" instead of "myTarget".
How can I get the value of myext.target here ?
I am using gradle 2.3
Try this:
Define an instance variable called "myext", of type "MyExt".
In the "apply" method, do this:
myext = project.extensions.create('myext',MyExt)
In the "getTarget" method, return "myext.target".
I have succeeded in getting what I wanted to in using project.afterEvaluate method. Thanks to this question
1) In gradle build task, startParameter.projectProperties is waiting for a map, not a closure. So the idea to put a closure for a lazy definition cannot work.
2) If I put directly in my plugin a reference to mExt.target or project.myext.target, then the initial value is set. The value put in my build.gradle file is not used because the plugin is already evaluated.
3) project.afterEvaluate() solve my problem. The closure ends configuring myTask in using the project's extension.
void apply(Project project) {
project.extensions.create('myext',MyExt)
project.task('myTask', type:GradleBuild){
buildFile = getMyBuildPath()
tasks = [
'build',
'generateDebugJavadocJar'
]
}
project.afterEvaluate { proj ->
proj.myTask.startParameter.projectProperties = [target:proj.myext.target]
}
}
I'm new to Gradle and having troubles creating a custom Task that can correctly determine when it's up to date. I'd like to set the member decorated with #OutputFile in the custom task's constructor based off the other input variables, but they all set to the defaults in the constructor. I do see the values set correctly in the TaskAction.
I'm sure I'm missing something simple but I've combed the docs, tried to dig up examples and I'm not finding much.
Here is a simplified version of the build script:
apply plugin: 'java'
sourceCompatibility = 1.5
version = '1.0'
class TestTask extends DefaultTask {
#Input
String [] className = []
#Input
String outputDir = ""
#OutputFile
File targetFile
TestTask() {
println("Constructor")
println("outputDir: " + outputDir)
// I'd like to set the targetFile here, based on the outputDir and className
}
#TaskAction
def action() {
println("Action")
println("outputDir: " + outputDir )
}
}
task runTest_Opus(type:TestTask) {
className = ['class.name.here']
outputDir = 'jni/outputDir/'
}
task runAll {
dependsOn tasks.withType(TestTask)
}
build.dependsOn.add("runAll")
If the value of targetFile is derived then use a getter method instead of an instance field.
#OutputFile
File getTargetFile() {
// code to resolve output file based on other inputs
}
Depending on your exact needs, #OutputDirectory File outputDir could be an easy solution. Derived defaults are more commonly set by a plugin. In any case, their computation needs to be deferred in one way or another (e.g. project.afterEvaluate { ... }).
Hi i'm trying to dynamically create and configure task based on plugin extension values, problem seems to be evaluation order, is there any way to work around it?
apply plugin: SetupPlugin
setup {
destDir = 'some directory set per project in build.gradle'
sourceFile = 'some file set per project in build.gradle'
}
class PluginExtension {
String destDir
String sourceFile
}
class SetupPlugin implements Plugin<Project> {
def placeholders
void apply(Project project) {
project.extensions.create("setup", PluginExtension)
project.task ("setupEnvironment", type: Copy) {
doFirst() {
//computes placeholders <-- project.setup has value here
}
into (project.setup.destDir){ //<-- project.setup is null
from project.setup.sourceFile
}
filter(org.apache.tools.ant.filters.ReplaceTokens, tokens: placeholders)
}
}
}
Moving this into the doFirst block can cause some sideeffects, as the gradle up to date task might run into problems as reconfigure the parameters of your copy task at execution time instead of configuration time. A quickfix which should do the trick is to defer the evaluation by using closures:
...
void apply(Project project) {
project.extensions.create("setup", PluginExtension)
project.task ("setupEnvironment", type: Copy) {
doFirst() {
//computes placeholders <-- project.setup has value here
}
into {project.setup.destDir}
from { project.setup.sourceFile }
filter(org.apache.tools.ant.filters.ReplaceTokens, tokens: placeholders)
}
}
...
hope that helped!
cheers,
René
It is because apply is called before applying setup settings.
It works for doFirst because it called after apply during build.
Maybe you may wrap your copy into doLast?
It turns out I asked a question that I think is very similar to this one. I may be missing a subtle difference, but in case it helps: here it is.