<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="pretty-atom-feed.xsl" type="text/xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <title>Tony Robalik</title>
  <subtitle>Raging about JVM things while the world burns.</subtitle>
  <link href="https://autonomousapps.com/feed/feed.xml" rel="self" />
  <link href="https://autonomousapps.com/" />
  <updated>2026-01-10T00:00:00Z</updated>
  <id>https://autonomousapps.com/</id>
  <author>
    <name>Tony Robalik</name>
  </author>
  <entry>
    <title>Reflections on Camus&#39;s &#39;The Rebel&#39;: Sade and the Trump regime</title>
    <link href="https://autonomousapps.com/blog/camus-rebel-sade/post/" />
    <updated>2026-01-10T00:00:00Z</updated>
    <id>https://autonomousapps.com/blog/camus-rebel-sade/post/</id>
    <content type="html">&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://autonomousapps.com/blog/camus-rebel-sade/post/uvzlTe8yVZ-1280.avif 1280w&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://autonomousapps.com/blog/camus-rebel-sade/post/uvzlTe8yVZ-1280.webp 1280w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://autonomousapps.com/blog/camus-rebel-sade/post/uvzlTe8yVZ-1280.png&quot; alt=&quot;Trump and several members of his administration standing in front of a cage for human beings&quot; width=&quot;1280&quot; height=&quot;640&quot;&gt;&lt;/picture&gt;
