Although I don't own a Android phone (but my wife does), I want to learn more about Android. The Android Labs website is a great source to start working with Android. It contains a lot of links and tutorials (labs) to start with Android.
Although I don't own a Android phone (but my wife does), I want to learn more about Android. The Android Labs website is a great source to start working with Android. It contains a lot of links and tutorials (labs) to start with Android.
Today when I started up my laptop the time was set back before 1 Jan. 2008 and my Wifi connection couldn't be restored. The best way to deal with this is to reset the System Management Controller (SMC). For my Macbook Pro I have to follow the following steps:
Gradle has plugins to provide functionality in a modularized way. One of the basic plugins is the base plugin. This plugin is not part of the public API of Gradle, so the functionality can change. In this post we look at what functionality the base plugin provides for Gradle version 0.9-rc1.
When we apply the base plugin to our project we get a couple of tasks we can use:
We get two configurations:
For each configuration we get a build and upload task:
And we get a task rule clean<taskname> that is capable of cleaning the ouput files of any task we create in our project.
Also the following properties are added to the project:
In the following sample we define a project with the base plugin and use the tasks, configurations and properties that are added by the plugin:
apply plugin: 'base'
version = '1.0'
archivesBaseName = 'sample'
distsDirName = 'dist'
libsDirName = 'projectlibs'
task simpleJar(type: Jar)
task simpleZip(type: Zip)
artifacts {
archives simpleJar
}
repositories {
flatDir name: 'localRepo', dirs: "$buildDir/repository"
}
uploadArchives {
repositories {
add project.repositories.localRepo
}
}
Let's run gradle with a couple of the tasks:
$ gradle clean assemble buildDefault uploadArchives cleanSimpleJar :clean :simpleJar :simpleZip :assemble :buildDefault :uploadArchives :cleanSimpleJar BUILD SUCCESSFUL Total time: 3.416 secs
If we look in the build directory we see what is created:
build
|
+- dist
| |
| +- sample-1.0.zip
|
+- projectlibs
|
+- repository
|
+- sample-1.0.jar
With the Gradle copy task we can define renaming rules for the files that are copied. We use the rename() method of the copy task to define the naming rules. We can use a closure where the filename is the argument of the closure. The name we return from the closure is the new name of copied file. Or we can define a regular expression and set the replacement value for the corresponding regular expression. We can use groups in the regular expression and use them in the replacement value like $<group>.
task copyFiles(type: Copy) {
from 'src/files'
into "$buildDir/files"
rename '(.*)-(.*).html', '$2/$1.html'
rename ~/(.*).template.(.*)/, '$1.$2'
rename { filename ->
filename.replace 'java', 'groovy'
}
}
Let's create some source files, so the renaming rules can be applied to them.
src/files/index-en.html:
<html>
<body>
<h1>Hello Gradle</h1>
</body>
</html>
src/files/index-nl_NL.html:
<html>
<body>
<h1>Hallo Gradle</h1>
</body>
</html>
src/files/sample.template.txt:
Sample file.
src/files/Sample.java:
public class Sample {
private String gradle = "Gradle";
}
We run $ gradle copyFiles and we get the following files in build/files:
nl_NL | +-- index.html en | +-- index.html Sample.groovy sample.txt
Gradle's copy task is very powerful and includes filtering capabilities. This means we can change the contents of the files that are copied before they reach their new destination. We use the filter() method to define a filter. The good news is we can reuse the Ant filtering classes from the org.apache.tools.ant.filters package. We define the filtering class and can pass parameters for the filter. Or we can pass a closure which is passed each line as an argument. Within the closure we must return the filtered line.
import org.apache.tools.ant.filters.*
task('filterCopy', type: Copy) {
from 'src/templates'
into buildDir
include '**/*.txt'
filter { line -> line.contains('Gradle') ? line : '' }
filter(ReplaceTokens, tokens: [author: 'mrhaki', gradleVersion: gradle.gradleVersion])
filter(ConcatFilter, prepend: file('src/include/header.txt'))
}
Now let's create a sample text file that will get filtered in src/templates/HelloGradle.txt:
This is just a simple text file. This line will not make it. We show filtering capabilities of Gradle copy. This file is written by @author@ with Gradle version @gradleVersion@.
And we create the file src/include/header.txt:
Include this header file. -------------------------
After we run $ gradle filterCopy we get the following contents for the file build/HelloGradle.txt:
Include this header file. ------------------------- We show filtering capabilities of Gradle copy in combination with the Ant filtering types. This file is written by mrhaki with Gradle version 0.9-rc-1.
With the copy task of Gradle we can copy files that are parsed by Groovy's SimpleTemplateEngine. This means we can expand properties in the source file and add Groovy code that is going to be executed. We must use the expand() method in the copy task where we can pass properties to be used in the source file.
version = 'DEMO'
group = 'com.mrhaki'
task copy(type: Copy) {
from 'src/templates'
into "$buildDir"
include 'projectinfo.html.template'
rename { file -> 'projectinfo.html' }
expand(project: project, title: 'ProjectInfo', generated: new Date())
}
We define the following source file in src/templates/projectinfo.html.template:
<html>
<head>
<title>${title}</title>
</head>
<body>
<h1>${project.name}</h1>
<ul>
<% project.properties.findAll { k,v -> v instanceof String }.each { key, value -> %>
<li>$key = $value</li>
<% } %>
</ul>
<hr />
<p>Generated on ${generated.format('dd-MM-yyyy')}</p>
</body>
</html>
When we run the copy task we get the following output:
To see which tasks are available for our build we can run Gradle with the command-line option -t or --tasks. Gradle outputs the available tasks from our build script. By default only the tasks which are dependencies on other tasks are shown. To see all tasks we must add the command-line option --all.
3.times { counter ->
task "lib$counter" {
description = "Build lib$counter"
if (counter > 0) {
dependsOn = ["lib${counter - 1}"]
}
}
}
task compile {
dependsOn {
project.tasks.findAll {
it.name.startsWith('lib')
}
}
description = "Compile sources"
}
$ gradle -q -t ------------------------------------------------------------ Root Project ------------------------------------------------------------ Tasks ----- :compile - Compile sources
$ gradle -q --tasks -all
------------------------------------------------------------
Root Project
------------------------------------------------------------
Tasks
-----
:compile - Compile sources
:lib0 - Build lib0
:lib1 - Build lib1
:lib2 - Build lib2
But if we add our tasks to a group, we get even more verbose output. Gradle will group the tasks together and without the --all option we get to see all tasks belonging to the group, even those that are dependency tasks. And with the --all option we see for each task on which tasks it depends on. So by setting the group property on the task we get much better output when we ask Gradle about the available tasks.
3.times { counter ->
task "lib$counter" {
description = "Build lib$counter"
if (counter > 0) {
dependsOn = ["lib${counter - 1}"]
}
}
}
task compile {
dependsOn {
project.tasks.findAll {
it.name.startsWith('lib')
}
}
description = "Compile sources"
}
tasks*.group = 'Compile'
$ gradle -q -t ------------------------------------------------------------ Root Project ------------------------------------------------------------ Compile tasks ------------- :compile - Compile sources :lib0 - Build lib0 :lib1 - Build lib1 :lib2 - Build lib2
$ gradle -q --tasks -all ------------------------------------------------------------ Root Project ------------------------------------------------------------ Compile tasks ------------- :compile - Compile sources [:lib0, :lib1, :lib2] :lib0 - Build lib0 :lib1 - Build lib1 [:lib0] :lib2 - Build lib2 [:lib1]
In Gradle we can create dependencies between tasks. But we can also exclude certain tasks from those dependencies. We use the command-line option -x or --exclude-task and specify the name of task we don't want to execute. Any dependencies of this task are also excluded from execution. Unless another task depends on the same task and is executed. Let's see how this works with an example:
task copySources << {
println 'Copy sources.'
}
task copyResources(dependsOn: copySources) << {
println 'Copy resources.'
}
task jar(dependsOn: [copySources, copyResources]) << {
println 'Create JAR.'
}
task deploy(dependsOn: [copySources, jar]) << {
println 'Deploy it.'
}
We execute the deploy task:
$ gradle -q deploy Copy sources. Copy resources. Create JAR. Deploy it.
Now we exclude the jar task. Notice how the copySources task is still executed because of the dependency in the deploy task:
$ gradle -q deploy -x jar Copy sources. Deploy it.
Gradle adds the task rule clean<Taskname> to our projects when we apply the base plugin. This task is able to remove any output files or directories we have defined for our task. For example we can assign an output file or directory to our task with the outputs property. Or we can use the @OutputFile and @OutputDirectories annotations for custom task classes. The clean<Taskname> rule can delete the output files or directories for the task with the name <Taskname> for us. We don't have to write the clean task ourselves we only have define the base plugin in our project. And Gradle will take care of the rest!.
apply plugin: 'base'
outputDir = file("$buildDir/generated-src")
outputFile = file("$buildDir/output.txt")
task generate << {
outputDir.mkdirs()
outputFile.write 'Generated by Gradle.'
}
generate.outputs.files outputFile
generate.outputs.dir outputDir
task showBuildDir << {
def files = buildDir.listFiles()
files.each {
print it.directory ? 'Dir: ' : 'File: '
println it.name
}
println "${files.size()} files in $buildDir.name"
}
We can first run the generate task and see the output file and directory.
$ gradle -q generate showBuildDir Dir: generated-src File: output.txt 2 files in build
Next we can run the task cleanGenerate, which is added to the project by Gradle, and see the output files are gone.
$ gradle -q cleanGenerate showBuildDir 0 files in build
The project version in a Gradle project is not limited to a String value, but can be any Object. We must implement the toString() method of the object so Gradle can use it for example in naming JAR files.
In the following build script we define a custom Version class. We create an instance of the Version class and assign it to the project version. In the task listJars we print out the project version and the name of the generated JAR file, which should contain our custom version number.
apply plugin: 'java'
version = new Version(major: 2, minor: 3, releaseType: 'beta', bugfix: 2)
task listJars << {
println "Project version: $project.version"
configurations.archives.allArtifactFiles.files.each {
println "Artifact: $it.name"
}
}
listJars.dependsOn 'assemble'
class Version {
int major
int minor
int bugfix
String releaseType
String toString() {
"$major.$minor-$releaseType${bugfix ?: ''}"
}
}
We can run our script and get the following output:
$ gradle -q listJars Project version: 2.3-beta2 Artifact: gradle-2.3-beta2.jar
In a previous post we wrote a custom task to generate a file with version information. When we created the task in our build file we had to provide values for the task properties version and outputFile. Now we want these values to have default values and we want to be able to set values with project properties instead of only task properties.
First we write a plugin where we create a new Plain Old Groovy Object (POGO) which will store the project properties in a convention object. Next we assign the values from the convention object properties to the task properties with a closure. This means the value of the properties are lazy set: only when the task gets executed the task property values are calculated.
Let's take a look at the source files for the plugin, POGO and task before we see what our new build script looks like.
// File: buildSrc/src/main/groovy/com/mrhaki/gradle/generate/GeneratePlugin.groovy
package com.mrhaki.gradle.generate
import org.gradle.api.Project
import org.gradle.api.Plugin
class GeneratePlugin implements Plugin<Project> {
void apply(Project project) {
def convention = new GeneratePluginConvention(project)
project.convention.plugins.generate = convention
project.tasks.withType(Generate.class).allTasks { Generate task ->
task.conventionMapping.version = { convention.outputVersion ?: project.version }
task.conventionMapping.outputFile = { convention.outputFile }
}
}
}
// File: buildSrc/src/main/groovy/com/mrhaki/gradle/generate/GeneratePluginConvention.groovy
package com.mrhaki.gradle.generate
import org.gradle.api.Project
class GeneratePluginConvention {
final Project project
String outputFilename
String outputVersion
public GeneratePluginConvention(Project project) {
this.project = project
this.outputFilename = 'version.txt'
}
File getOutputFile() {
project.file("$project.buildDir/$outputFilename")
}
}
// File: buildSrc/src/main/groovy/com/mrhaki/gradle/generate/Generate.groovy
package com.mrhaki.gradle.generate
import org.gradle.api.internal.ConventionTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
class Generate extends ConventionTask {
@Input
String version
@OutputFile
File outputFile
@TaskAction
void generate() {
def file = getOutputFile()
if (!file.isFile()) {
file.parentFile.mkdirs()
file.createNewFile()
}
file.write "Version: ${getVersion()}"
}
}
Our build script has changed to:
// File: build.gradle
import com.mrhaki.gradle.generate.*
apply plugin: GeneratePlugin
version = '3.0'
// Or use explicit values:
// outputVersion = 'OUTPUT'
// outputFilename = 'another-version.txt'
task generateVersionFile(type: Generate)
task showContents << {
println generateVersionFile.outputFile.text
}
showContents.dependsOn generateVersionFile
The version property value of the Generate task is now set by either a project property outputVersion or the project version. And the outputFile property is assigned from the default version.txt or the value of the property property outputFilename.
In a previous post we learned how we can use the inputs and outputs properties to set properties or files that need to be checked to see if a task is up to date. In this post we learn how a custom task class can use annotations to set input properties, file or files and output files or dir.
For input we can use @Input, @InputFile, @InputFiles or @InputDirectory annotations. Gradle uses the properties with annotations for checking if a task is up to date. Output file or directory can be marked with @OutputFile and @OutputDirectory.
task generateVersionFile(type: Generate) {
version = '2.0'
outputFile = file("$project.buildDir/version.txt")
}
task showContents << {
println generateVersionFile.outputFile.text
}
showContents.dependsOn generateVersionFile
class Generate extends DefaultTask {
@Input
String version
@OutputFile
File outputFile
@TaskAction
void generate() {
def file = getOutputFile()
if (!file.isFile()) {
file.parentFile.mkdirs()
file.createNewFile()
}
file.write "Version: ${getVersion()}"
}
}
We can run our task and get the following output:
$ gradle showContents :generateVersionFile :showContents Version: 2.0 BUILD SUCCESSFUL
And if we run it again we see the task is now up to date:
$ gradle showContents :generateVersionFile UP-TO-DATE :showContents Version: 2.0 BUILD SUCCESSFUL
We can change the version numer in our build script to 2.1 and see the output:
$ gradle showContents :generateVersionFile :showContents Version: 2.1 BUILD SUCCESSFUL
Gradle has a very powerful incremental build feature. This means Gradle will not execute a task unless it is necessary. We can help Gradle and configure our task so it is ready for an incremental build.
Suppose we have a task that generates a file. The file only needs to be generated if a certain property value has changed since the last task execution. Or the file needs be generated again if a source file is newer than the generated file. These conditions can be configured by us, so Gradle can use this to determine if a task is up to date or not. If the task is up to date Gradle doesn't execute the actions.
A Gradle task has an inputs and outputs property. We can assign a file(s), dir or properties as inputs to be checked. For outputs we can assign a file, dir or custom code in a closure to determine the output of the task. Gradle uses these values to determine if a task needs to be executed.
In the following sample build script we have a task generateVersionFile which create a file version.text in the project build directory. The contents of the file is the value of the version property. The file only needs to be generated if the value of version has changed since the last time the file was generated.
version = '1.0'
outputFile = file("$buildDir/version.txt")
task generateVersionFile << {
if (!outputFile.isFile()) {
outputFile.parentFile.mkdirs()
outputFile.createNewFile()
}
outputFile.write "Version: $version"
}
generateVersionFile.inputs.property "version", version
generateVersionFile.outputs.files outputFile
task showContents << {
println outputFile.text
}
showContents.dependsOn generateVersionFile
Let's run our script for the first time:
$ gradle showContents :generateVersionFile :showContents Version: 1.0 BUILD SUCCESSFUL
Now we run it again and notice how Gradle tells us the task is UP-TO-DATE:
$ gradle showContents :generateVersionFile UP-TO-DATE :showContents Version: 1.0 BUILD SUCCESSFUL
Let's change the build script and set the version to 1.1 and run Gradle:
$ gradle showContents :generateVersionFile :showContents Version: 1.1 BUILD SUCCESSFUL
In a follow-up post we see how can apply this logic to a custom task class via annotations.
In Gradle we can assign a task to a group. Gradle uses the group for example in the output of $ gradle -t to output all the tasks of the same group together. We only have to set the group property with a value and our task belongs to a group.
In the following sample we add the tasks hello and bye to the group Greeting:
def GREETING_GROUP = 'Greeting'
task hello << {
println 'Hello Gradle!'
}
hello.group = GREETING_GROUP
hello.description = 'Say hello.'
task bye {
description= 'Say goodbye.'
group = GREETING_GROUP
}
bye << {
println 'Goodbye.'
}
If we run $ gradle -t we get the following output:
:tasks ------------------------------------------------------------ Root Project ------------------------------------------------------------ Greeting tasks -------------- bye - Say goodbye. hello - Say hello. Help tasks ---------- dependencies - Displays a list of the dependencies of root project 'taskgroup'. help - Displays a help message projects - Displays a list of the sub-projects of root project 'taskgroup'. properties - Displays a list of the properties of root project 'taskgroup'. tasks - Displays a list of the tasks in root project 'taskgroup'. To see all tasks and more detail, run with --all.
Normally Gradle looks for a build script file with the name build.gradle in the current directory to execute a build. But we can easily use a different name or directory for the build file. We only have to use the -b or --build-file command-line option and tell Gradle the name of the build script file.
$ gradle -b builder.gradle $ gradle -b otherdir/build.gradle $ gradle --build-file otherdir/run.it
The Gradle command-line parser will add all non-option arguments as tasks to be executed to the build. So if we want to pass an extra argument to our build script via the command-line we must use the option -P or --project-prop. With this option we can set a project property and then use it in the build script.
// File: build.gradle
task showArgs << {
println "$word1 $word2"
}
We can run the script:
$ gradle showArgs -Pword1=hello --project-prop word2=world :showArgs hello world BUILD SUCCESSFUL
We can run executable commands from Gradle build scripts with the exec() method. We pass a closure with the name of the executable and arguments to the method. The executable is than executed with the given arguments and we get a ExecResult object back. If we want to grap the output from the executable and use it for example to do some parsing we can assign a custom outputstream to the executable. The output of the executable is than written to our outputstream and when the process is finished we can manipulate the content of the outputstream.
In the following sample script we invoke svn info to get Subversion information. We parse the output and get the last revision number from the output.
// File: build.gradle
task svninfo << {
new ByteArrayOutputStream().withStream { os ->
def result = exec {
executable = 'svn'
args = ['info']
standardOutput = os
}
def outputAsString = os.toString()
def matchLastChangedRev = outputAsString =~ /Last Changed Rev: (\d+)/
println "Latest Changed Revision #: ${matchLastChangedRev[0][1]}"
}
}
// Example output for svn info:
// Path: .
// URL: http://svn.host/svn/project
// Repository Root: http://svn.host/svn/
// Repository UUID: 9de3ae54-a9c2-4644-a1a1-838cb992bc8e
// Revision: 33
// Node Kind: directory
// Schedule: normal
// Last Changed Author: mrhaki
// Last Changed Rev: 33
// Last Changed Date: 2010-09-03 14:25:41 +0200 (Fri, 03 Sep 2010)