From Punched Cards to Prompts
AndroidIntroduction When computer programming was young, code was punched into cards. That is, holes were punched into a piece of cardboard in a format...
As Android Studio moves closer to release, more and more projects are being
moved over to this new IDE. One major stumbling block people often run into is
Android Studio integration with Gradle. In this blog post, I hope to shed some
light on what Gradle is, what it can do for you and some ways you can use it
with your applications.
Gradle is an extensive build tool and dependency manager for programming
projects. It has a domain specific language based on
Groovy. Gradle also provides
build-by-convention support for many types of projects including Java, Android
and Scala.
Android Studio comes with a working install of Gradle for building your Android
projects, but you will not be able to use it via the command line unless you
install Gradle yourself. I installed it using Homebrew and had no issues, but you
can also go to the Gradle downloads page in
order to get the source and binaries.
Your build scripts are how you tell Gradle how to build your application. The
application itself can be represented by many Gradle projects. A Gradle
“project” does not represent your application as a whole, but instead can represent
many different things. It could represent a library jar file you want to
build for your project, it could be a distribution zip file, or it could just
represent something you want done with the app, such as deploying it to a
server.
One example of having multiple projects in a single application would be
creating an Android Wear project, where there would be both a mobile project and a Wear
project in the application. Having multiple projects like this makes it
simple to have separate dependencies or to build tasks for the different
application parts.
How does Gradle know what counts as a project in your
application? It will create a project for each ‘build.gradle’ file in your
application. On the previous example, both the Mobile and Wear directories in
a Wear application would have a ‘build.gradle’ file letting Gradle know that it is
a separate project. For each project in your build, Gradle will create a Project
object. This object allows you to access Gradle features such as
adding tasks, properties and dependencies. Properties are defined in the
‘build.gradle’ file, typically in an extra block.
ext {
versionCode = '1.0.1'
versionNumber = '12'
}
Once properties are added, they can be read and set like predefined properties.
Properties can also be passed in via the command line using the ‘-P’ flag:
gradle buildTaks -P extraProperty
Projects represent a piece of your application, but they do not tell Gradle how
you want it to actually be built. For this, we use tasks.
Tasks are the discrete pieces of work performed by a build. A project can be
made up of many tasks it needs in order to do its work. Declaring tasks is very simple.
task hello {
doLast {
println 'hello world'
}
}
This defines the hello task if it is not already defined, and tells the task to
print the line ‘hello world’ last in its execution. In order to execute a task
from the command line, you type:
gradle hello
This will execute the hello task, but it will include logs in the output such as
which tasks were executed, whether the build was successful and the
total time of the build. If you would like the output without the logs, you
can pass in the -q flag:
gradle -q hello
Additional behavior can be added to pre-existing tasks. You can tell the
task whether to run the new behavior first or last in the execution order.
hello.doFirst {
println 'I will run first when the hello task is executed'
}
hello.doLast {
println 'I will be executed last'
}
hello << {
println "I'm a shortcut for the doLast method"
}
Tasks can also have dependencies on other tasks in the project. Say you have a task in your project that uploads your application to a Continuous
Integration server, but this task can only be run after the project has actually
been built. You can add an explicit dependency on ‘uploadBuild’ to ensure that the
project is built before it can be executed:
uploadBuildTask.dependsOn buildProjectTask
You can also declare this dependency when creating a task:
task uploadBuildTask(dependsOn: 'buildProjectTask') {
# work for uploadBuildTask to do
}
Tasks can also be skipped in various ways. One is to skip a task if some
condition is met using the ‘onlyIf’ predicate.
hello.onlyIf { !project.hasProperty('skipHello') }
StopExecutionException can also be used to stop the current task.
hello.doFirst {
if (project.hasProperty('shouldStopHello')) {
throw new StopExecutionException()
}
}
Throwing this exception will stop only the current task. The build will continue
on with the next task in the order.
Tasks can also be explicitly disabled.
hello.enabled = false
If you do not want to explicitly list the task you want to run on the command
line, you can define default tasks to run with the plain Gradle command.
defaultTasks 'clean', 'run'
The last thing to consider is how Gradle decides which tasks to run and in what
order. When you declare a task to run, Gradle will build a graph with that task
and any dependencies it may have. The graph must be acyclic, or the tasks will
not be able to run. Once the graph is created, Gradle will run the tasks with no
dependencies before running the others.
Now that we have a cursory understanding of the concept of Gradle projects
and tasks, we can take a look at managing dependencies. There are two parts to
managing application dependencies:
Here is an example of a ‘build.gradle’ file that defines some dependencies for a
Java project:
apply plugin: 'java'
repositories {
mavenCentral()
}
dependencies {
compile group: 'org.hibernate', name: 'hibernate-core', version: '3.6.7.Final'
testCompile group: 'junit', name: 'junit', version: '4.+'
}
Using the Java plugin gives us several different dependency configurations to
use. These configurations are a named set of dependencies, and for the Java
plugin they represent the classpath the plugin uses. Here are some of the
configurations the plugin gives you:
The ‘dependencies’ block is where all dependencies for the project are defined.
External dependencies are identified using the group, name and version
attributes. There is a shortcut version for external dependencies using the form
“group:name:version.”
You can also depend on a project in the same multi-project build. Declare your
project dependencies like this:
dependencies {
compile project(':wear')
}
Your project can also depend on files inside your project. You can include
individual files, or you can include all files in a tree. Here is how you would
add file dependencies:
dependencies {
runtime files('libs/a.jar', 'libs/b.jar')
runtime fileTree(dir: 'libs', include: '*.jar')
}
As for repositories, there are several ways to tell your project which one to
use.
repositories {
mavenCentral()
maven {
url "http://repo.mycompany.com/maven2"
}
maven {
credentials {
username 'user'
password 'password'
}
url "http://repo.mycompany.com/maven2"
}
ivy {
url "http://repo.mycompany.com/repo"
}
flatDir {
dirs 'libs'
}
}
The ‘mavenCentral()’ line tells Gradle to look for dependencies in the Central
Maven Repository. You can also specify your
own URL to a Maven or Ivy repository if your company has private repositories. If the
repositories require authentication, you can add credentials to the Maven block to gain
access. Finally, you can use a flat directory repository which is local to
the filesystem.
The last thing to discuss about dependency management is how Gradle decides from
which repository to select your dependencies. Given a dependency, Gradle
will look through each repository you listed for the module. Given a dynamic
module (ex: 4.+), Gradle will resolve the newest static version of the module.
Once all of the repositories have been searched, Gradle will select the ‘best’
one.
If you are worried about the effort of porting your application over to Gradle
from Ant, then I have some good news. You can use your pre-existing Ant scripts
in your Gradle build, so you do not have to move over to Gradle all at once. You
can slowly move over piece by piece.
There is an Ant property provided by Gradle that allows you to interact with
your Ant scripts. Importing a ‘build.xml’ file can be done like so:
ant.importBuild 'build.xml'
Once it is imported, you can execute Ant targets by name with Gradle. You can
also create Gradle tasks that depend on an Ant target in the same way as another
task.
task gradleTask(dependsOn: antTarget) << {
println "gradle task will run after the ant target"
}
You can also have an Ant target depend on a Gradle task.
In ‘build.gradle’
ant.importBuild 'build.xml'
task gradleTask << {
println "executing from Gradle"
}
In ‘build.xml’
<project>
<target name="antTarget" depends="gradleTask">
<echo>Executing from Ant</echo>
</target>
</project>
Finally, you can execute Ant tasks from Gradle as well. The next example creates
a Gradle task called ‘archive’ that uses the Ant Zip task to create an archive
of the project source.
task archive << {
ant.zip(destfile: 'archive.zip') {
fileset(dir: 'src') {
exclude(name: '**.xml')
include(name: '**.java')
}
}
}
Gradle on its own provides very little useful functionality for your real-world
projects. The useful features are all added by various Gradle plugins. These
plugins add new tasks to your Gradle projects with useful defaults, add
dependency configurations to your project and add conventions (e.g.,
Java source located at ‘src/main/java/’).
Plugins are applied to the project in your ‘build.gradle’ file.
apply plugin: 'java'
The Java plugin adds the dependency configurations discussed previously, and it
also adds several tasks useful for Java projects, such as:
It also defines the ‘main’ and ‘test’ source sets. A source set is a group of source
files that are compiled and executed together. These sets have an associated
compile classpath and runtime classpath.
The Java plugin also adds some conventions for how your project is laid out. It
will look for parts of your project in specific directories.
If you want to change these directory conventions, you just need to update the
appropriate source set.
sourceSets {
main {
java {
srcDir 'src/java'
}
}
}
The Android plugin adds many useful features for working on Android projects. It
allows you to configure ‘AndroidManifest.xml’ entries from the ‘build.gradle’ file.
android {
compileSdkVersion 19
buildToolsVersion "20.0.0"
defaultConfig {
versionCode 12
versionName "2.0"
minSdkVersion 8
targetSdkVersion 19
}
}
The values defined in the build.gradle file will override those in the manifest,
so make sure to update just the values in the build file.
Android also gives you the ability to create either a debug APK (Android
application package file) or a release APK. The tasks for creating these APKs are:
The plugin also includes ‘install’ and ‘uninstall’ tasks for the application.
If you would like to discover more abilities of the plugin, I suggest visiting the Android tools
project site
to find out more.
A single application can be made up of many Gradle projects. In order to have
these projects available, you would need to include them in the ‘settings.gradle’
file at the top level of the application.
In ‘settings.gradle’
include 'project1', 'project2'
Once the projects are included in the ‘settings.gradle’ file, you can define common
behavior for all of the projects using a special property. You will typically
use this to apply plugins to all of your projects and add repositories and
dependencies that all of your projects rely on.
allprojects {
task projectName << { task -> println "I'm $task.project.name" }
}
You can also add behavior to only the subprojects using another property.
subprojects {
task projectName << { println "I am a subproject" }
}
If you only want to add behavior to a single project, you can do that as well.
project(':project1').projectName << {
println "I have a specific project name task"
}
The tasks defined at the top-level project can also be extended or overridden by
the subprojects in their own ‘build.gradle’ files.
This turned out to be a not-so-brief intro, but you should now have a
good starting point for getting Gradle working with your projects. If you have
any questions, please leave a comment and I will be happy to help out.
Introduction When computer programming was young, code was punched into cards. That is, holes were punched into a piece of cardboard in a format...
Jetpack Compose is a declarative framework for building native Android UI recommended by Google. To simplify and accelerate UI development, the framework turns the...
Big Nerd Ranch is chock-full of incredibly talented people. Today, we’re starting a series, Tell Our BNR Story, where folks within our industry share...