As an Android developer, if your build.gradle is built from an IDE or copied and pasted from another project, you should read this article to get a grasp of the basics of Gradle that you don’t know.
All the pictures in this article come from the network and are deleted
Gradle is a jVM-based build tool. Currently, all projects built in Android Studio are based on Gradle. The features of Gradle and other build tools (Ant, Maven) include:
- Powerful DSL and rich Gradle API
- Gradle is groovy
- Strong dependency management
- Can expand sex
- Integration with other build tools
Three build scripts
Gradle scripts are configuration scripts. Each script type is actually a delegate of a class object in a specific Gradle API. The script executes the configuration of the delegate object. In a complete Gradle build system, there are three types of build scripts, each corresponding to three delegate objects
The script type | Delegate object |
---|---|
Init script | Gradle |
Settings script | Settings |
Build script | Project |
init.gradle
The Init script above is actually a Gradle object delegate, so any property references and methods called in this Init script will be delegated to this Gradle instance.
The execution of the Init script occurs before the start of the build and is the earliest step in the entire build.
Configure Init Scrip dependencies
Each script execution can configure the dependencies needed for the current script’s own execution. The Init scrip configuration is as follows:
// The initScript configuration block contains the configuration required for the execution of the script itself
// We can configure dependency paths and so on
initscript {
repositories {
mavenCentral()
}
dependencies {
classpath group: 'org.apache.commons'.name: 'commons-math'.version: '2.0'}}Copy the code
Using the Init scrip
There are several ways to use a defined Init scrip
-
When running gradle, specify the path of the script with the -i or –init-script command options
This approach can be tailored to a specific build.
-
Place an init.gradle file in the *USER_HOME*/. Gradle/directory
-
Place a file with a name ending in. Gradle in the gradle distribution *GRADLE_HOME*/init.d/
Both of these approaches are global and have an effect on the build in the machine
settings.gradle
This corresponds to the Settings script script type, which is the delegate of the Settings object. Any property references and methods invoked in the script are delegated to the Settings instance.
The execution of Settings script occurs during the initialization phase of Gradle’s build life cycle. The Settings script file declares the configuration required for the build and is used to instantiate the project hierarchy. When executing the Settings script and initializing the Settings object instance, a rootProject object is automatically built and participated in the entire build. (The default name of rootProject is the name of its folder and its path is the path containing the setting script file).
Here is a class diagram of the Settings object:
For every project object added to the build process through the include method, an instance of the ProjectDescriptor is created in the Settings script.
Therefore, in the Settings script file, we have access to the objects we use:
Settings
objectGradle
objectProjectDescriptor
object
Get Settings file
In Gradle, as long as the root project/any subproject directory contains component files, the build can be run at the appropriate location. To determine whether a build is a multi-project build, look for the Settings script file, which indicates whether subprojects are included in a multi-project build.
To find the Settings file, do the following:
- Search for the setting file in the master directory at the same level as the current directory
- If the Settings file is not found in 1, look for the Settings file in the parent directory starting from the current directory.
When the Settings file is found and the current directory is included in the file definition, the current directory is considered part of a multi-project build.
build.gradle
This corresponds to the Build script type mentioned earlier, which is the delegate of the Project object in Gradle. Any property references and methods invoked in the script are delegated to the Project instance.
Configuring script dependencies
In build.gradle there is a configuration block buildScipt{} to configure the path configuration required for the current script execution (similar to initScript).
buildscript {
// The configuration block here is repositories separate from the one in the Project instance
// The repository repository repository repository that the script itself depends on is delegated to ScriptHandler
repositories {
mavenLocal()
google()
jcenter()
}
// As in the previous configuration block, separate it from the Dependencies configuration block in Project
dependencies {
classpath 'com. Android. Tools. Build: gradle: 3.1.2'}}Copy the code
To add a key point, no matter where the buildScript{} configuration block is placed in the build.gradle file, it is always executed first in the entire script file
Three building blocks
Every Gradle build contains three basic building blocks:
- project
- task
- property
Each build contains at least one project, which in turn contains one or more tasks. Projects and tasks expose properties that can be used to control builds.
Project
Much of what we know about project comes from the build.gradle file in the project directory (because it is really the delegate for the project object). The class diagram for the Project object looks like this:
Project configuration
In the build.gradle script file, you can not only configure individual projects, but also define common logic for project blocks, as defined below.
Common examples are:
// Add the repository source configuration for all projects
allprojects {
repositories {
jcenter()
google()
}
}
// Add the mavenPublish configuration block for all subprojects
subprojects {
mavenPublish {
groupId = maven.config.groupId
releaseRepo = maven.config.releaseRepo
snapshotRepo = maven.config.snapshotRepo
}
}
Copy the code
Task
Tasks are one of the basic configuration blocks that Gradle builds, and the execution of gradle builds is the execution of tasks. The following is a class diagram for Task.
Task configuration and actions
When we set a task, we will include two parts of the content: configuration and action. For example:
task test{
println("Here's the configuration.")
doFirst{
// do something here
}
doLast(){
// do something here}}Copy the code
Task action declarations currently contain two main methods:
- doFirst
- doLast
These actions are invoked during the execution phase of gradle’s build life cycle. It is worth noting that a task can declare multiple doFirst and doLast actions. You can also add actions to tasks defined in existing plug-ins. Such as:
// Add a doLast action to the test task
test.doLast{
// do something here
}
Copy the code
In the definition of task, in addition to action blocks are configuration blocks, where we can declare variables, access properties, call methods, and so on. The content of these configuration blocks occurs during the configuration phase of Gradle’s build life cycle. So the configuration in the Task is executed every time. The action block is executed only when the task is actually called.
The task dependency
The order in which tasks are executed in Gradle is indeterminate. With dependencies between tasks, Gradle ensures that the dependent task will be executed first by the current task. A dependsOn() method for a task allows us to declare one or more task dependencies for our task.
task first{
doLast{
println("first")
}
}
task second{
doLast{
println("second")
}
}
task third{
doLast{
println("third")
}
}
task test(dependsOn:[second,first]){
doLast{
println("first")
}
}
third.dependsOn(test)
Copy the code
The type of task
By default, our common task is org. Gradle. API. DefaultTask type. However, gradle has a rich set of task types that we can use directly. To change the type of task, we can refer to the following example
task createDistribution(type:Zip){
}
Copy the code
For more information about the types of tasks, see gradle’s official documentation
Property
Properties are used throughout gradle builds to help control the existence of build logic. Gradle can declare properties in two main ways:
- Use the ext namespace to define extended attributes
- Use the Gradle properties file
gradle.properties
Custom properties
Ext namespace
Many model classes in Gradle provide special attribute support, such as Project. Inside Gradle, these properties are stored as key-value pairs. Using the Ext namespace, we can easily add attributes. The following methods are supported:
// Add a property named groupId to project
project.ext.groupId="tech.easily"
// Add attributes using ext blocks
ext{
artifactId='EasyDependency'
config=[
key:'value']}Copy the code
Note that we only need to use the Ext namespace when declaring attributes, and that the ext namespace can be omitted when using attributes.
Properties file
As we often see in Android projects, we can create a new gradle.properties file at the root of the project and define simple key-value pair properties in the file. These properties can be accessed by gradle scripts in your project. As follows:
# gradle.properties
Note that the file's comments begin with #
groupId=tech.easily
artifactId=EasyDependency
Copy the code
The java.util.properties class can be used to dynamically create Properties files in code (for example, when customizing plug-ins) and read Properties from the files. Such as:
void createPropertyFile() {
def localPropFile = new File(it.projectDir.absolutePath + "/local.properties")
def defaultProps = new Properties()
if(! localPropFile.exists()) { localPropFile.createNewFile() defaultProps.setProperty("debuggable".'true')
defaultProps.setProperty("groupId", GROUP)
defaultProps.setProperty("artifactId", project.name)
defaultProps.setProperty("versionName", VERSION_NAME)
defaultProps.store(new FileWriter(localPropFile), "properties auto generated for resolve dependencies")}else {
localPropFile.withInputStream { stream ->
defaultProps.load(stream)
}
}
}
Copy the code
The important thing about properties is that properties can be inherited. Attributes defined in a project are automatically inherited by its children, regardless of which way we add attributes.
Build life cycle
As mentioned earlier, gradle has multiple script types and they are all executed in different life cycles.
The three stages
In gradle builds, the build lifecycle consists of three phases:
-
Initialization
As mentioned earlier, at this stage, the Settings script is executed so that Gradle confirms which projects will participate in the build. Then create a Project object for each Project.
-
Configuration
After configuring the Project object created during Initialization, all configuration scripts are executed. (Configuration blocks including tasks defined in Project are also executed)
-
Execution (Configuration)
Gradle determines which tasks created and configured in the Configuration phase will be executed and which tasks will be executed depending on Gradle command parameters and the current directory
Listening life cycle
During the gradle build process, Gradle provides a rich set of hooks to help us customize the build logic to the needs of the project, as shown in the following figure:
There are two main ways to listen for these life cycles:
- Adding listeners
- Use the configuration block of the hook
Gradle and Project define the available hooks. Common hooks include:
Gradle
-
beforeProject()/afterProject()
Equivalent to beforeEvaluate and afterEvaluate in Project
-
settingsEvaluated()
The Settings script is executed and the Settings object is configured
-
projectsLoaded()
All projects that participate in the build are created from Settings
-
projectsEvaluated()
All projects involved in the build have been evaluated
TaskExecutionGraph
-
WhenReady ()
Task graph generation. All tasks that need to be executed have established dependencies between tasks
Project
- BeforeEvaluate ()
- AfterEvaluate ()
Dependency management
One of the main features of Gradle mentioned earlier is powerful dependency management. Gradle has a rich set of dependency types and supports multiple dependency repositories. Also, every dependency in Gradle is managed in groups based on a specific scope.
Add dependencies to your project in Gradle like this:
// build.gradle
// Add the dependency repository source
repositories {
google()
mavenCentral()
}
// Add dependencies
// Dependency types include file dependency, project dependency, and module dependency
dependencies {
// local dependencies.
implementation fileTree(dir: 'libs'.include: ['*.jar'])... }Copy the code
Four dependency types
There are four types of dependencies in Gradle:
-
Module is dependent on
This is a common dependency type in Gradle, and it usually points to a component in the repository, as follows:
dependencies { runtime group: 'org.springframework'.name: 'spring-core'.version: '2.5' runtime 'org. Springframework: spring - the core: 2.5'.'org. Springframework: spring - aop: 2.5' runtime( [group: 'org.springframework'.name: 'spring-core'.version: '2.5'], [group: 'org.springframework'.name: 'spring-aop'.version: '2.5'] ) runtime('org. Hibernate: hibernate: 3.0.5') { transitive = true } runtime group: 'org.hibernate'.name: 'hibernate'.version: '3.0.5'.transitive: true runtime(group: 'org.hibernate'.name: 'hibernate'.version: '3.0.5') { transitive = true}}Copy the code
Modules depend on the ExternalModuleDependency object in the API corresponding to Gradle
-
File is dependent on
dependencies { runtime files('libs/a.jar'.'libs/b.jar') runtime fileTree(dir: 'libs'.include: '*.jar')}Copy the code
-
Project depend on
dependencies { compile project(':shared')}Copy the code
The project depends on the ProjectDependency object in the API corresponding to Gradle
-
Specific Gradle distribution dependencies
dependencies { compile gradleApi() testCompile gradleTestKit() compile localGroovy() } Copy the code
Managing dependency Configuration
Each dependency of a gradle project is applied to a specific scope and is represented by a Configuration object in Gradle. Each Configuration object has a unique name. Gradle dependency configuration management looks like this:
The custom Configuration
In Gradle, it is very easy to customize a Configuration object. When you define your own Configuration object, you can also inherit the existing Configuration object, as shown below:
configurations {
jasper
// Define the inheritance relationship
smokeTest.extendsFrom testImplementation
}
repositories {
mavenCentral()
}
dependencies {
jasper 'org, apache tomcat. Embed: tomcat embed - jasper: 9.0.2'
}
Copy the code
Manage transitive dependencies
In actual project dependency management there is a dependency relationship like this:
- Module B depends on module C
- Module A depends on module B
- Module C becomes a transitive dependency of module A
Gradle provides powerful administrative capabilities when dealing with transitive dependencies like these
Using dependency constraints
Dependency constraints can help us control the version numbers (version ranges) of transitive dependencies and our own dependencies, such as:
dependencies {
implementation 'org.apache.httpcomponents:httpclient'
constraints {
// HttpClient is a dependency of the project itself
// This constraint means that the specified version number is enforced either for the project's own dependencies or for passing dependencies
implementation('org, apache httpcomponents: httpclient: 4.5.3') {
because 'previous versions have a bug impacting this application'
}
Commons-codec is not declared as a dependency of the project itself
// So this logic is only triggered if Commons -codec is transitive
implementation('Commons - codec: the Commons - the codec: 1.11') {
because 'version 1.9 pulled from httpclient has bugs affecting this application'}}}Copy the code
Exclude specific transitive dependencies
Sometimes we rely on projects/modules that introduce multiple transitive dependencies. However, we do not need some transitive dependencies. In this case, we can exclude some transitive dependencies by using exclude as follows:
dependencies {
implementation('the log4j: log4j: 1.2.15') {
exclude group: 'javax.jms'.module: 'jms'
exclude group: 'com.sun.jdmk'.module: 'jmxtools'
exclude group: 'com.sun.jmx'.module: 'jmxri'}}Copy the code
Enforces the use of the specified dependency version
Gradle resolves any dependency version conflicts by selecting the latest version found in the dependency diagram. Sometimes, however, some projects need to use an older version number as a dependency. At this point we can force a version to be specified. Such as:
dependencies {
implementation 'org, apache httpcomponents: httpclient: 4.5.4'
// Assume that the latest version of Commons-Codec is 1.10
implementation('Commons - codec: the Commons - the codec: 1.9') {
force = true}}Copy the code
Note that if a dependency project uses a newer version of the API and we force an older version of the transition-dependency, it will cause runtime errors
Transitive dependencies are disabled
dependencies {
implementation(: 'com. Google. Guava guava: 23.0') {
transitive = false}}Copy the code
Dependency resolution
Use dependency resolution rules
Dependency resolution rules provide a very powerful way to control the dependency resolution process and can be used to implement various advanced patterns in dependency management. Such as:
-
The version of the unified component group
Many times we rely on a company’s library to contain multiple modules, which are generally uniformly built, packaged, and distributed with the same version number. At this point we can achieve version number unification by controlling the dependency resolution process.
configurations.all { resolutionStrategy.eachDependency { DependencyResolveDetails details -> if (details.requested.group == 'org.gradle') { details.useVersion '1.4' details.because 'API breakage in higher versions'}}}Copy the code
-
Handles custom versions of Scheme
configurations.all { resolutionStrategy.eachDependency { DependencyResolveDetails details -> if (details.requested.version == 'default') { def version = findDefaultVersionInCatalog(details.requested.group, details.requested.name) details.useVersion version.version details.because version.because } } } def findDefaultVersionInCatalog(String group, String name) { //some custom logic that resolves the default version into a specific version [version: "1.0".because: 'tested by QA']}Copy the code
For more examples of dependency resolution rules, see ResolutionStrategy in gradle’s API
Use substitution rules for dependencies
The dependency substitution rules are somewhat similar to the dependency resolution rules above. In fact, many of the functions of dependency resolution rules can be achieved by dependency replacement rules. Dependency substitution rules allow Project Dependency and Module Dependency to be transparently replaced by specified substitution rules.
// Replace module dependencies with project dependencies
configurations.all {
resolutionStrategy.dependencySubstitution {
substitute module("org.utils:api") with project(":api") because "we work with the unreleased development version"
substitute module("Org. Utils: util: 2.5") with project(":util")}}// Replace project dependencies with module dependencies
configurations.all {
resolutionStrategy.dependencySubstitution {
substitute project(":api") with module("Org. Utils: API: 1.3") because "we use a stable version of utils"}}Copy the code
In addition to the above two, there are three other dependency rules. Customizing Dependency Resolution Behavior Customizing Dependency Resolution Behavior Customizing Dependency Resolution Behavior Customizing Dependency Resolution Behavior
- Use meta-data rules for components
- Use component selection rules
- Use the module replacement rule
Plug-in development
Plug-in development is a powerful tool in Gradle’s flexible build system. Using the PluginAPI in Gradle, you can customize your plugins and use common build logic as plugins. Such as are used in the Android project: com. Android. Application, kotlin – Android, Java, and so on.
There are plenty of articles about plug-in development on the web, and I won’t go into them here. Gradle dependency management is a plugin for Gradle dependency management. It is a plugin for Gradle dependency management.
-
EasyDependency
Gradle is a gradle plugin that helps improve the efficiency of componential development.
- The components of the publishing module are remote Maven repositories
- Dynamic replacement dependency configuration: Use source dependencies or maven repository component (AAR/JAR) dependencies on modules
Write in the last
This paper is basically organized and summarized according to my own ideas after reading gradle’s official documents and related materials. Welcome to discuss gradle usage and questions.