<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Tony Robalik</title>
    <description>The latest articles on Forem by Tony Robalik (@autonomousapps).</description>
    <link>https://forem.com/autonomousapps</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F8441%2F71c37167-9645-4128-977a-d1e67bc00f2a.png</url>
      <title>Forem: Tony Robalik</title>
      <link>https://forem.com/autonomousapps</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/autonomousapps"/>
    <language>en</language>
    <item>
      <title>Enshittification: Why I’m leaving dev.to</title>
      <dc:creator>Tony Robalik</dc:creator>
      <pubDate>Sun, 30 Nov 2025 20:47:58 +0000</pubDate>
      <link>https://forem.com/autonomousapps/enshittification-why-im-leaving-devto-3npn</link>
      <guid>https://forem.com/autonomousapps/enshittification-why-im-leaving-devto-3npn</guid>
      <description>&lt;p&gt;With thanks to &lt;a href="https://theoatmeal.com/comics/reaching_people" rel="noopener noreferrer"&gt;The Oatmeal&lt;/a&gt; for the cover art.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;See also: &lt;a href="https://en.wikipedia.org/wiki/Enshittification" rel="noopener noreferrer"&gt;enshittification&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Please consider reading this post on &lt;a href="https://autonomousapps.com/blog/enshittification/post/" rel="noopener noreferrer"&gt;autonomousapps.com&lt;/a&gt; instead.&lt;/p&gt;

&lt;p&gt;This will be my last post on the site dev.to. I’ve been posting my blog to that site since April 2018. My very first post was written when I was the Android team lead at Chess.com, titled &lt;a href="https://autonomousapps.com/blog/chess-dot-com/post/" rel="noopener noreferrer"&gt;Rewriting Chess.com’s Android app&lt;/a&gt;. Over the years, I’ve been more or less active, and have published 42 posts in all (counting this one).&lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;I’ve had concerns about the long-term viability of my presence on that site for a few years, as I’ve observed layers and layers of cruft accumulate around my actual content. I always had higher priorities though, and justified the cruft by recognizing that the site is providing a free service, and it frankly makes posting a tech blog to the internet fairly easy.&lt;/p&gt;

&lt;p&gt;The last straw came when, after my &lt;a href="https://autonomousapps.com/blog/java-cursed/post/" rel="noopener noreferrer"&gt;most recent post&lt;/a&gt;, two separate people contacted me about their experience trying to read it (one of them twice). First I was told that there’s a kind of soft login wall when visiting the post. I wasn’t aware of this because, as a writer on the site, I’m always logged in. The wall opens when interacting with one of those annoying popups so common on the Web these days:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F84j220jg0z1o4445d9mc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F84j220jg0z1o4445d9mc.png" alt="Mandatory pop-up asking readers to acknowledge a vague statement of appreciation" width="800" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note there’s only one option. Clicking it leads to this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvhuu467h0k55qmyw1qkv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvhuu467h0k55qmyw1qkv.png" alt="Very annoying pop-up implying that readers must log in to continue" width="800" height="837"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Which fortunately can be dismissed. This is a &lt;a href="https://en.wikipedia.org/wiki/Dark_pattern" rel="noopener noreferrer"&gt;dark pattern&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Then later, someone sent me a screenshot of what it looks like after the login cruft has been brushed away:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fukat1xdsheaekmjgqcs7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fukat1xdsheaekmjgqcs7.png" alt="Screencap showing that 79% of page is covered in non-content cruft. Most of it is an ad for something 'AI'-related" width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’ve helpfully highlighted the portions of this screencap that are actual content. 21% of the screen real estate! And more than half the remaining is an ad for some bullshit “AI” product.&lt;/p&gt;

&lt;p&gt;Let me state this very clearly: fuck “AI.” It’s pure marking speak for a set of technologies founded upon harm, whose primary use-cases are harm. I want nothing to do with it.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;An auspicious number. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>enshittification</category>
    </item>
    <item>
      <title>Is the Java ecosystem cursed? A dependency analysis perspective</title>
      <dc:creator>Tony Robalik</dc:creator>
      <pubDate>Mon, 24 Nov 2025 20:15:12 +0000</pubDate>
      <link>https://forem.com/autonomousapps/is-the-java-ecosystem-cursed-a-dependency-analysis-perspective-53ef</link>
      <guid>https://forem.com/autonomousapps/is-the-java-ecosystem-cursed-a-dependency-analysis-perspective-53ef</guid>
      <description>&lt;p&gt;I am the author of the moderately popular (⭐ 2k) &lt;a href="https://github.com/autonomousapps/dependency-analysis-gradle-plugin" rel="noopener noreferrer"&gt;Dependency Analysis Gradle Plugin&lt;/a&gt;, a static analysis tool that helps Gradle build authors maintain a healthy dependency graph. I also maintain some of the largest Gradle repos on the planet: a Kotlin backend repo with over 2500 subprojects, and an Android repo with more than 7200 subprojects (both proprietary). I have… seen some shit.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: I refer to both the cases above as being part of the "Java ecosystem," though both use Kotlin as the preferred language, and one runs on the JVM while the other runs on &lt;a href="https://source.android.com/docs/core/runtime" rel="noopener noreferrer"&gt;ART&lt;/a&gt; (the Android runtime) on mobile devices.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I come to you with a simple proposition: I believe the Java ecosystem is cursed. Hear me out.&lt;/p&gt;

&lt;h3&gt;
  
  
  We are cursed with…
&lt;/h3&gt;

&lt;p&gt;Lying metadata, overuse of "fat" jars with underuse of package relocation, split packages, undocumented usage of reflection to access upstream dependencies, usage of terms like "upstream" that have different meanings in different contexts, misuse of protobuffers, different compilers with different notions of their obligations vis-a-vis the Java class file format…&lt;/p&gt;

&lt;h4&gt;
  
  
  Lying metadata
&lt;/h4&gt;

&lt;p&gt;This was already covered in-depth in &lt;a href="https://dev.to/autonomousapps/this-is-why-we-cant-have-nice-things-when-pom-files-lie-3lm5"&gt;This is why we can't have nice things: When POM files lie&lt;/a&gt;, but the summary is: sometimes dependencies have hand-written metadata, which is certainly A Choice given that build tools exist. I suppose it's harder to teach a build tool to lie.&lt;/p&gt;

&lt;h4&gt;
  
  
  It's just a list, man
&lt;/h4&gt;

&lt;p&gt;Despite the bewildering complexity of dependency resolution engines in tools like Gradle and Maven, at the end of the day a classpath is just a list of class files (and jars that package class files). When your running program "sees" a class or interface for the first time, it has to load it. It does this with a &lt;a href="https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/lang/ClassLoader.html" rel="noopener noreferrer"&gt;&lt;code&gt;ClassLoader&lt;/code&gt;&lt;/a&gt;. The classloader searches the classpath (just a list of class files!)&lt;sup id="fnref1"&gt;1&lt;/sup&gt; and picks the &lt;strong&gt;first&lt;/strong&gt; class file that matches the class it just encountered. Importantly, your classpath may have more than one class file for that class. Even well-behaved builds may have this problem, for a variety of reasons, some of which are noted below.&lt;/p&gt;

&lt;p&gt;As I was writing this post, I saw yet another reason to fear the classpath, in the &lt;a href="https://newsletter.gradle.org/2025/11" rel="noopener noreferrer"&gt;November Gradle newsletter&lt;/a&gt;: &lt;a href="https://arxiv.org/pdf/2407.18760" rel="noopener noreferrer"&gt;Maven-Hijack: Software Supply Chain Attack: Exploiting Packaging Order&lt;/a&gt;. Bad actors can make use of this fundamental property of the JVM to insert malicious code into your applications. Or, as we'll see below, you can just do it to yourself!&lt;/p&gt;

&lt;h4&gt;
  
  
  Fat jars without package relocation
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://gradleup.com/shadow/" rel="noopener noreferrer"&gt;Shadow&lt;/a&gt; is a powerful tool for creating "uber" or "fat" jars, which are jars that contain &lt;em&gt;all&lt;/em&gt; their external dependencies rather than relying on a classpath. This can simplify deployments of applications since deployers only need to worry about a single jar instead of dozens, hundreds, or thousands of jars. This is fine. It becomes cursed when &lt;em&gt;libraries&lt;/em&gt; make use of this tool, resulting in broken classpaths that contain duplicate class files such that runtime behavior is dependent on the classpath's order. I would like to point the maintainers of these libraries at Shadow's powerful &lt;a href="https://gradleup.com/shadow/configuration/relocation/" rel="noopener noreferrer"&gt;relocation&lt;/a&gt; abilities, which enable it to change the package of bundled classes such that there can be no duplicate class problem.&lt;/p&gt;

&lt;p&gt;As I've said before, the extent to which Java's packages exist in a global namespace is not well-appreciated.&lt;/p&gt;

&lt;h4&gt;
  
  
  Split packages
&lt;/h4&gt;

&lt;p&gt;As will be discussed tangentially below, the existence of split packages complicates dependency analysis because it makes it harder to connect class names with the modules that provide them, since there is now a 1-to-many relationship between packages and modules.&lt;/p&gt;

&lt;p&gt;I mostly work in Kotlin repos, both backend and Android, neither of which use &lt;a href="https://en.wikipedia.org/wiki/Java_Platform_Module_System" rel="noopener noreferrer"&gt;JPMS&lt;/a&gt; (the Java Platform Module System). I can't say from direct experience how widely used is JPMS in the pure Java world, but as the maintainer of an increasingly complicated static analysis tool, I can say I wish more projects used it.&lt;/p&gt;

