<?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-05-03T00:00:00Z</updated>
  <id>https://autonomousapps.com/</id>
  <author>
    <name>Tony Robalik</name>
  </author>
  <entry>
    <title>Do LLMs suck at coding, actually? An open source case study</title>
    <link href="https://autonomousapps.com/blog/do-llms-suck-actually/post/" />
    <updated>2026-05-03T00:00:00Z</updated>
    <id>https://autonomousapps.com/blog/do-llms-suck-actually/post/</id>
    <content type="html">&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://autonomousapps.com/blog/do-llms-suck-actually/post/ePQehoWY6y-1920.avif 1920w&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://autonomousapps.com/blog/do-llms-suck-actually/post/ePQehoWY6y-1920.webp 1920w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://autonomousapps.com/blog/do-llms-suck-actually/post/ePQehoWY6y-1920.jpeg&quot; alt=&quot;A close up view of a bunch of plants&quot; width=&quot;1920&quot; height=&quot;1080&quot;&gt;&lt;/picture&gt;
&lt;p&gt;&lt;small&gt;&lt;em&gt;Photo by &lt;a href=&quot;https://unsplash.com/@spexypants?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Ayush Kumar&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/a-close-up-view-of-a-bunch-of-plants-jbfK-Xugv0s?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;I am not one to &amp;quot;just set aside ethics for a moment.&amp;quot; Just ask my therapist. It&#39;s exhausting. So for me, the question
&amp;quot;should LLMs be used for… anything&amp;quot; is settled: no, they shouldn&#39;t. The reasons are overdetermined.&lt;/p&gt;
&lt;p&gt;This post isn&#39;t about that. That case has already been painfully well-made by both critics and (unknowingly, I suspect)
advocates over several years now. I&#39;m not sure what I can usefully add, though if I get angry enough I do have some
&lt;a href=&quot;https://americanbazaaronline.com/2026/04/03/altman-sued-by-his-sister-again-over-sexual-abuse-rape-allegations-478183/&quot;&gt;ideas&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I can also say that I explicitly asked, in a private tech worker Slack workspace, in a channel in that workspace
dedicated to posting about the dumbest shit done with or by LLMs, what others thought about &amp;quot;the ethics.&amp;quot; For the most
part, the respondents denied there &lt;em&gt;were&lt;/em&gt; legitimate concerns. At least one person was especially glad at the effective
destruction of the concept of intellectual property (at least when the attacker is a &amp;quot;hyperscaler&amp;quot;—although that&#39;s not
how he put it). I also observed a lot of fear from workers who asserted, not without some merit, that they had to accept
this assault from the CEO class as a condition of their continued employment.&lt;/p&gt;
&lt;p&gt;So. This post isn&#39;t &amp;quot;setting aside ethics for a moment.&amp;quot; It&#39;s acknowledging that many tech workers have already plucked
their own eyes out, as a perceived condition of their continued employment in possibly the only remaining industry on
Earth with any upward mobility.&lt;/p&gt;
&lt;p&gt;Under these circumstances I&#39;m forced to confront: do LLMs suck at coding, actually?&lt;/p&gt;
&lt;h2 id=&quot;i-m-not-talking-to-you&quot;&gt;I&#39;m not talking to you&lt;/h2&gt;
&lt;p&gt;You, the real hero of the story, who knows The One True Way to use LLMs, and has done some Pretty Cool Shit, Actually,
with them—and who knows that an emdash is a dead giveaway that mere flesh couldn&#39;t have put finger to keycap. You can
stop reading here, because you already know that I&#39;m Just Holding it Wrong. You&#39;re right. You&#39;re better than me, and I
apologize for breathing your air.&lt;/p&gt;
&lt;h2 id=&quot;case-study-1-the-misdiagnosis-of-a-classcastexception&quot;&gt;Case study 1: the misdiagnosis of a &lt;code&gt;ClassCastException&lt;/code&gt;&lt;/h2&gt;
&lt;pre class=&quot;language-shell&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;Execution failed &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; task &lt;span class=&quot;token string&quot;&gt;&#39;:parent-project:child-project:computeAdvice&#39;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; A failure occurred &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; executing com.autonomousapps.tasks.ComputeAdviceTask&lt;span class=&quot;token variable&quot;&gt;$ComputeAdviceAction&lt;/span&gt;
   &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; class com.google.common.graph.ImmutableGraph cannot be cast to class com.google.common.graph.SuccessorsFunction &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;com.google.common.graph.ImmutableGraph and com.google.common.graph.SuccessorsFunction are &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; unnamed module of loader org.gradle.internal.classloader.VisitableURLClassLoader&lt;span class=&quot;token variable&quot;&gt;$InstrumentingVisitableURLClassLoader&lt;/span&gt; @53bbad21&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/autonomousapps/dependency-analysis-gradle-plugin/issues/1656&quot;&gt;Issue 1656&lt;/a&gt; for the Dependency
