Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion _layouts/talk.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
{% endif %}

{% for post in page.relevant_posts %}
<p><a href="{{ post.url | relative_url }}">Read {{ post.title }} →</a></p>
<p><a href="{{ post.url | relative_url }}">Read relevant blog post</a></p>
{% endfor %}

{{ content }}
Expand Down
22 changes: 16 additions & 6 deletions _posts/2026-08-22-where-should-gradle-build-logic-live.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ buildSrc/

The name of the file exposes the plugin as `sample.android.library`, which can then can already be applied to your project build scripts using `plugins { id("sample.android.library") }`

See this in [[Step 1] Extract build logic into buildSrc /w precompiled scripts](https://github.com/anthonymonori/sample-gradle-project/pull/5).
See this in [[Step 1] Extract build logic into buildSrc /w precompiled scripts](https://github.com/anthonymonori/sample-gradle-project/commit/ea9ca7606aaeff487ad91fb6d6330cdeb7438b9b).

## Is `buildSrc` the right place?

Expand All @@ -98,7 +98,7 @@ We can turn the `buildSrc` into a regular, explicitly included Gradle build proj

Composite builds are useful for more than build logic, but sharing plugins is one of their primary use cases. Read more about [composite builds](https://docs.gradle.org/current/userguide/composite_builds.html).

See this in [[Step 2] Move from buildSrc to build-logic /w precompiled scripts](https://github.com/anthonymonori/sample-gradle-project/pull/4).
See this in [[Step 2] Move from buildSrc to build-logic /w precompiled scripts](https://github.com/anthonymonori/sample-gradle-project/commit/a2dbee2ae7c62b8d035c88245e6be3d367c8e8ae).

If you have a small project and want the simplest possible setup, `buildSrc` remains a reasonable choice. If the build logic is growing, has dedicated ownership, or needs a clearer classpath and project boundary, an included build is likely a better fit.

Expand All @@ -108,15 +108,15 @@ Our `sample.android.library.gradle.kts` is already a plugin. Gradle generates a

Defining a [binary plugin](https://docs.gradle.org/current/userguide/implementing_gradle_plugins_binary.html#header) gives the implementation a clearer structure. We can split responsibilities across regular classes, use explicit types, isolate optional features, and test individual behaviours more easily — just like with our product source code. It also gives us greater control over how the plugin reacts to other plugins and which parts of its implementation become public.

See this in [[Step 3] Replace precompiled scripts with binary convention plugins](https://github.com/anthonymonori/sample-gradle-project/pull/3).
See this in [[Step 3] Replace precompiled scripts with binary convention plugins](https://github.com/anthonymonori/sample-gradle-project/commit/dc96b4de27d5e51c06110da3c7dfd807e28914dc).

This does not change the experience of the consuming module. It still applies the same convention plugin by ID. We are only changing how that convention is implemented behind the boundary.

## Does build logic only exist in project build scripts?

So far, we have focused on module configuration. However, a large amount of important build logic also lives in `settings.gradle.kts`. Settings may define repository policy, plugin resolution, build-cache configuration, feature previews, project discovery, and included builds. If this logic needs to be shared across multiple repositories, we can place it behind a settings plugin in exactly the same spirit as our project conventions.

See this in [[Step 4] Extract settings logic into a binary convention plugin](https://github.com/anthonymonori/sample-gradle-project/pull/2).
See this in [[Step 4] Extract settings logic into a binary convention plugin](https://github.com/anthonymonori/sample-gradle-project/commit/c05d905d77ac12cf1d317a9b7bec6f8319594696).

## What happens when modules need different capabilities?

Expand Down Expand Up @@ -164,7 +164,7 @@ sample {

The build logic can then apply the correct plugin and dependency configuration. Later, if we later make KSP the default, we can change that convention in one place. Modules that do not require a special override remain untouched.

See this in [[Step 5] Introduce a custom DSL for opt-in build features](https://github.com/anthonymonori/sample-gradle-project/pull/1).
See this in [[Step 5] Introduce a custom DSL for opt-in build features](https://github.com/anthonymonori/sample-gradle-project/commit/2a97f1e6a325de7a2d9c551e1132852573c13e88).

*In the next post, I’ll go into more detail about how to define a custom Gradle DSL, model its configuration lazily, and turn those declarations into plugin behaviour.*

Expand All @@ -180,7 +180,17 @@ As the conventions become more complex, binary plugins let us organise the imple

Finally, when modules need to select higher-level capabilities, a small custom DSL can become the public interface to that build logic. You do not necessarily need to reach the final step. Each abstraction should earn its place by removing a real source of repetition, inconsistency, or migration cost.

You can explore the complete progression in the [sample Gradle project](https://github.com/anthonymonori/sample-gradle-project), with each step preserved as a pull request.
You can explore the complete progression in the [sample Gradle project](https://github.com/anthonymonori/sample-gradle-project), with each step preserved as a commit.

## Watch the talk

This article accompanies my talk, *Scaling Your Build Logic*, presented at Londroid.

<div class="video-container">
<iframe src="https://www.youtube.com/embed/oVSvmUbnRd0" title="Scaling Your Build Logic — Londroid talk" loading="lazy" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
</div>

[View the talk page and slides →](/talks/scaling-your-build-logic)

---

Expand Down
78 changes: 67 additions & 11 deletions assets/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
--body-bg: #fff;
--primary-font-color: #000;
--border-color: #dadada;
--link-color: #0969da;
--link-hover-color: #0550ae;
--inline-code-bg: #eff1f3;
--inline-code-border: #d8dee4;
--code-block-border: #3f4347;
}

[data-theme="dark"] {
Expand All @@ -13,6 +18,11 @@
--body-bg: #10171e;
--primary-font-color: #bfbfbf;
--border-color: #6a6a6a;
--link-color: #79b8ff;
--link-hover-color: #a8d1ff;
--inline-code-bg: #252f3a;
--inline-code-border: #46515c;
--code-block-border: #59636e;
}

body{
Expand Down Expand Up @@ -69,7 +79,7 @@ body{
background: var(--bg-hover-color);
}

.post-list .post-title{width: 80%; color: var(--primary-font-color); font-weight: 600;}
.post-list .post-title{width: 80%; font-weight: 600;}
.post-list .post-date{width: 20%; text-align: right;}

.post-header{
Expand Down Expand Up @@ -116,8 +126,8 @@ body{
border:1px dashed var(--border-color);

}
.navigation .prev{width: 45%; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: var(--primary-font-color); font-weight: 600; padding: 5px 10px;}
.navigation .next{width: 45%; color: var(--primary-font-color); overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-weight: 600; text-align: right; padding: 5px 10px;}
.navigation .prev{width: 45%; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-weight: 600; padding: 5px 10px;}
.navigation .next{width: 45%; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-weight: 600; text-align: right; padding: 5px 10px;}

.pagination{
width: 100%;
Expand All @@ -130,15 +140,15 @@ body{
margin-bottom: 5px;
background:var(--bg-color);
}
.pagination .prev{color: var(--primary-font-color); font-weight: 600;}
.pagination .next{color: var(--primary-font-color); font-weight: 600; text-align: right;}
.pagination .prev{font-weight: 600;}
.pagination .next{font-weight: 600; text-align: right;}

.blog-post-content{
width: 100%;
font-size: 18px;
}

.post-tag{background: var(--bg-color); color:var(--primary-font-color); padding: 5px 10px;}
.post-tag{background: var(--bg-color); padding: 5px 10px;}

.profile{
width: 100%;
Expand Down Expand Up @@ -168,11 +178,57 @@ body{
.text-right{text-align: right;}
.w-100{width: 100%!important;}

a:hover{text-decoration: none;}
a{color: var(--primary-font-color); text-decoration: none;}
a{
color: var(--link-color);
text-decoration: underline;
text-decoration-thickness: 1px;
text-underline-offset: 0.16em;
}
a:hover{
color: var(--link-hover-color);
text-decoration-thickness: 2px;
}
.nav-links a,
.nav-links a:hover{
color: var(--primary-font-color);
text-decoration: none;
}
img{max-width: 100%;}
figure{margin:0; padding: 15px;}
pre{font-size: 14px; max-width: 90vw; overflow: auto;}
pre,
code{
font-family: ui-monospace, "SFMono-Regular", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-variant-ligatures: none;
}
.blog-post-content :not(pre) > code{
padding: 0.1em 0.32em;
background: var(--inline-code-bg);
border: 1px solid var(--inline-code-border);
border-radius: 4px;
font-size: 0.88em;
}
pre{
max-width: 90vw;
margin: 1.25em 0;
padding: 1rem 1.15rem;
overflow: auto;
font-size: 14px;
line-height: 1.55;
tab-size: 4;
}
.highlight{
margin: 1.25em 0;
overflow: hidden;
border: 1px solid var(--code-block-border);
border-radius: 7px;
}
.highlight pre{
max-width: none;
margin: 0;
}
.highlight code{
font-size: inherit;
}
.social-icon{height: 24px;}

@media (min-width: 1200px){
Expand Down Expand Up @@ -210,5 +266,5 @@ pre{font-size: 14px; max-width: 90vw; overflow: auto;}
.section-subheader .post-title {font-size: 1em; opacity: 0.7;}

.video-container {position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; margin: 1.5em 0;}
.video-container iframe {position: absolute; top: 0; left: 0; width: 100%; height: 100%;}
.talk-header {font-size: 1.1em; margin-bottom: 1.5em;}
.video-container iframe {position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;}
.talk-header {font-size: 1.1em; margin-bottom: 1.5em;}
Loading