&lt;p&gt;(Kotlin users would say they get the benefits of JPMS thanks to the &lt;code&gt;internal&lt;/code&gt; visibility modifier, but they're wrong.)&lt;/p&gt;

&lt;h4&gt;
  
  
  Funhouse mirrors (aka reflection)
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;com.amazonaws:aws-java-sdk-core&lt;/code&gt; has a &lt;a href="https://github.com/aws/aws-sdk-java/blob/master/aws-java-sdk-core/src/main/java/com/amazonaws/auth/profile/internal/securitytoken/STSProfileCredentialsServiceProvider.java#L50C59-L66" rel="noopener noreferrer"&gt;method&lt;/a&gt;, &lt;code&gt;getProfileCredentialService()&lt;/code&gt;, which uses reflection to access a class from the &lt;code&gt;com.amazonaws:aws-java-sdk-sts&lt;/code&gt; library. This is a compound curse, composed of these properties:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;com.amazonaws:aws-java-sdk-sts&lt;/code&gt; &lt;a href="https://central.sonatype.com/artifact/com.amazonaws/aws-java-sdk-sts" rel="noopener noreferrer"&gt;depends on&lt;/a&gt; &lt;code&gt;com.amazonaws:aws-java-sdk-core&lt;/code&gt;, not the other way around.
&lt;/li&gt;
&lt;li&gt;Triggering the code path for &lt;code&gt;getProfileCredentialService()&lt;/code&gt; will throw an exception if &lt;code&gt;com.amazonaws:aws-java-sdk-sts&lt;/code&gt; is not on the classpath, raising the question of why the dependencies are structured this way.
&lt;/li&gt;
&lt;li&gt;The Java ecosystem has first-class functionality, &lt;a href="https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/ServiceLoader.html" rel="noopener noreferrer"&gt;Service Loaders&lt;/a&gt;, for dynamically instantiating something that might or might not be on the classpath.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The Dependency Analysis Gradle Plugin has had support for Service Loaders since the beginning of its existence. First-class features such as service loading are great for static analysis tools like DAGP, as they give it well-known places to search during analysis. Ad hoc approaches like reflection are trickier, and require substantially more complex approaches to handle. DAGP added support for &lt;code&gt;Class.forName("...")&lt;/code&gt; in &lt;a href="https://github.com/autonomousapps/dependency-analysis-gradle-plugin/blob/main/CHANGELOG.md#version-330" rel="noopener noreferrer"&gt;v3.3.0&lt;/a&gt;. Pre-3.3.0, DAGP would suggest removing the &lt;code&gt;sts&lt;/code&gt; dependency as unused if it couldn't detect any direct reference to any of the classes it provides in the bytecode, leading to runtime failures, either in CI (ok) or post-deployment (bad).&lt;/p&gt;

&lt;h4&gt;
  
  
  Protocol Buffers
&lt;/h4&gt;

&lt;p&gt;Protocol buffers, aka &lt;a href="https://protobuf.dev/" rel="noopener noreferrer"&gt;protobufs&lt;/a&gt;, are an amazing tool for making a build engineer's days a living nightmare. First we must note that there are at least two competing protobuf compilers in the JVM world: &lt;a href="https://github.com/protocolbuffers/protobuf" rel="noopener noreferrer"&gt;Google's protoc&lt;/a&gt; and &lt;a href="https://square.github.io/wire/" rel="noopener noreferrer"&gt;Square's Wire&lt;/a&gt;. I happen to work at a company that uses &lt;em&gt;both&lt;/em&gt;. I don't think I hate myself, but maybe God does. These compilers generate code (Java or Kotlin) from the protobuf format that are mutually incompatible without adapters,&lt;sup id="fnref2"&gt;2&lt;/sup&gt; meaning that once you have both in your codebase, you will probably always have both—congrats.&lt;/p&gt;

&lt;p&gt;I have also seen several modules with both plugins in use simultaneously. Well.&lt;/p&gt;

&lt;p&gt;I work with Gradle. Each of the competing compilers comes with a Gradle plugin. I may be slightly biased, but I think the Wire Gradle Plugin is better. Nevertheless, the relative ease with which either can be configured leads to Fun Situations such as: two modules can each depend on the same proto files, possibly at different versions, leading to generated code with the same exact class name but different definitions. And now if you have a third module that depends on these two modules, you're in a situation where your module may fail to compile if you just so happen to change the order of your dependency declarations, or worse, it may compile in both cases but fail at runtime for a similar reason. This is because, as discussed above, a classpath is &lt;em&gt;just&lt;/em&gt; a collection of jars and class files, and whichever class file gets loaded first wins forever.&lt;/p&gt;

&lt;p&gt;I have now worked in two separate extremely large codebases that have significant usage of protos, and it is no exaggeration to say that dealing with them is &lt;em&gt;almost&lt;/em&gt; the worst part of my job ("AI" has recently taken that crown).&lt;/p&gt;

&lt;h4&gt;
  
  
  Yolo compilers
&lt;/h4&gt;

&lt;p&gt;It turns out that different compilers have different ideas of what the resultant class files should look like. &lt;a href="https://docs.oracle.com/javase/specs/jvms/se25/html/jvms-4.html#jvms-4.4" rel="noopener noreferrer"&gt;Chapter 4 of the JVM specification&lt;/a&gt; discusses the &lt;strong&gt;Constant Pool&lt;/strong&gt;. &lt;code&gt;class&lt;/code&gt; files contain a table, known as the &lt;code&gt;constant_pool&lt;/code&gt;, which contains a reference to every constant present in the source code of a Java file. This is useful for static analysis because the various JVM compilers all&lt;sup id="fnref3"&gt;3&lt;/sup&gt; inline constants for runtime efficiency. This means that a constant like &lt;code&gt;public static final String CONSTANT = "magic"&lt;/code&gt; gets turned into simply &lt;code&gt;"magic"&lt;/code&gt; at the use-site, and similarly for Kotlin's &lt;code&gt;const val&lt;/code&gt;. Therefore simply analyzing the bytecode directly with a tool like &lt;a href="https://asm.ow2.io/index.html" rel="noopener noreferrer"&gt;asm&lt;/a&gt; won't enable static analysis tools to connect the user of a constant to the maybe-separate module that provides the constant. Thanks to the constant pool, however, we can see the full reference to the provider and make the connection.&lt;/p&gt;

&lt;p&gt;This only works for class files compiled with &lt;code&gt;javac&lt;/code&gt;, however. For both &lt;code&gt;kotlinc&lt;/code&gt; and &lt;code&gt;ec4j&lt;/code&gt; (the Eclipse compiler for Java, yes this does exist and Real Teams in the world &lt;a href="https://github.com/autonomousapps/dependency-analysis-gradle-plugin/issues/735" rel="noopener noreferrer"&gt;rely on it&lt;/a&gt;), keeping these full references to inlined constants in the constant pool is considered unnecessary.&lt;/p&gt;

&lt;p&gt;The Dependency Analysis Gradle Plugin has a class, &lt;a href="https://github.com/autonomousapps/dependency-analysis-gradle-plugin/blob/main/src/main/kotlin/com/autonomousapps/internal/ConstantPoolParser.kt" rel="noopener noreferrer"&gt;&lt;code&gt;ConstantPoolParser&lt;/code&gt;&lt;/a&gt;, which parses the constant pool of a class file and extracts the set of class file references for all the class's inlined constants. When passed a reference to a class file compiled with something other than &lt;code&gt;javac&lt;/code&gt;, the returned set is empty. This leads to "unused dependency" false positives, when a dependency is only used for the constants it contains, a surprisingly common situation.&lt;/p&gt;

&lt;p&gt;As a consequence, DAGP utilizes some heuristics to try to workaround this situation—with imperfect results. I won't go into details here, but they involve parsing source code for import statements,&lt;sup id="fnref4"&gt;4&lt;/sup&gt; looking at the &lt;a href="https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/lang/classfile/Opcode.html#LDC" rel="noopener noreferrer"&gt;&lt;code&gt;ldc&lt;/code&gt; bytecode instruction&lt;/a&gt;,&lt;sup id="fnref5"&gt;5&lt;/sup&gt; etc. Source parsing falls over in the presence of split packages, and the &lt;code&gt;ldc&lt;/code&gt; bytecode only provides the constant &lt;em&gt;value&lt;/em&gt;, not its &lt;em&gt;name&lt;/em&gt;. Together, the heuristics get most of the way there, and it's unlikely the tool will get more accurate here without much more sophisticated source code analysis. Happy to work on that if you want to fund me!&lt;/p&gt;

&lt;h2&gt;
  
  
  Special thanks
&lt;/h2&gt;

&lt;p&gt;Special thanks to &lt;a href="https://bsky.app/profile/luis.cortes.social" rel="noopener noreferrer"&gt;Luis Cortés&lt;/a&gt; once again for the thorough review!&lt;/p&gt;

&lt;h2&gt;
  
  
  Who stalks us in the darkness?
&lt;/h2&gt;

&lt;p&gt;The above list of &lt;del&gt;grievances&lt;/del&gt; curses should not be taken as comprehensive. It is merely the list of things that have most recently destroyed my will to live.&lt;sup id="fnref6"&gt;6&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;…wait, who is that behind me, in the dark wood…?  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fshq20d62aeyty2btw2pe.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fshq20d62aeyty2btw2pe.jpg" alt="Baba Yaga" width="800" height="1024"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Baba_Yaga" rel="noopener noreferrer"&gt;Baba Yaga&lt;/a&gt; (no not &lt;a href="https://en.wikipedia.org/wiki/John_Wick" rel="noopener noreferrer"&gt;that one&lt;/a&gt;) This is what I think of when I imagine the Kotlin compiler given human form.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;I'm eliding some complexity around the classloader hierarchy, and the possibility you may have a custom classloader that doesn't follow standard behavior. See &lt;a href="https://dev.to/autonomousapps/build-compile-run-a-crash-course-in-classpaths-f4g"&gt;A crash course in classpaths&lt;/a&gt; for more information. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;To be clear, this is not about Java/Kotlin interop, but the fact that each compiler (&lt;code&gt;protoc&lt;/code&gt; and &lt;code&gt;wire&lt;/code&gt;) simply emit different code from the same protobuf schema. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;I think they all do, but I haven't checked exhaustively. At least &lt;code&gt;java&lt;/code&gt;, &lt;code&gt;ec4j&lt;/code&gt;, and &lt;code&gt;kotlinc&lt;/code&gt; do. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;See &lt;a href="https://github.com/autonomousapps/dependency-analysis-gradle-plugin/blob/main/src/main/kotlin/com/autonomousapps/internal/parse/SourceListener.kt#L14" rel="noopener noreferrer"&gt;here&lt;/a&gt; for where DAGP parses source code in a very simplified way. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn5"&gt;
&lt;p&gt;This post is already too long for me to explain in depth what I mean here. You can see where DAGP visits the LDC instruction &lt;a href="https://github.com/autonomousapps/dependency-analysis-gradle-plugin/blob/main/src/main/kotlin/com/autonomousapps/internal/asm.kt#L536-L539" rel="noopener noreferrer"&gt;here&lt;/a&gt;. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn6"&gt;
&lt;p&gt;Kidding! Once again the thing that's killing my will to live is just "AI." ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>java</category>
      <category>kotlin</category>
      <category>gradle</category>
    </item>
    <item>
      <title>This is why we can't have nice things: When POM files lie</title>
      <dc:creator>Tony Robalik</dc:creator>
      <pubDate>Fri, 07 Feb 2025 23:54:47 +0000</pubDate>
      <link>https://forem.com/autonomousapps/this-is-why-we-cant-have-nice-things-when-pom-files-lie-3lm5</link>
      <guid>https://forem.com/autonomousapps/this-is-why-we-cant-have-nice-things-when-pom-files-lie-3lm5</guid>
      <description>&lt;p&gt;&lt;small&gt;&lt;em&gt;Photo by &lt;a href="https://unsplash.com/@mbicca?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Marco Bicca&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/empty-water-tunnel-1XWR9oI9AFA?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;. I really hope there's a light at the end of this sewer.&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;Sorry, my country is dissolving into a Nazi sewer of world-historical proportions and so I'm dealing with my otherwise fruitless rage by yelling about JVM things.&lt;/p&gt;

&lt;p&gt;The fact that Java classes exist in a global namespace is not well-appreciated, even by vendors of major parts of the ecosystem (apparently).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Caused by: java.lang.ClassCastException: class com.google.common.graph.ImmutableGraph cannot be cast to class com.google.common.graph.SuccessorsFunction (com.google.common.graph.ImmutableGraph and com.google.common.graph.SuccessorsFunction are in unnamed module of loader org.gradle.internal.classloader.VisitableURLClassLoader$InstrumentingVisitableURLClassLoader @4617120)  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A confusing error at the best of times, but to a build engineer (i.e. me) trying to help out during a SEV, a despair-inducing example of how Dependency Management on the JVM is Completely Broken, What Are We Even Doing Here.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;ClassCastException&lt;/code&gt; &lt;em&gt;always&lt;/em&gt; means Someone Somewhere Hates You, or at least values their own KPIs more than not polluting the entire goddamn ecosystem.&lt;/p&gt;

&lt;p&gt;Let's debug! My first step is to navigate to the &lt;code&gt;Graph&lt;/code&gt; class and check its hierarchy. I can confirm that, yes, in Guava 33.3.1-jre at least, that class does indeed implement the &lt;code&gt;SuccessorsFunction&lt;/code&gt; interface. It should not be throwing a &lt;code&gt;ClassCastException&lt;/code&gt;!&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;Next step. In Intellij (the vendor which is ironically the source of this amongst most of the rest of my woes), I set a breakpoint at the exception and evaluate the following expression.&lt;sup&gt;1&lt;/sup&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// `this` is an instance of the `com.google.common.graph.Graph` class
this.javaClass.superclass.superclass.superclass.interfaces.first().protectionDomain.codeSource.location
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That expression resolves to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;file:/Users/&amp;lt;&amp;lt;ME!&amp;gt;&amp;gt;/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-compiler/2.0.21/88f09afc2536e38d528e78eb8349504de10ac436/kotlin-compiler-2.0.21.jar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What the fuck! That is not the right jar!! Let's double-check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jar tf path/to/kotlin-compiler-2.0.21.jar
…
com/google/common/graph/Graph.class
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;😭&lt;/p&gt;

&lt;p&gt;Maybe it's a bug! Let's look at the latest version of the jar at time of writing, 2.2.10 (nope, same problem). Not only has Jetbrains, the inventor of the Kotlin language, released a library that is &lt;em&gt;bundling Guava&lt;/em&gt; (among many other things!!) in a fatjar, they're not even bothering to &lt;em&gt;relocate the damn packages&lt;/em&gt;. Ok, I'm curious, what does the pom.xml have to say for itself?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;project&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://maven.apache.org/POM/4.0.0"&lt;/span&gt; &lt;span class="na"&gt;xsi:schemaLocation=&lt;/span&gt;&lt;span class="s"&gt;"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"&lt;/span&gt; &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;modelVersion&amp;gt;&lt;/span&gt;4.0.0&lt;span class="nt"&gt;&amp;lt;/modelVersion&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.jetbrains.kotlin&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;kotlin-compiler&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;2.1.10&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;name&amp;gt;&lt;/span&gt;Kotlin Compiler&lt;span class="nt"&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;description&amp;gt;&lt;/span&gt;Kotlin Compiler&lt;span class="nt"&gt;&amp;lt;/description&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;url&amp;gt;&lt;/span&gt;https://kotlinlang.org/&lt;span class="nt"&gt;&amp;lt;/url&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;licenses&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;license&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;name&amp;gt;&lt;/span&gt;The Apache License, Version 2.0&lt;span class="nt"&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;url&amp;gt;&lt;/span&gt;http://www.apache.org/licenses/LICENSE-2.0.txt&lt;span class="nt"&gt;&amp;lt;/url&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/license&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/licenses&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;developers&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;developer&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;name&amp;gt;&lt;/span&gt;Kotlin Team&lt;span class="nt"&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;organization&amp;gt;&lt;/span&gt;JetBrains&lt;span class="nt"&gt;&amp;lt;/organization&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;organizationUrl&amp;gt;&lt;/span&gt;https://www.jetbrains.com&lt;span class="nt"&gt;&amp;lt;/organizationUrl&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/developer&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/developers&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;scm&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;connection&amp;gt;&lt;/span&gt;scm:git:https://github.com/JetBrains/kotlin.git&lt;span class="nt"&gt;&amp;lt;/connection&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;developerConnection&amp;gt;&lt;/span&gt;scm:git:https://github.com/JetBrains/kotlin.git&lt;span class="nt"&gt;&amp;lt;/developerConnection&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;url&amp;gt;&lt;/span&gt;https://github.com/JetBrains/kotlin&lt;span class="nt"&gt;&amp;lt;/url&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/scm&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.jetbrains.kotlin&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;kotlin-stdlib-jdk8&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;2.1.10&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;scope&amp;gt;&lt;/span&gt;compile&lt;span class="nt"&gt;&amp;lt;/scope&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.jetbrains.kotlin&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;kotlin-script-runtime&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;2.1.10&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;scope&amp;gt;&lt;/span&gt;compile&lt;span class="nt"&gt;&amp;lt;/scope&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.jetbrains.kotlin&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;kotlin-reflect&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.6.10&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;scope&amp;gt;&lt;/span&gt;compile&lt;span class="nt"&gt;&amp;lt;/scope&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;exclusions&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;exclusion&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;*&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;*&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/exclusion&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/exclusions&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.jetbrains.intellij.deps&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;trove4j&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.0.20200330&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;scope&amp;gt;&lt;/span&gt;compile&lt;span class="nt"&gt;&amp;lt;/scope&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.jetbrains.kotlinx&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;kotlinx-coroutines-core-jvm&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.6.4&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;scope&amp;gt;&lt;/span&gt;compile&lt;span class="nt"&gt;&amp;lt;/scope&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is &lt;em&gt;no indication that this is a fatjar in the metadata&lt;/em&gt;, not to mention the hilarious fact that they are declaring a dependency on kotlin-reflect &lt;em&gt;1.6.10&lt;/em&gt;. But someone at some point noticed that this was Causing A Problem, so they &lt;em&gt;excluded all of its dependencies&lt;/em&gt;. Are you kidding me. Guys, you &lt;em&gt;&lt;a href="https://central.sonatype.com/artifact/org.jetbrains.kotlin/kotlin-bom/versions" rel="noopener noreferrer"&gt;publish a BOM&lt;/a&gt;&lt;/em&gt;, just use it!&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;It is entirely unclear why some dependencies are being declared and others are being silently bundled into the final artifact. Maybe I can get an answer at &lt;a href="https://youtrack.jetbrains.com/issue/KT-74969/ClassCastException-between-com.google.common.graph.ImmutableGraph-and-com.google.common.graph.SuccessorsFunction" rel="noopener noreferrer"&gt;this issue I raised&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why am I even using this dependency?
&lt;/h2&gt;

&lt;p&gt;Well, I had been using &lt;code&gt;kotlin-compiler-embeddable&lt;/code&gt; for source-code analysis,&lt;sup&gt;2&lt;/sup&gt; which at least relocates its bundled classes. However, using that in the same project that has a dependency on the Kotlin Gradle Plugin leads to this build warning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;w: The artifact `org.jetbrains.kotlin:kotlin-compiler-embeddable` is present in the build classpath along Kotlin Gradle plugin. 
This may lead to unpredictable and inconsistent behavior.   
For more details, see: https://kotl.in/gradle/internal-compiler-symbols 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A search online for a suitable replacement for &lt;code&gt;kotlin-compiler-embeddable&lt;/code&gt; turned up this on &lt;a href="https://discuss.kotlinlang.org/t/kotlin-compiler-embeddable-vs-kotlin-compiler/3196/2" rel="noopener noreferrer"&gt;discuss.kotlinlang.org&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;kotlin-compiler-embeddable&lt;/code&gt; should be used in scenarios when it’s necessary to have the compiler packaged as a single jar with no external dependencies. In all other cases, use the regular &lt;code&gt;kotlin-compiler&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;That answer implies that &lt;code&gt;kotlin-compiler&lt;/code&gt; &lt;em&gt;does&lt;/em&gt; have external dependencies (which is true), while also further implying that it abides by the standard JVM library contract by Actually Declaring all its dependencies (which is false).&lt;sup&gt;3&lt;/sup&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Coping
&lt;/h2&gt;

&lt;p&gt;This raises the question, how can we cope with this? Well, assuming your use-case for this is in a Gradle task and your task is relatively well-written, you can migrate the task action to use the Worker API with classloader isolation. The &lt;a href="https://kotlinlang.org/docs/whatsnew21.html#compiler-symbols-hidden-from-the-kotlin-gradle-plugin-api" rel="noopener noreferrer"&gt;link&lt;/a&gt; provided in the warning does a fair job of explaining how to do that, assuming you're ok with drawing the owl.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe1jptgagl17o1l0zfd8i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe1jptgagl17o1l0zfd8i.png" alt="How to draw an owl: first, draw some circles. Second, draw the rest of the fucking owl." width="530" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Ah, fuck it, let's draw the owl
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Drawing the build script
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// plugin/build.gradle.kts&lt;/span&gt;
&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="n"&gt;etc&lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;

&lt;span class="nf"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;project&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":shared-lib"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;exclude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"org.jetbrains.kotlin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"kotlin-compiler"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Jetbrains' docs suggest using &lt;code&gt;compileOnly("org.jetbrains.kotlin:kotlin-compiler-embeddable:2.1.10")&lt;/code&gt;, which isn't wrong, but also won't work if that lib is actually coming from a transitive dependency.&lt;/p&gt;

&lt;h3&gt;
  
  
  Drawing the task
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OwlTask&lt;/span&gt; &lt;span class="nd"&gt;@Inject&lt;/span&gt; &lt;span class="k"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;workerExecutor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WorkerExecutor&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;DefaultTask&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nc"&gt;Classpath&lt;/span&gt; 
  &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;kotlinCompiler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ConfigurableFileCollection&lt;/span&gt;

  &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nc"&gt;Input&lt;/span&gt;
  &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;otherInput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Property&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nd"&gt;@TaskAction&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;action&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;workerExecutor&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;classLoaderIsolation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="c1"&gt;// please note that from() adds to the&lt;/span&gt;
        &lt;span class="c1"&gt;// classpath, while setFrom() would&lt;/span&gt;
        &lt;span class="c1"&gt;// completely override it.&lt;/span&gt;
        &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;classpath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kotlinCompiler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Action&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;otherInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;otherInput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;Parameters&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WorkParameters&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;otherInput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Property&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WorkAction&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;otherInput&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;otherInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="err"&gt;…&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Drawing the plugin
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OwlPlugin&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Plugin&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Use the project's version of Kotlin, if present. Else default to what we use.&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;kotlinVersion&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="n"&gt;some&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;getting&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="nc"&gt;Kotlin&lt;/span&gt; &lt;span class="n"&gt;you&lt;/span&gt; &lt;span class="n"&gt;want&lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;dependencyScope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;configurations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dependencyScope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"owl"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;resolvable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;configurations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolvable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"owlClasspath"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;extendsFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dependencyScope&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dependencyScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"org.jetbrains.kotlin:kotlin-compiler:$kotlinVersion"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"owl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OwlTask&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kotlinCompiler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resolvable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;otherInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// demonstration purposes only&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Hello, owl!
&lt;/h4&gt;

&lt;p&gt;I can confirm this works, it eliminated the &lt;code&gt;ClassCastException&lt;/code&gt;. Now, a day and incredible amounts of frustration later, I guess I can go back to my original problem?&lt;/p&gt;

&lt;h2&gt;
  
  
  I'm just some asshole
&lt;/h2&gt;

&lt;p&gt;Look, I'm just some asshole who always tries to do the right thing. But what the fuck am I supposed to do when larger and better-resourced teams just abdicate responsibility? Fuck! I guess I'll write a blog post!&lt;/p&gt;

&lt;h2&gt;
  
  
  Special thanks
&lt;/h2&gt;

&lt;p&gt;I am much obliged &lt;a href="https://bsky.app/profile/luis.cortes.social" rel="noopener noreferrer"&gt;Luis Cortes&lt;/a&gt;, who convinced me that some of my salty language was in fact acidic. All the remaining salt and/or acid is entirely of my own devising.&lt;/p&gt;

&lt;h2&gt;
  
  
  Endnotes
&lt;/h2&gt;

&lt;p&gt;&lt;sup&gt;1&lt;/sup&gt; Guava's &lt;code&gt;Graph&lt;/code&gt; class has a complicated hierarchy! &lt;small&gt;up&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;sup&gt;2&lt;/sup&gt; This dependency exhibits many of the same metadata problems. &lt;small&gt;up&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;sup&gt;3&lt;/sup&gt; To be fair, this is a very widespread problem. &lt;small&gt;up&lt;/small&gt;&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>gradle</category>
      <category>jvm</category>
    </item>
    <item>
      <title>Gradle extensions part 2: Now with shenanigans</title>
      <dc:creator>Tony Robalik</dc:creator>
      <pubDate>Wed, 11 Dec 2024 23:05:12 +0000</pubDate>
      <link>https://forem.com/autonomousapps/gradle-extensions-part-2-now-with-shenanigans-12m6</link>
      <guid>https://forem.com/autonomousapps/gradle-extensions-part-2-now-with-shenanigans-12m6</guid>
      <description>&lt;p&gt;&lt;em&gt;&lt;small&gt;Photo by &lt;a href="https://unsplash.com/@karsten_wuerth?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Karsten Würth&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/flowing-river-between-tall-trees-7BjhtdogU3A?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/small&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Welcome to the spiritual successor to &lt;a href="https://dev.to/autonomousapps/gradle-plugins-and-extensions-a-primer-for-the-bemused-51lp"&gt;Gradle plugins and extensions: A primer for the bemused&lt;/a&gt; (one of my most popular posts, such that it competes for space with actual Gradle documentation at the top of a Google search).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4g1j7omdx9twg2n228b7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4g1j7omdx9twg2n228b7.png" alt="Google search results for " width="800" height="544"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As part of my long-running quest to Destroy &lt;code&gt;buildSrc&lt;/code&gt; With Fire, I have recently had occasion to learn how to add extensions to other kinds of types, such as tasks. We have code like this duplicated across many many repos that are under our care:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// buildSrc/src/main/kotlin/magic/Magic.kt&lt;/span&gt;
&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;magic&lt;/span&gt;

&lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;Magic&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;shouldPracticeTheDarkArts&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DO_ANCIENT_MAGIC"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;toBoolean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DO_SLIGHTLY_MORE_MODERN_MAGIC"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;toBoolean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code is used in build scripts like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// build.gradle.kts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;magic.Magic&lt;/span&gt;

&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;withType&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;().&lt;/span&gt;&lt;span class="nf"&gt;configureEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Magic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shouldPracticeTheDarkArts&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;quiet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"👻"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are several things about this that I'd like to improve:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I don't want this code in buildSrc. I want a version of it in our build-logic that is under test and which is shared widely (instead of duplicated in a dozen different repos).&lt;/li&gt;
&lt;li&gt;I don't like the import. It is Unclean. (Build scripts should be simple, declarative, easy for tools to parse.)&lt;/li&gt;
&lt;li&gt;I'm not a big fan of calling &lt;code&gt;System.getenv()&lt;/code&gt; in a Gradle context. I prefer to use the &lt;a href="https://docs.gradle.org/current/javadoc/org/gradle/api/provider/ProviderFactory.html" rel="noopener noreferrer"&gt;&lt;code&gt;ProviderFactory&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Extending &lt;code&gt;Test&lt;/code&gt; tasks
&lt;/h2&gt;

&lt;p&gt;Many Gradle types, including all &lt;code&gt;Task&lt;/code&gt;s (and of course the &lt;code&gt;Project&lt;/code&gt; type), are &lt;a href="https://docs.gradle.org/current/kotlin-dsl/gradle/org.gradle.api.plugins/-extension-aware/index.html" rel="noopener noreferrer"&gt;&lt;code&gt;ExtensionAware&lt;/code&gt;&lt;/a&gt;. This means they all have an &lt;code&gt;ExtensionContainer&lt;/code&gt; available on which new extensions can be created and added.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// build-logic/src/main/kotlin/magic/TestMagicExtension.kt&lt;/span&gt;
&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;magic&lt;/span&gt;

&lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestMagicExtension&lt;/span&gt; &lt;span class="nd"&gt;@Inject&lt;/span&gt; &lt;span class="k"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ProviderFactory&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;companion&lt;/span&gt; &lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;NAME&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"magic"&lt;/span&gt;

    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;testTask&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ProviderFactory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;testTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="nc"&gt;TestMagicExtension&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;shouldPracticeTheDarkArts&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;providers&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;environmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DO_ANCIENT_MAGIC"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;orElse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;environmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DO_SLIGHTLY_MORE_MODERN_MAGIC"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isNotEmpty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOrElse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in our plugin, we can add this to all our &lt;code&gt;Test&lt;/code&gt; tasks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;withType&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;().&lt;/span&gt;&lt;span class="nf"&gt;configureEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class="nc"&gt;TestMagicExtension&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now we can update our build scripts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// build.gradle.kts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;magic.TestMagicExtension&lt;/span&gt;

&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;withType&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;().&lt;/span&gt;&lt;span class="nf"&gt;configureEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// the "extensions" call is on the Test instance,&lt;/span&gt;
  &lt;span class="c1"&gt;// not the project instance&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;magic&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TestMagicExtension&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;magic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shouldPracticeTheDarkArts&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;quiet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"👻"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...that's not better at all!&lt;/p&gt;

&lt;h2&gt;
  
  
  Groovy: An interlude
&lt;/h2&gt;

&lt;p&gt;First of all, let's take a step back and remind ourselves that "we love Kotlin, type safety is great, I don't care that performance is worse..." We can say that over and over again a few times while rocking in a fetal position on the floor till we feel better. Now, here's the same build script but in Groovy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="c1"&gt;// build.gradle&lt;/span&gt;
&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withType&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;configureEach&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;magic&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;shouldPracticeTheDarkArts&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// I'm being cheeky by also omitting the &lt;/span&gt;
    &lt;span class="c1"&gt;// "redundant" parentheses&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;quiet&lt;/span&gt; &lt;span class="s2"&gt;"👻"&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Groovy isn't supposed to be better! Damnit!&lt;/p&gt;

&lt;h2&gt;
  
  
  Sprinkle on some shenanigans
&lt;/h2&gt;

&lt;p&gt;How the heck does Gradle Kotlin DSL do it? Why isn't it generating "typesafe accessors" for my test task extension? Well, that second one is a good question and I have no answer. But for the first... let's just "generate" (that is, write) our own typesafe accessors!&lt;/p&gt;

&lt;p&gt;We add some code in a new (to us) package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;org.gradle.kotlin.dsl&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;magic.TestMagicExtension&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;magic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;TestMagicExtension&lt;/span&gt;
  &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TestMagicExtension&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;magic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;TestMagicExtension&lt;/span&gt;&lt;span class="p"&gt;.()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TestMagicExtension&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now we can update our &lt;em&gt;Kotlin DSL&lt;/em&gt; build script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// build.gradle.kts&lt;/span&gt;
&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;withType&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;().&lt;/span&gt;&lt;span class="nf"&gt;configureEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;magic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shouldPracticeTheDarkArts&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;quiet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"👻"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we're (ab)using the fact that &lt;a href="https://github.com/gradle/gradle/blob/master/platforms/core-configuration/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/ImplicitImports.kt#L44" rel="noopener noreferrer"&gt;Gradle automatically imports&lt;/a&gt; everything in the &lt;code&gt;org.gradle.kotlin.dsl&lt;/code&gt; package into build scripts, so all those functions are Just There (in a global namespace, so be careful!).&lt;/p&gt;

&lt;p&gt;This is a common enough pattern that Gradle itself uses it in its &lt;a href="https://github.com/gradle/test-retry-gradle-plugin/blob/main/plugin/src/main/kotlin/org/gradle/kotlin/dsl/testRetry.kt" rel="noopener noreferrer"&gt;test-retry-gradle-plugin&lt;/a&gt;. There's also an &lt;a href="https://github.com/gradle/gradle/issues/7557" rel="noopener noreferrer"&gt;open issue&lt;/a&gt; (from, er, 2018) on Gradle's issue tracker with a feature request to permit custom plugins to add their own default imports without resorting to using split packages like this.&lt;/p&gt;

&lt;p&gt;Now go forth and be merry, for it is that time of year.&lt;/p&gt;

</description>
      <category>gradle</category>
      <category>kotlin</category>
      <category>java</category>
    </item>
    <item>
      <title>One click dependencies fix</title>
      <dc:creator>Tony Robalik</dc:creator>
      <pubDate>Tue, 08 Oct 2024 18:48:51 +0000</pubDate>
      <link>https://forem.com/autonomousapps/one-click-dependencies-fix-191p</link>
      <guid>https://forem.com/autonomousapps/one-click-dependencies-fix-191p</guid>
      <description>&lt;p&gt;&lt;em&gt;&lt;small&gt;Photo by &lt;a href="https://unsplash.com/@nampoh?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Maxim Hopman&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/black-metal-gate-near-concrete-wall-UMg-jcWPLrI?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/small&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;If you maintain a JVM&lt;sup&gt;1&lt;/sup&gt; or Android project, chances are you've heard of the &lt;a href="https://github.com/autonomousapps/dependency-analysis-gradle-plugin" rel="noopener noreferrer"&gt;Dependency Analysis Gradle Plugin&lt;/a&gt; (DAGP). With over 1800 stars, it's used by some of largest Gradle projects in the world, as well as by &lt;a href="https://github.com/gradle/gradle/blob/master/build-logic/root-build/src/main/kotlin/gradlebuild.dependency-analysis.gradle.kts" rel="noopener noreferrer"&gt;Gradle itself&lt;/a&gt;. It fills what would otherwise be a substantial hole in the Gradle ecosystem: without it, I know of no other way to eliminate unused dependencies and to correctly declare all your actually-used dependencies. In other words, when you use this plugin, your dependency declarations are exactly what you need to build your project: nothing more, nothing less.&lt;/p&gt;

&lt;p&gt;That might sound like a small thing, but for industrial-scale projects, a healthy dependency graph is a superpower that prevents bugs, eases debugging (at build and runtime), keeps builds faster, and keeps artifacts smaller. If developer productivity work is the public health of the software engineering world, then a healthy dependency graph is a working sewer system. You don't know how much you rely on it till it stops working and you've got shit everywhere.&lt;/p&gt;

&lt;p&gt;The problem is that, if your tool only tells you all the problems you have but doesn't also fix them, you might have a massive(ly annoying) problem on your hands. I mentioned this as an important consideration in my &lt;a href="https://dev.to/autonomousapps/acab-fire-the-code-style-cop-in-your-head-m8b"&gt;recent rant against code style formatters&lt;/a&gt;. This is why, since &lt;a href="https://github.com/autonomousapps/dependency-analysis-gradle-plugin/blob/main/CHANGELOG.md#version-1110-1111-1112-1113" rel="noopener noreferrer"&gt;v1.11.0&lt;/a&gt;, DAGP has had a &lt;code&gt;fixDependencies&lt;/code&gt; task, which takes the problem report and rewrites build scripts in-place. Even before that, in &lt;a href="https://github.com/autonomousapps/dependency-analysis-gradle-plugin/blob/main/CHANGELOG.md#version-0460" rel="noopener noreferrer"&gt;v0.46.0&lt;/a&gt;, the plugin had first-class support for registering a "post-processing task" to enable advanced users to consume the "build health" report in any manner of their choosing. &lt;a href="https://github.com/slackhq/foundry" rel="noopener noreferrer"&gt;Foundry&lt;/a&gt; (née The Slack Gradle Plugin), for example, has a feature called the "&lt;a href="https://github.com/slackhq/foundry/blob/main/platforms/gradle/foundry-gradle-plugin/src/main/kotlin/foundry/gradle/dependencyrake/DependencyRake.kt" rel="noopener noreferrer"&gt;dependency rake&lt;/a&gt;", which predates and inspired &lt;code&gt;fixDependencies&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;fixDependencies&lt;/code&gt; hasn't always worked well, though. For one thing, there might be a bug in the analysis such that, if you "fix" all the issues, your build might break. (DAGP is under very active development, so if this ever happens to you, please &lt;a href="https://github.com/autonomousapps/dependency-analysis-gradle-plugin/issues/new/choose" rel="noopener noreferrer"&gt;file an issue&lt;/a&gt;!) In this case, it can take an expert to understand what broke and how to fix it, or you can fall back to manual changes and iteration.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;For another thing, the build script rewriter has relied on a simplified &lt;a href="https://www.antlr.org/" rel="noopener noreferrer"&gt;grammar&lt;/a&gt; for parsing and rewriting Gradle Groovy and Kotlin DSL build scripts. That grammar can fail if your scripts are complex.&lt;sup&gt;2&lt;/sup&gt; This problem will soon be solved with the introduction of a Gradle Kotlin DSL parser built on the &lt;a href="https://github.com/cashapp/kotlin-editor" rel="noopener noreferrer"&gt;KotlinEditor&lt;/a&gt; grammar, which has full support for the Kotlin language. (Gradle Groovy DSL scripts will continue to use the old simplified grammar, for now.)&lt;/p&gt;

&lt;p&gt;There have also been many recent bugfixes to (1) improve the correctness of the analysis and (2) make the rewriting process more robust in the face of various common idioms. DAGP now has much better support for version catalog accessors, for example (no support yet for experimental project accessors).&lt;/p&gt;

&lt;p&gt;With these improvements (real and planned), it's become feasible to imagine automating large-scale dependency fixes across hundreds of repos containing millions of lines of code and have it all &lt;em&gt;just work&lt;/em&gt;. Here's the situation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Over 500 repositories.&lt;/li&gt;
&lt;li&gt;Each with its own version catalog.&lt;/li&gt;
&lt;li&gt;Most of the entries in the version catalogs use the same names, but there's some incidental skew in the namespace (multiple keys pointing to the same dependency coordinates).&lt;/li&gt;
&lt;li&gt;Over 2000 Gradle modules.&lt;/li&gt;
&lt;li&gt;
&lt;a&gt;&lt;/a&gt;Close to 15 million lines of Kotlin and Java code spread out over more than 100 thousand files, along with over 150 thousand lines of "Gradle" code in more than 3 thousand build scripts. This last point isn't as relevant as the first four, but helps to demonstrate what I mean when I say "industrial scale."&lt;sup&gt;3&lt;/sup&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Additionally, the build code we want to write to manage all this should follow Gradle best practices: it should be &lt;a href="https://docs.gradle.org/current/userguide/build_cache.html" rel="noopener noreferrer"&gt;cacheable&lt;/a&gt; to the extent possible, should work with the &lt;a href="https://docs.gradle.org/current/userguide/configuration_cache.html" rel="noopener noreferrer"&gt;configuration cache&lt;/a&gt;, and for bonus points should not violate the &lt;a href="https://docs.gradle.org/current/userguide/isolated_projects.html" rel="noopener noreferrer"&gt;isolated projects&lt;/a&gt; contract either (which is also good for maximal performance). The ultimate goal is for developers and build maintainers to be able to run a single task and have it (1) fix all dependency declarations, which might mean adding new declarations to build scripts; (2) all build script declarations should have a version catalog entry wherever possible; (3) and all version catalog entries should come from the same global namespace so that the entire set of 500+ repositories are fully consistent with each other. This last part is an important requirement because we're migrating these repos into a single mono/mega repo for other reasons.&lt;/p&gt;

&lt;p&gt;Here's the task they can now run, for the record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gradle :fixAllDependencies
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(nb: we use &lt;code&gt;gradle&lt;/code&gt; and not &lt;code&gt;./gradlew&lt;/code&gt; because we manage gradle per-repo with &lt;a href="https://github.com/cashapp/hermit" rel="noopener noreferrer"&gt;hermit&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;So, how do we do it?&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-processing
&lt;/h2&gt;

&lt;p&gt;The first step was creating the global version catalog namespace. We did not attempt to actually create a single &lt;a href="https://docs.gradle.org/current/userguide/platforms.html#sec:version-catalog-plugin" rel="noopener noreferrer"&gt;published global version catalog&lt;/a&gt; because, until we finish our megarepo migration, an important contract is that each repo maintains its own dependencies (and their versions). So instead, we collected the full map of version catalog names to dependency identifiers (the dependency coordinates less the version string). We eliminated all the duplication using pre-existing large-scale change tools we have, and then populated the final global set (now with 1:1 mappings) into our convention plugin that is already applied everywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conceptual framework
&lt;/h2&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;The Gradle framework, in general, takes the &lt;a href="https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html" rel="noopener noreferrer"&gt;Project&lt;/a&gt; as the most important point of reference.&lt;sup&gt;4&lt;/sup&gt; A &lt;code&gt;Project&lt;/code&gt; instance is what backs all your &lt;code&gt;build.gradle[.kts]&lt;/code&gt; scripts, for example, and most plugins implement the &lt;code&gt;Plugin&amp;lt;Project&amp;gt;&lt;/code&gt; interface. Safe, performant, &lt;em&gt;high-quality&lt;/em&gt; build code respects this conceptual boundary and treats each project (AKA "module") as an atomic unit.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;If &lt;code&gt;Task&lt;/code&gt;s have well-defined inputs and outputs (literally annotated &lt;code&gt;@Input&amp;lt;X&amp;gt;&lt;/code&gt; and &lt;code&gt;@Output&amp;lt;X&amp;gt;&lt;/code&gt;), then it might help to also think of projects as having inputs and outputs. In general, a project's inputs are its source code (which by convention is in the &lt;code&gt;src/&lt;/code&gt; directory at the project root), and its dependencies. A project's outputs are the artifacts it produces. For &lt;a href="https://docs.gradle.org/current/userguide/java_plugin.html" rel="noopener noreferrer"&gt;Java projects&lt;/a&gt;, the primary artifacts are jars (for external consumption), or class files (for consumption by other projects in a multi-project build).&lt;sup&gt;5&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;With that in mind, we can decide that if two projects need to talk to each other, they should do so via their well-defined inputs and outputs. We define relationships between projects via dependencies (&lt;code&gt;A&lt;/code&gt; -&amp;gt; &lt;code&gt;B&lt;/code&gt; means &lt;code&gt;A&lt;/code&gt; depends on &lt;code&gt;B&lt;/code&gt;, so &lt;code&gt;B&lt;/code&gt; is an input to &lt;code&gt;A&lt;/code&gt;), and we can flavor that connection such that we tell Gradle which of &lt;code&gt;B&lt;/code&gt;'s outputs &lt;code&gt;A&lt;/code&gt; cares about. The default is the &lt;strong&gt;primary artifact&lt;/strong&gt; (usually class files for classpath purposes), but it can also be &lt;em&gt;anything (that can be written to disk)&lt;/em&gt;. It can, for example, be some metadata about B. It can also be both! (You can declare multiple dependencies between the same two projects, with each edge having a different "flavor," that is, representing a different variant.) This may make more sense in a bit when we get to a concrete example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation: &lt;code&gt;:fixAllDependencies&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The rest of this post will focus on implementation, but at a relatively high level of detail. Some of the code will essentially be pseudocode. My goal is to demonstrate the full flow at a conceptual level, such that a (highly) motivated reader could implement something similar in their own workflow or, more likely, simply learn about how to do something Cool™️ with Gradle.&lt;/p&gt;

&lt;p&gt;Here's a sketch of the simplified task graph with &lt;a href="https://excalidraw.com/" rel="noopener noreferrer"&gt;Excalidraw&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj1nx9mcg4lz4mepg5745.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj1nx9mcg4lz4mepg5745.png" alt="A sketch of the task graph, also described at length in the narrative that follows" width="800" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note how each project is independent of the other. Well-defined Gradle builds maximize concurrency by respecting project boundaries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: The global namespace
&lt;/h3&gt;

&lt;p&gt;As mentioned in the &lt;strong&gt;pre-processing&lt;/strong&gt; section, we need a global namespace. We want all dependency declarations to refer to version catalog entries, i.e., &lt;code&gt;libs.amazingMagic&lt;/code&gt;, rather than &lt;code&gt;"com.amazing:magic:1.0"&lt;/code&gt;. Since DAGP &lt;em&gt;already supports&lt;/em&gt; version catalog references in its analysis, this will Just Work if your version catalog already has an entry for &lt;code&gt;amazingMagic = "com.amazing:magic:1.0"&lt;/code&gt;. However, if you don't, DAGP defaults to the "raw string" declaration. If we want, we can tell DAGP about other mappings that it can't detect by default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// root build script&lt;/span&gt;
&lt;span class="nf"&gt;dependencyAnalysis&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;structure&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;putAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s"&gt;"com.amazing:magic"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s"&gt;"libs.amazingMagic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// more entries&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where &lt;code&gt;dependencyAnalysis.structure.map&lt;/code&gt; is a &lt;code&gt;MapProperty&amp;lt;String, String&amp;gt;&lt;/code&gt;, which you can modify directly in your build scripts or via a plugin. Note the "raw string" version of the declaration doesn't include version information; this is important because the version you declare may not match the version that Gradle resolves.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Update the version catalog, part 1
&lt;/h3&gt;

&lt;p&gt;With &lt;strong&gt;Step 1&lt;/strong&gt;, DAGP will rewrite build scripts via the built-in &lt;code&gt;fixDependencies&lt;/code&gt; task to match your desired schema, but your next build will fail because you'll have dependencies referencing things like &lt;code&gt;libs.amazingMagic&lt;/code&gt; which aren't actually present in your version catalog. So now we have to update the version catalog to ensure it has all of these new entries. This will be a multi-step process.&lt;/p&gt;

&lt;p&gt;First, we have to calculate the possibly-missing entries. We write a new task, &lt;code&gt;ComputeNewVersionCatalogEntriesTask&lt;/code&gt;, and have it extend &lt;code&gt;AbstractPostProcessingTask&lt;/code&gt;, which comes from DAGP itself. This exposes a function, &lt;code&gt;projectAdvice()&lt;/code&gt;, which gives subclasses access to the "project advice" that DAGP emits to the console, but in a form amenable to computer processing. We'll take that output, filter it for "add advice", and then write those values out to disk via our task's output. We only care about the &lt;a href="https://dev.to/autonomousapps/the-proper-care-and-feeding-of-your-gradle-build-d8g"&gt;add advice&lt;/a&gt; because that's the only type that might represent a dependency not in a version catalog.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// in a custom task action&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;newEntries&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;projectAdvice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dependencyAdvice&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isAnyAdd&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;coordinates&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;ModuleCoordinates&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;coordinates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gav&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toSortedSet&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;outputFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newEntries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joinToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;separator&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"\n"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that with Gradle task outputs, it's best practice to always sort outputs for stability and to enable use of the remote build cache.&lt;/p&gt;

&lt;p&gt;Next we tell DAGP about this post-processing task (which is how it can access &lt;code&gt;projectAdvice()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// subproject's build script&lt;/span&gt;
&lt;span class="n"&gt;computeNewVersionCatalogEntries&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.)&lt;/span&gt;

&lt;span class="nf"&gt;dependencyAnalysis&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;registerPostProcessingTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;computeNewVersionCatalogEntries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally we also have to register our new task's output as an artifact of this project!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;publisher&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;interProjectPublisher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nc"&gt;MyArtifacts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Kind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;VERSION_CATALOG_ENTRIES&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;publisher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;computeNewVersionCatalogEntries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatMap&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newVersionCatalogEntries&lt;/span&gt; 
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where the &lt;code&gt;interProjectPublisher&lt;/code&gt; and related code is heavily inspired by DAGP's &lt;a href="https://github.com/autonomousapps/dependency-analysis-gradle-plugin/tree/main/src/main/kotlin/com/autonomousapps/internal/artifacts" rel="noopener noreferrer"&gt;artifacts&lt;/a&gt; package, because I wrote both. The tl;dr is that this is what teaches Gradle about a project's &lt;strong&gt;secondary artifacts&lt;/strong&gt;. I wish Gradle had a first-class API for this, alas.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Update the version catalog, part 2
&lt;/h3&gt;

&lt;p&gt;Back in the root project, we need to declare our dependencies to each subproject, flavoring that declaration to say we want the &lt;code&gt;VERSION_CATALOG_ENTRIES&lt;/code&gt; artifact:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// root project&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;resolver&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;interProjectResolver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nc"&gt;MyArtifacts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Kind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;VERSION_CATALOG_ENTRIES&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Yes, this CAN BE OK, but you must only access &lt;/span&gt;
&lt;span class="c1"&gt;// IMMUTABLE PROPERTIES of each project p.&lt;/span&gt;
&lt;span class="c1"&gt;// This sets up the dependencies from the root to &lt;/span&gt;
&lt;span class="c1"&gt;// each "real" subproject, where "real" filters&lt;/span&gt;
&lt;span class="c1"&gt;// out intermediate directories that don't have&lt;/span&gt;
&lt;span class="c1"&gt;// any code&lt;/span&gt;
&lt;span class="n"&gt;allprojects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class="c1"&gt;// implementation left to reader&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isRealProject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;resolver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;declarable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
      &lt;span class="c1"&gt;// p.path is an immutable property, so we're&lt;/span&gt;
      &lt;span class="c1"&gt;// good&lt;/span&gt;
      &lt;span class="n"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;project&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;mapOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"path"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;fixVersionCatalog&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s"&gt;"fixVersionCatalog"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="nc"&gt;UpdateVersionCatalogTask&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newEntries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resolver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;internal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;globalNamespace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;putAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.)&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;versionCatalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;projectDirectory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"gradle/libs.versions.toml"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The root project is the correct place to register this task, because the version catalog will typically live in the root at &lt;code&gt;gradle/libs.versions.toml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With this setup, a user could now run &lt;code&gt;gradle :fixVersionCatalog&lt;/code&gt;, and it would essentially run &lt;code&gt;&amp;lt;every module&amp;gt;*:projectHealth&lt;/code&gt;, followed by &lt;code&gt;&amp;lt;every module&amp;gt;*:computeNewVersionCatalogEntries&lt;/code&gt;, followed finally by &lt;code&gt;:fixVersionCatalog&lt;/code&gt;, because those are the necessary steps as we've declared and wired them.&lt;/p&gt;

&lt;p&gt;This updates the version catalog to contain every necessary reference to resolve all the potential &lt;code&gt;libs.&amp;lt;foo&amp;gt;&lt;/code&gt; dependency declaration throughout the build.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Fix all the dependency declarations
&lt;/h3&gt;

&lt;p&gt;This step leverages DAGP's &lt;code&gt;fixDependencies&lt;/code&gt; task, and is really just about wrapping everything up in a neat package.&lt;/p&gt;

&lt;p&gt;We want a single task registered on the root. Let's call it &lt;code&gt;:fixAllDependencies&lt;/code&gt;. This will be a lifecycle task, and invoking it will trigger &lt;code&gt;:fixVersionCatalog&lt;/code&gt; as well as all the &lt;code&gt;&amp;lt;every module&amp;gt;*:fixDependencies&lt;/code&gt; tasks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// root project&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;fixDependencies&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mutableListOf&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;

&lt;span class="n"&gt;allprojects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isRealProject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...as before...&lt;/span&gt;

    &lt;span class="c1"&gt;// do not use something like `p.tasks.findByName()`,&lt;/span&gt;
    &lt;span class="c1"&gt;// that violates Isolated Projects as well as&lt;/span&gt;
    &lt;span class="c1"&gt;// lazy task configuration.&lt;/span&gt;
    &lt;span class="n"&gt;fixDependencies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"${p.path}:fixDependencies"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"fixAllDependencies"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dependsOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fixVersionCatalog&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dependsOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fixDependencies&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;And we're done.&lt;sup&gt;6&lt;/sup&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  (Optional) Step 5: Sort dependency blocks
&lt;/h3&gt;

&lt;p&gt;If you do all the preceding, you should have a successful build with a minimal dependency graph. 🎉 But your dependency blocks will be horribly out-of-order, which can make them hard to visually scan. DAGP makes no effort to keep the declarations sorted because that is an orthogonal concern and different teams might have different ordering preferences. This is why I've also authored and published the &lt;a href="https://github.com/square/gradle-dependencies-sorter" rel="noopener noreferrer"&gt;Gradle Dependencies Sorter&lt;/a&gt; CLI and plugin, which applies what I consider to be a reasonable default. If you apply this to your builds (which we do to all of our builds via our convention plugins), you can follow-up &lt;code&gt;:fixAllDependencies&lt;/code&gt; with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gradle sortDependencies
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and this will usually Just Work. This plugin is in fact already using the enhanced Kotlin grammar from KotlinEditor, so Gradle Kotlin DSL build scripts shouldn't pose a problem for it.&lt;/p&gt;

&lt;p&gt;And now we're really done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Endnotes
&lt;/h2&gt;

&lt;p&gt;&lt;sup&gt;1&lt;/sup&gt; Currently supported languages: Groovy, Java, Kotlin, and Scala. &lt;small&gt;up&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;sup&gt;2&lt;/sup&gt; This is one reason why I think it's important to keep scripts &lt;a href="https://developer.squareup.com/blog/herding-elephants/" rel="noopener noreferrer"&gt;simple and declarative&lt;/a&gt;. &lt;small&gt;up&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;sup&gt;3&lt;/sup&gt; Measured with the &lt;a href="https://github.com/AlDanial/cloc" rel="noopener noreferrer"&gt;cloc&lt;/a&gt; tool. &lt;small&gt;up&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;sup&gt;4&lt;/sup&gt; Gradle's biggest footgun, in my opinion, is that the API doesn't enforce this conceptual boundary. &lt;small&gt;up&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;sup&gt;5&lt;/sup&gt; This paragraph is an oversimplification for discussion purposes. &lt;small&gt;up&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;sup&gt;6&lt;/sup&gt; Well, except for &lt;a href="https://github.com/autonomousapps/dependency-analysis-gradle-plugin/tree/main/testkit" rel="noopener noreferrer"&gt;automated testing&lt;/a&gt; and &lt;a href="https://dev.to/autonomousapps/one-click-dependencies-fix-191p"&gt;blog-post writing&lt;/a&gt;. &lt;small&gt;up&lt;/small&gt;&lt;/p&gt;

</description>
      <category>gradle</category>
      <category>java</category>
      <category>kotlin</category>
    </item>
    <item>
      <title>ACAB: Fire the (code style) cop in your head</title>
      <dc:creator>Tony Robalik</dc:creator>
      <pubDate>Sun, 29 Sep 2024 18:47:32 +0000</pubDate>
      <link>https://forem.com/autonomousapps/acab-fire-the-code-style-cop-in-your-head-m8b</link>
      <guid>https://forem.com/autonomousapps/acab-fire-the-code-style-cop-in-your-head-m8b</guid>
      <description>&lt;p&gt;&lt;em&gt;Photo by &lt;a href="https://unsplash.com/@karsten116?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Karsten Winegeart&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/a-couple-of-dogs-sitting-next-to-each-other-RmuC5p_CPSE?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let's get something out of the way right at the start: I hate enforced automated code style formatters. This post started because I had an axe to grind, and I made the ill-advised decision to &lt;a href="https://mstdn.social/@autonomousapps/113030638543848445" rel="noopener noreferrer"&gt;haul my grindstone out into the public square&lt;/a&gt;. &lt;a href="https://mstdn.social/@autonomousapps/113166050596455765" rel="noopener noreferrer"&gt;Multiple times&lt;/a&gt;. &lt;a href="https://mstdn.social/@autonomousapps/113202332051085238" rel="noopener noreferrer"&gt;Over weeks&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Disclaimer! I primarily write Kotlin code that I build with Gradle, in Intellij IDEA (in order of importance). Since many of my complaints are about specific implementation-level issues with code style formatting tools, if you don't also build Kotlin with Gradle, don't worry about it! I'm just an internet crank and you are relieved from the duty of telling me how wrong I am.&lt;/p&gt;

&lt;p&gt;Second disclaimer! Enforced code style is not the same thing as linting for usage issues. I like linters! Please tell me I forgot to close a stream, or am calling a Java API with ambiguous nullability.&lt;/p&gt;

&lt;p&gt;Ok, let's gooooo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Arguments in favor of enforcement of consistent code style
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Consistent code style is better than inconsistent code style&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sure. But what do we mean by better? The general claims are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;More readable&lt;/li&gt;
&lt;li&gt;More maintainable&lt;/li&gt;
&lt;li&gt;Aids with achieving flow&lt;/li&gt;
&lt;li&gt;Contains fewer errors&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I think we've already run into an issue, which is we need to clarify what "style" means. Are we just talking about where we break lines and how long those lines are? Or do we mean something deeper? If we are talking about line breaks, I think the argument is pretty weak. Sure, all else being equal, if two pieces of source code follow the same general style, I will have an easier time with pattern-matching and "understanding" them. But what if my pattern-matching misleads me? What if I gloss over the fact that two structurally-identical blocks contain a single semantic difference that entirely changes the behavior? Maybe that single difference is easier to spot if the whitespace is identical, or maybe it's easier to miss because my brain too-readily dismisses the second block as identical to the first. This is actually what comprehensive tests are for. If this is the kind of error you want to catch, you actually just want tests.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;What if we mean "style" in the deeper sense? I had a colleague once who routinely pumped out massive amounts of code, all &lt;em&gt;formatted&lt;/em&gt; the same as older code near it, but whose &lt;em&gt;style&lt;/em&gt; was so different that loading it into memory (that is, reading and understanding it) was a huge effort.&lt;sup&gt;1&lt;/sup&gt; The naming conventions were different; there was very heavy use of Kotlin scoping functions like &lt;code&gt;run&lt;/code&gt; and &lt;code&gt;let&lt;/code&gt;; and lazy callbacks, generic extension functions, lambdas and lambdas-with-receivers were simply everywhere. (None of that is to say there was anything "wrong" with it. It was just different from all the other code nearby.) Automated style enforcement failed to achieve any of the above-listed goals regarding this code, although I suppose one could argue they prevented comprehensibility from being somehow even worse.&lt;/p&gt;

&lt;p&gt;Ok, so, automated style formatters can't possibly help with "code style" in this second sense. We can dismiss it from further consideration but we also must acknowledge that our goals with automated style enforcement may be entirely defeated by orthogonal, essentially social, concerns.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;Another argument I've seen in favor of consistent code style is that it can help spot code smells, or usages of known-problematic APIs, such as Java's &lt;code&gt;ObjectOutputStream&lt;/code&gt;.&lt;sup&gt;2&lt;/sup&gt; I would say that if this is something you care about, then what you really want is a linter. As I said above, linters and code style formatters are different things. We can forbid &lt;code&gt;ObjectOutputStream&lt;/code&gt;s without going anywhere near the question of consistent code style enforcement.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;To conclude this section, I will say that, all else being equal, consistent code style is Good™️. Its goals, however, are easily defeated by orthogonal issues, or actually achieved with other &lt;em&gt;kinds&lt;/em&gt; of tools (tests, linters) that are themselves completely orthogonal to code style per se.&lt;sup&gt;3&lt;/sup&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Arguments against enforcement of consistent code style
&lt;/h2&gt;

&lt;p&gt;To reiterate part of the conclusion above, in some cases I think we are simply making a category error. We don't want consistent code style, we want more and better tests, and stricter linting for issues that can't be caught by the compiler.&lt;/p&gt;

&lt;p&gt;Therefore, my primary argument against enforcing code style is that its proponents haven't made a strong enough case. In a world where managers are telling me to do "less with less," and there have been mass layoffs and productivity concerns and increasingly strict requirements that all work be explicitly tied to &lt;em&gt;identified business needs&lt;/em&gt;, why am I spending any time at all on whitespace-normalization tools?&lt;/p&gt;

&lt;p&gt;I want peer-reviewed, high-quality research showing that enforcing consistent code style helps with business outcomes, or I would like to never talk about this again. There is a massive &lt;em&gt;opportunity cost&lt;/em&gt; here that is simply not being acknowledged.&lt;/p&gt;

&lt;p&gt;Most of the rest of my arguments are around how painful it has proven to use the available tools. That is, they're implementation-level issues that could, in principle, be resolved. But I will simply point to the above paragraph and ask why I should spend any time on this.&lt;/p&gt;

&lt;p&gt;For a code formatter to even be considered usable, it has to be able to autoremediate all issues it can report. If it can report issues—as &lt;em&gt;errors&lt;/em&gt; even—then it must also be able to fix them. Yet, not all code formatting tools can do this. What's worse, they may change their rules in minor or patch releases, breaking automated dependency updates because they'll require a human to manually add line breaks to dozens or hundreds of source files. This might be a bug or it might be a design limitation. Either way, we can simply dismiss these immediately from consideration. (Bearing in mind that we won't be fixing them, because who has time for that?)&lt;/p&gt;

&lt;p&gt;Now, what remains are the class of tools that can autoremediate all issues. This is great! Maybe we can just skip this argument about enforcing code style and just slap this tool onto our project and away we go! But... when should we run these tools? Do we run them only in CI to avoid adding friction to the inner dev loop? This would certainly lead to an explosion of commits like "appeasing code style cop" and longer time-to-merge. Can we run them locally instead? Ok, when? Here are some options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;a&gt;&lt;/a&gt;Pre-compile. Using Gradle, simply add a configuration block like this:&lt;sup&gt;4&lt;/sup&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;named&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kotlinCompile"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dependsOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"fixStyle"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;But that's not great, because if there's a compilation error, then the &lt;code&gt;fixStyle&lt;/code&gt; task will fail with its own opaque parsing error, instead of the more expected compilation failure. Ok, what about...&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Post-compile. Using Gradle, simply add a configuration block like this:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;named&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kotlinCompile"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finalizedBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"fixStyle"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Turns out that's not great either. The &lt;code&gt;fixStyle&lt;/code&gt; whitespace-normalizer will rewrite your source code &lt;em&gt;after&lt;/em&gt; it's been compiled, meaning there is now a mismatch between the compiled class files and the source files. Debugging breakpoints can move around, and if you are indeed in a debugging session and commenting/uncommenting blocks of code, your style formatter will treat the block comment differently from a source block, such that when you later uncomment it, it may not be syntactically correct, requiring an additional step in this innermost part of your dev loop. And since you're debugging, that suggests your focus is on &lt;em&gt;fixing bugs&lt;/em&gt;, and now you have to contend with the annoying friction of this tool you already don't like breaking your code.&lt;/p&gt;

&lt;p&gt;Both of these approachs have another issue, which is they make your builds slower. A well-configured tool will at least only run on changed files, but nevertheless it is "wasting time" when you just want to run your code.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Pre-commit. Add a git pre-commit hook that runs the tool before you can commit. This approach is better, but you have to make a choice:&lt;/p&gt;

&lt;p&gt;a. &lt;a&gt;&lt;/a&gt;Pre-commit with Gradle task. This is going to make your &lt;code&gt;git commit&lt;/code&gt; much slower, because Gradle is slow.&lt;sup&gt;5&lt;/sup&gt; That's annoying.&lt;/p&gt;

&lt;p&gt;b. Pre-commit with CLI tool. This can be much faster, since you won't have to deal with the Gradle configuration overhead, but now you have to be very careful to ensure that the CLI tool and the Gradle task do the same thing, else you might end up with mysterious CI failures that frustrate your developers. So be careful.&lt;/p&gt;

&lt;p&gt;In both these cases, you might run into issues if you use &lt;code&gt;git add -p&lt;/code&gt; to make fine-grained commits. There are ways to resolve this, but it makes the tooling more complex to maintain. And of course, &lt;code&gt;git&lt;/code&gt; is usually very fast, so making it slower can be frustrating. But, in some tests my team ran, the cost was on the order of ~2 seconds. Not great, but tolerable I guess, especially if it lets us move on from this topic forever.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Pre-push. Add a git pre-push hook that runs the tool before you can push.&lt;/p&gt;

&lt;p&gt;We have similar concerns to 3a and 3b above, so I won't repeat them. The biggest difference here is it resolves some issues with pre-commit, but also will require developers to add a new commit, orthogonal to their main concern, that is basically &lt;code&gt;git commit -am "Appease code formatter."&lt;/code&gt; Not the worst thing in the world.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Grinding my axe
&lt;/h3&gt;

&lt;p&gt;As I said at top, this post is really about grinding an axe in public. So now I want to share some of my least-favorite formatting decisions. I truly do not understand how any of these can be considered optimal for readability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;before&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nc"&gt;PathSensitive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PathSensitivity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RELATIVE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nc"&gt;InputFiles&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;buildFiles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;SetProperty&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;File&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;after&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nc"&gt;PathSensitive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PathSensitivity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RELATIVE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nc"&gt;InputFiles&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;buildFiles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;SetProperty&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;File&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wtf? The code formatter, rather than treating the max line length as a maximum, appears to be treating it as an &lt;em&gt;ideal&lt;/em&gt;. If the property plus all of its annotations can fit on one line, it does that! Otherwise, it leaves the declaration untouched.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;before&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;testCases&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;flatMap&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;testCase&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;gradleVersions&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;gradleVersion&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nf"&gt;arrayOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;testCase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gradleVersion&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;after&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;testCases&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;flatMap&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;testCase&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;gradleVersions&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;gradleVersion&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;arrayOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;testCase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gradleVersion&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same problem with treating the max line length like an ideal to be achieved, with the additional wrinkle that now it's collapsing multiple scopes onto a single line, making it harder to visually match curly brace pairs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;before&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;veryLongFunctionThatReturnsABooleanYeeeeeaahhhhhhhhhhhhhhhh&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="nf"&gt;veryLongFunctionThatReturnsABooleanYeeeeeaahhhhhhhhhhhhhhhh&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// do something&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;after&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;veryLongFunctionThatReturnsABooleanYeeeeeaahhhhhhhhhhhhhhhh&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt;
    &lt;span class="nf"&gt;veryLongFunctionThatReturnsABooleanYeeeeeaahhhhhhhhhhhhhhhh&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// do something&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This one really annoys me. In the before-case, while I'm iterating on my branching logic, I can easily comment-out individual lines and the code remains syntactically correct. In the after-case, I simply cannot. It takes more keypresses or even mouse movement where before it was a single keypress. I like to minimize mouse movement to avoid repetitive strain injuries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Axe-grinding in the community
&lt;/h3&gt;

&lt;p&gt;In the Mastodon thread linked at top, some math-centric devs &lt;a href="https://cosocial.ca/@jessewilson/113207251226884479" rel="noopener noreferrer"&gt;posted examples&lt;/a&gt; of auto-formatting that made their code less readable in their view, when they have data that is best represented as a table. I typed their examples directly into my editor and ran my team's tool on it—it produced an even worse result than the screenshot!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;before&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;v&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;++].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="mh"&gt;0xffL&lt;/span&gt; &lt;span class="n"&gt;shl&lt;/span&gt; &lt;span class="mi"&gt;56&lt;/span&gt;
  &lt;span class="nf"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;++].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="mh"&gt;0xffL&lt;/span&gt; &lt;span class="n"&gt;shl&lt;/span&gt; &lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;++].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="mh"&gt;0xffL&lt;/span&gt; &lt;span class="n"&gt;shl&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;++].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="mh"&gt;0xffL&lt;/span&gt; &lt;span class="n"&gt;shl&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;++].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="mh"&gt;0xffL&lt;/span&gt; &lt;span class="n"&gt;shl&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;++].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="mh"&gt;0xffL&lt;/span&gt; &lt;span class="n"&gt;shl&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;++].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="mh"&gt;0xffL&lt;/span&gt; &lt;span class="n"&gt;shl&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;++].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="mh"&gt;0xffL&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;after&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;v&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;++].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt;
    &lt;span class="mh"&gt;0xffL&lt;/span&gt; &lt;span class="n"&gt;shl&lt;/span&gt;
    &lt;span class="mi"&gt;56&lt;/span&gt; &lt;span class="nf"&gt;or&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;++].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="mh"&gt;0xffL&lt;/span&gt; &lt;span class="n"&gt;shl&lt;/span&gt; &lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;or&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;++].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="mh"&gt;0xffL&lt;/span&gt; &lt;span class="n"&gt;shl&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;or&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;++].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="mh"&gt;0xffL&lt;/span&gt; &lt;span class="n"&gt;shl&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;or&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;++].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="mh"&gt;0xffL&lt;/span&gt; &lt;span class="n"&gt;shl&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;or&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;++].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="mh"&gt;0xffL&lt;/span&gt; &lt;span class="n"&gt;shl&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;or&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;++].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="mh"&gt;0xffL&lt;/span&gt; &lt;span class="n"&gt;shl&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;or&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;++].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="mh"&gt;0xffL&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wat. Among other weird problems, it exhibits the same behavior as my complex boolean above: it moves the &lt;code&gt;or&lt;/code&gt; from a prefix-position to a postfix-position.&lt;/p&gt;

