Some questions
what is a build?
When programmers write software, they type out text files containing code. But a computer canβt run those raw text files directly. A build is the process of taking those raw code files and turning them into a finished, usable program (like an app you can download or a website you can visit).
what is an automator?
Doing the build process manually is slow and prone to human error. An automator (or build automation tool) is a program that does all this work automatically. When a programmer finishes their code, they press a button, and the automator handles the compiling, packing, and testing of the app all by itself.
Dependencies
Transitive dependencies
Indirect dependencies (dependencies of dependencies)
conflicts
dependency A requires B at version 1, dependency C requires B at version 2
conflict management
Introduce dependency ranges -> testing all possible combination impossible -> lock files give you a good set (that should work)
Build tools
Imperative bt
- example: CMake (C/C++)
- search for what to build / define target / manage dependencies / test, in a manual/imperative way (basically you have to specify everything, full control)
- extreme flexibility
Declarative bt
- example: Python Poetry, Apache Maven,
- favor markup files over scripts (xml, toml, json, yaml)
- lock file for dependency resolution
- super standard (rule follower)
hybrid automators
- example: Gradle
- a hybrid get something from both, hopefully in a way that help the engineer :)
Checklist of things to do when working with gradle
generated with claude opus 4.6 from some of my lecture notes
ποΈ Project Setup & Wrapper
- Create a
build.gradle.ktsin the project root β Gradle treats the folder itβs invoked in as a project; the file instructs it on the organization. - Generate the Gradle Wrapper at the desired version β
gradle wrapper --gradle-version=<VERSION>producesgradlew/gradlew.batscripts. - Always use
./gradlewinstead of baregradleβ The wrapper is the correct way to use Gradle; it pins the version and avoids global dependency issues. - Create
settings.gradle.ktsfor multi-project setups β Initialization phase: Gradle reads this to understand what is part of the build. - Understand the 3-phase lifecycle: Initialization β Configuration β Execution β Code outside
doLast/doFirstruns at configuration time, even if the task is never invoked!
βοΈ Tasks & Task Dependencies
- Register tasks with
tasks.register(configuration avoidance) β Preferregisterovercreate; configuration only happens when the task is actually needed. - Place actual work inside
doLast { }ordoFirst { }β Code directly in the task block runs at configuration time β a classic Gradle pitfall. - Declare task dependencies with
dependsOnβ E.g.,runJavamustdependsOn(compileJava), otherwise sources wonβt be compiled first. - Declare
inputsandoutputsfor incremental builds β Enables Gradleβs up-to-date checking; tasks are skipped when nothing changed. - Use typed tasks (e.g.
Exec, custom types) to encapsulate imperative logic β Specify the type at registration:tasks.register<Exec>("myTask"). - Ensure the task DAG is acyclic and correctly ordered β Tasks form a Directed Acyclic Graph; circular dependencies will cause build failures.
π¦ Configurations & Dependencies
- Define configurations (e.g.
compileClasspath,runtimeClasspath) β A configuration groups dependencies and has three roles: declare, resolve, and present. - Use
extendsFromto model configuration relationships β E.g.,runtimeClasspath.extendsFrom(compileClasspath)β runtime inherits compile deps. - Declare dependencies with the correct scope (
api/implementation/testImplementation) βapileaks to consumers,implementationis internal,testImplementationis test-only. - Be aware of transitive dependencies and potential conflicts β Dep A needs B@v1, dep C needs B@v2 β use
./gradlew dependenciesto inspect. - Use a version catalog (
gradle/libs.versions.toml) for DRY declarations β Centralizes versions, libraries, bundles, and plugins; Gradle generates type-safe accessors. - Use
dependencyInsightto debug specific dependency resolution β./gradlew dependencyInsight --dependency <name> --configuration <conf>
π§© Plugins & Conventions
- Apply relevant built-in plugins (
java,java-library,kotlin, etc.) β Built-in plugins provide a full lifecycle: compile, test, jar, check, assemble, build. - Apply plugins via the
plugins { }block with version if needed β Plugins not found locally are fetched from the Gradle Plugin Portal. - Extract shared build logic into
buildSrcconvention plugins β Place them inbuildSrc/src/main/kotlin/convention-name.gradle.kts. - Follow convention over configuration: keep defaults where sensible β Standard source layout (
src/main/java,src/test/java) works out of the box. - Isolate imperativity: hide it in task types / plugins, expose declarative API β Divide β Conquer β Encapsulate β Adorn: the general plugin design approach.
β Quality Control & Testing
- Make quality tasks depend on
checkβcheckis meant to run all QA tasks; by default it depends ontest. - Configure test framework (e.g. JUnit 5 / Kotest) β Ensure
tasks.test { useJUnitPlatform() }is set for JUnit 5. - Set up code coverage (Jacoco / Kover) β Coverage can only spot uncovered code β it canβt tell you if covered code is correct.
- Enable style checking (Ktlint / Checkstyle) β Opinionated formatters enforce consistent code style across the team.
- Enable static analysis (Detekt / Spotbugs / PMD) β Catches suboptimal patterns, potential bugs, and code smells without execution.
- Set Kotlin compiler to βwarnings as errorsβ mode β An aggressive but effective way to catch potential issues at compile time.
- Use Gradle Test Kit for testing custom plugins β
GradleRunner.create().withProjectDir(...).withPluginClasspath(...).build()to run and inspect. - Inspect QA reports under
build/reports/β Test results, coverage reports, and analysis findings are published there.
π§ Build Toolchains
- Distinguish compilation target, runtime target, and build-time runtime β You may use Java 17 to run Gradle, compile to Java 8 bytecode, and test on Java 11.
- Configure JVM toolchains independently from the Gradle JVM β Gradle defaults to using the same JVM for build, compile, and test β override when needed.
- For multi-platform (Kotlin): configure targets (JVM, JS, Native) explicitly β Each platform has its own source sets, dependencies, and compilation pipeline.
π Publishing & Distribution
- Generate API documentation automatically (Dokka / Javadoc) β Automate doc generation as part of the build lifecycle.
- Create artifacts in a Maven-repository-compatible layout β Required for publishing to Maven Central or other repositories.
- Sign all artifacts for publication β Maven Central requires GPG signing of all published artifacts.
- Configure plugin publishing credentials securely β Use
~/.gradle/gradle.propertiesor-Pflags β never hardcode secrets in build files. - For Gradle plugins: publish via the Gradle Plugin Portal β Register on the portal, use the
com.gradle.plugin-publishplugin.
π Debugging & Inspection
- Run
./gradlew tasksto see available tasks β Includes built-in, plugin, and custom tasks grouped by category. - Run
./gradlew dependenciesto inspect dependency trees β Shows the resolved dependency tree for each configuration. - Use the task-tree plugin to visualize task dependencies β Plugin:
com.dorongold.task-treeβ generates ataskTreetask. - Use
--scanfor a full Gradle Build Scan β Rich web-based report for performance, dependencies, and plugin behavior. - Configure automated build scans in
settings.gradle.kts(Develocity) β Useful for CI β setuploadInBackgroundbased on CI environment variable.