// Copyright 2018 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

[[authoring_maintainable_build_scripts]]
= Best practices for authoring maintainable builds

Gradle has a rich API with several approaches to creating build logic.
The associated flexibility can easily lead to unnecessarily complex builds with custom code commonly added directly to build scripts.
In this chapter, we present several best practices that will help you develop expressive and maintainable builds that are easy to use.

NOTE: The third-party link:https://github.com/nebula-plugins/gradle-lint-plugin[Gradle lint plugin] helps with enforcing a desired code style in build scripts if that's something that would interest you.

[[sec:avoid_imperative_logic_in_scripts]]
== Avoid using imperative logic in scripts

The Gradle runtime does not enforce a specific style for build logic.
For that very reason, it's easy to end up with a build script that mixes declarative DSL elements with imperative, procedural code.
Let's talk about some concrete examples.


* _Declarative code:_ Built-in, language-agnostic DSL elements (e.g. link:{groovyDslPath}/org.gradle.api.Project.html#org.gradle.api.Project:dependencies(groovy.lang.Closure)[Project.dependencies{}] or link:{groovyDslPath}/org.gradle.api.Project.html#org.gradle.api.Project:repositories(groovy.lang.Closure)[Project.repositories{}]) or DSLs exposed by plugins
* _Imperative code:_ Conditional logic or very complex task action implementations

The end goal of every build script should be to only contain declarative language elements which makes the code easier to understand and maintain.
Imperative logic should live in binary plugins and which in turn is applied to the build script.
As a side product, you automatically enable your team to link:{guidesUrl}/designing-gradle-plugins/#reusable_logic_should_be_written_as_binary_plugin[reuse the plugin logic in other projects] if you publish the artifact to a binary repository.

The following sample build shows a negative example of using conditional logic directly in the build script.
While this code snippet is small, it is easy to imagine a full-blown build script using numerous procedural statements and the impact it would have on readability and maintainability.
By moving the code into a class link:{guidesUrl}/testing-gradle-plugins/[testability] also becomes a valid option.

.A build script using conditional logic to create a task
====
include::sample[dir="snippets/userguide/bestPractices/conditionalLogic/dont/groovy", files="build.gradle[]"]
include::sample[dir="snippets/userguide/bestPractices/conditionalLogic/dont/kotlin", files="build.gradle.kts[]"]
====

Let's compare the build script with the same logic implemented as a binary plugin.
The code might look more involved at first but clearly looks more like typical application code.
This particular plugin class lives in the <<organizing_gradle_projects.adoc#sec:build_sources,`buildSrc` directory>> which makes it available to the build script automatically.

.A binary plugin implementing imperative logic
====
include::sample[dir="snippets/userguide/bestPractices/conditionalLogic/do/groovy/buildSrc/src/main/java/com/enterprise", files="ReleasePlugin.java[]"]
====

Now that the build logic has been translated into a plugin, you can apply it in the build script.
The build script has been shrunk from 8 lines of code to a one liner.

.A build script applying a plugin that encapsulates imperative logic
====
include::sample[dir="snippets/userguide/bestPractices/conditionalLogic/do/groovy", files="build.gradle[]"]
include::sample[dir="snippets/userguide/bestPractices/conditionalLogic/do/kotlin", files="build.gradle.kts[]"]
====

[[sec:avoiding_gradle_internal_apis]]
== Avoid using internal Gradle APIs

Use of Gradle internal APIs in plugins and build scripts has the potential to break builds when either Gradle or plugins change.

The following packages are listed in the link:https://github.com/gradle/gradle/blob/180b9d3fa84b91768364c603380e82947437eda1/buildSrc/subprojects/configuration/src/main/kotlin/org/gradle/gradlebuild/public-api.kt[Gradle public API definition], with the exception of any subpackage with `internal` in the name:

----
org/gradle/*
org/gradle/api/**
org/gradle/authentication/**
org/gradle/buildinit/**
org/gradle/caching/**
org/gradle/concurrent/**
org/gradle/deployment/**
org/gradle/external/javadoc/**
org/gradle/ide/**
org/gradle/includedbuild/**
org/gradle/ivy/**
org/gradle/jvm/**
org/gradle/language/**
org/gradle/maven/**
org/gradle/nativeplatform/**
org/gradle/normalization/**
org/gradle/platform/**
org/gradle/play/**
org/gradle/plugin/devel/**
org/gradle/plugin/repository/*
org/gradle/plugin/use/*
org/gradle/plugin/management/*
org/gradle/plugins/**
org/gradle/process/**
org/gradle/testfixtures/**
org/gradle/testing/jacoco/**
org/gradle/tooling/**
org/gradle/swiftpm/**
org/gradle/model/**
org/gradle/testkit/**
org/gradle/testing/**
org/gradle/vcs/**
org/gradle/workers/**
----

=== Alternatives for oft-used internal APIs

To provide a nested DSL for your custom task, don't use `org.gradle.internal.reflect.Instantiator`; use link:{javadocPath}/org/gradle/api/model/ObjectFactory.html[ObjectFactory] instead.
It may also be helpful to read <<lazy_configuration.adoc#lazy_configuration,the chapter on lazy configuration>>.

Don't use `org.gradle.api.internal.ConventionMapping`.
Use link:{javadocPath}/org/gradle/api/provider/Provider.html[Provider] and/or link:{javadocPath}/org/gradle/api/provider/Property.html[Property].
You can find an example for capturing user input to configure runtime behavior in the link:{guidesUrl}/implementing-gradle-plugins/#capturing_user_input_to_configure_plugin_runtime_behavior[implementing plugins guide].

Instead of `org.gradle.internal.os.OperatingSystem`, use another method to detect operating system, such as link:https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/SystemUtils.html[Apache commons-lang SystemUtils] or `System.getProperty("os.name")`.

Use other collections or I/O frameworks instead of `org.gradle.util.CollectionUtils`, `org.gradle.util.GFileUtils`, and other classes under `org.gradle.util.*`.

Gradle plugin authors may find the Designing Gradle Plugins subsection on link:{guidesUrl}/designing-gradle-plugins/#restricting_the_plugin_implementation_to_gradle_s_public_api[restricting the plugin implementation to Gradle's public API] helpful.

[[sec:declaring_tasks]]
== Follow conventions when declaring tasks

The task API gives a build author a lot of flexibility to declare tasks in a build script.
For optimal readability and maintainability follow these rules:

* The task type should be the only key-value pair within the parentheses after the task name.
* Other configuration should be done within the task's configuration block.
* <<tutorial_using_tasks.adoc#sec:hello_world,Task actions>> added when declaring a task should only be declared with the methods link:{groovyDslPath}/org.gradle.api.Task.html#org.gradle.api.Task:doFirst(org.gradle.api.Action)[Task.doFirst{}] or link:{groovyDslPath}/org.gradle.api.Task.html#org.gradle.api.Task:doLast(org.gradle.api.Action)[Task.doLast{}].
* When declaring an ad-hoc task — one that doesn't have an explicit type — you should use link:{groovyDslPath}/org.gradle.api.Task.html#org.gradle.api.Task:doLast(org.gradle.api.Action)[Task.doLast{}] if you're only declaring a single action.
* A task should <<#sec:improving_task_discoverability,define a group and description>>.

.Definition of tasks following best practices
====
include::sample[dir="snippets/userguide/bestPractices/taskDefinition/groovy", files="build.gradle[]"]
include::sample[dir="snippets/userguide/bestPractices/taskDefinition/kotlin", files="build.gradle.kts[]"]
====

[[sec:improving_task_discoverability]]
== Improve task discoverability

Even new users to a build should to be able to find crucial information quickly and effortlessly.
In Gradle you can declare a link:{groovyDslPath}/org.gradle.api.Project.html#org.gradle.api.Project:group[group] and a link:{groovyDslPath}/org.gradle.api.Project.html#org.gradle.api.Project:description[description] for any task of the build.
The <<command_line_interface.adoc#sec:listing_tasks,tasks report>> uses the assigned values to organize and render the task for easy discoverability.
Assigning a group and description is most helpful for any task that you expect build users to invoke.

The example task `generateDocs` generates documentation for a project in the form of HTML pages.
The task should be organized underneath the bucket `Documentation`.
The description should express its intent.

.A task declaring the group and description
====
include::sample[dir="snippets/userguide/bestPractices/taskGroupDescription/groovy", files="build.gradle[]"]
include::sample[dir="snippets/userguide/bestPractices/taskGroupDescription/kotlin", files="build.gradle.kts[]"]
====

The output of the tasks report reflects the assigned values.

----
> gradle tasks

> Task :tasks

Documentation tasks
-------------------
generateDocs - Generates the HTML documentation for this project.
----

[[sec:minimize_logic_executed_configuration_phase]]
== Minimize logic executed during the configuration phase

It's important for every build script developer to understand the different phases of the <<build_lifecycle.adoc#build_lifecycle,build lifecycle>> and their implications on performance and evaluation order of build logic.
During the configuration phase the project and its domain objects should be _configured_, whereas the execution phase only executes the actions of the task(s) requested on the command line plus their dependencies.
Be aware that any code that is not part of a task action will be executed with _every single run_ of the build.
A link:https://scans.gradle.com/get-started[build scan] can help you with identifying the time spent during each of the lifecycle phases.
It's an invaluable tool for diagnosing common performance issues.

Let's consider the following incantation of the anti-pattern described above.
In the build script you can see that the dependencies assigned to the configuration `printArtifactNames` are resolved outside of the task action.

.Executing logic during configuration should be avoided
====
include::sample[dir="snippets/userguide/bestPractices/logicDuringConfiguration/dont/groovy", files="build.gradle[tags=config-logic]"]
include::sample[dir="snippets/userguide/bestPractices/logicDuringConfiguration/dont/kotlin", files="build.gradle.kts[tags=config-logic]"]
====

The code for resolving the dependencies should be moved into the task action to avoid the performance impact of resolving the dependencies before they are actually needed.

.Executing logic during execution phase is preferred
====
include::sample[dir="snippets/userguide/bestPractices/logicDuringConfiguration/do/groovy", files="build.gradle[tags=config-logic]"]
include::sample[dir="snippets/userguide/bestPractices/logicDuringConfiguration/do/kotlin", files="build.gradle.kts[tags=config-logic]"]
====

[[sec:avoiding_use_of_gradlebuild]]
== Avoid using the `GradleBuild` task type

The link:{groovyDslPath}/org.gradle.api.tasks.GradleBuild.html[GradleBuild] task type allows a build script to define a task that invokes another Gradle build.
The use of this type is generally discouraged.
There are some corner cases where the invoked build doesn't expose the same runtime behavior as from the command line or through the Tooling API leading to unexpected results.

Usually, there's a better way to model the requirement.
The appropriate approach depends on the problem at hand. Here're some options:

* Model the build as <<multi_project_builds.adoc#multi_project_builds,multi-project build>> if the intention is to execute tasks from different modules as unified build.
* Use <<composite_builds.adoc#composite_builds,composite builds>> for projects that are physically separated but should occasionally be built as a single unit.

[[sec:avoiding_inter_project_configuration]]
== Avoid inter-project configuration

Gradle does not restrict build script authors from reaching into the domain model from one project into another one in a <<multi_project_builds.adoc#multi_project_builds,multi-project build>>.
Strongly-coupled projects hurts <<multi_project_builds.adoc#sec:parallel_execution,build execution performance>> as well as readability and maintainability of code.

The following practices should be avoided:

* Explicitly depending on a task from another project via link:{groovyDslPath}/org.gradle.api.Task.html#org.gradle.api.Task:dependsOn(java.lang.Object++[]++)[Task.dependsOn(java.lang.Object++...++)].
* Setting property values or calling methods on domain objects from another project.
* Executing another portion of the build with <<#sec:avoiding_use_of_gradlebuild,GradleBuild>>.
* Declaring unnecessary <<declaring_dependencies.adoc#sub:project_dependencies,project dependencies>>.

[[sec:avoiding_passwords_in_plain_text]]
== Avoid storing passwords in plain text

Most builds need to consume one or many passwords.
The reasons for this need may vary.
Some builds need a password for publishing artifacts to a secured binary repository, other builds need a password for downloading binary files.
Passwords should always kept safe to prevent fraud.
Under no circumstance should you add the password to the build script in plain text or declare it in a `gradle.properties` file.
Those files usually live in a version control repository and can be viewed by anyone that has access to it.

Passwords should be stored in encrypted fashion. At the moment Gradle does not provide a built-in mechanism for encrypting, storing and accessing passwords.
A good solution for solving this problem is the link:https://github.com/etiennestuder/gradle-credentials-plugin[Gradle Credentials plugin].