&lt;p&gt;Someone else &lt;a href="https://mastodon.jakewharton.com/@jw/113209743648483053" rel="noopener noreferrer"&gt;suggested&lt;/a&gt; adding &lt;code&gt;0 or&lt;/code&gt; at the front... and yes, that plus an additional pair of parenthese did help (note, I am not personally very good at bit operations so I'm not sure if I didn't just break this code!).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;before&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;v&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0L&lt;/span&gt;
    &lt;span class="nf"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;++].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="mh"&gt;0xffL&lt;/span&gt; &lt;span class="n"&gt;shl&lt;/span&gt; &lt;span class="mi"&gt;56&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;++].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="mh"&gt;0xffL&lt;/span&gt; &lt;span class="n"&gt;shl&lt;/span&gt; &lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;++].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="mh"&gt;0xffL&lt;/span&gt; &lt;span class="n"&gt;shl&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;++].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="mh"&gt;0xffL&lt;/span&gt; &lt;span class="n"&gt;shl&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;++].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="mh"&gt;0xffL&lt;/span&gt; &lt;span class="n"&gt;shl&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;++].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="mh"&gt;0xffL&lt;/span&gt; &lt;span class="n"&gt;shl&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;++].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="mh"&gt;0xffL&lt;/span&gt; &lt;span class="n"&gt;shl&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;++].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="mh"&gt;0xffL&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;after&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;v&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0L&lt;/span&gt; &lt;span class="nf"&gt;or&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;++].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="mh"&gt;0xffL&lt;/span&gt; &lt;span class="n"&gt;shl&lt;/span&gt; &lt;span class="mi"&gt;56&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;or&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;++].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="mh"&gt;0xffL&lt;/span&gt; &lt;span class="n"&gt;shl&lt;/span&gt; &lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;or&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;++].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="mh"&gt;0xffL&lt;/span&gt; &lt;span class="n"&gt;shl&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;or&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;++].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="mh"&gt;0xffL&lt;/span&gt; &lt;span class="n"&gt;shl&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;or&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;++].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="mh"&gt;0xffL&lt;/span&gt; &lt;span class="n"&gt;shl&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;or&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;++].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="mh"&gt;0xffL&lt;/span&gt; &lt;span class="n"&gt;shl&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;or&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;++].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="mh"&gt;0xffL&lt;/span&gt; &lt;span class="n"&gt;shl&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;or&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;++].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="mh"&gt;0xffL&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is, admittedly, better. However, it's still moving the &lt;code&gt;or&lt;/code&gt; over to the end, which I just do not understand. And maybe more to the point, I've just had to adjust my behavior to appease a tool I don't like in the first place. It's producing uglier code that's harder to read and maintain, and now it's also making me dance! Badly! Not a fan!&lt;/p&gt;

