liutikas / gradle-best-practices Goto Github PK
View Code? Open in Web Editor NEWLicense: Apache License 2.0
License: Apache License 2.0
I've been reading your guide, pretty cool summary. I got hung up on this suggestion, could you please expand on it a bit more or link to some resources?
Don't use Kotlin lambdas in your public API
Why is that? What will break?
Gradle enhances the bytecode at runtime to provide a nicer DSL experience for users of your plugin.
That's great, but what if my users are using includeBuild(gradle/plugins)
, at that point my plugin will be a normal dependency and Gradle doesn't provide a nice DSL magic transformation. Any suggestion to support both?
It's very easy to share outputs between projects in a way that breaks Gradle optimisations, or ordering.
These workarounds are really easy to do, but they break Gradle optimisations and caching, and can make refactoring more difficult.
I've always found the Gradle docs are quite confusing on this topic. They contain a lot of information, assume a lot of knowledge, start with a "don't do this!" example, and are generally not easy for me to follow. (This example was more clear, but it's now been removed).
But sharing outputs is not as hard as the docs make it seem - aside from some weird names for things.
I've written a more to-the-point guide, and I'd like to contribute it to this project. I think it's too large to include in the main README though. What do you think?
('Configurations' doesn't mean 'how to configure your project', it's more like how a naval fleet might have a 'battle configuration', with ships in specific positions to engage the enemy, or a 'restock at the harbour' configuration.)
Create some 'variant attributes'. Again, the docs make them sound way more complicated than they are. They're just key-value tags, used to differentiate between different files. Files might be tagged as 'Java source files' or 'JaCoCo coverage data'.
The values can be any string value, and Gradle provides some default keys which we can re-use. (Custom keys can be manually registered, which is more effort than it's worth.)
Any Configuration can be tagged with these attributes. Gradle will use the tags to play matchmaker between the Configurations.
It's nice to define the attribute-tags in buildSrc
so they can be more easily re-used.
// buildSrc/src/main/kotlin/distributions/attributes.kt
package distributions
import org.gradle.api.artifacts.Configuration
import org.gradle.api.attributes.Usage.USAGE_ATTRIBUTE
import org.gradle.api.model.ObjectFactory
import org.gradle.kotlin.dsl.*
fun Configuration.factorioModAttributes(objects: ObjectFactory): Configuration =
attributes {
attribute(USAGE_ATTRIBUTE, objects.named("my.project.factorio_mod"))
}
USAGE_ATTRIBUTE
is a built-in Gradle attribute-key, which we can re-use - so long as we make the value distinct enough, it won't clash!
Configurations are used to both provide and consume artifacts. It's a bit awkward trying to remember which combinations of booleans are needed (there is a table (Table 1. Configuration roles), but it's always hard to remember). So I like creating some helper utils - again, in buildSrc
.
// buildSrc/src/main/kotlin/distributions/configurationUtils.kt
package distributions
import org.gradle.api.artifacts.Configuration
import org.gradle.kotlin.dsl.*
/** Mark this [Configuration] as one that will be consumed by other subprojects. */
fun Configuration.asProvider() {
isVisible = false
isCanBeResolved = false
isCanBeConsumed = true
}
/** Mark this [Configuration] as one that will consume (also known as 'resolving') artifacts from other subprojects */
fun Configuration.asConsumer() {
isVisible = false
isCanBeResolved = true
isCanBeConsumed = false
}
This makes it easier to declare Configurations as outgoing providers, or incoming consumers.
In the 'provider' project, create an 'outgoing' Configuration, that will be consumed by other projects. Use register()
, because this configuration will be resolved on-demand. Add files using the Configuration.outgoing
property.
// subproject-factorio-mod/build.gradle.kts
import distributions.asProvider
import distributions.factorioModAttributes
val factorioModProvider by configurations.registering<Configuration> {
asProvider()
factorioModAttributes(objects)
outgoing.artifact(tasks.distZip.flatMap { it.archiveFile }) // using 'map'/'flatMap'
}
When adding outgoing artifacts, try to use map()
on task providers to get the output, so Gradle can be clever and automatically run the task when the 'outgoing' Configuration is consumed.
Create an 'incoming' Configuration that will consume Configurations from other projects.
Use create()
so the configuration plays nicely with the Kotlin DSL (see next step). The handy mnemonic I use is "Create Consumers"
// subproject-factorio-server/build.gradle.kts
import distributions.asConsumer
import distributions.factorioModAttributes
val factorioMod by configurations.creating<Configuration> {
asConsumer()
description = "Consumes Factorio Mod zip files from other subprojects"
factorioModAttributes(objects)
}
In the consumer project, add dependencies on another project using the 'incoming' Configuration.
// subproject-factorio-server/build.gradle.kts
dependencies {
factorioMod(project(":subproject-factorio-mod"))
}
The 'incoming' Configuration can now be resolved in a task, which will trigger the provider project to run its task and share them.
// subproject-factorio-server/build.gradle.kts
val deployModToFactorioServer by tasks.registering(Copy::class) {
description = "Copy the mod to the Factorio Docker server"
dependsOn(factorioMod)
from(
{ factorioMod.incoming.artifacts.artifactFiles.files }
)
into(layout.projectDirectory.dir("factorio-server/mods"))
doLast {
logger.lifecycle("Copying mods ${source.files} into $destinationDir")
}
}
It is great you put the items together, however, it would be great to add them to Gradle documentation.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.