&lt;em&gt;&lt;a href=&quot;https://www.wsj.com/politics/policy/ice-detention-tent-cities-5c2e135d&quot;&gt;Newly Flush With Cash, ICE Races to Build Migrant Tent Camps&lt;/a&gt;&lt;/em&gt;
&lt;p&gt;I&#39;m reading &lt;a href=&quot;https://en.wikipedia.org/wiki/The_Rebel_(book)&quot;&gt;&lt;em&gt;The Rebel&lt;/em&gt;&lt;/a&gt;, by Camus. In the section on the
&lt;a href=&quot;https://en.wikipedia.org/wiki/Marquis_de_Sade&quot;&gt;Marquis de Sade&lt;/a&gt; early in the book, he describes Sade&#39;s &lt;em&gt;Society of the
Friends of Crime&lt;/em&gt;,&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://autonomousapps.com/blog/camus-rebel-sade/post/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; and I, definitely not-a-philosopher,&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://autonomousapps.com/blog/camus-rebel-sade/post/#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt; was struck by the resemblance of this &lt;em&gt;Society&lt;/em&gt; to the
billionaires and pedophiles&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://autonomousapps.com/blog/camus-rebel-sade/post/#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt; who control most of the levers of official power in the world.&lt;/p&gt;
&lt;p&gt;First, Camus provides a succinct description, or perhaps mission statement, for the Republican Party:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In his &lt;em&gt;Society of the Friends of Crime&lt;/em&gt; he [Sade] declares himself ostensibly in favor of the government and its
laws, which he meanwhile has every intention of violating. It is the same impulse that makes the lowest form of
criminal vote for conservative candidates.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;By this I believe he means the kind of uncommon criminal whose crimes are fully premeditated, perhaps even knowing that,
for members of their class (the in-group), there are no negative consequences for their crimes. One might even note how
frequently and assiduously American conservatives praise the Constitution as if it were some kind of religious text,
while also finding in it every justification for evil their vile little hearts can imagine.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://autonomousapps.com/blog/camus-rebel-sade/post/#fn4&quot; id=&quot;fnref4&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Camus goes on:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The advocate of crime really only respects two kinds of power: one, which he finds among his own class, founded on the
accident of birth, and the other by which through sheer villainy, an underdog raises himself to the level of the
libertines of noble birth whom Sade makes his heroes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You&#39;re either born white or, through sheer villainy, are adopted into whiteness
(&lt;a href=&quot;https://en.wikipedia.org/wiki/Whiteness_theory&quot;&gt;whiteness is a social construct&lt;/a&gt;).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This powerful little group of initiates knows that it has all the rights.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Non-whites have no rights.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Anyone who doubts, even for a second, these formidable privileges is immediately driven from the flock, and once more
becomes a victim.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Whiteness is a revocable privilege, not an immutable fact.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Thus a sort of aristocratic morality is created through which a little group of men and women manage to entrench
themselves above a caste of slaves because they possess the secret of a strange knowledge.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The elites in power understand the basis of their power and wield it relentlessly. They are moral &lt;em&gt;because&lt;/em&gt; they have
power; using that power is definitionally moral. Anyone who doesn&#39;t have power is a resource to be consumed.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The only problem for them consists in organizing themselves so as to be able to exercise fully their rights which have
the terrifying scope of desire.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;They also have group chats.&lt;/p&gt;
&lt;p&gt;And as if writing directly for us in our time, Camus then says:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;But if crime and desire are not the law of the entire universe… it is necessary to create from all these fragments a
world that exactly coincides with the new law… [but] the law of power never has the patience to await complete control
of the world. It must fix the boundaries, without delay, of the territory where it holds sway, even if it means
surrounding it with barbed wire and observation towers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The wall isn&#39;t just for keeping Others out, but the rest of us in.&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot;&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;From &lt;em&gt;Juliette&lt;/em&gt;. &lt;a href=&quot;https://autonomousapps.com/blog/camus-rebel-sade/post/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;To my chagrin? &lt;a href=&quot;https://autonomousapps.com/blog/camus-rebel-sade/post/#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;But I repeat myself. &lt;a href=&quot;https://autonomousapps.com/blog/camus-rebel-sade/post/#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn4&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;As befits a religious text for those on the right. &lt;a href=&quot;https://autonomousapps.com/blog/camus-rebel-sade/post/#fnref4&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  <entry>
    <title>Enshittification: Why I’m leaving dev.to</title>
    <link href="https://autonomousapps.com/blog/enshittification/post/" />
    <updated>2025-11-30T00:00:00Z</updated>
    <id>https://autonomousapps.com/blog/enshittification/post/</id>
    <content type="html">&lt;a href=&quot;https://theoatmeal.com/comics/reaching_people&quot;&gt;
  &lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://autonomousapps.com/blog/enshittification/post/Hr1a8V6Dne-800.avif 800w&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://autonomousapps.com/blog/enshittification/post/Hr1a8V6Dne-800.webp 800w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://autonomousapps.com/blog/enshittification/post/Hr1a8V6Dne-800.png&quot; alt=&quot;Reaching people on the Internet, by The Oatmeal&quot; width=&quot;800&quot; height=&quot;800&quot;&gt;&lt;/picture&gt;
&lt;/a&gt;
&lt;p&gt;&lt;em&gt;See also: &lt;a href=&quot;https://en.wikipedia.org/wiki/Enshittification&quot;&gt;enshittification&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;!-- Please consider reading this post on [autonomousapps.com](https://autonomousapps.com/blog/enshittification/post/) instead. --&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=&quot;https://autonomousapps.com/blog/chess-dot-com/post/&quot;&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 class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://autonomousapps.com/blog/enshittification/post/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&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=&quot;https://autonomousapps.com/blog/java-cursed/post/&quot;&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;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://autonomousapps.com/blog/enshittification/post/x3YvLnP4dN-2864.avif 2864w&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://autonomousapps.com/blog/enshittification/post/x3YvLnP4dN-2864.webp 2864w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://autonomousapps.com/blog/enshittification/post/x3YvLnP4dN-2864.png&quot; alt=&quot;Mandatory pop-up asking readers to acknowledge a vague statement of appreciation&quot; width=&quot;2864&quot; height=&quot;862&quot;&gt;&lt;/picture&gt;
&lt;p&gt;Note there’s only one option. Clicking it leads to this:&lt;/p&gt;
&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://autonomousapps.com/blog/enshittification/post/5CES6wxV1n-1562.avif 1562w&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://autonomousapps.com/blog/enshittification/post/5CES6wxV1n-1562.webp 1562w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://autonomousapps.com/blog/enshittification/post/5CES6wxV1n-1562.png&quot; alt=&quot;Very annoying pop-up implying that readers must log in to continue&quot; width=&quot;1562&quot; height=&quot;1636&quot;&gt;&lt;/picture&gt;
&lt;p&gt;Which fortunately can be dismissed. This is a &lt;a href=&quot;https://en.wikipedia.org/wiki/Dark_pattern&quot;&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;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://autonomousapps.com/blog/enshittification/post/MVJbNGpM1W-3024.avif 3024w&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://autonomousapps.com/blog/enshittification/post/MVJbNGpM1W-3024.webp 3024w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://autonomousapps.com/blog/enshittification/post/MVJbNGpM1W-3024.png&quot; alt=&quot;Screencap showing that 79% of page is covered in non-content cruft. Most of it is an ad for something &#39;AI&#39;-related&quot; width=&quot;3024&quot; height=&quot;1674&quot;&gt;&lt;/picture&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 marketing 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;hr class=&quot;footnotes-sep&quot;&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;An auspicious number. &lt;a href=&quot;https://autonomousapps.com/blog/enshittification/post/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  <entry>
    <title>Is the Java ecosystem cursed? A dependency analysis perspective</title>
    <link href="https://autonomousapps.com/blog/java-cursed/post/" />
    <updated>2025-11-24T00:00:00Z</updated>
    <id>https://autonomousapps.com/blog/java-cursed/post/</id>
    <content type="html">&lt;img src=&quot;https://autonomousapps.com/blog/java-cursed/post/O7qjuGzLU_-1000.webp&quot; alt=&quot;A man pointing at a board of evidence in a conspiratorial way&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1000&quot; height=&quot;420&quot;&gt;
&lt;p&gt;I am the author of the moderately popular (⭐ 2k) &lt;a href=&quot;https://github.com/autonomousapps/dependency-analysis-gradle-plugin&quot;&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 &amp;quot;Java ecosystem,&amp;quot; though both use Kotlin as the preferred language, and one runs on the JVM while the other runs on &lt;a href=&quot;https://source.android.com/docs/core/runtime&quot;&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 id=&quot;we-are-cursed-with&quot;&gt;We are cursed with…&lt;/h3&gt;
&lt;p&gt;Lying metadata, overuse of &amp;quot;fat&amp;quot; jars with underuse of package relocation, split packages, undocumented usage of reflection to access upstream dependencies, usage of terms like &amp;quot;upstream&amp;quot; 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 id=&quot;lying-metadata&quot;&gt;Lying metadata&lt;/h4&gt;
&lt;p&gt;This was already covered in-depth in &lt;a href=&quot;https://dev.to/autonomousapps/this-is-why-we-cant-have-nice-things-when-pom-files-lie-3lm5&quot;&gt;This is why we can&#39;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&#39;s harder to teach a build tool to lie.&lt;/p&gt;
&lt;h4 id=&quot;its-just-a-list-man&quot;&gt;It&#39;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 &amp;quot;sees&amp;quot; a class or interface for the first time, it has to load it. It does this with a &lt;a href=&quot;https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/lang/ClassLoader.html&quot;&gt;&lt;code&gt;ClassLoader&lt;/code&gt;&lt;/a&gt;. The classloader searches the classpath (just a list of class files!)&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://autonomousapps.com/blog/java-cursed/post/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&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=&quot;https://newsletter.gradle.org/2025/11&quot;&gt;November Gradle newsletter&lt;/a&gt;: &lt;a href=&quot;https://arxiv.org/pdf/2407.18760&quot;&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&#39;ll see &lt;a href=&quot;https://autonomousapps.com/blog/java-cursed/post/#protocol-buffers&quot;&gt;below&lt;/a&gt;, you can just do it to yourself!&lt;/p&gt;
&lt;h4 id=&quot;fat-jars-without-package-relocation&quot;&gt;Fat jars without package relocation&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://gradleup.com/shadow/&quot;&gt;Shadow&lt;/a&gt; is a powerful tool for creating &amp;quot;uber&amp;quot; or &amp;quot;fat&amp;quot; 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&#39;s order. I would like to point the maintainers of these libraries at Shadow&#39;s powerful &lt;a href=&quot;https://gradleup.com/shadow/configuration/relocation/&quot;&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&#39;ve said before, the extent to which Java&#39;s packages exist in a global namespace is not well-appreciated.&lt;/p&gt;
&lt;h4 id=&quot;split-packages&quot;&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=&quot;https://en.wikipedia.org/wiki/Java_Platform_Module_System&quot;&gt;JPMS&lt;/a&gt; (the Java Platform Module System). I can&#39;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&#39;re wrong.)&lt;/p&gt;
&lt;h4 id=&quot;funhouse-mirrors-aka-reflection&quot;&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=&quot;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&quot;&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=&quot;https://central.sonatype.com/artifact/com.amazonaws/aws-java-sdk-sts&quot;&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=&quot;https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/ServiceLoader.html&quot;&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(&amp;quot;...&amp;quot;)&lt;/code&gt; in &lt;a href=&quot;https://github.com/autonomousapps/dependency-analysis-gradle-plugin/blob/main/CHANGELOG.md#version-330&quot;&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&#39;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 id=&quot;protocol-buffers&quot;&gt;Protocol Buffers&lt;/h4&gt;
&lt;p&gt;Protocol buffers, aka &lt;a href=&quot;https://protobuf.dev/&quot;&gt;protobufs&lt;/a&gt;, are an amazing tool for making a build engineer&#39;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=&quot;https://github.com/protocolbuffers/protobuf&quot;&gt;Google&#39;s protoc&lt;/a&gt; and &lt;a href=&quot;https://square.github.io/wire/&quot;&gt;Square&#39;s Wire&lt;/a&gt;. I happen to work at a company that uses &lt;em&gt;both&lt;/em&gt;. I don&#39;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 class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://autonomousapps.com/blog/java-cursed/post/#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&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&#39;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 (&amp;quot;AI&amp;quot; has recently taken that crown).&lt;/p&gt;
&lt;h4 id=&quot;yolo-compilers&quot;&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=&quot;https://docs.oracle.com/javase/specs/jvms/se25/html/jvms-4.html#jvms-4.4&quot;&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 class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://autonomousapps.com/blog/java-cursed/post/#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt; inline constants for runtime efficiency. This means that a constant like &lt;code&gt;public static final String CONSTANT = &amp;quot;magic&amp;quot;&lt;/code&gt; gets turned into simply &lt;code&gt;&amp;quot;magic&amp;quot;&lt;/code&gt; at the use-site, and similarly for Kotlin&#39;s &lt;code&gt;const val&lt;/code&gt;. Therefore simply analyzing the bytecode directly with a tool like &lt;a href=&quot;https://asm.ow2.io/index.html&quot;&gt;asm&lt;/a&gt; won&#39;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=&quot;https://github.com/autonomousapps/dependency-analysis-gradle-plugin/issues/735&quot;&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=&quot;https://github.com/autonomousapps/dependency-analysis-gradle-plugin/blob/main/src/main/kotlin/com/autonomousapps/internal/ConstantPoolParser.kt&quot;&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&#39;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 &amp;quot;unused dependency&amp;quot; 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&#39;t go into details here, but they involve parsing source code for import statements,&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://autonomousapps.com/blog/java-cursed/post/#fn4&quot; id=&quot;fnref4&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt; looking at the &lt;a href=&quot;https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/lang/classfile/Opcode.html#LDC&quot;&gt;&lt;code&gt;ldc&lt;/code&gt; bytecode instruction&lt;/a&gt;,&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://autonomousapps.com/blog/java-cursed/post/#fn5&quot; id=&quot;fnref5&quot;&gt;[5]&lt;/a&gt;&lt;/sup&gt; etc. Source parsing falls over in the presence of &lt;a href=&quot;https://autonomousapps.com/blog/java-cursed/post/#split-packages&quot;&gt;split packages&lt;/a&gt;, 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&#39;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 id=&quot;special-thanks&quot;&gt;Special thanks&lt;/h2&gt;
&lt;p&gt;Special thanks to &lt;a href=&quot;https://bsky.app/profile/luis.cortes.social&quot;&gt;Luis Cortés&lt;/a&gt; once again for the thorough review!&lt;/p&gt;
&lt;h2 id=&quot;who-stalks-us-in-the-darkness&quot;&gt;Who stalks us in the darkness?&lt;/h2&gt;
&lt;p&gt;The above list of &lt;s&gt;grievances&lt;/s&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 class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://autonomousapps.com/blog/java-cursed/post/#fn6&quot; id=&quot;fnref6&quot;&gt;[6]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;…wait, who is that behind me, in the dark wood…?&lt;/p&gt;
&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://autonomousapps.com/blog/java-cursed/post/TGT9ATVHs_-1280.avif 1280w&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://autonomousapps.com/blog/java-cursed/post/TGT9ATVHs_-1280.webp 1280w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://autonomousapps.com/blog/java-cursed/post/TGT9ATVHs_-1280.jpeg&quot; alt=&quot;Baba Yaga&quot; width=&quot;1280&quot; height=&quot;1640&quot;&gt;&lt;/picture&gt;
&lt;p&gt;&lt;em&gt;By Ivan Bilibin - Self-scanned, Public Domain, &lt;a href=&quot;https://commons.wikimedia.org/w/index.php?curid=15092&quot;&gt;Wikimedia&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Baba_Yaga&quot;&gt;Baba Yaga&lt;/a&gt; (no not &lt;a href=&quot;https://en.wikipedia.org/wiki/John_Wick&quot;&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;hr class=&quot;footnotes-sep&quot;&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;I&#39;m eliding some complexity around the classloader hierarchy, and the possibility you may have a custom classloader that doesn&#39;t follow standard behavior. See &lt;a href=&quot;https://dev.to/autonomousapps/build-compile-run-a-crash-course-in-classpaths-f4g&quot;&gt;A crash course in classpaths&lt;/a&gt; for more information. &lt;a href=&quot;https://autonomousapps.com/blog/java-cursed/post/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&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;a href=&quot;https://autonomousapps.com/blog/java-cursed/post/#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;I think they all do, but I haven&#39;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;a href=&quot;https://autonomousapps.com/blog/java-cursed/post/#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn4&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;See &lt;a href=&quot;https://github.com/autonomousapps/dependency-analysis-gradle-plugin/blob/main/src/main/kotlin/com/autonomousapps/internal/parse/SourceListener.kt#L14&quot;&gt;here&lt;/a&gt; for where DAGP parses source code in a very simplified way. &lt;a href=&quot;https://autonomousapps.com/blog/java-cursed/post/#fnref4&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn5&quot; class=&quot;footnote-item&quot;&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=&quot;https://github.com/autonomousapps/dependency-analysis-gradle-plugin/blob/main/src/main/kotlin/com/autonomousapps/internal/asm.kt#L536-L539&quot;&gt;here&lt;/a&gt;. &lt;a href=&quot;https://autonomousapps.com/blog/java-cursed/post/#fnref5&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn6&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Kidding! Once again the thing that&#39;s killing my will to live is just &amp;quot;AI.&amp;quot; &lt;a href=&quot;https://autonomousapps.com/blog/java-cursed/post/#fnref6&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  <entry>
    <title>Shrinking Elephants</title>
    <link href="https://autonomousapps.com/blog/shrinking-elephants/post/" />
    <updated>2025-10-14T00:00:00Z</updated>
    <id>https://autonomousapps.com/blog/shrinking-elephants/post/</id>
    <content type="html">&lt;img src=&quot;https://autonomousapps.com/blog/shrinking-elephants/post/yIdWoxILU_-1200.webp&quot; alt=&quot;A photo of a happy baby elephant&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1200&quot; height=&quot;800&quot;&gt;
&lt;p&gt;&lt;small&gt;&lt;em&gt;Photo by &lt;a href=&quot;https://unsplash.com/@lili_343&quot;&gt;Lili Koslowski&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/young-elephant-near-green-grass-DNPo8OrTlbU&quot;&gt;Unsplash&lt;/a&gt;.&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;So you want Intellij IDEA to sync 5 million lines of Kotlin code,&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://autonomousapps.com/blog/shrinking-elephants/post/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; spread across more than 2000 Gradle projects.&lt;/p&gt;
&lt;p&gt;No, you don&#39;t.&lt;/p&gt;
&lt;p&gt;In one of our largest repos, a Kotlin backend project, syncing it all takes an average of about 8.4 minutes, and that&#39;s
being generous. Achieving that required a fully up-to-date build, with all dependencies already downloaded—meaning this
was 8.4 minutes for what is essentially a no-op. This no-op sync used the full 24 GiB of heap provided to the Gradle
daemon, and an additional 12 GiB heap provided to the IDE process, or 36 GiB in total. If this is the best you can offer
your developers, do not expect them to be very productive, and do not expect to ship software quickly.&lt;/p&gt;
&lt;p&gt;A more realistic scenario is triggering the first sync of the day after a &lt;nobr&gt;&lt;code&gt;git pull&lt;/code&gt;&lt;/nobr&gt; that updated dependencies. In that
&amp;quot;cold sync&amp;quot; scenario, the same build described above took 24.7 minutes, more than 7 minutes of which was spent
downloading over 26000 files (jars plus metadata) totalling nearly 4 GiB over the wire. This build maxed out the heap
very quickly, and it wouldn&#39;t be hard to imagine a scenario where the build thrashed gc for an hour before finally
OOMing.&lt;/p&gt;
&lt;p&gt;While you may &lt;em&gt;want&lt;/em&gt; the full project loaded into memory, you almost certainly can&#39;t bear the consequences: extremely
long sync, sluggish editing, the spinning beachball of death…&lt;/p&gt;
&lt;p&gt;Fortunately, our developers never have to do this (sometimes a build engineer might… for analysis purposes). The actual
experience we target for our developers is benchmarked at 15 seconds, a 97% improvement over the baseline outlined
above; and only using 3.5 GiB heap, a 75% improvement. How did we do it?&lt;/p&gt;
&lt;h2 id=&quot;what-is-ide-sync&quot;&gt;What is IDE sync?&lt;/h2&gt;
&lt;p&gt;Gradle Sync is how the IDE loads important information about your build (known as the
&lt;a href=&quot;https://plugins.jetbrains.com/docs/intellij/project-model.html&quot;&gt;project model&lt;/a&gt;) to enable smart editing features like
autocomplete, code navigation, viewing sources for dependencies, etc. To make this work, IntelliJ injects a library into
the Gradle daemon containing the
&lt;a href=&quot;https://docs.gradle.org/current/kotlin-dsl/gradle/org.gradle.tooling/-model-builder/index.html&quot;&gt;model builders&lt;/a&gt; that
construct the IntelliJ project models. IntelliJ then requests that the Gradle daemon build the project models using the
&lt;a href=&quot;https://docs.gradle.org/current/userguide/tooling_api.html&quot;&gt;Tooling API&lt;/a&gt;. Gradle will configure your build, execute the
model builders requested by IntellIJ, then serialize the data back to the IDE. At this point, the project sync is now
completed and many smart editor features will be available. IDE indexing is a separate process that takes place after
sync to enable the rest of the editor features, which this post won&#39;t cover.&lt;/p&gt;
&lt;p&gt;The sync process is notoriously slow because of
&lt;a href=&quot;https://www.youtube.com/live/qg6tj8Tf36E?t=4269s&quot;&gt;how much work needs to be done&lt;/a&gt;. The entire build must be configured
(&lt;a href=&quot;https://docs.gradle.org/current/userguide/configuration_on_demand.html&quot;&gt;configure-on-demand&lt;/a&gt; is not applicable here),
project dependencies and their sources need to be downloaded if they aren&#39;t cached locally, and this work is largely
performed in serial, leaving your CPU mostly idle. If any error occurs in the process, or any build.gradle(.kts),
settings.gradle(.kts) or version catalog file is updated, the sync process must be restarted from scratch.&lt;/p&gt;
&lt;h2 id=&quot;benchmarks-vs-telemetry&quot;&gt;Benchmarks vs telemetry&lt;/h2&gt;
&lt;p&gt;Telemetry (that is, real data from real users) is the gold standard for tracking build performance. This is what makes
&lt;a href=&quot;https://gradle.com/develocity/&quot;&gt;Develocity&lt;/a&gt; such an indispensable tool for tracking and understanding our Gradle builds
(about 2 million every week). Even with such a firehose of data, however, there are gaps. For purposes of this post, the
biggest gap is the wall-clock time for an IDE sync (as described above). IDE-specific overhead contributes anywhere from
10% to over 50% of total sync time, so if we&#39;re only tracking the Gradle portion of the sync, we&#39;re missing the full
picture.&lt;/p&gt;
&lt;p&gt;We solved this problem just a couple of months ago with a new combination IDE and Gradle plugin we call
&lt;a href=&quot;https://plugins.jetbrains.com/plugin/28394-build-sync-metrics&quot;&gt;build-sync-metrics&lt;/a&gt;.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://autonomousapps.com/blog/shrinking-elephants/post/#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt; With this, we now have complete
telemetry from our developers on the performance of the IDE in their local environments.&lt;/p&gt;
&lt;p&gt;We can, for example, state the following, thanks to our telemetry:&lt;/p&gt;
&lt;p&gt;Over the course of Q3, the Backend Build Team was able to reduce mean sync time for our backend developers from over 4
minutes to about 90 seconds, saving 2.5 minutes per sync. With over 100k syncs per year, this translates to
&lt;strong&gt;2-3 eng-years saved every year&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;While this is incredibly powerful, there are limitations. This population data isn&#39;t really amenable to rapid
experimentation, or experimentation across a large combination of dimensions, and is—by defintion—coming from an
uncontrolled environment with an uncountably large number of exogenous inputs. There are, to borrow a phrase, unknown
unknowns.&lt;/p&gt;
&lt;p&gt;This is where &lt;em&gt;benchmarks&lt;/em&gt; really shine. Essentially, they enable us to test possible &amp;quot;interventions&amp;quot; (described below)
&lt;em&gt;before&lt;/em&gt; we inflict them as A/B tests on our developers. They give us confidence that something might work, which saves
time and potential pain. Once we see value from an intervention via a benchmark, we can start rolling it out to our
users and see the impacts in real-time. We use &lt;a href=&quot;https://github.com/gradle/gradle-profiler&quot;&gt;gradle-profiler&lt;/a&gt; for creating
these benchmarks.&lt;/p&gt;
&lt;p&gt;The rest of this post makes exclusive use of &lt;em&gt;benchmarks&lt;/em&gt; to describe what we&#39;ve done to make use of the IDE a more
pleasant experience for our developers. This allows us to make precise statements about the impacts of these
interventions. Please note that all of these benchmarks are also backed up by real-world telemetry, which is necessarily
fuzzier in nature.&lt;/p&gt;
&lt;h2 id=&quot;interventions&quot;&gt;Interventions&lt;/h2&gt;
&lt;p&gt;The primary cost of syncing a Gradle project is configuring its project model. In fact, sync duration appears to be
slightly quadratic over the number of subprojects (or modules) in the project, according to our benchmarks:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://autonomousapps.com/blog/shrinking-elephants/post/YBC4f2r7uv-1328.avif 1328w&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://autonomousapps.com/blog/shrinking-elephants/post/YBC4f2r7uv-1328.webp 1328w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://autonomousapps.com/blog/shrinking-elephants/post/YBC4f2r7uv-1328.png&quot; alt=&quot;A line chart showing sync duration vs project count, along with an interpolated quadratic curve with, with the equation y = 1.41 + 0.066x + 0.0000672x^2.&quot; width=&quot;1328&quot; height=&quot;822&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;This chart was created using data from a real project, not something artificially generated.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://autonomousapps.com/blog/shrinking-elephants/post/#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt; How this was done will
be clear in a moment. As to &lt;em&gt;why&lt;/em&gt; the data might be quadratic: that&#39;s hard to say. Discussions with Gradle engineers
suggest it might have to do with the project structure, such as depth of the project graph, number of edges, etc. In
other words, while this graph accurately reflects &lt;em&gt;our&lt;/em&gt; project, it may not reflect others. It is almost certainly true
that there is at least a linear relationship, however.&lt;/p&gt;
&lt;p&gt;The chart above suggests that anything that can reduce subproject count is likely to improve sync performance. However,
there are other design concerns that determine the number of subprojects:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Project &lt;em&gt;architecture&lt;/em&gt; is a first-class consideration, and we strongly believe that the project structure should
reflect its architecture.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://autonomousapps.com/blog/shrinking-elephants/post/#fn4&quot; id=&quot;fnref4&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;Sync performance is only one component of developer productivity. We must also consider &lt;em&gt;build&lt;/em&gt; performance, which
includes both project &lt;em&gt;configuration&lt;/em&gt; and &lt;em&gt;build execution&lt;/em&gt;. Weighing configuration too heavily will result in
extremely slow builds that don&#39;t benefit at all from incremental or avoided execution.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We refer to the following practices as &amp;quot;interventions&amp;quot; because they&#39;re &lt;em&gt;out-of-band&lt;/em&gt; of the normal build process. They
are not first-class features of the build system, though they &lt;em&gt;do&lt;/em&gt; use public APIs where relevant. They also require the
ability to know something &lt;em&gt;a priori&lt;/em&gt; about the software-under-development, and this &lt;em&gt;prerequisite&lt;/em&gt; is neatly
encapsulated by the requirement that the build be fully conventionalized prior to making any further intervention.
&lt;a href=&quot;https://github.com/autonomousapps/gradle-glossary#convention-plugin&quot;&gt;Conventionalization&lt;/a&gt; is out-of-scope of this post,
so please read the prior two posts in this series,
&lt;a href=&quot;https://developer.squareup.com/blog/herding-elephants/&quot;&gt;Herding Elephants&lt;/a&gt; and
&lt;a href=&quot;https://developer.squareup.com/blog/stampeding-elephants/&quot;&gt;Stampeding Elephants&lt;/a&gt;, for more information.&lt;/p&gt;
&lt;p&gt;These interventions are listed in order of ease of implementation, more or less:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In IDEA, enable the experimental &lt;strong&gt;Parallel Model Fetching&lt;/strong&gt; feature.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://autonomousapps.com/blog/shrinking-elephants/post/#fn5&quot; id=&quot;fnref5&quot;&gt;[5]&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;Use a tool such as &lt;a href=&quot;https://github.com/joshfriend/spotlight&quot;&gt;Spotlight&lt;/a&gt; to trim the project dependency graph to a&lt;/li&gt;
&lt;li&gt;subset of the full project set.&lt;/li&gt;
&lt;li&gt;Use a tool such as &lt;a href=&quot;https://github.com/joshfriend/fastsync&quot;&gt;Fastsync&lt;/a&gt; to reduce the amount of work the Gradle&lt;/li&gt;
&lt;li&gt;dependency resolution engine has to do (by mangling the runtime classpath) during IDE sync.&lt;/li&gt;
&lt;li&gt;Take a hammer to your project graph and replace as many project dependencies with external module dependencies as&lt;/li&gt;
&lt;li&gt;possible. We call this &amp;quot;artifact-swap.&amp;quot;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And finally, included here because of its impact, which is challenging to measure via benchmarks:&lt;/p&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;Pre-fetch dependencies.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;All together, these changes can reduce your IDE sync time by 97%, according to our benchmarks.
&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://autonomousapps.com/blog/shrinking-elephants/post/u-1miov2EH-1446.avif 1446w&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://autonomousapps.com/blog/shrinking-elephants/post/u-1miov2EH-1446.webp 1446w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://autonomousapps.com/blog/shrinking-elephants/post/u-1miov2EH-1446.png&quot; alt=&quot;A stacked bar chart showing sync duration vs intervention (cumulative, left to right).&quot; width=&quot;1446&quot; height=&quot;894&quot;&gt;&lt;/picture&gt;
&lt;em&gt;Cumulative impact of interventions.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;there-is-a-cost&quot;&gt;There is a cost&lt;/h3&gt;
&lt;p&gt;While we firmly believe that these interventions&#39; benefits outweigh their costs, we can&#39;t ignore those costs. There is
obviously the implementation and maintenance cost—some of these improvements will require a dedicated build team (which
you should have if your repos are large enough to benefit from these sorts of interventions). There&#39;s also a DX cost,
since users to have to learn new workflows. These downsides are real, but not prohibitive. Let&#39;s continue!&lt;/p&gt;
&lt;h3 id=&quot;baseline&quot;&gt;Baseline&lt;/h3&gt;
&lt;p&gt;In order to contextualize the interventions, we must first describe the baseline scenario, which is quite simply a
&lt;strong&gt;Kotlin JVM project with over 2000 Gradle subprojects&lt;/strong&gt;. Syncing this scenario with Intellij IDEA 2025.2.3 and Gradle
9.1.0, absent any of the &amp;quot;interventions&amp;quot; described below, took an average of &lt;strong&gt;8.4 min&lt;/strong&gt;, or about &lt;strong&gt;220 ms / project&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;As noted in the introduction, all benchmarks are necessarily &amp;quot;warm&amp;quot; or &amp;quot;no-op&amp;quot; in nature. We run the sync three times as
a warm-up, and then another eight times, averaging the result. Therefore these syncs should involve, among other
simplifications, minimal network overhead (they might still make HEAD requests to validate the up-to-date-ness of
dependencies).&lt;/p&gt;
&lt;p&gt;The metrics provided below are the result of layering on each intervention cumulatively. We also have independent
benchmarks validating that each intervention is successful on its own.&lt;/p&gt;
&lt;h3 id=&quot;parallel-model-fetching&quot;&gt;Parallel model fetching&lt;/h3&gt;
&lt;p&gt;Compared to the baseline scenario, enabling parallel model fetching &lt;strong&gt;reduced sync duration by 57%&lt;/strong&gt;, to about
&lt;strong&gt;3.6 min&lt;/strong&gt;, or about &lt;strong&gt;95 ms / project&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This &amp;quot;intervention&amp;quot; barely qualifies as one, but must be mentioned due to the high impact for near-zero cost. It is an
&amp;quot;incubating&amp;quot; feature in Intellij IDEA, and is disabled by default. To enable it, navigate to
&lt;code&gt;Settings &amp;gt; Build, Execution, Deployment &amp;gt; Gradle&lt;/code&gt; and check the &amp;quot;Enable parallel Gradle model fetching&amp;quot; box, as
indicated in the screencap below. Note that this checkbox only affects fetching of IntelliJ models, and Android model
builders are already fully parallelized.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; enabling this can result in random failures, but this is extremely rare and, in our opinion, worth it
for the impressive performance benefit conferred, especially since the failures are trivially resolved by another sync.
The failures seem to be a result of race conditions due to configuring the Gradle project model in parallel, which it
simply isn&#39;t designed for (yet). One issue you can track related to this is
&lt;a href=&quot;https://youtrack.jetbrains.com/issue/IDEA-355309/Failed-to-apply-plugin-class-org.gradle.plugins.ide.idea.IdeaPlugin&quot;&gt;IDEA-355309&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://autonomousapps.com/blog/shrinking-elephants/post/zs1gPCfSAv-1380.avif 1380w&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://autonomousapps.com/blog/shrinking-elephants/post/zs1gPCfSAv-1380.webp 1380w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://autonomousapps.com/blog/shrinking-elephants/post/zs1gPCfSAv-1380.png&quot; alt=&quot;A screenshot from Intellij IDEA showing how to enable the parallel model fetching feature.&quot; width=&quot;1380&quot; height=&quot;542&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h3 id=&quot;spotlight&quot;&gt;Spotlight&lt;/h3&gt;
&lt;p&gt;Compared to the baseline scenario, adding Spotlight to our build &lt;strong&gt;reduced sync duration by 82%&lt;/strong&gt;, to about &lt;strong&gt;1.5 min&lt;/strong&gt;,
or about &lt;strong&gt;120 ms / project&lt;/strong&gt;. (This is &lt;em&gt;without&lt;/em&gt; parallel model fetching, to be clear.) Combined with parallel model
fetching, the full reduction was &lt;strong&gt;90%&lt;/strong&gt;, or about &lt;strong&gt;48% better&lt;/strong&gt; than parallel model fetching alone.&lt;/p&gt;
&lt;p&gt;Sometimes referred to as &amp;quot;focus&amp;quot;, this intervention is &lt;em&gt;required&lt;/em&gt; if you want to manage very large Gradle builds
(meaning it has a few hundred projects). This intervention works by dramatically reducing the number of active projects
Gradle must configure. Spotlight scans the project buildscript files to parse the project dependency tree without
configuring any projects, then only &lt;code&gt;include&lt;/code&gt;s any projects from the build that are in the dependency tree of the
projects the user requested to load in the IDE. For the purposes of this post, we cut the number of projects from more
than 2000 to about 750. This cutoff captures 95% of all syncs we observe from our developers. That is, only 5% of all
syncs target a project set larger than this. The figures above make use of a Spotlight-like tool to &amp;quot;target&amp;quot; specific
subsets of our larger monorepo.&lt;/p&gt;
&lt;p&gt;If you&#39;re able to abide by a few constraints, as clearly outlined in
&lt;a href=&quot;https://github.com/joshfriend/spotlight#limitations&quot;&gt;the Spotlight documentation&lt;/a&gt;, you can benefit from this
intervention today. We apply some &lt;a href=&quot;https://github.com/paulhundal530/conventions-enforcer&quot;&gt;automated enforcement&lt;/a&gt; of
these rules to prevent issues from popping up.&lt;/p&gt;
&lt;h3 id=&quot;intransitive-sync&quot;&gt;Intransitive sync&lt;/h3&gt;
&lt;p&gt;Compared to the baseline scenario, layering on intransitive-sync &lt;strong&gt;reduced sync duration by 94%&lt;/strong&gt;, to about &lt;strong&gt;28 sec&lt;/strong&gt;,
or about &lt;strong&gt;40 ms / project&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The original inspiration for this idea came from the MDX Android team, which manages the Square-Android Point-of-Sale
repo (extremely large at almost 7000 projects). In discussions between this team and the Backend Build Team (which
manages the aforementioned very large Kotlin JVM project, among other things), we realized that IDE-based development
and code completion only needed access to the compile classpath—not the runtime classpath. We therefore
&lt;a href=&quot;https://github.com/joshfriend/fastsync&quot;&gt;disabled transitive dependency resolution for all runtime classpaths&lt;/a&gt;
(&lt;code&gt;runtimeClasspath&lt;/code&gt;, &lt;code&gt;testRuntimeClasspath&lt;/code&gt;, and so on). We rolled this out to users over a period of about one month
while ironing out minor kinks in the implementation, before finally enabling it universally. Note that this feature may
break debugging and UI previews in Android Studio.&lt;/p&gt;
&lt;h3 id=&quot;artifact-swap&quot;&gt;Artifact swap&lt;/h3&gt;
&lt;p&gt;Compared to the baseline scenario, layering on &lt;a href=&quot;https://github.com/block/artifact-swap&quot;&gt;artifact-swap&lt;/a&gt;
&lt;strong&gt;reduced sync duration by 97%&lt;/strong&gt;, to about &lt;strong&gt;15 sec&lt;/strong&gt;, or about &lt;strong&gt;84 ms / project&lt;/strong&gt;. Note that the cost-per-project has
gone up a bit, reflecting the fact that the sync-time vs project-count model is dominated by some constant overhead at
low project count.&lt;/p&gt;
&lt;p&gt;Spotlight still requires us to load all projects in the dependency tree of our target projects into the IDE. We go even
further and swap out any projects that were not requested by a user with a precompiled jar artifact (or aar for android
projects). Doing this lets us minimize the Gradle project list to only what the user has requested to be loaded in the
IDE. Inspiration for this came from a
&lt;a href=&quot;https://www.droidcon.com/2023/10/06/supercharging-ide-and-sync-performance/&quot;&gt;Droidcon talk&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If a Gradle project is referenced in a build file, it must also be &lt;code&gt;include&lt;/code&gt;-ed in settings and configured by Gradle,
which is what we are trying to avoid. We start by ensuring that all &lt;code&gt;project()&lt;/code&gt; references in build files are rewritten
to maven coordinate of a precompiled artifact. In Groovy buildscripts we do this by using Groovy metaprogramming with a
project plugin to override the &lt;nobr&gt;&lt;code&gt;project()&lt;/code&gt;&lt;/nobr&gt; function to do what we want instead of the default behavior. This project
plugin is applied to every requested project in the build by a settings plugin. Projects using Kotlin buildscripts can
actually inject a DSL override more directly using
&lt;a href=&quot;https://dev.to/autonomousapps/gradle-extensions-part-2-now-with-shenanigans-12m6&quot;&gt;this One Weird Trick&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This process actually goes too far and may end up rewriting references to projects a user is working on to a maven
coordinate, but we fix this in a second stage by applying
&lt;a href=&quot;https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/DependencySubstitutions.html&quot;&gt;dependency substitution&lt;/a&gt;
to rewrite those references back to a project reference. This also makes any artifact that references a project artifact
that a user currently has loaded be rewritten back to the active Gradle project.&lt;/p&gt;
&lt;p&gt;This brief overview of artifact swap elides a lot of additional infrastructure used to publish project artifacts,
prefetch those artifacts for improved performance (see below), IDE plugin support for navigating the swapped project,
and tooling to reliably configure all of this. Most of our implementation for these features is available in the
&lt;a href=&quot;https://github.com/block/artifact-swap&quot;&gt;artifact-swap&lt;/a&gt; repository.&lt;/p&gt;
&lt;h3 id=&quot;pre-fetch-dependencies&quot;&gt;Pre-fetch dependencies&lt;/h3&gt;
&lt;p&gt;This intervention is of a somewhat different nature from the others discussed above. It helps not only with syncs but
with all builds, by downloading dependencies before the user requests them. Our data suggest that the middle quartile
case (p25-75) dropped from nearly 2 minutes to about 20 seconds. That is, Gradle spends about 83% less time downloading
dependencies, during IDE sync, post-intervention, according to Develocity.&lt;/p&gt;
&lt;p&gt;We can&#39;t make statements on this based on benchmarks since those are, by definition and as noted above, fully
warm—dependencies have already been fetched. The Develocity telemetry, however, gives us high confidence that this
improves the local development experience.&lt;/p&gt;
&lt;p&gt;Our pre-fetching works in two ways: via a git post-checkout hook, and also on a periodic schedule. In both cases, the
background process gets the list of dependencies from a Gradle task registered by the
&lt;a href=&quot;https://github.com/autonomousapps/dependency-analysis-gradle-plugin/blob/main/src/main/kotlin/com/autonomousapps/subplugin/RootPlugin.kt#L102&quot;&gt;Dependency Analysis Gradle Plugin&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;./gradlew :computeAllDependencies&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This generates a file at &lt;nobr&gt;&lt;code&gt;build/reports/dependency-analysis/allLibs.versions.toml&lt;/code&gt;.&lt;/nobr&gt; This
&lt;a href=&quot;https://docs.gradle.org/current/userguide/version_catalogs.html&quot;&gt;version catalog&lt;/a&gt; is then used by Gradle in a
&lt;a href=&quot;https://engineering.block.xyz/blog/rethinking-idle-time&quot;&gt;special background process&lt;/a&gt; that is installed on all developer
computers by Block&#39;s MDM solution and &lt;a href=&quot;https://saltproject.io/&quot;&gt;Salt&lt;/a&gt;. This background process uses Gradle to resolve
all dependencies (including transitive dependencies) needed by the build.&lt;/p&gt;
&lt;h2 id=&quot;future-interventions&quot;&gt;Future interventions&lt;/h2&gt;
&lt;p&gt;We&#39;ve already achieved fairly amazing results, but as we know, the build engineer&#39;s work is never done. What else can we
do? Three things come immediately to mind, and are part of our plans for 2026: we want to clean up our dependency graph,
we want to cleanup our project structure, and we want to make our build fully compliant with Gradle&#39;s
&lt;a href=&quot;https://docs.gradle.org/current/userguide/isolated_projects.html&quot;&gt;Isolated Projects&lt;/a&gt; feature.&lt;/p&gt;
&lt;p&gt;We think that cleaning up our dependency graph (with the help of the
&lt;a href=&quot;https://github.com/autonomousapps/dependency-analysis-gradle-plugin&quot;&gt;Dependency Analysis Gradle Plugin&lt;/a&gt;, aka DAGP)
would have several important benefits, not least of which is enhanced maintainability and easier debugging of build
failures. But we also think it could have a positive impact on performance: eliminating unused external dependencies
means less network overhead, and eliminating edges between local projects would enhance the effectiveness of
interventions like Spotlight and Artifact Swap (discussed above).&lt;/p&gt;
&lt;p&gt;DAGP is also able to identify projects applying the Android Gradle Plugin (AGP) that do not need to do so. AGP is a
particularly “heavy” plugin, and reducing its use in the build has yielded performance wins for us previously. We also
plan to refine our &lt;a href=&quot;https://speakerdeck.com/vrallev/android-at-scale-at-square&quot;&gt;module structure&lt;/a&gt; by combining certain
module types to reduce the overall project count in our build. Using Gradle&#39;s
&lt;a href=&quot;https://docs.gradle.org/current/userguide/java_testing.html#sec:java_test_fixtures&quot;&gt;test fixtures feature&lt;/a&gt; is another
way that modules can be combined to reduce the overall count.&lt;/p&gt;
&lt;p&gt;Isolated Projects, currently a pre-alpha feature of Gradle, promises to be a game-changer. It would enable fine-grained
and incremental caching of the configuration phase of the build, which would make dependency updates (such as might
affect only a single Gradle subproject) significantly less painful. We have done quite a bit of work already to be early
adopters of this feature, including submitting
&lt;a href=&quot;https://android.googlesource.com/platform/tools/idea/+/52a09b80c06b9279c99115df76a342dc5d1b261d&quot;&gt;patches to Android Studio&lt;/a&gt;,
and Square&#39;s Android build is already compliant with the isolated projects contract for sync. So far Isolated Projects
has not delivered us any performance benefits, but Gradle is currently focused on validating the correctness of the
implementation before focusing on performance in later 9.x releases.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Tony Robalik is a build engineer on Block&#39;s Backend Build team, and a Gradle Fellow. Josh Friend is a build engineer on
Block&#39;s Android Developer Experience team and helps maintain what might be the largest Gradle build in the world. Thanks
to Tim Mellor, Yissachar Radcliffe, and Gábor Pap for reviewing the draft.&lt;/em&gt;&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot;&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Although to be clear, there is no direct relationship between IDE sync and lines of code. This figure is provided
only as a reference point to help indicate scale. &lt;a href=&quot;https://autonomousapps.com/blog/shrinking-elephants/post/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Please note that the IDE plugin will not work out of the box for non-Block projects, but the code is
straightforward and we hope it provides inspiration to other users. Contributions are also welcome. &lt;a href=&quot;https://autonomousapps.com/blog/shrinking-elephants/post/#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Via &lt;a href=&quot;https://cdsap.github.io/ProjectGenerator/&quot;&gt;https://cdsap.github.io/ProjectGenerator/&lt;/a&gt;, for example. &lt;a href=&quot;https://autonomousapps.com/blog/shrinking-elephants/post/#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn4&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Although in practice it is probably more likely to reflect the org chart—for potentially very good reasons. &lt;a href=&quot;https://autonomousapps.com/blog/shrinking-elephants/post/#fnref4&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn5&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Note that, for Android Studio users, this is the default behavior. &lt;a href=&quot;https://autonomousapps.com/blog/shrinking-elephants/post/#fnref5&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  <entry>
    <title>This is why we can&#39;t have nice things: When POM files lie</title>
    <link href="https://autonomousapps.com/blog/when-pom-files-lie/post/" />
    <updated>2025-02-07T00:00:00Z</updated>
    <id>https://autonomousapps.com/blog/when-pom-files-lie/post/</id>
    <content type="html">&lt;img src=&quot;https://autonomousapps.com/blog/when-pom-files-lie/post/iKG6zV-fF0-1000.webp&quot; alt=&quot;A photo from the inside of a stormwater drain&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1000&quot; height=&quot;420&quot;&gt;
&lt;p&gt;&lt;small&gt;&lt;em&gt;Photo by &lt;a href=&quot;https://unsplash.com/@mbicca?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;Marco Bicca&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/empty-water-tunnel-1XWR9oI9AFA?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;Unsplash&lt;/a&gt;. I really hope there&#39;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&#39;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;/p&gt;
&lt;pre&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;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&#39;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 name=&quot;source-1&quot;&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;a href=&quot;https://autonomousapps.com/blog/when-pom-files-lie/post/#endnotes&quot;&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;pre&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;p&gt;That expression resolves to this:&lt;/p&gt;
&lt;pre&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;p&gt;What the fuck! That is not the right jar!! Let&#39;s double-check:&lt;/p&gt;
&lt;pre&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;p&gt;😭&lt;/p&gt;
&lt;p&gt;Maybe it&#39;s a bug! Let&#39;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&#39;re not even bothering to &lt;em&gt;relocate the damn packages&lt;/em&gt;. Ok, I&#39;m curious, what does the pom.xml have to say for itself?&lt;/p&gt;
&lt;pre class=&quot;language-xml&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token prolog&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;project&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;xmlns&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;http://maven.apache.org/POM/4.0.0&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;xsi:&lt;/span&gt;schemaLocation&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;xmlns:&lt;/span&gt;xsi&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;http://www.w3.org/2001/XMLSchema-instance&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;modelVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;4.0.0&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;modelVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;groupId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;org.jetbrains.kotlin&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;groupId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;artifactId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;kotlin-compiler&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;artifactId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;2.1.10&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Kotlin Compiler&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;description&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Kotlin Compiler&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;description&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;https://kotlinlang.org/&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;licenses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;license&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;The Apache License, Version 2.0&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;http://www.apache.org/licenses/LICENSE-2.0.txt&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;license&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;licenses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;developers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;developer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Kotlin Team&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;organization&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;JetBrains&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;organization&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;organizationUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;https://www.jetbrains.com&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;organizationUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;developer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;developers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;scm&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;connection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;scm:git:https://github.com/JetBrains/kotlin.git&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;connection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;developerConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;scm:git:https://github.com/JetBrains/kotlin.git&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;developerConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;https://github.com/JetBrains/kotlin&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;scm&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;dependencies&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;dependency&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;groupId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;org.jetbrains.kotlin&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;groupId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;artifactId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;kotlin-stdlib-jdk8&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;artifactId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;2.1.10&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;scope&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;compile&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;scope&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;dependency&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;dependency&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;groupId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;org.jetbrains.kotlin&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;groupId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;artifactId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;kotlin-script-runtime&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;artifactId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;2.1.10&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;scope&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;compile&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;scope&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;dependency&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;dependency&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;groupId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;org.jetbrains.kotlin&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;groupId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;artifactId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;kotlin-reflect&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;artifactId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;1.6.10&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;scope&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;compile&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;scope&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;exclusions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;exclusion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;groupId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;*&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;groupId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;artifactId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;*&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;artifactId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;exclusion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;exclusions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;dependency&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;dependency&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;groupId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;org.jetbrains.intellij.deps&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;groupId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;artifactId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;trove4j&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;artifactId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;1.0.20200330&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;scope&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;compile&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;scope&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;dependency&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;dependency&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;groupId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;org.jetbrains.kotlinx&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;groupId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;artifactId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;kotlinx-coroutines-core-jvm&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;artifactId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;1.6.4&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;scope&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;compile&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;scope&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;dependency&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;dependencies&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;project&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&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=&quot;https://central.sonatype.com/artifact/org.jetbrains.kotlin/kotlin-bom/versions&quot;&gt;publish a BOM&lt;/a&gt;&lt;/em&gt;, just use it!&lt;/p&gt;
&lt;p&gt;&lt;a name=&quot;source-2&quot;&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=&quot;https://youtrack.jetbrains.com/issue/KT-74969/ClassCastException-between-com.google.common.graph.ImmutableGraph-and-com.google.common.graph.SuccessorsFunction&quot;&gt;this issue I raised&lt;/a&gt;.&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;why-am-i-even-using-this-dependency&quot;&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;a href=&quot;https://autonomousapps.com/blog/when-pom-files-lie/post/#endnotes&quot;&gt;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&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;/p&gt;
&lt;pre&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;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=&quot;https://discuss.kotlinlang.org/t/kotlin-compiler-embeddable-vs-kotlin-compiler/3196/2&quot;&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 name=&quot;source-3&quot;&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;a href=&quot;https://autonomousapps.com/blog/when-pom-files-lie/post/#endnotes&quot;&gt;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;coping&quot;&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=&quot;https://kotlinlang.org/docs/whatsnew21.html#compiler-symbols-hidden-from-the-kotlin-gradle-plugin-api&quot;&gt;link&lt;/a&gt; provided in the warning does a fair job of explaining how to do that, assuming you&#39;re ok with drawing the owl.&lt;/p&gt;
&lt;img src=&quot;https://autonomousapps.com/blog/when-pom-files-lie/post/g3N9cbAXb2-530.webp&quot; alt=&quot;How to draw an owl: first, draw some circles. Second, draw the rest of the fucking owl.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;530&quot; height=&quot;453&quot;&gt;
&lt;h3 id=&quot;ah-fuck-it-lets-draw-the-owl&quot;&gt;Ah, fuck it, let&#39;s draw the owl&lt;/h3&gt;
&lt;h4 id=&quot;drawing-the-build-script&quot;&gt;Drawing the build script&lt;/h4&gt;
&lt;pre class=&quot;language-kotlin&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// plugin/build.gradle.kts&lt;/span&gt;
…etc…

dependencies &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;implementation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;:shared-lib&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;exclude&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;group &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;org.jetbrains.kotlin&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; module &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;kotlin-compiler&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Jetbrains&#39; docs suggest using &lt;code&gt;compileOnly(&amp;quot;org.jetbrains.kotlin:kotlin-compiler-embeddable:2.1.10&amp;quot;)&lt;/code&gt;, which isn&#39;t wrong, but also won&#39;t work if that lib is actually coming from a transitive dependency.&lt;/p&gt;
&lt;h3 id=&quot;drawing-the-task&quot;&gt;Drawing the task&lt;/h3&gt;
&lt;pre class=&quot;language-kotlin&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; OwlTask &lt;span class=&quot;token annotation builtin&quot;&gt;@Inject&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; workerExecutor&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; WorkerExecutor
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;DefaultTask&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;token annotation builtin&quot;&gt;@get:Classpath&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; kotlinCompiler&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ConfigurableFileCollection

  &lt;span class=&quot;token annotation builtin&quot;&gt;@get:Input&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; otherInput&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Property&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Boolean&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;

  &lt;span class=&quot;token annotation builtin&quot;&gt;@TaskAction&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    workerExecutor
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;classLoaderIsolation&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; spec &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// please note that from() adds to the&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// classpath, while setFrom() would&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// completely override it.&lt;/span&gt;
        spec&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;classpath&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;kotlinCompiler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;submit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Action&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;java&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; params &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
        params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;otherInput&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;otherInput&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; Parameters &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; WorkParameters &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; otherInput&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Property&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Boolean&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; Action &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; WorkAction&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Parameters&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; otherInput &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; parameters&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;otherInput&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      …
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;drawing-the-plugin&quot;&gt;Drawing the plugin&lt;/h4&gt;
&lt;pre class=&quot;language-kotlin&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; OwlPlugin &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Plugin&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Project&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;target&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Project&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Use the project&#39;s version of Kotlin, if present. Else default to what we use.&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; kotlinVersion &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; …some method &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; getting the version of Kotlin you want…
    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; dependencyScope &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; project&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;configurations&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;dependencyScope&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;owl&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; resolvable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; project&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;configurations&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolvable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;owlClasspath&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;extendsFrom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dependencyScope&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    project&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dependencies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dependencyScope&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;org.jetbrains.kotlin:kotlin-compiler:&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;kotlinVersion&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    project&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tasks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;owl&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; OwlTask&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;java&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; t &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;kotlinCompiler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setFrom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resolvable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;otherInput&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// demonstration purposes only&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;hello-owl&quot;&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 id=&quot;i-m-just-some-asshole&quot;&gt;I&#39;m just some asshole&lt;/h2&gt;
&lt;p&gt;Look, I&#39;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&#39;ll write a blog post!&lt;/p&gt;
&lt;h2 id=&quot;special-thanks&quot;&gt;Special thanks&lt;/h2&gt;
&lt;p&gt;I am much obliged &lt;a href=&quot;https://bsky.app/profile/luis.cortes.social&quot;&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 id=&quot;endnotes&quot;&gt;Endnotes&lt;/h2&gt;
&lt;p&gt;&lt;sup&gt;1&lt;/sup&gt; Guava&#39;s &lt;code&gt;Graph&lt;/code&gt; class has a complicated hierarchy! &lt;a href=&quot;https://autonomousapps.com/blog/when-pom-files-lie/post/#source-1&quot;&gt;&lt;small&gt;up&lt;/small&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;sup&gt;2&lt;/sup&gt; This dependency exhibits many of the same metadata problems. &lt;a href=&quot;https://autonomousapps.com/blog/when-pom-files-lie/post/#source-2&quot;&gt;&lt;small&gt;up&lt;/small&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;sup&gt;3&lt;/sup&gt; To be fair, this is a very widespread problem. &lt;a href=&quot;https://autonomousapps.com/blog/when-pom-files-lie/post/#source-3&quot;&gt;&lt;small&gt;up&lt;/small&gt;&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Gradle extensions part 2: Now with shenanigans</title>
    <link href="https://autonomousapps.com/blog/extensions-part-2-shenanigans/post/" />
    <updated>2024-12-11T00:00:00Z</updated>
    <id>https://autonomousapps.com/blog/extensions-part-2-shenanigans/post/</id>
    <content type="html">&lt;img src=&quot;https://autonomousapps.com/blog/extensions-part-2-shenanigans/post/76FLCPTTin-1000.webp&quot; alt=&quot;A photo of a beautiful forest&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1000&quot; height=&quot;420&quot;&gt;
&lt;p&gt;&lt;em&gt;&lt;small&gt;Photo by &lt;a href=&quot;https://unsplash.com/@karsten_wuerth?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;Karsten Würth&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/flowing-river-between-tall-trees-7BjhtdogU3A?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&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=&quot;https://dev.to/autonomousapps/gradle-plugins-and-extensions-a-primer-for-the-bemused-51lp&quot;&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;img src=&quot;https://autonomousapps.com/blog/extensions-part-2-shenanigans/post/1ch2UbijUv-800.webp&quot; alt=&quot;Google search results for &#39;gradle extensions&#39;, with my post at the second position&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;800&quot; height=&quot;544&quot;&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;/p&gt;
&lt;pre class=&quot;language-kotlin&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// buildSrc/src/main/kotlin/magic/Magic.kt&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;package&lt;/span&gt; magic

&lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt; Magic &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;shouldPracticeTheDarkArts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Boolean &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; System&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getenv&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;DO_ANCIENT_MAGIC&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBoolean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;?:&lt;/span&gt; System&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getenv&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;DO_SLIGHTLY_MORE_MODERN_MAGIC&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBoolean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;?:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code is used in build scripts like this:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// build.gradle.kts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; magic&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Magic

tasks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;withType&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Test&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;configureEach&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Magic&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;shouldPracticeTheDarkArts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    logger&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;quiet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;👻&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are several things about this that I&#39;d like to improve:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I don&#39;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&#39;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&#39;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=&quot;https://docs.gradle.org/current/javadoc/org/gradle/api/provider/ProviderFactory.html&quot;&gt;&lt;code&gt;ProviderFactory&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;extending-test-tasks&quot;&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=&quot;https://docs.gradle.org/current/kotlin-dsl/gradle/org.gradle.api.plugins/-extension-aware/index.html&quot;&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;/p&gt;
&lt;pre class=&quot;language-kotlin&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// build-logic/src/main/kotlin/magic/TestMagicExtension.kt&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;package&lt;/span&gt; magic

&lt;span class=&quot;token keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; TestMagicExtension &lt;span class=&quot;token annotation builtin&quot;&gt;@Inject&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; providers&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ProviderFactory
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;internal&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;companion&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; NAME &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;magic&quot;&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      testTask&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Test&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      providers&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ProviderFactory&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      testTask&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;extensions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        NAME&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        TestMagicExtension&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;java&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        providers&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;shouldPracticeTheDarkArts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Boolean &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; providers
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;environmentVariable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;DO_ANCIENT_MAGIC&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;orElse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;providers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;environmentVariable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;DO_SLIGHTLY_MORE_MODERN_MAGIC&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; it&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isNotEmpty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getOrElse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And in our plugin, we can add this to all our &lt;code&gt;Test&lt;/code&gt; tasks:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;project&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tasks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;withType&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Test&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;configureEach&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; t &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
  TestMagicExtension&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; project&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;providers&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And now we can update our build scripts:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// build.gradle.kts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; magic&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TestMagicExtension

tasks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;withType&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Test&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;configureEach&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// the &quot;extensions&quot; call is on the Test instance,&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// not the project instance&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; magic &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; extensions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getByType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;TestMagicExtension&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;java&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;magic&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;shouldPracticeTheDarkArts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    logger&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;quiet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;👻&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;...that&#39;s not better at all!&lt;/p&gt;
&lt;h2 id=&quot;groovy-an-interlude&quot;&gt;Groovy: An interlude&lt;/h2&gt;
&lt;p&gt;First of all, let&#39;s take a step back and remind ourselves that &amp;quot;we love Kotlin, type safety is great, I don&#39;t care that performance is worse...&amp;quot; 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&#39;s the same build script but in Groovy:&lt;/p&gt;
&lt;pre class=&quot;language-groovy&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// build.gradle&lt;/span&gt;
tasks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Test&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;configureEach &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;magic&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;shouldPracticeTheDarkArts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// I&#39;m being cheeky by also omitting the&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// &quot;redundant&quot; parentheses&lt;/span&gt;
    logger&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;quiet &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;👻&quot;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Groovy isn&#39;t supposed to be better! Damnit!&lt;/p&gt;
&lt;h2 id=&quot;sprinkle-on-some-shenanigans&quot;&gt;Sprinkle on some shenanigans&lt;/h2&gt;
&lt;p&gt;How the heck does Gradle Kotlin DSL do it? Why isn&#39;t it generating &amp;quot;typesafe accessors&amp;quot; for my test task extension? Well, that second one is a good question and I have no answer. But for the first... let&#39;s just &amp;quot;generate&amp;quot; (that is, write) our own typesafe accessors!&lt;/p&gt;
&lt;p&gt;We add some code in a new (to us) package:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;package&lt;/span&gt; org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gradle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;kotlin&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dsl

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; magic&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TestMagicExtension

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; Test&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;magic&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; TestMagicExtension
  &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; extensions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getByType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;TestMagicExtension&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;java&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; Test&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;magic&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;configure&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; TestMagicExtension&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; Unit&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;configure&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;TestMagicExtension&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;NAME&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; configure&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And now we can update our &lt;em&gt;Kotlin DSL&lt;/em&gt; build script:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// build.gradle.kts&lt;/span&gt;
tasks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;withType&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Test&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;configureEach&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;magic&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;shouldPracticeTheDarkArts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    logger&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;quiet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;👻&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we&#39;re (ab)using the fact that &lt;a href=&quot;https://github.com/gradle/gradle/blob/master/platforms/core-configuration/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/ImplicitImports.kt#L44&quot;&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=&quot;https://github.com/gradle/test-retry-gradle-plugin/blob/main/plugin/src/main/kotlin/org/gradle/kotlin/dsl/testRetry.kt&quot;&gt;test-retry-gradle-plugin&lt;/a&gt;. There&#39;s also an &lt;a href=&quot;https://github.com/gradle/gradle/issues/7557&quot;&gt;open issue&lt;/a&gt; (from, er, 2018) on Gradle&#39;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;
</content>
  </entry>
  <entry>
    <title>One click dependencies fix</title>
    <link href="https://autonomousapps.com/blog/one-click-dependencies-fix/post/" />
    <updated>2024-10-08T00:00:00Z</updated>
    <id>https://autonomousapps.com/blog/one-click-dependencies-fix/post/</id>
    <content type="html">&lt;img src=&quot;https://autonomousapps.com/blog/one-click-dependencies-fix/post/tCFpQ5Jvn4-1000.webp&quot; alt=&quot;A photo of a derelict room&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1000&quot; height=&quot;420&quot;&gt;
&lt;p&gt;&lt;em&gt;&lt;small&gt;Photo by &lt;a href=&quot;https://unsplash.com/@nampoh?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;Maxim Hopman&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/black-metal-gate-near-concrete-wall-UMg-jcWPLrI?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;Unsplash&lt;/a&gt;&lt;/small&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a name=&quot;source-1&quot;&gt;If you maintain a JVM&lt;a href=&quot;https://autonomousapps.com/blog/one-click-dependencies-fix/post/#endnotes&quot;&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt; or Android project, chances are you&#39;ve heard of the &lt;a href=&quot;https://github.com/autonomousapps/dependency-analysis-gradle-plugin&quot;&gt;Dependency Analysis Gradle Plugin&lt;/a&gt; (DAGP). With over 1800 stars, it&#39;s used by some of largest Gradle projects in the world, as well as by &lt;a href=&quot;https://github.com/gradle/gradle/blob/master/build-logic/root-build/src/main/kotlin/gradlebuild.dependency-analysis.gradle.kts&quot;&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;/a&gt;&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&#39;t know how much you rely on it till it stops working and you&#39;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&#39;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=&quot;https://dev.to/autonomousapps/acab-fire-the-code-style-cop-in-your-head-m8b&quot;&gt;recent rant against code style formatters&lt;/a&gt;. This is why, since &lt;a href=&quot;https://github.com/autonomousapps/dependency-analysis-gradle-plugin/blob/main/CHANGELOG.md#version-1110-1111-1112-1113&quot;&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=&quot;https://github.com/autonomousapps/dependency-analysis-gradle-plugin/blob/main/CHANGELOG.md#version-0460&quot;&gt;v0.46.0&lt;/a&gt;, the plugin had first-class support for registering a &amp;quot;post-processing task&amp;quot; to enable advanced users to consume the &amp;quot;build health&amp;quot; report in any manner of their choosing. &lt;a href=&quot;https://github.com/slackhq/foundry&quot;&gt;Foundry&lt;/a&gt; (née The Slack Gradle Plugin), for example, has a feature called the &amp;quot;&lt;a href=&quot;https://github.com/slackhq/foundry/blob/main/platforms/gradle/foundry-gradle-plugin/src/main/kotlin/foundry/gradle/dependencyrake/DependencyRake.kt&quot;&gt;dependency rake&lt;/a&gt;&amp;quot;, 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&#39;t always worked well, though. For one thing, there might be a bug in the analysis such that, if you &amp;quot;fix&amp;quot; all the issues, your build might break. (DAGP is under very active development, so if this ever happens to you, please &lt;a href=&quot;https://github.com/autonomousapps/dependency-analysis-gradle-plugin/issues/new/choose&quot;&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 name=&quot;source-2&quot;&gt;For another thing, the build script rewriter has relied on a simplified &lt;a href=&quot;https://www.antlr.org/&quot;&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;a href=&quot;https://autonomousapps.com/blog/one-click-dependencies-fix/post/#endnotes&quot;&gt;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; This problem will soon be solved with the introduction of a Gradle Kotlin DSL parser built on the &lt;a href=&quot;https://github.com/cashapp/kotlin-editor&quot;&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;/a&gt;&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&#39;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&#39;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&#39;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 name=&quot;source-3&quot;&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 &amp;quot;Gradle&amp;quot; code in more than 3 thousand build scripts. This last point isn&#39;t as relevant as the first four, but helps to demonstrate what I mean when I say &amp;quot;industrial scale.&amp;quot;&lt;a href=&quot;https://autonomousapps.com/blog/one-click-dependencies-fix/post/#endnotes&quot;&gt;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt;&lt;/a&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=&quot;https://docs.gradle.org/current/userguide/build_cache.html&quot;&gt;cacheable&lt;/a&gt; to the extent possible, should work with the &lt;a href=&quot;https://docs.gradle.org/current/userguide/configuration_cache.html&quot;&gt;configuration cache&lt;/a&gt;, and for bonus points should not violate the &lt;a href=&quot;https://docs.gradle.org/current/userguide/isolated_projects.html&quot;&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&#39;re migrating these repos into a single mono/mega repo for other reasons.&lt;/p&gt;
&lt;p&gt;Here&#39;s the task they can now run, for the record:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;gradle :fixAllDependencies&lt;/code&gt;&lt;/pre&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=&quot;https://github.com/cashapp/hermit&quot;&gt;hermit&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;So, how do we do it?&lt;/p&gt;
&lt;h2 id=&quot;pre-processing&quot;&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=&quot;https://docs.gradle.org/current/userguide/platforms.html#sec:version-catalog-plugin&quot;&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 id=&quot;conceptual-framework&quot;&gt;Conceptual framework&lt;/h2&gt;
&lt;p&gt;&lt;a name=&quot;source-4&quot;&gt;The Gradle framework, in general, takes the &lt;a href=&quot;https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html&quot;&gt;Project&lt;/a&gt; as the most important point of reference.&lt;a href=&quot;https://autonomousapps.com/blog/one-click-dependencies-fix/post/#endnotes&quot;&gt;&lt;sup&gt;4&lt;/sup&gt;&lt;/a&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 &amp;quot;module&amp;quot;) as an atomic unit.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a name=&quot;source-5&quot;&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&#39;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&#39;s outputs are the artifacts it produces. For &lt;a href=&quot;https://docs.gradle.org/current/userguide/java_plugin.html&quot;&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;a href=&quot;https://autonomousapps.com/blog/one-click-dependencies-fix/post/#endnotes&quot;&gt;&lt;sup&gt;5&lt;/sup&gt;&lt;/a&gt;&lt;/a&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;&#39;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 &amp;quot;flavor,&amp;quot; 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 id=&quot;implementation-fixalldependencies&quot;&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:tm: with Gradle.&lt;/p&gt;
&lt;p&gt;Here&#39;s a sketch of the simplified task graph with &lt;a href=&quot;https://excalidraw.com/&quot;&gt;Excalidraw&lt;/a&gt;:&lt;/p&gt;
&lt;img src=&quot;https://autonomousapps.com/blog/one-click-dependencies-fix/post/GFgGqAswdj-800.webp&quot; alt=&quot;A sketch of the task graph, also described at length in the narrative that follows&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;800&quot; height=&quot;501&quot;&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 id=&quot;step-1-the-global-namespace&quot;&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;&amp;quot;com.amazing:magic:1.0&amp;quot;&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 = &amp;quot;com.amazing:magic:1.0&amp;quot;&lt;/code&gt;. However, if you don&#39;t, DAGP defaults to the &amp;quot;raw string&amp;quot; declaration. If we want, we can tell DAGP about other mappings that it can&#39;t detect by default:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// root build script&lt;/span&gt;
dependencyAnalysis &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  structure &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    map&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;putAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;com.amazing:magic&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;libs.amazingMagic&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// more entries&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&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 &amp;quot;raw string&amp;quot; version of the declaration doesn&#39;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 id=&quot;step-2-update-the-version-catalog-part-1&quot;&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&#39;ll have dependencies referencing things like &lt;code&gt;libs.amazingMagic&lt;/code&gt; which aren&#39;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 &amp;quot;project advice&amp;quot; that DAGP emits to the console, but in a form amenable to computer processing. We&#39;ll take that output, filter it for &amp;quot;add advice&amp;quot;, and then write those values out to disk via our task&#39;s output. We only care about the &lt;a href=&quot;https://dev.to/autonomousapps/the-proper-care-and-feeding-of-your-gradle-build-d8g&quot;&gt;add advice&lt;/a&gt; because that&#39;s the only type that might represent a dependency not in a version catalog.&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// in a custom task action&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; newEntries &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;projectAdvice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dependencyAdvice
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; it&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isAnyAdd&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; it&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;coordinates &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; ModuleCoordinates &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; it&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;coordinates&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;gav&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toSortedSet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

outputFile&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newEntries&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;joinToString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;separator &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&#92;n&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that with Gradle task outputs, it&#39;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;/p&gt;
&lt;pre class=&quot;language-kotlin&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// subproject&#39;s build script&lt;/span&gt;
computeNewVersionCatalogEntries &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tasks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

dependencyAnalysis &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;registerPostProcessingTask&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;computeNewVersionCatalogEntries&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And finally we also have to register our new task&#39;s output as an artifact of this project!&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; publisher &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;interProjectPublisher&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  project&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  MyArtifacts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Kind&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;VERSION_CATALOG_ENTRIES
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
publisher&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;publish&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  computeNewVersionCatalogEntries&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flatMap&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    it&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;newVersionCatalogEntries
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;where the &lt;code&gt;interProjectPublisher&lt;/code&gt; and related code is heavily inspired by DAGP&#39;s &lt;a href=&quot;https://github.com/autonomousapps/dependency-analysis-gradle-plugin/tree/main/src/main/kotlin/com/autonomousapps/internal/artifacts&quot;&gt;artifacts&lt;/a&gt; package, because I wrote both. The tl;dr is that this is what teaches Gradle about a project&#39;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 id=&quot;step-3-update-the-version-catalog-part-2&quot;&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;/p&gt;
&lt;pre class=&quot;language-kotlin&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// root project&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; resolver &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;interProjectResolver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  project&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  MyArtifacts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Kind&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;VERSION_CATALOG_ENTRIES
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Yes, this CAN BE OK, but you must only access&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// IMMUTABLE PROPERTIES of each project p.&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// This sets up the dependencies from the root to&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// each &quot;real&quot; subproject, where &quot;real&quot; filters&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// out intermediate directories that don&#39;t have&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// any code&lt;/span&gt;
allprojects&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; p &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// implementation left to reader&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isRealProject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    dependencies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      resolver&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;declarable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// p.path is an immutable property, so we&#39;re&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// good&lt;/span&gt;
      dependencies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;mapOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;to&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; fixVersionCatalog &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tasks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;fixVersionCatalog&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  UpdateVersionCatalogTask&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;java
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; t &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
    t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;newEntries&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setFrom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resolver&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;internal&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;globalNamespace&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;putAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;versionCatalog&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;layout&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;projectDirectory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;gradle/libs.versions.toml&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&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&#39;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 id=&quot;step-4-fix-all-the-dependency-declarations&quot;&gt;Step 4: Fix all the dependency declarations&lt;/h3&gt;
&lt;p&gt;This step leverages DAGP&#39;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&#39;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;/p&gt;
&lt;pre class=&quot;language-kotlin&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// root project&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; fixDependencies &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; mutableListOf&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;String&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

allprojects&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; p &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isRealProject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...as before...&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// do not use something like `p.tasks.findByName()`,&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// that violates Isolated Projects as well as&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// lazy task configuration.&lt;/span&gt;
    fixDependencies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;:fixDependencies&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

tasks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;fixAllDependencies&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; t &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
  t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;dependsOn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fixVersionCatalog&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;dependsOn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fixDependencies&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a name=&quot;source-6&quot;&gt;And we&#39;re done.&lt;a href=&quot;https://autonomousapps.com/blog/one-click-dependencies-fix/post/#endnotes&quot;&gt;&lt;sup&gt;6&lt;/sup&gt;&lt;/a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;optional-step-5-sort-dependency-blocks&quot;&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. :tada: 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&#39;ve also authored and published the &lt;a href=&quot;https://github.com/square/gradle-dependencies-sorter&quot;&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;/p&gt;
&lt;pre&gt;&lt;code&gt;gradle sortDependencies
&lt;/code&gt;&lt;/pre&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&#39;t pose a problem for it.&lt;/p&gt;
&lt;p&gt;And now we&#39;re really done.&lt;/p&gt;
&lt;h2 id=&quot;endnotes&quot;&gt;Endnotes&lt;/h2&gt;
&lt;p&gt;&lt;sup&gt;1&lt;/sup&gt; Currently supported languages: Groovy, Java, Kotlin, and Scala. &lt;a href=&quot;https://autonomousapps.com/blog/one-click-dependencies-fix/post/#source-1&quot;&gt;&lt;small&gt;up&lt;/small&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;sup&gt;2&lt;/sup&gt; This is one reason why I think it&#39;s important to keep scripts &lt;a href=&quot;https://developer.squareup.com/blog/herding-elephants/&quot;&gt;simple and declarative&lt;/a&gt;. &lt;a href=&quot;https://autonomousapps.com/blog/one-click-dependencies-fix/post/#source-2&quot;&gt;&lt;small&gt;up&lt;/small&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;sup&gt;3&lt;/sup&gt; Measured with the &lt;a href=&quot;https://github.com/AlDanial/cloc&quot;&gt;cloc&lt;/a&gt; tool. &lt;a href=&quot;https://autonomousapps.com/blog/one-click-dependencies-fix/post/#source-3&quot;&gt;&lt;small&gt;up&lt;/small&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;sup&gt;4&lt;/sup&gt; Gradle&#39;s biggest footgun, in my opinion, is that the API doesn&#39;t enforce this conceptual boundary. &lt;a href=&quot;https://autonomousapps.com/blog/one-click-dependencies-fix/post/#source-4&quot;&gt;&lt;small&gt;up&lt;/small&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;sup&gt;5&lt;/sup&gt; This paragraph is an oversimplification for discussion purposes. &lt;a href=&quot;https://autonomousapps.com/blog/one-click-dependencies-fix/post/#source-5&quot;&gt;&lt;small&gt;up&lt;/small&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;sup&gt;6&lt;/sup&gt; Well, except for &lt;a href=&quot;https://github.com/autonomousapps/dependency-analysis-gradle-plugin/tree/main/testkit&quot;&gt;automated testing&lt;/a&gt; and &lt;a href=&quot;https://dev.to/autonomousapps/one-click-dependencies-fix-191p&quot;&gt;blog-post writing&lt;/a&gt;. &lt;a href=&quot;https://autonomousapps.com/blog/one-click-dependencies-fix/post/#source-6&quot;&gt;&lt;small&gt;up&lt;/small&gt;&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>ACAB: Fire the (code style) cop in your head</title>
    <link href="https://autonomousapps.com/blog/acab-fire-code-stye-cop/post/" />
    <updated>2024-09-24T00:00:00Z</updated>
    <id>https://autonomousapps.com/blog/acab-fire-code-stye-cop/post/</id>
    <content type="html">&lt;img src=&quot;https://autonomousapps.com/blog/acab-fire-code-stye-cop/post/JAyWCL_tle-1000.webp&quot; alt=&quot;A photo of two unreasonably cute dogs wearing sheriff hats&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1000&quot; height=&quot;420&quot;&gt;
&lt;p&gt;&lt;small&gt;&lt;em&gt;Photo by &lt;a href=&quot;https://unsplash.com/@karsten116?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;Karsten Winegeart&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/a-couple-of-dogs-sitting-next-to-each-other-RmuC5p_CPSE?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&quot;&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;Let&#39;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=&quot;https://mstdn.social/@autonomousapps/113030638543848445&quot;&gt;haul my grindstone out into the public square&lt;/a&gt;.
&lt;a href=&quot;https://mstdn.social/@autonomousapps/113166050596455765&quot;&gt;Multiple times&lt;/a&gt;.
&lt;a href=&quot;https://mstdn.social/@autonomousapps/113202332051085238&quot;&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&#39;t also
build Kotlin with Gradle, don&#39;t worry about it! I&#39;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&#39;s gooooo.&lt;/p&gt;
&lt;h2 id=&quot;arguments-in-favor-of-enforcement-of-consistent-code-style&quot;&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&#39;ve already run into an issue, which is we need to clarify what &amp;quot;style&amp;quot; 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 &amp;quot;understanding&amp;quot; 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&#39;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;What if we mean &amp;quot;style&amp;quot; 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 class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://autonomousapps.com/blog/acab-fire-code-stye-cop/post/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&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 &amp;quot;wrong&amp;quot; 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&#39;t possibly help with &amp;quot;code style&amp;quot; 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;Another argument I&#39;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&#39;s &lt;code&gt;ObjectOutputStream&lt;/code&gt;.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://autonomousapps.com/blog/acab-fire-code-stye-cop/post/#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&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;To conclude this section, I will say that, all else being equal, consistent code style is Good:tm:.
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 class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://autonomousapps.com/blog/acab-fire-code-stye-cop/post/#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h2 id=&quot;arguments-against-enforcement-of-consistent-code-style&quot;&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&#39;t want
consistent code style, we want more and better tests, and stricter linting for issues that can&#39;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&#39;t made a strong enough case. In
a world where managers are telling me to do &amp;quot;less with less,&amp;quot; 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&#39;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&#39;s worse, they may change their rules in minor or patch releases, breaking automated dependency updates
because they&#39;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&#39;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 &amp;quot;appeasing code style cop&amp;quot; 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;strong&gt;Pre-compile&lt;/strong&gt;. Using Gradle, simply add a configuration block like this:&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://autonomousapps.com/blog/acab-fire-code-stye-cop/post/#fn4&quot; id=&quot;fnref4&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;tasks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;named&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;kotlinCompile&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; t &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
  t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;dependsOn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;fixStyle&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But that&#39;s not great, because if there&#39;s a compilation error, then the &lt;nobr&gt;&lt;code&gt;fixStyle&lt;/code&gt;&lt;/nobr&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;&lt;strong&gt;Post-compile&lt;/strong&gt;. Using Gradle, simply add a configuration block like this:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;tasks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;named&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;kotlinCompile&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; t &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
  t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;finalizedBy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;fixStyle&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Turns out that&#39;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&#39;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&#39;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&#39;t like breaking your code.&lt;/p&gt;
&lt;p&gt;Both of these approaches 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 &amp;quot;wasting time&amp;quot; when you just want to run your code.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pre-commit&lt;/strong&gt;. 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;strong&gt;Pre-commit with Gradle task&lt;/strong&gt;. This is going to make your &lt;code&gt;git commit&lt;/code&gt; much slower, because Gradle is slow.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://autonomousapps.com/blog/acab-fire-code-stye-cop/post/#fn5&quot; id=&quot;fnref5&quot;&gt;[5]&lt;/a&gt;&lt;/sup&gt;
That&#39;s annoying.&lt;/p&gt;
&lt;p&gt;b. &lt;strong&gt;Pre-commit with CLI tool&lt;/strong&gt;. This can be much faster, since you won&#39;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;&lt;strong&gt;Pre-push&lt;/strong&gt;. 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&#39;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;nobr&gt;&lt;code&gt;git commit -am &amp;quot;Appease code formatter.&amp;quot;&lt;/code&gt;&lt;/nobr&gt; Not the worst thing in the world.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;grinding-my-axe&quot;&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;/p&gt;
&lt;pre class=&quot;language-kotlin&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@get:PathSensitive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PathSensitivity&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;RELATIVE&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation builtin&quot;&gt;@get:InputFiles&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; buildFiles&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SetProperty&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;File&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;after&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@get:PathSensitive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PathSensitivity&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;RELATIVE&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token annotation builtin&quot;&gt;@get:InputFiles&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; buildFiles&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SetProperty&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;File&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;pre class=&quot;language-kotlin&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token function&quot;&gt;testCases&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flatMap&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; testCase &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;gradleVersions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; gradleVersion &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;arrayOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;testCase&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; gradleVersion&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;after&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token function&quot;&gt;testCases&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flatMap&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; testCase &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;gradleVersions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; gradleVersion &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;arrayOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;testCase&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; gradleVersion&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&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&#39;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;/p&gt;
&lt;pre class=&quot;language-kotlin&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;veryLongFunctionThatReturnsABooleanYeeeeeaahhhhhhhhhhhhhhhh&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;veryLongFunctionThatReturnsABooleanYeeeeeaahhhhhhhhhhhhhhhh&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// do something&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;after&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;veryLongFunctionThatReturnsABooleanYeeeeeaahhhhhhhhhhhhhhhh&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;veryLongFunctionThatReturnsABooleanYeeeeeaahhhhhhhhhhhhhhhh&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// do something&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This one really annoys me. In the before-case, while I&#39;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 id=&quot;axe-grinding-in-the-community&quot;&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=&quot;https://cosocial.ca/@jessewilson/113207251226884479&quot;&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&#39;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;/p&gt;
&lt;pre class=&quot;language-kotlin&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; v &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; 0xffL &lt;span class=&quot;token operator&quot;&gt;shl&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;56&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; 0xffL &lt;span class=&quot;token operator&quot;&gt;shl&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;48&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; 0xffL &lt;span class=&quot;token operator&quot;&gt;shl&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; 0xffL &lt;span class=&quot;token operator&quot;&gt;shl&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; 0xffL &lt;span class=&quot;token operator&quot;&gt;shl&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;24&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; 0xffL &lt;span class=&quot;token operator&quot;&gt;shl&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; 0xffL &lt;span class=&quot;token operator&quot;&gt;shl&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; 0xffL&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;after&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; v &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt;
    0xffL &lt;span class=&quot;token operator&quot;&gt;shl&lt;/span&gt;
    &lt;span class=&quot;token number&quot;&gt;56&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;or&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; 0xffL &lt;span class=&quot;token operator&quot;&gt;shl&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;48&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;or&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; 0xffL &lt;span class=&quot;token operator&quot;&gt;shl&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;or&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; 0xffL &lt;span class=&quot;token operator&quot;&gt;shl&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;or&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; 0xffL &lt;span class=&quot;token operator&quot;&gt;shl&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;24&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;or&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; 0xffL &lt;span class=&quot;token operator&quot;&gt;shl&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;or&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; 0xffL &lt;span class=&quot;token operator&quot;&gt;shl&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;or&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; 0xffL&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&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=&quot;https://mastodon.jakewharton.com/@jw/113209743648483053&quot;&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&#39;m not
sure if I didn&#39;t just break this code!).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;before&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; v &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0L&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; 0xffL &lt;span class=&quot;token operator&quot;&gt;shl&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;56&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; 0xffL &lt;span class=&quot;token operator&quot;&gt;shl&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;48&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; 0xffL &lt;span class=&quot;token operator&quot;&gt;shl&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; 0xffL &lt;span class=&quot;token operator&quot;&gt;shl&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; 0xffL &lt;span class=&quot;token operator&quot;&gt;shl&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;24&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; 0xffL &lt;span class=&quot;token operator&quot;&gt;shl&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; 0xffL &lt;span class=&quot;token operator&quot;&gt;shl&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; 0xffL&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;after&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; v &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0L&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;or&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; 0xffL &lt;span class=&quot;token operator&quot;&gt;shl&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;56&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;or&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; 0xffL &lt;span class=&quot;token operator&quot;&gt;shl&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;48&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;or&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; 0xffL &lt;span class=&quot;token operator&quot;&gt;shl&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;or&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; 0xffL &lt;span class=&quot;token operator&quot;&gt;shl&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;or&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; 0xffL &lt;span class=&quot;token operator&quot;&gt;shl&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;24&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;or&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; 0xffL &lt;span class=&quot;token operator&quot;&gt;shl&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;or&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; 0xffL &lt;span class=&quot;token operator&quot;&gt;shl&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;or&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; 0xffL&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is, admittedly, better. However, it&#39;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&#39;ve just had to adjust my behavior to appease a tool I don&#39;t like in the first place. It&#39;s
producing uglier code that&#39;s harder to read and maintain, and now it&#39;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 id=&quot;summing-up&quot;&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&#39;ll just link back
to this post. Thanks for reading! Looking forward to not reading your contrary arguments ❤️&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot;&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&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;a href=&quot;https://autonomousapps.com/blog/acab-fire-code-stye-cop/post/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://cosocial.ca/@jessewilson/113207468657921369&quot;&gt;Described as&lt;/a&gt; &amp;quot;the JVM equivalent of contracting a minor illness&amp;quot;. &lt;a href=&quot;https://autonomousapps.com/blog/acab-fire-code-stye-cop/post/#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;See also &lt;a href=&quot;https://stackoverflow.com/questions/1325374/research-into-advantages-of-having-a-standard-coding-style&quot;&gt;Research into Advantages of Having a Standard Coding Style&lt;/a&gt; &lt;a href=&quot;https://autonomousapps.com/blog/acab-fire-code-stye-cop/post/#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn4&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;There is no task (that I&#39;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&#39;t care about). &lt;a href=&quot;https://autonomousapps.com/blog/acab-fire-code-stye-cop/post/#fnref4&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn5&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;I am obligated to say that it&#39;s Gradle&#39;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;a href=&quot;https://autonomousapps.com/blog/acab-fire-code-stye-cop/post/#fnref5&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  <entry>
    <title>Rewriting Chess.com&#39;s Android App</title>
    <link href="https://autonomousapps.com/blog/chess-dot-com/post/" />
    <updated>2018-04-06T00:00:00Z</updated>
    <id>https://autonomousapps.com/blog/chess-dot-com/post/</id>
    <content type="html">&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://autonomousapps.com/blog/chess-dot-com/post/81ETVE0pdi-568.avif 568w&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://autonomousapps.com/blog/chess-dot-com/post/81ETVE0pdi-568.webp 568w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://autonomousapps.com/blog/chess-dot-com/post/81ETVE0pdi-568.png&quot; alt=&quot;Chess.com on Android&quot; width=&quot;568&quot; height=&quot;320&quot;&gt;&lt;/picture&gt;
&lt;p&gt;Chess.com&#39;s Android app, which is used by about three hundred thousand people daily, and a million monthly, and who speak over 69 languages, is getting a rewrite!&lt;/p&gt;
&lt;p&gt;Now, you may well ask &amp;quot;but your rating on &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.chess&quot;&gt;Google Play&lt;/a&gt; is 4.4 stars, your crash-free rate is 99.97%, and you&#39;re profitable -- why go through all that effort?&amp;quot; First of all, thank you, it&#39;s good to be appreciated. Second of all, you would not be surprised to know that our CEO, Erik Allebest, had the same questions. Here&#39;s what I told him:&lt;/p&gt;
&lt;p&gt;We are currently planning a radical redesign of the app (oh, by the way, we&#39;re planning a radical redesign and you should BE EXCITED, because it&#39;s great), and after months of hovering my finger over the &amp;quot;send&amp;quot; button on an email I spent way too much time on, I finally concluded that it would simply be impossible to get there incrementally. Chess.com has big plans for its mobile apps (Android and iOS), and the Android team determined that the best, safest way to get there was to start from scratch.&lt;/p&gt;
&lt;p&gt;The current app works really well, but it definitely has its quirks (&amp;quot;that&#39;s not a bug, it&#39;s a feature!&amp;quot; -- you know), and it&#39;s sometimes a challenge to add new features. As a company, we always want to be using the latest stable technology and, to be frank, the existing app had its first commit to version control in January of 2011! I&#39;m not actually sure when the first line of code was written, but the app was compatible with Android 2 (or was it 1? No one remembers). Android 9 is coming out this year. I think it&#39;s safe to say that a project designed originally for Android 2 can&#39;t be considered the &amp;quot;latest stable technology.&amp;quot;&lt;/p&gt;
&lt;p&gt;Let&#39;s all take a moment to pay our respects, however, to a mobile app that has truly stood the test of time. We haven&#39;t always been perfect, but we&#39;ve always been committed to improving the experience for our players, offering them new features, and giving them what is arguably the pre-eminent live chess experience on mobile.&lt;/p&gt;
&lt;p&gt;Now with that out of the way, what will the future look like? It&#39;s a moving target, but here are a few things I know for certain:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The new app will be completely redesigned, and it will be gorgeous. &lt;a href=&quot;https://material.io/guidelines/&quot;&gt;Material design&lt;/a&gt;, slick animations and transitions, faster, and generally just a delight to use.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The new app will have a minimum required Android version of 5.0 / Marshmallow (minSdk 21 if you&#39;re an Android dev). We project that this means 5% of our existing base of players will not be able to upgrade on their current devices, and we&#39;re really sorry about that. For the other 95%, though, this will mean a smoother experience using better, more feature-rich technology. And it also means we&#39;ll get the new version done faster, because we won&#39;t have to work as hard to support older versions of Android.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The new app will be written primarily in the &lt;a href=&quot;https://kotlinlang.org/&quot;&gt;Kotlin&lt;/a&gt; programming language, instead of Java. I admit I was skeptical about Kotlin for a long time (I prefer stability above all else), but when Google &lt;a href=&quot;https://youtu.be/fPzxfeDJDzY&quot;&gt;announced support&lt;/a&gt; for the language as a primary language alongside Java, I had to jump on board. Why? Kotlin makes whole classes of bugs simply go away. Kotlin is way less verbose than Java, meaning my team is more productive. Kotlin has extension functions, meaning I can extend the API of some class with missing features. Kotlin makes immutability super-easy -- see above point about doing away with whole classes of bugs. I could go on -- and I will, in a future post.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The new app will have proper dependency injection with &lt;a href=&quot;https://google.github.io/dagger/&quot;&gt;Dagger2&lt;/a&gt; and its &lt;code&gt;dagger.android&lt;/code&gt; package right from the get-go, rather than jamming it in with a crowbar 10 years after the fact. What does this mean? It means improved testability (and therefore stability), better memory management, and a clear separation of concerns between object creation and object use. I will be writing a much more technical post about this later -- stay tuned!&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To reiterate the first point, what we&#39;re really focused on here is a better experience for our players. Even the most esoteric technical point is about improving that good ol&#39; UX. I hope you&#39;re as excited as I am, and I welcome you to follow along with the Chess.com Android team&#39;s adventures, as we rewrite a two hundred thousand lines-of-code app for the modern era! Thanks.&lt;/p&gt;
</content>
  </entry>
</feed>