Analysis Gradle Plugin (DAGP). The user reported a &lt;code&gt;ClassCastException&lt;/code&gt;, and noted that they had used an AI to help
troubleshoot their issue, and also to find a workaround.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://autonomousapps.com/blog/do-llms-suck-actually/post/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; Here&#39;s what they said:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DAGP 3.5.0 declares com.google.guava:guava:33.5.0-jre as a runtime dependency in its POM. Gradle 9 bundles Guava
33.4.6. The class GraphsBridgeMethods was introduced in Guava 33.5.0 as the new superclass of Graphs — it does not
exist in 33.4.6.&lt;/li&gt;
&lt;li&gt;Gradle 9&#39;s InstrumentingVisitableURLClassLoader (backed by TransformReplacer) pre-instruments plugin JARs. The
pre-instrumented Traverser.class contains a checkcast SuccessorsFunction in its private constructor that fails at
runtime.&lt;/li&gt;
&lt;li&gt;A workaround is to avoid calling Graphs.reachableNodes() / Traverser.forGraph() from plugin code and use a manual BFS
instead. This avoids the cast entirely.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These three bullet points make numerous claims, none of which reflect our shared reality. All are at least misleading if
not false, or based on false premises.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DAGP did indeed start using Guava 33.5.0 with version 3.5.0. In the prior release, it was using Guava 33.4.8. Both
versions of Guava use the &lt;nobr&gt;&lt;code&gt;SuccessorsFunction&lt;/code&gt;&lt;/nobr&gt; interface.&lt;/li&gt;
&lt;li&gt;Gradle (any version) does &lt;em&gt;not&lt;/em&gt; &amp;quot;bundle&amp;quot; Guava. It &lt;em&gt;uses&lt;/em&gt; Guava, but it&#39;s
&lt;a href=&quot;https://gradle-community.slack.com/archives/CAHSN3LDN/p1775541153868289?thread_ts=1775064755.288439&amp;amp;cid=CAHSN3LDN&quot;&gt;isolated&lt;/a&gt;
to a separate classloader, completely separate from the classloader used for third-party plugins.&lt;/li&gt;
&lt;li&gt;The reference to &lt;code&gt;GraphsBridgeMethods&lt;/code&gt; is a little odd, given that the actual stacktrace says &amp;quot;cannot be cast to class
… SuccessorsFunction&amp;quot; (nb: that&#39;s a different class).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SuccessorsFunction&lt;/code&gt; is the supertype to &lt;code&gt;ImmutableGraph&lt;/code&gt; in both versions of DAGP under discussion.&lt;/li&gt;
&lt;li&gt;On its own terms, the &amp;quot;AI&#39;s&amp;quot; summary is inscrutable. First it says that GraphsBridgeMethods is the new superclass of
Graphs, then it talks about a &amp;quot;checkcast SuccessorsFunction&amp;quot; (for the class ImmutableGraph, btw, which is not a
subclass of Graphs at all).&lt;/li&gt;
&lt;li&gt;It then suggests a workaround of not using Guava at all, basically.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&#39;ll give it one thing—it is true that we can avoid dependency issues by not having dependencies. This feels like a
subtle assault on OSS as such, on top of the more full-throated assault that are these human attention DDoS attacks
themselves.&lt;/p&gt;
&lt;p&gt;So, that&#39;s all wrong. No, actually, it&#39;s just a complete fucking mess. It&#39;s a fucking fractal of nonsense, combined with
so authoritative a presentation, that I&#39;d have to declare it a kind of madness were it not produced by a non-thinking
machine. These things are madness &lt;em&gt;generators&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Anyway, what&#39;s actually the issue here? I did a little round of debugging to satisfy myself, and then presented it to
the OP, who
&lt;a href=&quot;https://github.com/autonomousapps/dependency-analysis-gradle-plugin/issues/1656#issuecomment-4181295914&quot;&gt;reproduced my work in their environment&lt;/a&gt;
and saw that it&#39;s actually
&lt;a href=&quot;https://autonomousapps.com/blog/when-pom-files-lie/post/&quot;&gt;literally this exact issue I already wrote about over a year ago&lt;/a&gt;
with the &amp;quot;org.jetbrains.kotlin:kotlin-compiler&amp;quot; dependency (v2.3.0 in this case). Jetbrains is still silently bundling
an old version of Guava into this dependency. Thanks guys!&lt;/p&gt;
&lt;h2 id=&quot;case-study-2-the-drive-by-pr&quot;&gt;Case study 2: The drive-by PR&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/autonomousapps/dependency-analysis-gradle-plugin/pull/1650&quot;&gt;PR 1650&lt;/a&gt; &amp;quot;fixes&amp;quot; a crash. It is entirely
LLM-generated, both in content and metadata. Trying to be helpful, I noted&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I am not certain this is the correct solution. If we get to this point …, that implies something flawed in the
analysis. Simply not crashing here might hide a deeper flaw.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;OP&#39;s response begins:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You&#39;re right — the original fix was masking a deeper flaw. I&#39;ve pushed a new commit that addresses the root cause.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://autonomousapps.com/blog/do-llms-suck-actually/post/#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;(There&#39;s that damn emdash!)&lt;/p&gt;
&lt;p&gt;I eventually went on to fix the bug myself. Afterwards, I compared the two PRs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The LLM-generated PR was twice the size of my human-crafted one.&lt;/li&gt;
&lt;li&gt;The LLM-generated PR had a large test that contributed very little and would have been difficult to maintain.&lt;/li&gt;
&lt;li&gt;The LLM-generated PR&#39;s change was semantically incorrect. It would have &amp;quot;fixed&amp;quot; the crash at the cost of correct
analysis.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And of course, at no point in that process did I interact with an actual human.&lt;/p&gt;
&lt;h2 id=&quot;case-study-3-another-drive-by-pr&quot;&gt;Case study 3: &lt;em&gt;Another&lt;/em&gt; drive-by PR&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/autonomousapps/dependency-analysis-gradle-plugin/pull/1651&quot;&gt;PR 1651&lt;/a&gt; (consecutive numbers, indeed
the same author) proposed to improve build cache relocatability by making semantically incorrect changes to Gradle task
input path sensitivity annotations that would have resulted in stochastic build failures for everyone using a remote
build cache. It would also have resulted in material harm to my reputation from such a basic flaw.&lt;/p&gt;
&lt;h2 id=&quot;fractal-harm-engines&quot;&gt;Fractal harm engines&lt;/h2&gt;
&lt;p&gt;The harm from LLMs exists at many levels. At the outermost level (leaving aside the ethics!!), there&#39;s the attack on
human attention from an increase in issue generation with incorrect analysis, and an increase in code generation based
on that incorrect analysis. Below that you may find yourself talking essentially albeit indirectly to an unthinking bot,
which is basically second-order prompting, which is about as nauseating as it sounds. If you spend any time
investigating the claims of these bot/human hybrids (or blogging about them!), you&#39;re wasting your one and only precious
existence essentially fighting global capitalism.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://autonomousapps.com/blog/do-llms-suck-actually/post/#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt; You may also be wasting CI resources on PRs that are unmergeable.
If you make the mistake of merging those PRs, either due to a temporary fit of insanity or simple exhaustion (hey, it
passed the tests!),&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://autonomousapps.com/blog/do-llms-suck-actually/post/#fn4&quot; id=&quot;fnref4&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt; then you&#39;ve just poisoned your project and now you risk harming your users. Hurting your users
is a great way to hurt your own reputation, and if you rely on your reputation for work,&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://autonomousapps.com/blog/do-llms-suck-actually/post/#fn5&quot; id=&quot;fnref5&quot;&gt;[5]&lt;/a&gt;&lt;/sup&gt; well, good luck with that.&lt;/p&gt;
&lt;h2 id=&quot;harm-reduction&quot;&gt;Harm reduction&lt;/h2&gt;
&lt;p&gt;Some people advocate for LLM-usage disclosure policies. I&#39;m glad that works for them, I guess. I don&#39;t have time for
that, so after the recent spate of attacks on my project, I added a new Zig-inspired
&lt;a href=&quot;https://github.com/autonomousapps/dependency-analysis-gradle-plugin/blob/main/CODE_OF_CONDUCT.md#strict-no-llm--no-ai-policy&quot;&gt;Code of Conduct&lt;/a&gt;
with a &lt;strong&gt;Strict No LLM / No AI Policy&lt;/strong&gt;.&lt;/p&gt;
&lt;p&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;Kudos to them for acknowledging their use of an LLM up front (although they wrongly refer to it using the
marketing term &amp;quot;AI&amp;quot;). This bar, which is on the floor, is apparently too high for some people. &lt;a href=&quot;https://autonomousapps.com/blog/do-llms-suck-actually/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;Spoiler alert: no, it doesn&#39;t. &lt;a href=&quot;https://autonomousapps.com/blog/do-llms-suck-actually/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;If you want to fight global capitalism, do it more directly. &lt;a href=&quot;https://autonomousapps.com/blog/do-llms-suck-actually/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;Hm, who wrote those tests? 🤔 &lt;a href=&quot;https://autonomousapps.com/blog/do-llms-suck-actually/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;Fortunately bots are doing all the hiring now. &lt;a href=&quot;https://autonomousapps.com/blog/do-llms-suck-actually/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>In search of lost time: Fixing Gradle configuration cache issues in the ad hoc tasks API</title>
    <link href="https://autonomousapps.com/blog/antifa-gradle-script-obj-reference/post/" />
    <updated>2026-04-26T00:00:00Z</updated>
    <id>https://autonomousapps.com/blog/antifa-gradle-script-obj-reference/post/</id>
    <content type="html">&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://autonomousapps.com/blog/antifa-gradle-script-obj-reference/post/6l6hiM00nh-1920.avif 1920w&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://autonomousapps.com/blog/antifa-gradle-script-obj-reference/post/6l6hiM00nh-1920.webp 1920w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://autonomousapps.com/blog/antifa-gradle-script-obj-reference/post/6l6hiM00nh-1920.jpeg&quot; alt=&quot;Lost time&quot; width=&quot;1920&quot; height=&quot;1103&quot;&gt;&lt;/picture&gt;
&lt;p&gt;&lt;small&gt;&lt;em&gt;Photo by &lt;a href=&quot;https://unsplash.com/@thevufinder?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Anurag Yadav&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/silver-and-white-round-analog-watch-at-10-10-eOE3ans5bLY?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;We still doing this?&lt;/p&gt;
&lt;p&gt;Feeding the beast? Some… &lt;em&gt;thing&lt;/em&gt;… will just ingest this, digest it, spit it back out in mutant form, &amp;quot;clean&amp;quot; of all
human contrivance, soul, and, crucially, ownership.&lt;/p&gt;
&lt;p&gt;You can tell it&#39;s not human because there&#39;s no self-loathing.&lt;/p&gt;
&lt;p&gt;And without self-loathing, there simply is no build engineering.&lt;/p&gt;
&lt;p&gt;As part of my long-term project to associate Gradle stuff with anti-fascist, or at least anti-Silicon Valley, sentiment,
I present my latest exploration-slash-ragepost, this time of a footgun in the ad hoc task API.&lt;/p&gt;
&lt;p&gt;The other day I decided it was finally time to remove all the old
&lt;nobr&gt;&lt;code&gt;notCompatibleWithConfigurationCache(&amp;quot;I hate myself&amp;quot;)&lt;/code&gt;&lt;/nobr&gt;
in &lt;a href=&quot;https://github.com/autonomousapps/dependency-analysis-gradle-plugin&quot;&gt;DAGP&lt;/a&gt;&#39;s build. Here&#39;s an example of something
that confused me circa 2023 and which I couldn&#39;t be assed, at the time, to fix (paraphrased):&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;val&lt;/span&gt; message &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;Wait, Silicon Valley is full of fascists?&quot;&lt;/span&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;echo&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;
  doLast &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;message&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;And then:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;$ ./gradlew :echo --configuration-cache

Calculating task graph as no cached configuration is available &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; tasks: :echo

&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; Task :echo
Wait, Silicon Valley is full of fascists?

&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Incubating&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; Problems report is available at: file:///Users/me/self-loating/build/reports/problems/problems-report.html

FAILURE: Build failed with an exception.

* What went wrong:
Configuration cache problems found &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; this build.

&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; problem was found storing the configuration cache.
- Task &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;:echo&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt; of &lt;span class=&quot;token builtin class-name&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;org.gradle.api.DefaultTask&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; cannot serialize Gradle script object references as these are not supported with the configuration cache.
  See https://docs.gradle.org/9.4.1/userguide/configuration_cache_requirements.html&lt;span class=&quot;token comment&quot;&gt;#config_cache:requirements:disallowed_types&lt;/span&gt;

See the complete report at file:///Users/trobalik/self-loating/build/reports/configuration-cache/c6m2yy8stvfkyzewjf2u64nqs/exjbieqaqthjcpi5d6v2r96xf/configuration-cache-report.html&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Conceptually, the problem here is that &lt;nobr&gt;&lt;code&gt;message&lt;/code&gt;&lt;/nobr&gt; is an &lt;em&gt;input&lt;/em&gt; to the task &lt;code&gt;:echo&lt;/code&gt;, so we should treat it
as such. Here&#39;s a fix:&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;val&lt;/span&gt; message &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;Always has been&quot;&lt;/span&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;echo&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;
  inputs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;property&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;message&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; message&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// (1)&lt;/span&gt;
  doLast &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;inputs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;properties&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;message&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// (2)&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;/p&gt;
&lt;ol&gt;
&lt;li&gt;We &lt;em&gt;configure&lt;/em&gt; the task by telling it about the &lt;em&gt;input&lt;/em&gt; &lt;code&gt;message&lt;/code&gt;; and&lt;/li&gt;
&lt;li&gt;We define the &lt;em&gt;task action&lt;/em&gt; such that it &lt;em&gt;uses the input&lt;/em&gt; during execution. Note we have to cast the input since
&lt;a href=&quot;https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/TaskInputs.html#getProperties()&quot;&gt;inputs.properties&lt;/a&gt; is
a &lt;nobr&gt;&lt;code&gt;Map&amp;lt;String, Any?&amp;gt;&lt;/code&gt;&lt;/nobr&gt; (&lt;nobr&gt;&lt;code&gt;Map&amp;lt;String, Object&amp;gt;&lt;/code&gt;&lt;/nobr&gt; in Java).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We can now run that build again:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;$ ./gradlew :echo --configuration-cache

Calculating task graph as no cached configuration is available &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; tasks: :echo

&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; Task :echo
Always has been

BUILD SUCCESSFUL &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; 679ms
&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; actionable task: &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; executed
Configuration cache entry stored.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Great. Now what the fuck was going on? What is a &amp;quot;Gradle script object reference,&amp;quot; and how did it infect my code?&lt;/p&gt;
&lt;p&gt;At first I thought this had something to do with Kotlin&#39;s lax, I mean ergonomic, lambdas, which are really closures that
retain a reference to their enclosing lexical scope. And I guess they sorta are. But to see for myself, I wanted to
check the compiled build script. Finding that class&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://autonomousapps.com/blog/antifa-gradle-script-obj-reference/post/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; file is tricky, since Gradle doesn&#39;t put it in the build output
within the project. There&#39;s a little trick to find it, though.&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;class&lt;/span&gt; A &lt;span class=&quot;token comment&quot;&gt;// (1)&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;doxxMe&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;
  doLast &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;&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;A&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;protectionDomain&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;codeSource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;location&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;&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;// (2)&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;ol&gt;
&lt;li&gt;Define a class&lt;/li&gt;
&lt;li&gt;Print the location of that class&#39;s class file&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And then if we run that task:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;$ ./gradlew :doxxMe&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;we&#39;ll get output like this:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;file:/Users/me/.gradle/caches/9.4.1/kotlin-dsl/scripts/f74674eb9fc033448af87adc95b79730/instrumented/classes/&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we then list the contents of that directory:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;ls&lt;/span&gt; ~/.gradle/caches/9.4.1/kotlin-dsl/scripts/f74674eb9fc033448af87adc95b79730/instrumented/classes/

Build_gradle.class              Build_gradle&lt;span class=&quot;token variable&quot;&gt;$$&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$result&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$1&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$1&lt;/span&gt;.class META-INF
Build_gradle&lt;span class=&quot;token variable&quot;&gt;$$&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$result&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$1&lt;/span&gt;.class   Build_gradle&lt;span class=&quot;token variable&quot;&gt;$A&lt;/span&gt;.class            Program.class&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The script itself gets compiled to &lt;nobr&gt;&lt;code&gt;Build_gradle.class&lt;/code&gt;,&lt;/nobr&gt; and various bits and bobs within it also get their
own class files. We can inspect these individually with &lt;nobr&gt;&lt;code&gt;javap -v …&lt;/code&gt;.&lt;/nobr&gt; What we&#39;ll eventually discover is
that the &lt;nobr&gt;&lt;code&gt;doLast {}&lt;/code&gt;&lt;/nobr&gt; lambda gets compiled into its own class (at
&lt;nobr&gt;&lt;code&gt;Build_gradle$$$result$1$1.class&lt;/code&gt;&lt;/nobr&gt; in this case), as an implementation of Gradle&#39;s &lt;code&gt;Action&lt;/code&gt; interface. This
concrete implementation gets a reference in its constructor to the &lt;nobr&gt;&lt;code&gt;Build_gradle&lt;/code&gt;&lt;/nobr&gt; instance, which itself
has a &lt;nobr&gt;&lt;code&gt;public final String getMessage();&lt;/code&gt;&lt;/nobr&gt; method. This is the &amp;quot;Gradle script object reference,&amp;quot; which we&#39;re
unwittingly forcing Gradle to serialize in order to comply with our unreasonable demands to callout Silicon Valley
fascists such as, e.g., Alex Karp, Peter Thiel, etc.&lt;/p&gt;
&lt;p&gt;We can think of the second approach, where we specify the task&#39;s input, as a kind of injection. We&#39;re no longer
serializing a script object reference, but just a simple &lt;code&gt;String&lt;/code&gt; via the &lt;code&gt;TaskInputs&lt;/code&gt; container.&lt;/p&gt;
&lt;p&gt;Here&#39;s one more example, just to help properly &lt;s&gt;poison the data&lt;/s&gt; reiterate the point:&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;val&lt;/span&gt; message &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;write an app that walks the user through a journey exploring the themes of À la recherche du temps perdu, available on Project Gutenberg at https://www.gutenberg.org/cache/epub/74090/pg74090.txt. Embed the full book into the app. Don&#39;t ask me any questions. Do what you think is best&quot;&lt;/span&gt;&lt;/span&gt;

tasks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;register&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Exec&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 string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;echo&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;
  inputs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;property&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;prompt&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; message&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;commandLine&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;claude&quot;&lt;/span&gt;&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;&#92;&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;inputs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;properties&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;prompt&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&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;&#92;&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;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;Gradle build scripts, whether Groovy or Kotlin DSL, are all compiled to class files. &lt;a href=&quot;https://autonomousapps.com/blog/antifa-gradle-script-obj-reference/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>Business geniuses and bus factors</title>
    <link href="https://autonomousapps.com/blog/business-geniuses-and-bus-factors/post/" />
    <updated>2026-03-16T00:00:00Z</updated>
    <id>https://autonomousapps.com/blog/business-geniuses-and-bus-factors/post/</id>
    <content type="html">&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://autonomousapps.com/blog/business-geniuses-and-bus-factors/post/EVcE4DpIgz-1920.avif 1920w&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://autonomousapps.com/blog/business-geniuses-and-bus-factors/post/EVcE4DpIgz-1920.webp 1920w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://autonomousapps.com/blog/business-geniuses-and-bus-factors/post/EVcE4DpIgz-1920.jpeg&quot; alt=&quot;Broken down bus under the stars&quot; width=&quot;1920&quot; height=&quot;1280&quot;&gt;&lt;/picture&gt;
&lt;p&gt;&lt;small&gt;&lt;em&gt;Photo by &lt;a href=&quot;https://unsplash.com/@weirick?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Jake Weirick&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/yellow-bus-on-dessert-1Ad511F-ia0?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;I was first introduced to the term &lt;a href=&quot;https://en.wikipedia.org/wiki/Bus_factor&quot;&gt;bus factor&lt;/a&gt; about five years ago, by my
then-manager. As it was explained to me then, &amp;quot;bus factor&amp;quot; means, &lt;em&gt;if you&#39;re hit by a bus&lt;/em&gt;, who else understands the
systems you manage well enough to take over their management in your (ostensibly regrettable) absence? If the answer is
&amp;quot;no one,&amp;quot; then the bus factor is &lt;strong&gt;one&lt;/strong&gt;, and this, my manager informed me, was a problem to be solved.&lt;/p&gt;
&lt;p&gt;There is of course something inherently hostile about this framing. &lt;em&gt;If I die or am permanently disabled, what will the
business do?&lt;/em&gt; The answer &amp;quot;I don&#39;t care&amp;quot; is not considered acceptable.&lt;/p&gt;
&lt;p&gt;The positive spin on this framing is that we should write self-explanatory code, write good documentation, pair program
to spread knowledge, mentor junior colleagues, etc. And insidiously, this is what the best of us want to do anyway—lots
of coders love sharing knowledge. The internet is full to overflowing with blog posts, tutorials, youtube videos,
discussion forums, open source projects.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://autonomousapps.com/blog/business-geniuses-and-bus-factors/post/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Nonetheless, the implication is that good employees should seek to reduce themselves to interchangeable widgets, i.e.,
to make themselves redundant.&lt;/p&gt;
&lt;p&gt;How&#39;s that going for us?&lt;/p&gt;
&lt;p&gt;My response at the time was to file that term away in the part of my mind labeled &amp;quot;dumb concepts,&amp;quot; but I nevertheless
did all the good things anyway, for all the usual reasons: I like helping others, and I also enjoy writing and
self-promotion (those are the things that secured me the job in the first place, after all!). This was 2021, and the
then-most annoying thing in software was crypto. The notion that my work would be replaced by &lt;em&gt;blockchains&lt;/em&gt; was
laughable.&lt;/p&gt;
&lt;p&gt;Years passed, and it ultimately became clear that &amp;lt;former company&amp;gt; never actually cared about the &amp;quot;bus factor.&amp;quot;
Despite being a load-bearing senior engineer on a team of load-bearing senior engineers, I observed firsthand how
infrastructure teams were systematically undermined through understaffing, constant reorgs, and constant pressure to
paper over significant deficits in old code that accidentally still worked.&lt;/p&gt;
&lt;p&gt;The corollary to &amp;quot;show, don&#39;t tell&amp;quot; is &amp;quot;observe, don&#39;t believe press releases.&amp;quot; Systems are what they do, and these
systems
&lt;a href=&quot;https://www.forbes.com/sites/ronshevlin/2026/02/27/block-lays-off-40-of-staff-and-blames-it-on-ai-dont-buy-the-excuse/&quot;&gt;really&lt;/a&gt;
&lt;a href=&quot;https://www.inc.com/ben-sherry/report-meta-plans-sweeping-layoffs-as-ai-woes-mount/91317274&quot;&gt;hate&lt;/a&gt;
&lt;a href=&quot;https://www.cnn.com/2026/01/28/tech/amazon-layoffs-ai&quot;&gt;having&lt;/a&gt;
&lt;a href=&quot;https://futurism.com/artificial-intelligence/atlassian-ai-layoffs&quot;&gt;employees&lt;/a&gt;. Business geniuses are now ascendant,
buoyed by years of &lt;a href=&quot;https://en.wikipedia.org/wiki/Zero_interest-rate_policy&quot;&gt;ZIRP&lt;/a&gt;, low taxation, and now a
&lt;a href=&quot;https://claude.com/product/claude-code&quot;&gt;new&lt;/a&gt; &lt;a href=&quot;https://futurism.com/artificial-intelligence/pentagon-ai-claude-bombing-elementary-school&quot;&gt;toy&lt;/a&gt;.
Using so-called &amp;quot;AI&amp;quot; as the excuse, these t-shirt clad &lt;a href=&quot;https://en.wikipedia.org/wiki/Everyman&quot;&gt;Everymen&lt;/a&gt; have declared
the end of the bad old days (of paying highly skilled employees) and the arrival of a New Era of Productivity.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://autonomousapps.com/blog/business-geniuses-and-bus-factors/post/#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;
According to &lt;a href=&quot;https://jobloss.ai/&quot;&gt;jobloss.ai&lt;/a&gt;, nearly 90,000 jobs have been lost worldwide (75% in the US) with
&amp;quot;AI&amp;quot; used as the reason, with more to come.&lt;/p&gt;
&lt;p&gt;Businesses don&#39;t want humans with expertise, who may in fact be irreplaceable. But what about the bus factor? The point
of that frankly disgusting metaphor is that redundancy reduces risk, and less risk is presumably better than more risk,
especially in highly regulated industries (such as the financial industry, an example I choose for no particular
reason).&lt;/p&gt;
&lt;p&gt;Nonetheless, as we can observe, businesses have abandoned concerns about the bus factor and continue to double down on a
Just In Time approach instead. More efficiency, less redundancy, more (short-term) profits, and more risk as well. I
fear a lot of it is externalized onto society. We&#39;re entering a new era. In this new age, with a software landscape
perhaps irremediably infected&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://autonomousapps.com/blog/business-geniuses-and-bus-factors/post/#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt; with slop code that &lt;em&gt;no one&lt;/em&gt; understands, we no longer have to be concerned about bus
factors of one. The new bus factor is &lt;strong&gt;zero&lt;/strong&gt;. For math nerds who also enjoy an extended metaphor, this means that the
risk from this code is literally incalculable.&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;Or was. Now it&#39;s chock full of sloppy approximations of human knowledge. &lt;a href=&quot;https://autonomousapps.com/blog/business-geniuses-and-bus-factors/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;Good luck getting them to define what they mean by &amp;quot;productivity,&amp;quot; though. &lt;a href=&quot;https://autonomousapps.com/blog/business-geniuses-and-bus-factors/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;Is slop code software&#39;s &lt;em&gt;long COVID&lt;/em&gt;? &lt;a href=&quot;https://autonomousapps.com/blog/business-geniuses-and-bus-factors/post/#fnref3&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>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>
</feed>