&lt;p&gt;What about selectively switching formatting off and on? Maybe we just wrap our tabular data in &lt;code&gt;//stylecop:off&lt;/code&gt; and &lt;code&gt;//stylecop:on&lt;/code&gt;. Boom, problem solved. Well, the fact that this is an option means that we &lt;em&gt;cannot&lt;/em&gt; just slap this tool onto our codebase and never talk about whitespace again. Now we have to talk about it in every damn PR where these options appear, or could appear in the eye of the reviewer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summing up
&lt;/h2&gt;

&lt;p&gt;I put off writing this post for a long time because, as I said, I find this whole subject a waste of time in the face of competing priorities. My hope is that, having written it, I will never have to write about it again—I'll just link back to this post. Thanks for reading! Looking forward to not reading your contrary arguments ❤️&lt;/p&gt;

&lt;h2&gt;
  
  
  Endnotes
&lt;/h2&gt;

&lt;p&gt;&lt;sup&gt;1&lt;/sup&gt; He used to work at Google where, &lt;em&gt;the rumors say&lt;/em&gt;, developers are rewarded for writing complex code. &lt;small&gt;up&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;sup&gt;2&lt;/sup&gt; &lt;a href="https://cosocial.ca/@jessewilson/113207468657921369" rel="noopener noreferrer"&gt;Described as&lt;/a&gt; "the JVM equivalent of contracting a minor illness". &lt;small&gt;up&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;sup&gt;3&lt;/sup&gt; See also &lt;a href="https://stackoverflow.com/questions/1325374/research-into-advantages-of-having-a-standard-coding-style" rel="noopener noreferrer"&gt;Research into Advantages of Having a Standard Coding Style&lt;/a&gt; &lt;small&gt;up&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;sup&gt;4&lt;/sup&gt; There is no task (that I'm aware of) named &lt;code&gt;fixStyle&lt;/code&gt;. I have invented it for this post to avoid getting into fights with specific tool makers, who are working on truly difficult problems (that I simply don't care about). &lt;small&gt;up&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;sup&gt;5&lt;/sup&gt; I am obligated to say that it's Gradle's single-threaded configuration phase that is slow and annoying. Task execution will be basically identical to running the CLI tool. Configuration caching and the promised land of isolated projects may yet save us. &lt;small&gt;up&lt;/small&gt;&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>gradle</category>
    </item>
    <item>
      <title>Announcing Dependency Analysis Gradle Plugin 2.0.0!</title>
      <dc:creator>Tony Robalik</dc:creator>
      <pubDate>Tue, 27 Aug 2024 21:24:18 +0000</pubDate>
      <link>https://forem.com/gradle-community/announcing-dependency-analysis-gradle-plugin-200-426c</link>
      <guid>https://forem.com/gradle-community/announcing-dependency-analysis-gradle-plugin-200-426c</guid>
      <description>&lt;p&gt;&lt;em&gt;Cover photo by &lt;a href="https://unsplash.com/@honeypoppet?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Sandie Clarke&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/person-holding-brown-and-black-frog-q13Zq1Jufks?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The Dependency Analysis Plugin v2.0.0 has just been released!&lt;/p&gt;

