// Copyright (C) 2023 Gradle, Inc.
//
// Licensed under the Creative Commons Attribution-Noncommercial-ShareAlike 4.0 International License.;
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      https://creativecommons.org/licenses/by-nc-sa/4.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:../samples/sample_publishing_convention_plugins.html[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, it can also be tested individually.

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

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 <<sharing_build_logic_between_subprojects.adoc#sec:using_buildsrc,`buildSrc` directory>> which makes it available to the build script automatically.

.A binary plugin implementing imperative logic
====
include::sample[dir="snippets/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/bestPractices/conditionalLogic-do/kotlin", files="build.gradle.kts[]"]
include::sample[dir="snippets/bestPractices/conditionalLogic-do/groovy", files="build.gradle[]"]
====

[[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/6961503e70aa634653e71413224831fd3fa3ec2d/build-logic/basics/src/main/kotlin/gradlebuild/basics/PublicApi.kt[Gradle public API definition]
and the
link:https://github.com/gradle/gradle/blob/6961503e70aa634653e71413224831fd3fa3ec2d/build-logic/basics/src/main/kotlin/gradlebuild/basics/PublicKotlinDslApi.kt[Kotlin DSL API definition], except any subpackage with `internal` in the name.

// If these list is modified due changes in the public API definitions, update the permalinks too
.Gradle API packages
----
org.gradle
org.gradle.api.*
org.gradle.authentication.*
org.gradle.build.*
org.gradle.buildinit.*
org.gradle.caching.*
org.gradle.concurrent.*
org.gradle.deployment.*
org.gradle.external.javadoc.*
org.gradle.ide.*
org.gradle.ivy.*
org.gradle.jvm.*
org.gradle.language.*
org.gradle.maven.*
org.gradle.nativeplatform.*
org.gradle.normalization.*
org.gradle.platform.*
org.gradle.plugin.devel.*
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.work.*
org.gradle.workers.*
org.gradle.util.*
----

.Kotlin DSL API packages
----
org.gradle.kotlin.dsl
org.gradle.kotlin.dsl.precompile
----

=== 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 <<implementing_gradle_plugins_binary.adoc#capturing_user_input_to_configure_plugin_runtime_behavior,implementing plugins section>>.

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.internal.GFileUtils`, and other classes under `org.gradle.util.*`.

[[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/bestPractices/taskDefinition/kotlin", files="build.gradle.kts[]"]
include::sample[dir="snippets/bestPractices/taskDefinition/groovy", files="build.gradle[]"]
====

[[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/bestPractices/taskGroupDescription/kotlin", files="build.gradle.kts[]"]
include::sample[dir="snippets/bestPractices/taskGroupDescription/groovy", files="build.gradle[]"]
====

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/bestPractices/logicDuringConfiguration-dont/kotlin", files="build.gradle.kts[tags=config-logic]"]
include::sample[dir="snippets/bestPractices/logicDuringConfiguration-dont/groovy", files="build.gradle[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/bestPractices/logicDuringConfiguration-do/kotlin", files="build.gradle.kts[tags=config-logic]"]
include::sample[dir="snippets/bestPractices/logicDuringConfiguration-do/groovy", files="build.gradle[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 <<performance.adoc#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]]
== Externalize and encrypt your passwords

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 `gradle.properties` file in the project's directory.
Those files usually live in a version control repository and can be viewed by anyone that has access to it.

Passwords together with any other sensitive data should be kept external from the version controlled project files.
Gradle exposes an API for providing credentials in link:{groovyDslPath}/org.gradle.api.provider.ProviderFactory.html#org.gradle.api.provider.ProviderFactory:credentials(java.lang.Class,%20java.lang.String)[ProviderFactory]
as well as <<declaring_repositories.adoc#sec:handling_credentials,Artifact Repositories>>
that allows to supply credential values using <<build_environment#sec:gradle_configuration_properties,Gradle properties>>
when they are needed by the build. This way the credentials can be stored in the `gradle.properties` file that
resides in the user's home directory or be injected to the build using command line arguments or environment variables.

If you store sensitive credentials in user home's `gradle.properties`, consider encrypting them. 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].

[[sec:dont_anticipate_configuration_creation]]
== Don't anticipate configuration creation

Gradle will create certain configurations, such as `default` or `archives`, using a "check if needed" strategy.
That means it will only create these configurations if they **do not** already exist.

You should **not** ever create these configurations yourself.
Names such as these, and the names of configurations associated with source sets, should be considered implicitly "reserved".
The exact list of reserved names depends on which plugins are applied and how your build is configured.

This situation will be announced with the following deprecation warnings:

```
Configuration customCompileClasspath already exists with permitted usage(s):
	Consumable - this configuration can be selected by another project as a dependency
	Resolvable - this configuration can be resolved by this project to a set of files
	Declarable - this configuration can have dependencies added to it
Yet Gradle expected to create it with the usage(s):
	Resolvable - this configuration can be resolved by this project to a set of files
```

Gradle will then attempt to mutate the allowed usage to match the expected usage and will emit a second warning:

```
Gradle will mutate the usage of this configuration to match the expected usage. This may cause unexpected behavior. Creating configurations with reserved names has been deprecated. This is scheduled to be removed in Gradle 9.0. Create source sets prior to creating or accessing the configurations associated with them.
```

Some configurations may have their usage locked against mutation.
In this case your build will fail and this warning will be immediately followed by an exception with the message:

```
Gradle cannot mutate the usage of configuration 'customCompileClasspath' because it is locked.
```

If you encounter this error you must either:

1. Change the name of your configuration to avoid the conflict.
2. If changing the name is not possible, ensure the allowed usage (consumable, resolvable, declarable against) for your configuration is aligned with Gradle's expectations.

As a best practice, you should not "anticipate" configuration creation - let Gradle create the configuration first and then adjust it.
Or, if possible, use non-conflicting names for your custom configurations by renaming them when you see this warning.