&lt;h2&gt;
  
  
  Why 2.0.0?
&lt;/h2&gt;

&lt;p&gt;Breaking changes, of course. The ABI has been updated, which is relevant for anyone accessing the analysis results programmatically. There has also been an important behavioral change, which will be relevant to all users.&lt;/p&gt;

&lt;h3&gt;
  
  
  ABI changes
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;PluginAdvice&lt;/code&gt; class has been moved from the &lt;code&gt;com.autonomousapps.advice&lt;/code&gt; package to the &lt;code&gt;com.autonomousapps.model&lt;/code&gt; package, where all the other public model classes live.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;FindInlineMembersTask&lt;/code&gt; has been renamed to &lt;code&gt;FindKotlinMagicTask&lt;/code&gt;, to better reflect what it actually does. This is unlikely to impact any users, but since it has public visibility, it might.&lt;/p&gt;

&lt;p&gt;Finally, the deprecated &lt;code&gt;ignoreKtx()&lt;/code&gt; option has been removed from the &lt;code&gt;issues&lt;/code&gt; block. It can now only be configured from the &lt;code&gt;structure&lt;/code&gt; block.&lt;/p&gt;

&lt;h3&gt;
  
  
  Behavioral changes
&lt;/h3&gt;

&lt;p&gt;The plugin no longer automatically applies itself to every subproject in your build. Therefore the &lt;code&gt;dependency.analysis.autoapply&lt;/code&gt; flag is now a no-op, and using it will either emit a warning (when the value is set to false) or fail your build (if the value is set to true, which was the old default). Users now have two choices if they want to analyze subprojects in their build:&lt;/p&gt;

&lt;p&gt;First, you can apply the plugin directly to every module you want to analyze. This has long been the best practice for how to use this plugin.&lt;/p&gt;

&lt;p&gt;Second, you can use the new &lt;code&gt;com.autonomousapps.build-health&lt;/code&gt; plugin! Apply it to your settings.gradle[.kts] like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// settings.gradle[.kts]&lt;/span&gt;
&lt;span class="nf"&gt;plugins&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.autonomousapps.build-health"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="s"&gt;"2.0.0"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// And you can configure it from the settings&lt;/span&gt;
&lt;span class="c1"&gt;// script, too!&lt;/span&gt;
&lt;span class="nf"&gt;dependencyAnalysis&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This new plugin is only available from Gradle 8.8. See &lt;a href="https://github.com/autonomousapps/dependency-analysis-gradle-plugin/wiki/Adding-to-your-project#via-the-build-health-settings-plugin" rel="noopener noreferrer"&gt;the wiki&lt;/a&gt; for more information, &lt;em&gt;especially&lt;/em&gt; if you also use Android or Kotlin.&lt;/p&gt;

&lt;p&gt;In order to assist users who might update to 2.0.0 without reading the release notes, the plugin has been updated to attempt to detect the case where the plugin has been applied only to the root project but not to any subproject; this would almost certainly be a user error.&lt;/p&gt;

&lt;h2&gt;
  
  
  Happy building!
&lt;/h2&gt;

&lt;p&gt;Thanks for being a user, and please keep &lt;a href="https://github.com/autonomousapps/dependency-analysis-gradle-plugin/issues" rel="noopener noreferrer"&gt;filing issues&lt;/a&gt; if you run into any bugs.&lt;/p&gt;

</description>
      <category>gradle</category>
      <category>android</category>
      <category>jvm</category>
    </item>
    <item>
      <title>Gradle's leaky abstractions: Declarative(ish) shell, imperative core: Implementing a safe(ish) global configuration DSL</title>
      <dc:creator>Tony Robalik</dc:creator>
      <pubDate>Sat, 23 Mar 2024 19:20:30 +0000</pubDate>
      <link>https://forem.com/autonomousapps/gradles-leaky-abstractions-declarativeish-shell-imperative-core-implementing-a-safeish-global-configuration-dsl-5e63</link>
      <guid>https://forem.com/autonomousapps/gradles-leaky-abstractions-declarativeish-shell-imperative-core-implementing-a-safeish-global-configuration-dsl-5e63</guid>
      <description>&lt;p&gt;Gradle is &lt;a href="https://github.com/gradle/declarative-gradle/tree/main/unified-prototype" rel="noopener noreferrer"&gt;very&lt;/a&gt; &lt;a href="https://blog.gradle.org/declarative-gradle" rel="noopener noreferrer"&gt;aware&lt;/a&gt; &lt;a href="https://blog.jetbrains.com/blog/2023/11/09/amper-improving-the-build-tooling-user-experience/" rel="noopener noreferrer"&gt;they have a complexity problem&lt;/a&gt;. Fundamentally, the problem is that Gradle build scripts use an Actual Programming Language (either Groovy or Kotlin), and therefore provide users access to the complete Java/Groovy/Kotlin ecosystem—the JDK, the standard libraries, and all the other libraries too.&lt;/p&gt;

&lt;p&gt;Gradle wants you to write your scripts like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// build.gradle.kts&lt;/span&gt;
&lt;span class="c1"&gt;// so declarative!&lt;/span&gt;
&lt;span class="nf"&gt;plugins&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;libs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;coolThing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;myExtension&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but what that boils down to is just this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// er, imperative?&lt;/span&gt;
&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pluginManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"implementation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;libs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;coolThing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MyExtension&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which in turn is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// yeah, definitely imperative&lt;/span&gt;
&lt;span class="nc"&gt;FooPlugin&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nc"&gt;FooPlugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;etc&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where one of the most important takeaways here is that &lt;em&gt;build scripts are evaluated top-down, one "declarative block" after the other&lt;/em&gt;. Although, even that is not necessarily true. Kotlin DSL confuses this. Here's a valid settings script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// settings.gradle.kts&lt;/span&gt;
&lt;span class="n"&gt;rootProject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"..."&lt;/span&gt;
&lt;span class="nf"&gt;pluginManagement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;buildscript&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;dependencyResolutionManagement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;plugins&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gradle will not complain if you do this, but actually some of these blocks are Special and get evaluated not in top-down order. &lt;code&gt;buildscript&lt;/code&gt; is the Primordial Special block; it is always evaluated first (which makes sense, as it contributes dependencies &lt;em&gt;to the script itself&lt;/em&gt;). &lt;code&gt;pluginManagement&lt;/code&gt; is evaluated next, followed by &lt;code&gt;plugins&lt;/code&gt;, followed by everything else. Sometimes, when I'm working on a build, I will reorder blocks to match actual evaluation order because I want users to remember this (implicitly), but mostly I've given up. Note that, in a Groovy DSL script, Gradle will actually complain and refuse to compile a script that is written out-of-order. ¯\_(ツ)_/¯&lt;/p&gt;

&lt;h3&gt;
  
  
  What about &lt;code&gt;afterEvaluate&lt;/code&gt; and various other lazy callbacks?
&lt;/h3&gt;

&lt;p&gt;This isn't a master's thesis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sigh
&lt;/h2&gt;

&lt;p&gt;Anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can I create a custom extension that is configured in the root project and which configures all subprojects, and is safe for &lt;a href="https://docs.gradle.org/nightly/userguide/isolated_projects.html" rel="noopener noreferrer"&gt;isolated projects&lt;/a&gt;?
&lt;/h2&gt;

&lt;p&gt;Yes, but not easily. Here's what we want to do, from a "UI" perspective:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// root build.gradle.kts&lt;/span&gt;
&lt;span class="nf"&gt;plugins&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"my-root-plugin"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;myExtension&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// subproject/build.gradle.kts&lt;/span&gt;
&lt;span class="nf"&gt;plugins&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"my-sub-plugin"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// values set in rootProject.myExtension are&lt;/span&gt;
&lt;span class="c1"&gt;// available here, safely&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where "safely" means these projects aren't, uh, &lt;em&gt;unsafely&lt;/em&gt; coupled, and in particular don't access one another's mutable state. In very particular, they should follow the &lt;a href="https://docs.gradle.org/nightly/userguide/isolated_projects.html" rel="noopener noreferrer"&gt;Isolated Projects contract&lt;/a&gt; (which is currently a WIP). I'll elaborate on "unsafely coupled" in a moment.&lt;/p&gt;

&lt;p&gt;Naively, what we'd like to do is something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MySubPlugin&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Plugin&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;myExtension&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rootProject&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extensions&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MyExtension&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// do things with values from rootProject's&lt;/span&gt;
    &lt;span class="c1"&gt;// myExtension extension.&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That, however, is &lt;em&gt;not safe&lt;/em&gt;. A project's &lt;code&gt;ExtensionContainer&lt;/code&gt; is &lt;em&gt;mutable&lt;/em&gt; and accessing another project's &lt;em&gt;mutable state&lt;/em&gt; is &lt;em&gt;not safe&lt;/em&gt; (maybe the API should forbid this 🤔).&lt;/p&gt;

&lt;p&gt;So instead of getting access to that state directly, we will instead use one of Gradle's few blessed ways to manage global mutable state for access at configuration time: the &lt;a href="https://docs.gradle.org/current/userguide/build_services.html" rel="noopener noreferrer"&gt;Shared Build Service&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The data flow will be as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Users configure the extension in the root project.&lt;/li&gt;
&lt;li&gt;Method calls on the extension immediately push data into mutable objects held by the build service.&lt;/li&gt;
&lt;li&gt;Subprojects query the data in the service during configuration.&lt;/li&gt;
&lt;li&gt;With a little bit of cleverness, we could also have extensions in subprojects configure data just for &lt;em&gt;their&lt;/em&gt; subprojects. (This is left as an exercise for the reader.)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Important! Note that subprojects &lt;em&gt;are&lt;/em&gt; coupled to parent projects! (The root project, in this case.) But so long as we don't access the parent projects' mutable state directly, it's ok! I promise! Gradle promises to always configure parent projects before child projects, so there's always this &lt;em&gt;temporal&lt;/em&gt; coupling. (The main caveat to this is if you use an API like &lt;a href="https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html#evaluationDependsOn-java.lang.String-" rel="noopener noreferrer"&gt;&lt;code&gt;evaluationDependsOn()&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Finally, evaluate each Project by executing its "build.gradle" file, if present, against the project. The projects are evaluated in breadth-wise order, such that a project is evaluated before its child projects. This order can be overridden by calling &lt;code&gt;evaluationDependsOnChildren()&lt;/code&gt; or by adding an explicit evaluation dependency using &lt;code&gt;evaluationDependsOn(String)&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, uh, don't do that or everything goes sideways.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing the root extension
&lt;/h3&gt;

&lt;p&gt;The following demonstrates a custom DSL that's configured at the root level. The existence of an inner DSL is strictly unnecessary, but included as a demonstration because it's often useful. From the user perspective, it would be configured like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// root build.gradle.kts&lt;/span&gt;
&lt;span class="nf"&gt;myExtension&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;handler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* true or false */&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* a String */&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's important that the inner handler expose methods, not properties. Methods let us do whatever we want most easily. (A Kotlin property with custom setter would also work, but I think that's unnecessarily complicated.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyExtension&lt;/span&gt; &lt;span class="nd"&gt;@Inject&lt;/span&gt; &lt;span class="k"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ObjectFactory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;service&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MyService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;myHandler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MyHandler&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dslService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MyHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;myHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;companion&lt;/span&gt; &lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;MyExtension&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"myExtension"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;MyExtension&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyHandler&lt;/span&gt; &lt;span class="nd"&gt;@Inject&lt;/span&gt; &lt;span class="k"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MyService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;objects&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;foo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;bar&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disallowChanges&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;configureHandlerFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disallowChanges&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;configureHandlerFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bar&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;We could actually skip the &lt;code&gt;Property&lt;/code&gt; creation entirely, but I like it because of the &lt;code&gt;disallowChanges()&lt;/code&gt; API, which ensures the method can only ever be called once, and I want to ensure a single source of truth for most cases.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Implementing the build service
&lt;/h3&gt;

&lt;p&gt;A ("shared") build service is &lt;em&gt;kind of like&lt;/em&gt; a singleton, in that when you register one in &lt;em&gt;any&lt;/em&gt; project, it's available in &lt;em&gt;all&lt;/em&gt; projects as a single instance. (This unfortunately turns out &lt;a href="https://github.com/gradle/gradle/issues/14697" rel="noopener noreferrer"&gt;not to be true&lt;/a&gt;, in some cases, when using composite builds, but can be worked around.) An &lt;em&gt;actual&lt;/em&gt; singleton (global static instance) doesn't work at all, for the record—try it if you want to lose some sanity. Anyway, use a build service whenever you need global mutable state in your build.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Please note that the examples in this post demonstrate a project extension having a reference to the build service. Do &lt;em&gt;not&lt;/em&gt; attempt to do this in the other direction. You don't want a globally-available object (the build service) having access to a mutable object "owned by" any individual project.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;BuildService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;BuildServiceParameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;handlerConfigurations&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;createConfigMap&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HandlerConfiguration&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;

  &lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;configureHandlerFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HandlerConfiguration&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;handlerConfigurations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nc"&gt;HandlerConfiguration&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;findHandlerConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;HandlerConfiguration&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;handlerConfigurations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;createConfigMap&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;MutableMap&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mutableMapOf&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="nf"&gt;findConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rootProject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;companion&lt;/span&gt; &lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MyService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gradle&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sharedServices&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerIfAbsent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="s"&gt;"myService"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nc"&gt;MyService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;project.rootProject.path&lt;/code&gt; is okay because &lt;code&gt;project.path&lt;/code&gt; is &lt;em&gt;immutable state&lt;/em&gt; and safe to access from another project.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;HandlerConfig&lt;/code&gt; is just a mutable value object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HandlerConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Consuming the values in a subproject
&lt;/h3&gt;

&lt;p&gt;In your convention plugin that you apply to your subprojects (&lt;a href="https://developer.squareup.com/blog/herding-elephants/" rel="noopener noreferrer"&gt;you're doing that, right?&lt;/a&gt;), you can get a reference to the shared build service, and then query the configuration object and do whatever you like with it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MySubPlugin&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Plugin&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;service&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MyService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findHandlerConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;let&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="c1"&gt;// do something with config.foo&lt;/span&gt;
      &lt;span class="c1"&gt;// do something with config.bar&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And there we have it. If you want the ability to configure a big project from the root project, with complex data, in a way that keeps your projects mostly decoupled (sigh) from each other, I have a variant of the above running in a large production system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is there another way?
&lt;/h2&gt;

&lt;p&gt;Sure. You could rely entirely on &lt;a href="https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties" rel="noopener noreferrer"&gt;Gradle properties&lt;/a&gt; and organize your configuration knobs as a set of key-value pairs. This is much easier to implement but much harder to use because Gradle properties don't support code completion, don't have easily accessible Javadoc, don't have any IDE support really, and it's shockingly easy to scatter your properties across your entire codebase, making them practically undiscoverable by feature engineers.&lt;/p&gt;

</description>
      <category>gradle</category>
      <category>java</category>
      <category>kotlin</category>
    </item>
    <item>
      <title>Configuration roles and the blogging-industrial complex</title>
      <dc:creator>Tony Robalik</dc:creator>
      <pubDate>Tue, 17 Oct 2023 21:52:27 +0000</pubDate>
      <link>https://forem.com/autonomousapps/configuration-roles-and-the-blogging-industrial-complex-21mn</link>
      <guid>https://forem.com/autonomousapps/configuration-roles-and-the-blogging-industrial-complex-21mn</guid>
      <description>&lt;p&gt;&lt;em&gt;On the perils of getting what you ask for&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Or, building cool stuff and making everyone think you're a wizard&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Gradle 8.4 has released an &lt;em&gt;exciting new feature&lt;/em&gt;, an API for easily creating &lt;a href="https://docs.gradle.org/8.4/release-notes.html#easier-to-create-role-focused-configurations" rel="noopener noreferrer"&gt;role-focused &lt;code&gt;Configuration&lt;/code&gt;s&lt;/a&gt;. For those who have been around the block a few times, it is well-known that the &lt;a href="https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/Configuration.html" rel="noopener noreferrer"&gt;&lt;code&gt;Configuration&lt;/code&gt;&lt;/a&gt; interface is amongst the most confusing in Gradle lore, not to mention having a very &lt;a href="https://github.com/autonomousapps/gradle-glossary#configuration" rel="noopener noreferrer"&gt;overloaded name&lt;/a&gt;. It also has many responsibilities and a massive memory footprint.&lt;/p&gt;

&lt;p&gt;The new API is meant as a step towards disambiguation. The new methods on &lt;a href="https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/ConfigurationContainer.html" rel="noopener noreferrer"&gt;&lt;code&gt;ConfigurationContainer&lt;/code&gt;&lt;/a&gt; (aka &lt;a href="https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html#getConfigurations--" rel="noopener noreferrer"&gt;&lt;code&gt;configurations&lt;/code&gt;&lt;/a&gt;) are designed to simplify the creation of immutable, purpose-built &lt;code&gt;Configuration&lt;/code&gt; instances. They do that. But they also expose complexity that was previously obscured by the prior &lt;em&gt;legacy&lt;/em&gt; situation. On the plus side, this additional complexity is a boon to bloggers and build engineers eager to justify their (my) existence.&lt;/p&gt;

&lt;p&gt;All of the code used in this post is available on &lt;a href="https://github.com/autonomousapps/blog-configuration-roles" rel="noopener noreferrer"&gt;Github&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  First a warning
&lt;/h2&gt;

&lt;p&gt;Most Gradle projects won’t need this kind of setup, and most Gradle plugins won’t require this elaboration of responsibilities in the &lt;code&gt;Configuration&lt;/code&gt;s they create. The primary use-case is for &lt;a href="https://github.com/autonomousapps/gradle-glossary#ecosystem-plugin" rel="noopener noreferrer"&gt;ecosystem plugins&lt;/a&gt; (aka “core” plugins), but what we’ll do here is also fairly common. Even if you don’t need this yourself, understanding &lt;code&gt;Configuration&lt;/code&gt;s more deeply can be very valuable.&lt;/p&gt;
&lt;h2&gt;
  
  
  What to expect if you keep reading
&lt;/h2&gt;

&lt;p&gt;A brief exploration of the three kinds of &lt;code&gt;Configuration&lt;/code&gt; types, an explanation of how to use them, and a dip of the toes in the deep waters of safe cross-project publishing and aggregation.&lt;/p&gt;

&lt;p&gt;To guide our exploration, we will consider a concrete use-case. We want to be able to aggregate data from all subprojects at the root project (a natural location), and we want to do it in a safe way that doesn’t violate project boundaries. Here’s how we want our build scripts to look:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="c1"&gt;// aggregator/build.gradle (or root build.gradle)&lt;/span&gt;
&lt;span class="n"&gt;plugins&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="s1"&gt;'org.jetbrains.kotlin.jvm'&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="s1"&gt;'mutual.aid.configuration-roles'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;sourceFiles&lt;/span&gt; &lt;span class="nf"&gt;project&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;':feature-1'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;sourceFiles&lt;/span&gt; &lt;span class="nf"&gt;project&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;':feature-2'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="c1"&gt;// both *feature-1/build.gradle* and *feature-2/build.gradle*&lt;/span&gt;
&lt;span class="n"&gt;plugins&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="s1"&gt;'org.jetbrains.kotlin.jvm'&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="s1"&gt;'mutual.aid.configuration-roles'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;We will apply our plugin to each of our projects.&lt;sup&gt;1&lt;/sup&gt; It will set up publication of interesting data from the subprojects, as well as aggregation of that data in our aggregator, or root, project. The data we’re collecting for this example is information about the source files in our subprojects, hence the name of our imaginary new &lt;code&gt;Configuration&lt;/code&gt;, &lt;code&gt;sourceFiles&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are &lt;code&gt;Configuration&lt;/code&gt;s, anyway? The three kinds
&lt;/h2&gt;

&lt;p&gt;Configurations are used for…&lt;br&gt;
…declaring dependencies&lt;br&gt;
…resolving dependencies within a project&lt;br&gt;
…sharing artifacts between projects&lt;/p&gt;

&lt;p&gt;To create these three kinds of instances in the past (before Gradle 8.4), we had to do the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;configurations&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// you declare dependencies here&lt;/span&gt;
  &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"implementation"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="n"&gt;isCanBeResolved&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt; 
    &lt;span class="n"&gt;isCanBeConsumed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// plugins will resolve dependencies from this one&lt;/span&gt;
  &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"runtimeClasspath"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;isCanBeResolved&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;  &lt;span class="c1"&gt;// Defaults to true&lt;/span&gt;
    &lt;span class="n"&gt;isCanBeConsumed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
    &lt;span class="nf"&gt;extendsFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;configurations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"implementation"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; 
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// plugins expose artifacts to other projects on this one&lt;/span&gt;
  &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"runtimeElements"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
   &lt;span class="n"&gt;isCanBeResolved&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
   &lt;span class="n"&gt;isCanBeConsumed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;  &lt;span class="c1"&gt;// Defaults to true&lt;/span&gt;
   &lt;span class="nf"&gt;extendsFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;configurations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"implementation"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; 
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that we first create the instances, and then we mutate them in configuration blocks (yes, the word “configuration” is being used in two separate senses here).&lt;/p&gt;

&lt;p&gt;In practice, most community plugins were not so scrupulous as to create three separate configurations for these three separate use-cases—and nor were they required to, since Gradle’s API is generally very permissive and legacy-friendly. The new APIs (see below) expose this complexity and enforce rigor… without, unfortunately, much (if anything) in the way of documentation or explanation.&lt;/p&gt;

&lt;p&gt;Here are the new methods:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;configurations&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="nf"&gt;dependencyScope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"implementation"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;resolvable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"runtimeClasspath"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;extendsFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;configurations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"implementation"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; 
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;consumable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"runtimeElements"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="nf"&gt;extendsFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;configurations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"implementation"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; 
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can all agree that this is fewer lines of code, but this obscures the additional rigor that is suddenly required to actually interact with these configurations. Because in point of fact, in the past, plugin authors would probably just do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;configurations&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;implementation&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a &lt;em&gt;single&lt;/em&gt; configuration that Gradle will happily let you use to…&lt;br&gt;
…declare dependencies&lt;br&gt;
…resolve dependencies within a project&lt;br&gt;
…share artifacts between projects&lt;/p&gt;

&lt;p&gt;For reasons that are outside of the scope of this post, this is Bad™. Everyone™® agrees™®© that the &lt;code&gt;Configuration&lt;/code&gt; API should be destroyed in a fire and/or thrown into the seam between universes, never to be seen again. The new API &lt;a href="https://docs.google.com/document/d/1_1WxfEfTJFbo7PZUv3k7_M0X8gY4ehk-B_Vg8PhUZjM/edit#heading=h.610fausqnpu6" rel="noopener noreferrer"&gt;is meant as a step towards this glorious future&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to do something useful and interesting with this stuff
&lt;/h2&gt;

&lt;p&gt;With that out of the way, how do we actually use this API, keep up with the times, and make our plugins (slightly more) future-proof?&lt;/p&gt;

&lt;p&gt;Let’s create a plugin! (Yes, this is my answer to everything.) The purpose of our new plugin is to aggregate information across all projects in our build in a way that is safe, that doesn’t violate project boundaries. It will demonstrate the usage of all three kinds of configuration, as well as very lightly touching on &lt;em&gt;variant attributes&lt;/em&gt;. This is how Gradle models things like compile and runtime classpath separation, among other things, and are a very powerful (although &lt;em&gt;deeply&lt;/em&gt; verbose and annoying) concept.&lt;/p&gt;
&lt;h3&gt;
  
  
  Setting up our configurations
&lt;/h3&gt;

&lt;p&gt;First let’s create our three configurations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ConfigurationRolesPlugin&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Plugin&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Following the naming pattern established by the Java Library&lt;/span&gt;
    &lt;span class="c1"&gt;// plugin. More on this in a moment.&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;declarableName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"sourceFiles"&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;internalName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"${declarableName}Classpath"&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;externalName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"${declarableName}Elements"&lt;/span&gt;

    &lt;span class="c1"&gt;// Dependencies are declared on this configuration&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;declarable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;configurations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dependencyScope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;declarableName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="c1"&gt;// The new APIs return the new configuration wrapped in a lazy&lt;/span&gt;
      &lt;span class="c1"&gt;// Provider, for consistency with other Gradle APIs. However,&lt;/span&gt;
      &lt;span class="c1"&gt;// there is no value in having a lazy Configuration, since we&lt;/span&gt;
      &lt;span class="c1"&gt;// use  it immediately anyway. So, call get() to realize it, and&lt;/span&gt;
      &lt;span class="c1"&gt;// call it a day.&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;// The plugin will resolve dependencies against this internal&lt;/span&gt;
    &lt;span class="c1"&gt;// configuration, which extends from  the declared dependencies&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;internal&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;configurations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolvable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;internalName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extendsFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;declarable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="c1"&gt;// Same as below&lt;/span&gt;
      &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attributes&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nc"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CATEGORY_ATTRIBUTE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     
          &lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;named&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Category&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DOCUMENTATION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// The plugin will expose dependencies on this configuration, &lt;/span&gt;
    &lt;span class="c1"&gt;// which extends from the declared dependencies&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;external&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;configurations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;consumable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;externalName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extendsFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;declarable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="c1"&gt;// Same as above&lt;/span&gt;
      &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attributes&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nc"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CATEGORY_ATTRIBUTE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;named&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Category&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DOCUMENTATION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As indicated by the comments, we are following the naming convention &lt;a href="https://docs.gradle.org/current/userguide/java_library_plugin.html#sec:java_library_configurations_graph" rel="noopener noreferrer"&gt;established&lt;/a&gt; by the Java Library plugin.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fofm0p39l5fyo3bdlhnui.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fofm0p39l5fyo3bdlhnui.png" alt="java-library plugin configurations" width="763" height="261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;Our &lt;strong&gt;declarable&lt;/strong&gt; configuration is named “sourceFiles”, and corresponds to one of the green configurations in the diagram (let’s say “implementation” for a concrete example). It is on this configuration that we declare our dependencies. We extend this configuration with a resolvable (&lt;strong&gt;internal&lt;/strong&gt;) configuration (colored blue), named “sourceFilesClasspath”,&lt;sup&gt;2&lt;/sup&gt; and this is used within the project by tasks which resolve these dependencies. Finally, we also extend the original configuration with a consumable (&lt;strong&gt;external&lt;/strong&gt;) configuration (colored pink), upon which we will publish artifacts for use by dependent projects.&lt;/p&gt;

&lt;p&gt;We use the &lt;a href="https://docs.gradle.org/current/userguide/cross_project_publications.html#sec:variant-aware-sharing" rel="noopener noreferrer"&gt;attributes&lt;/a&gt; to tell Gradle what kind of artifact we’ll be attaching to our configurations. In this case, we say we are publishing “documentation.” Note that this is, in a manner of speaking, arbitrary, though it is important to be consistent. If we own a Very Important ecosystem plugin such as the java-library or Android Gradle plugin, then it’s also important that our attributes be meaningful or our users will hate us. For this post, I just picked something easy and readily available without giving it a lot of thought.&lt;/p&gt;

&lt;h3&gt;
  
  
  Publishing a custom (non-jar) artifact
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;kotlin&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;extensions&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;KotlinJvmProjectExtension&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// src/main&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;mainSource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kotlin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sourceSets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;named&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;// src/main/{kotlin,java} as a FileTree&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;mainKotlinSource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mainSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kotlin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Register a task to produce a custom output for consumption by &lt;/span&gt;
  &lt;span class="c1"&gt;// other projects&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;producer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"collectSources"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ProducerTask&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mainKotlinSource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;layout&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buildDirectory&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"reports/configuration-roles/sources.txt"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="n"&gt;configurations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;consumable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;externalName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

  &lt;span class="c1"&gt;// Teach Gradle which task produces the artifact associated with&lt;/span&gt;
  &lt;span class="c1"&gt;// this external/consumable configuration&lt;/span&gt;
  &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outgoing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;artifact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatMap&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s a few lines of code, but what we’ve basically done is simply:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Registered a task that takes as input all of the source files of &lt;em&gt;this&lt;/em&gt; project.&lt;/li&gt;
&lt;li&gt;Register the output of that task as an outgoing artifact on our consumable (external) configuration.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The task that produces the artifact we’re sharing is not very interesting for our purposes here, but if you want to take a look, &lt;a href="https://github.com/autonomousapps/blog-configuration-roles/blob/main/build-logic/configuration-roles/src/main/kotlin/mutual/aid/ProducerTask.kt" rel="noopener noreferrer"&gt;here&lt;/a&gt; it is.&lt;/p&gt;

&lt;h3&gt;
  
  
  Consuming the artifacts from dependency projects
&lt;/h3&gt;

&lt;p&gt;Here we define and register a task that will consume the published artifacts of other projects. Recall this works because our aggregator project has this in its build script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="c1"&gt;// aggregator/build.gradle (or root build.gradle)&lt;/span&gt;
&lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;sourceFiles&lt;/span&gt; &lt;span class="nf"&gt;project&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;':feature-1'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;sourceFiles&lt;/span&gt; &lt;span class="nf"&gt;project&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;':feature-2'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what links our aggregator to the other projects we’re collecting data from. Now let’s register our new task:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Register a task to consume the custom outputs produced by other &lt;/span&gt;
&lt;span class="c1"&gt;// projects&lt;/span&gt;
&lt;span class="n"&gt;tasks&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"printDependencySources"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ConsumerTask&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;internal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ConsumerTask&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;DefaultTask&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nc"&gt;PathSensitive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PathSensitivity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RELATIVE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nc"&gt;InputFiles&lt;/span&gt;
  &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;reports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ConfigurableFileCollection&lt;/span&gt;

  &lt;span class="nd"&gt;@TaskAction&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;action&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;globalSources&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joinToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;separator&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"\n"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
      &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readText&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;quiet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;globalSources&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There’s nothing special here. The artifacts that our other projects are publishing are ergonomically available (as simple text files in this case) on our new consumable configurations, which we can feed directly and easily into our custom task, which reads those files into memory and then prints them to console for reading by your users.&lt;/p&gt;

&lt;p&gt;Happy Gradling!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cover image: a developer trying to find something in Gradle's documentation&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Endnotes
&lt;/h2&gt;

&lt;p&gt;&lt;sup&gt;1&lt;/sup&gt; I can imagine we might want two separate plugins for this, one for publishing and one for consuming or aggregating. But this is not strictly necessary. &lt;small&gt;up&lt;/small&gt;&lt;br&gt;
&lt;sup&gt;2&lt;/sup&gt; Yes, even though this won’t be a classpath in any normal sense. It’s just a convenient name. &lt;small&gt;up&lt;/small&gt;&lt;/p&gt;

</description>
      <category>gradle</category>
      <category>java</category>
      <category>kotlin</category>
    </item>
    <item>
      <title>Become a Gradle expert in 3 easy steps!</title>
      <dc:creator>Tony Robalik</dc:creator>
      <pubDate>Thu, 15 Jun 2023 19:47:18 +0000</pubDate>
      <link>https://forem.com/autonomousapps/become-a-gradle-expert-in-3-easy-steps-3em5</link>
      <guid>https://forem.com/autonomousapps/become-a-gradle-expert-in-3-easy-steps-3em5</guid>
      <description>&lt;p&gt;&lt;strong&gt;Step 1.&lt;/strong&gt; Your build fails "for no reason" and your small team has no dedicated build support. Everyone is complaining in Slack and you can't bear to look at another poor quality Geordi meme.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsj7itp8w7we2wxqjnstz.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsj7itp8w7we2wxqjnstz.jpg" alt="A Geordi meme template. Geordi scoffs at the notion of fixing his broken builds, and prefers instead to complain about them" width="500" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You copy a portion of the stacktrace and paste it into Google. Turns out jcenter is experiencing &lt;a href="https://blog.gradle.org/jcenter-shutdown" rel="noopener noreferrer"&gt;yet&lt;/a&gt; &lt;a href="https://stackoverflow.com/a/70687500" rel="noopener noreferrer"&gt;another&lt;/a&gt; &lt;a href="https://www.reddit.com/r/androiddev/comments/yi23k8/jcenter_down/" rel="noopener noreferrer"&gt;outage&lt;/a&gt;. You look at the suggested workarounds and do a find-and-replace: &lt;code&gt;s/jcenter/mavenCentral&lt;/code&gt;. Your build works again! You don't really understand why, but you also don't care. Back to writing beautiful UI!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2.&lt;/strong&gt; Your build fails "for no reason" and your small team has no dedicated build support. Everyone remembers that you fixed it last time and ask you to look into it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3.&lt;/strong&gt; Now you're the expert. Congrats!&lt;/p&gt;

</description>
      <category>gradle</category>
      <category>java</category>
      <category>android</category>
      <category>jokes</category>
    </item>
    <item>
      <title>Nihilism and the anti-corruption layer</title>
      <dc:creator>Tony Robalik</dc:creator>
      <pubDate>Fri, 24 Jun 2022 21:29:36 +0000</pubDate>
      <link>https://forem.com/autonomousapps/nihilism-and-the-anti-corruption-layer-560n</link>
      <guid>https://forem.com/autonomousapps/nihilism-and-the-anti-corruption-layer-560n</guid>
      <description>&lt;p&gt;&lt;a&gt;&lt;/a&gt;Every now and then someone asks me if I intend a more technical follow-up to &lt;a href="https://developer.squareup.com/blog/herding-elephants/" rel="noopener noreferrer"&gt;Herding Elephants&lt;/a&gt;, which was a fairly high-level overview of the strategy I initiated to modernize that large Android repo I help maintain. And the answer is—not really. It is hard to appreciate how many moving parts exist in a repo at that scale without seeing it for yourself. Many of the lessons learned are irrelevant for smaller projects, which is most of them. The solutions to these medium-large problems&lt;sup&gt;1&lt;/sup&gt; are expensive to build and expensive to maintain, and the cost-benefit analysis is only in their favor at a certain, well, scale. Dozens of developers and hundreds or thousands of modules. Millions of lines of code.&lt;/p&gt;

&lt;p&gt;Of all the components that form part of our current build system, however, one stands out as &lt;em&gt;particularly cool&lt;/em&gt; and also as a metaphor for the human condition, which is what I really want to talk about.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to expect if you keep reading
&lt;/h2&gt;

&lt;p&gt;I will elaborate on the anti-corruption layer I built to protect our build from upstream changes coming from the rapidly-evolving Android Gradle Plugin, and discuss how this has helped our build maintain stability and migrate to newer releases reliably and with increasing rapidity. I will wave my hands at some of the guard rails I built. I will discourage you from attempting this yourself. I will then pivot to how this relates to nihilism and the human condition.&lt;/p&gt;

&lt;h2&gt;
  
  
  The anti-corruption layer
&lt;/h2&gt;

&lt;p&gt;As any Android developer with more than a year or two of experience knows, the Android Gradle Plugin (AGP) is constantly evolving. For the very simplest projects, these changes are largely transparent, but I would guess that &lt;em&gt;most&lt;/em&gt; Android projects have had frustrations with these changes over the years.&lt;/p&gt;

&lt;p&gt;One of the pain points of AGP is that its API is constantly changing. (Google even has a documented "&lt;a href="https://developer.android.com/studio/releases/gradle-plugin-roadmap" rel="noopener noreferrer"&gt;migration timeline&lt;/a&gt;" for this API; it spans years.) Google also takes the idiosyncratic perspective that many of its public classes aren't really public API, but internal implementation detail, and because of this, it often pushes breaking changes without any corresponding signal via &lt;a href="https://semver.org/" rel="noopener noreferrer"&gt;semantic versioning&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are two extreme solutions to this problem. At one extreme, &lt;em&gt;which I highly recommend you follow&lt;/em&gt;, is being a &lt;strong&gt;Conformist&lt;/strong&gt;. This is a design pattern whereby you essentially "give up" and admit that you will never really be able to craft a model specific to your project. Your model is AGP's model. This is fine.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhbixqd9omkldy6rhzl5d.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhbixqd9omkldy6rhzl5d.gif" alt="This is fine" width="498" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;At the other extreme we have the &lt;strong&gt;Anti-corruption layer&lt;/strong&gt;. You probably shouldn't do this.&lt;sup&gt;2&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;An anti-corruption layer is used when a downstream project has a dependency on an upstream that it (1) has no control over; (2) can't avoid; and (3) doesn't trust. This precisely describes our relationship with AGP. The concept is often summarized as "wrapping" dependencies, but it is more than that. Let's consider the implementation of this pattern that we use in the "convention plugins" I wrote about in &lt;a href="https://developer.squareup.com/blog/herding-elephants/" rel="noopener noreferrer"&gt;Herding elephants&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I want to be clear here that I'm speaking in a highly technical sense. I am not making normative judgments about AGP's structure nor the direction of its evolution. In point of fact, I often have fruitful discussions with members of the team that maintains AGP, whom I like and respect. Nevertheless, the only power I hold over that upstream dependency is the power of persuasion, which doesn't cut it when my code is used tens of thousands of times per day to build software that powers a multi-billion dollar business.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We start with the &lt;em&gt;facade&lt;/em&gt;, or wrapper interface. A facade is the interface we wish we had, and which takes care to redirect to the actual interface provided by the dependency (AGP in this case). Here's a simplified example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyoch4qcn0ymodliamaqa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyoch4qcn0ymodliamaqa.png" alt="Wrapper interface" width="800" height="257"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the right is AGP, which has complicated methods like &lt;code&gt;majorArcanum(...)&lt;/code&gt; and &lt;code&gt;minorArcanum(...)&lt;/code&gt;, which each take many arguments. On the left we have our desired interface, which is just a single method that takes no arguments. We can do this because, while AGP is a &lt;em&gt;general purpose&lt;/em&gt; build tool that has to solve problems for Every Android Developer Everywhere, we have a different, smaller problem: how to build &lt;em&gt;our&lt;/em&gt; apps for &lt;em&gt;our&lt;/em&gt; developers.&lt;/p&gt;

&lt;p&gt;We wrap our facade in an "adapter" that knows how to talk to different versions of AGP. This is how we solve the problem of AGP's rapidly changing interface. Our adapter can provide a different implementation for each version of AGP we support, which is the current version plus the next two release candidates in the AGP pipeline. Through this mechanism, we can update to each new release with ease—no more months-long migrations.&lt;/p&gt;

&lt;p&gt;To help you visualize how this works, consider this simplified implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AndroidGradlePluginFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;agpVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AgpVersion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;companion&lt;/span&gt; &lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;AndroidGradlePlugin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;AndroidGradlePluginFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;project&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;agpVersion&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AgpVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;newAdapter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;newAdapter&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;AndroidGradlePlugin&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;agpVersion&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nc"&gt;AgpVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"7.3"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;AndroidGradlePlugin7_3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;agpVersion&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nc"&gt;AgpVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"7.2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;AndroidGradlePlugin7_2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;agpVersion&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nc"&gt;AgpVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"7.1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;AndroidGradlePlugin7_1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;UnknownAgpException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"No adapter for AGP $agpVersion"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please be warned that &lt;code&gt;AgpVersion.current(project)&lt;/code&gt; encapsulates a non-trivial implementation for retrieving the actual version of AGP that is present at runtime. Also note that each implementation is encapsulated in its own project (module) to isolate its classpath.&lt;/p&gt;

&lt;p&gt;To use this factory, your plugins should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyPlugin&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Plugin&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;agp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AndroidGradlePluginFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// all accesses of AGP functionality _must_ go through `agp`&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also inject an &lt;code&gt;agp&lt;/code&gt; instance into your custom extension and then require your developers to use that extension (rather than the backing &lt;code&gt;android&lt;/code&gt; extension) for customizing their projects.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CircleExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AndroidGradlePlugin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;buildMyAppPlease&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;how&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="err"&gt;…&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/build.gradle&lt;/span&gt;
&lt;span class="n"&gt;plugins&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="s1"&gt;'com.circle.android.app'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;circle&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;buildMyAppPlease&lt;/span&gt; &lt;span class="s1"&gt;'with style'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Guard rails
&lt;/h3&gt;

&lt;p&gt;This is already pretty complicated, yet it's also not enough. This is why you shouldn't do it. But if you insist, below are some of the guards we've had to institute to keep from going off the rails.&lt;/p&gt;

&lt;p&gt;Please note that, in each case, I'm &lt;em&gt;dramatically simplifying&lt;/em&gt; our build setup so that it fits into a blog post shorter than a master's thesis. This isn't easy and you should think long and hard before deciding to do this.&lt;/p&gt;

&lt;h4&gt;
  
  
  Comprehensive test suite
&lt;/h4&gt;

&lt;p&gt;All of our plugins are thoroughly tested against all supported versions of AGP. This is very &lt;a href="https://dev.to/autonomousapps/defensive-development-gradle-plugin-development-for-busy-engineers-486c"&gt;hard to get right&lt;/a&gt;, but utterly necessary.&lt;/p&gt;

&lt;h4&gt;
  
  
  NoAgpAtRuntime
&lt;/h4&gt;

&lt;p&gt;We have a task, &lt;code&gt;noAgpAtRuntime&lt;/code&gt;, that is registered on all of our build-logic modules. This task will fail if it discovers a class from AGP on the build classpath. We expect the build that applies our plugins (the "main build") to be responsible for providing AGP.&lt;/p&gt;

&lt;h4&gt;
  
  
  Strict dependencies
&lt;/h4&gt;

&lt;p&gt;We set a strict dependency constraint on our root build classpath to ensure it's using the version of AGP we specify for every build.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="c1"&gt;// root build.gradle of main build&lt;/span&gt;
&lt;span class="n"&gt;buildscript&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;constraints&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;classpath&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'com.android.tools.build:gradle'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="n"&gt;strictly&lt;/span&gt; &lt;span class="n"&gt;agpVersion&lt;/span&gt; &lt;span class="cm"&gt;/* a string */&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  DependencyGuard
&lt;/h4&gt;

&lt;p&gt;We use &lt;a href="https://github.com/dropbox/dependency-guard" rel="noopener noreferrer"&gt;DependencyGuard&lt;/a&gt; to fail our build if the build classpath changes unexpectedly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="c1"&gt;// root build.gradle&lt;/span&gt;
&lt;span class="n"&gt;plugins&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="s1"&gt;'com.dropbox.dependency-guard'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;dependencyGuard&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'classpath'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Nihilism gets a bad rap
&lt;/h2&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;What has any of this to do with nihilism?&lt;sup&gt;3&lt;/sup&gt; As a concept, it's something I've been flirting with my entire life, and it came to a head recently when I happened to catch an &lt;a href="https://radiolab.org/episodes/dust-planet" rel="noopener noreferrer"&gt;episode of Radiolab&lt;/a&gt; from 2014, about a book called &lt;a href="https://www.johnhuntpublishing.com/zer0-books/our-books/in-the-dust-of-this-planet" rel="noopener noreferrer"&gt;"In the Dust of This Planet"&lt;/a&gt;—a book I subsequently bought and devoured.&lt;/p&gt;

&lt;p&gt;I think most people have weird ideas about what nihilism means; my first encounter with it was probably when I saw &lt;em&gt;The Big Lebowski&lt;/em&gt; as a teenager.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg66wykj4964z0m15m36n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg66wykj4964z0m15m36n.png" alt="Nihilists in The Big Lebowski" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is not the kind of nihilism I'm talking about. I mean it as more of a renunciation of received truth. In the context of software development, we have lots of received truths, and simply accepting them as such leads to the &lt;em&gt;conformist&lt;/em&gt; approach (or "pattern") discussed above. Rejecting those truths and going your own way contains, for me, an element of nihilism that I find myself embracing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nothing matters, so do what you want
&lt;/h3&gt;

&lt;p&gt;I was already working on this post when I finally saw &lt;em&gt;Everything Everywhere All at Once&lt;/em&gt; the other day. That movie is profoundly nihilistic and I &amp;lt;3 it so much. The primary antagonist in the film, Jobu Tupaki, has come to realize that nothing matters and so has created the everything-bagel-of-nothing, a thing which annihilates all who gaze upon it; this self-destruction is what she herself seeks. Her mother, Evelyn, comes to the same realization, but lands on a different conclusion, which is that, because nothing matters, she can do what she wants, and what she wants is to spend her one life with her loved ones.&lt;/p&gt;

&lt;p&gt;One of the ways I know that nothing matters is that we've radically destabilized the climate such that it is a foregone conclusion that billions will die and civilizations will fall, and the present mass extinction will continue. We're far too late to stop it, and this against a background of the majority of people &lt;em&gt;wanting to stop it&lt;/em&gt;, but our public institutions being so corrupt that we can only watch in horror as they go in the opposite direction. Truly, "nothing matters."&lt;/p&gt;

&lt;p&gt;I guess you could say I'm an &lt;em&gt;eco-nihilist&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;On a day like today, I must also point out that the US Supreme Court, in an act of destructive nihilism, has further proven my point by going against the democratic majority to expand gun rights and constrict women's rights, and is almost certainly setting up to further curtail personal liberty with attacks on LGBTQ rights in the coming years.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The anti-corruption layer, take 2
&lt;/h3&gt;

&lt;p&gt;If you've read this far, you have the patience of a saint, and so I'm going to cut to the chase. My anti-corruption layer, that lets me continue to persist despite &lt;em&gt;knowing&lt;/em&gt;, to my core, that nothing matters, is a deep well of incandescent rage. Conformity would be so much easier—it's what practically everyone around me wants me to do. But I find it literally impossible. We live on a garden world in an endlessly empty universe; it could be a paradise yet we've built a death machine instead. I am &lt;em&gt;so angry&lt;/em&gt;. That anger is my shield, but increasingly my nihilism is my shield, too. Nothing matters, so I'm going to do what I want.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5gh1ke0ehiv7a7ziv6bw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5gh1ke0ehiv7a7ziv6bw.jpg" alt="An updated wrapper interface, this time around The World" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Endnotes
&lt;/h2&gt;

&lt;p&gt;&lt;sup&gt;1&lt;/sup&gt; We're not &lt;em&gt;truly&lt;/em&gt; large (cf Google). &lt;small&gt;up&lt;/small&gt;&lt;br&gt;
&lt;sup&gt;2&lt;/sup&gt; The world of software development is thick with metaphor. This post uses numerous metaphors from the world of design patterns, with a focus on two in particular: &lt;strong&gt;Conformist&lt;/strong&gt; and &lt;strong&gt;Anti-corruption layer&lt;/strong&gt;. I learned the names of these patterns from the book &lt;a href="https://en.wikipedia.org/wiki/Domain-driven_design" rel="noopener noreferrer"&gt;&lt;em&gt;Domain-driven design&lt;/em&gt;&lt;/a&gt; by Eric Evans, but discovered them independently through an intent focus on solving the kinds of problems I often write about on this blog. See also &lt;a href="https://en.wikipedia.org/wiki/Christopher_Alexander" rel="noopener noreferrer"&gt;Christopher Alexander&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/A_Pattern_Language" rel="noopener noreferrer"&gt;A Pattern Language&lt;/a&gt;. &lt;small&gt;up&lt;/small&gt;&lt;br&gt;
&lt;sup&gt;3&lt;/sup&gt; I am not a philosopher (IANAP). &lt;small&gt;up&lt;/small&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>gradle</category>
      <category>android</category>
    </item>
    <item>
      <title>Tony's rules for Gradle plugin authors</title>
      <dc:creator>Tony Robalik</dc:creator>
      <pubDate>Mon, 25 Apr 2022 01:30:01 +0000</pubDate>
      <link>https://forem.com/autonomousapps/tonys-rules-for-gradle-plugin-authors-28k3</link>
      <guid>https://forem.com/autonomousapps/tonys-rules-for-gradle-plugin-authors-28k3</guid>
      <description>&lt;p&gt;The Gradle &lt;a href="https://docs.gradle.org/current/javadoc/org/gradle/api/package-summary.html" rel="noopener noreferrer"&gt;API surface&lt;/a&gt; is huge. It is also littered with unspoken rules whose enforcement mechanism is inscrutable runtime failures.&lt;/p&gt;

&lt;p&gt;I want to say "we can do better," but really, &lt;a href="https://github.com/gradle/gradle/issues/16344" rel="noopener noreferrer"&gt;we can't&lt;/a&gt;. The best we can do at present is mitigate by internalizing the following rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  A rule by any other name
&lt;/h2&gt;

&lt;p&gt;I call these "rules," but in many cases they can be only guidelines. Sometimes we have to break a rule because there really is no other way to achieve our goals. Nevertheless, the following rules were all learned the &lt;em&gt;hard way&lt;/em&gt;, and should only be violated consciously.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rules
&lt;/h2&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;An important bit of context for the following is that a Gradle build is divided into two&lt;sup&gt;1&lt;/sup&gt; primary &lt;a href="https://docs.gradle.org/current/userguide/build_lifecycle.html" rel="noopener noreferrer"&gt;phases&lt;/a&gt;: &lt;strong&gt;configuration&lt;/strong&gt; and &lt;strong&gt;execution&lt;/strong&gt;. Most of the rules are about what it is permissible to do in one phase or the other. Each phase carries with it different restrictions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't do expensive computations in the configuration phase&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It slows down the build. Such computations should be encapsulated in a task action.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Avoid the &lt;a href="https://docs.gradle.org/current/javadoc/org/gradle/api/NamedDomainObjectContainer.html#create-java.lang.String-org.gradle.api.Action-" rel="noopener noreferrer"&gt;&lt;code&gt;create&lt;/code&gt;&lt;/a&gt; method on Gradle's container types&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use &lt;a href="https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/TaskContainer.html#register-java.lang.String-java.lang.Class-org.gradle.api.Action-" rel="noopener noreferrer"&gt;&lt;code&gt;register&lt;/code&gt;&lt;/a&gt; instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Avoid the &lt;a href="https://docs.gradle.org/current/javadoc/org/gradle/api/DomainObjectCollection.html#all-org.gradle.api.Action-" rel="noopener noreferrer"&gt;&lt;code&gt;all&lt;/code&gt;&lt;/a&gt; callback on Gradle's container types&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use &lt;a href="https://docs.gradle.org/current/javadoc/org/gradle/api/DomainObjectCollection.html#configureEach-org.gradle.api.Action-" rel="noopener noreferrer"&gt;&lt;code&gt;configureEach&lt;/code&gt;&lt;/a&gt; instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't assume your plugin is applied after another&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead, use &lt;a href="https://docs.gradle.org/current/dsl/org.gradle.api.plugins.PluginManager.html#org.gradle.api.plugins.PluginManager:withPlugin(java.lang.String,%20org.gradle.api.Action)" rel="noopener noreferrer"&gt;&lt;code&gt;pluginManager.withPlugin()&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Avoid making any ordering assumptions of any kind&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.gradle.org/current/userguide/lazy_configuration.html" rel="noopener noreferrer"&gt;Lazy configuration&lt;/a&gt;, callbacks, and provider chains are the name of the game.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't access a &lt;code&gt;Project&lt;/code&gt; instance inside a task action&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It breaks the &lt;a href="https://docs.gradle.org/current/userguide/configuration_cache.html" rel="noopener noreferrer"&gt;configuration cache&lt;/a&gt;, and will eventually be deprecated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't access another project's &lt;code&gt;Project&lt;/code&gt; instance&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is called cross-project configuration and is &lt;em&gt;extremely&lt;/em&gt; fragile. It creates implicit, nearly un-modelable dependencies between projects and can only lead to grief. Instead, &lt;a href="https://docs.gradle.org/current/userguide/cross_project_publications.html" rel="noopener noreferrer"&gt;share artifacts&lt;/a&gt; across projects by declaring dependencies. &lt;/p&gt;

&lt;p&gt;It also breaks the experimental &lt;a href="https://gradle.github.io/configuration-cache/#project_isolation" rel="noopener noreferrer"&gt;project isolation&lt;/a&gt; feature, but that won't be truly relevant for a while.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Avoid &lt;code&gt;afterEvaluate&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It introduces subtle ordering issues which can be very challenging to debug.&lt;/p&gt;

&lt;p&gt;What you're looking for is probably a &lt;a href="https://docs.gradle.org/current/javadoc/org/gradle/api/provider/Provider.html" rel="noopener noreferrer"&gt;&lt;code&gt;Provider&lt;/code&gt;&lt;/a&gt; or &lt;a href="https://docs.gradle.org/current/javadoc/org/gradle/api/provider/Property.html" rel="noopener noreferrer"&gt;&lt;code&gt;Property&lt;/code&gt;&lt;/a&gt; (see also &lt;a href="https://docs.gradle.org/current/userguide/lazy_configuration.html" rel="noopener noreferrer"&gt;lazy configuration&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't call &lt;code&gt;get()&lt;/code&gt; on a &lt;code&gt;Provider&lt;/code&gt; outside a task action&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The whole point of using a provider is to evaluate it as late as possible. Calling &lt;code&gt;get()&lt;/code&gt;—evaluating it—will lead to painful ordering issues if done too early.&lt;/p&gt;

&lt;p&gt;Instead, use &lt;a href="https://docs.gradle.org/current/javadoc/org/gradle/api/provider/Provider.html#map-org.gradle.api.Transformer-" rel="noopener noreferrer"&gt;&lt;code&gt;map&lt;/code&gt;&lt;/a&gt; or &lt;a href="https://docs.gradle.org/current/javadoc/org/gradle/api/provider/Provider.html#flatMap-org.gradle.api.Transformer-" rel="noopener noreferrer"&gt;&lt;code&gt;flatMap&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't use internal APIs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Gradle considers internal APIs fair game for making breaking changes in even minor releases. Therefore, using such an API is inherently fragile and will lead to major, completely avoidable, headaches.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't use Kotlin lambdas in your public API&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I know, it's tempting. They're &lt;em&gt;right there&lt;/em&gt;. Use &lt;a href="https://docs.gradle.org/current/javadoc/org/gradle/api/Action.html" rel="noopener noreferrer"&gt;&lt;code&gt;Action&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/a&gt; instead. Gradle enhances the bytecode at runtime  to provide a nicer DSL experience for users of your plugin&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't create objects yourself&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use the &lt;a href="https://docs.gradle.org/current/javadoc/org/gradle/api/model/ObjectFactory.html" rel="noopener noreferrer"&gt;&lt;code&gt;ObjectFactory&lt;/code&gt;&lt;/a&gt; instead. (This is a &lt;em&gt;configuration time&lt;/em&gt; concern.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't use lists in your custom extensions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use &lt;a href="https://docs.gradle.org/current/javadoc/org/gradle/api/model/ObjectFactory.html#domainObjectContainer-java.lang.Class-" rel="noopener noreferrer"&gt;domain object containers&lt;/a&gt; instead. Once again, Gradle is able to provide enhanced DSL support this way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't skip the &lt;a href="https://docs.gradle.org/current/userguide/userguide.html" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I know, it's a lot.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do test your plugins&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Especially if you publish them. See this &lt;a href="https://dev.to/autonomousapps/gradle-all-the-way-down-testing-your-gradle-plugin-with-gradle-testkit-2hmc"&gt;two&lt;/a&gt; &lt;a href="https://dev.to/autonomousapps/defensive-development-gradle-plugin-development-for-busy-engineers-486c"&gt;part&lt;/a&gt; series for help.&lt;/p&gt;

&lt;h2&gt;
  
  
  Special thanks
&lt;/h2&gt;

&lt;p&gt;Special thanks to &lt;a href="https://twitter.com/ZacSweers" rel="noopener noreferrer"&gt;Zac Sweers&lt;/a&gt; for offering feedback on this post.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Endnotes
&lt;/h2&gt;

&lt;p&gt;&lt;sup&gt;1&lt;/sup&gt; There are in fact &lt;a href="https://docs.gradle.org/current/userguide/build_lifecycle.html#sec:build_phases" rel="noopener noreferrer"&gt;three phases&lt;/a&gt;, but the Initialization phase is rarely of interest to the typical build maintainer. &lt;small&gt;up&lt;/small&gt;&lt;/p&gt;

</description>
      <category>gradle</category>
    </item>
  </channel>
</rss>
