<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.8.7">Jekyll</generator><link href="https://oehme.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://oehme.github.io/" rel="alternate" type="text/html" /><updated>2020-07-08T15:30:18+00:00</updated><id>https://oehme.github.io/feed.xml</id><title type="html">Mnmlst Dvlpr</title><subtitle>Minimalist, vegan, independent software developer.</subtitle><author><name>Stefan Oehme</name></author><entry><title type="html">Stop rerunning your tests</title><link href="https://oehme.github.io/stop-rerunning-tests" rel="alternate" type="text/html" title="Stop rerunning your tests" /><published>2018-09-08T00:00:00+00:00</published><updated>2018-09-08T00:00:00+00:00</updated><id>https://oehme.github.io/stop-rerunning-tests</id><content type="html" xml:base="https://oehme.github.io/stop-rerunning-tests">&lt;p&gt;Tests are usually the longest running operation in your development process.
Running them unnecessarily is the ultimate time waster.
Gradle helps you avoid this cost with its &lt;a href=&quot;https://docs.gradle.org/current/userguide/build_cache.html&quot;&gt;build cache&lt;/a&gt; 
and &lt;a href=&quot;https://docs.gradle.org/current/userguide/more_about_tasks.html#sec:up_to_date_checks&quot;&gt;incremental build&lt;/a&gt; features.
It knows when any of your test inputs, like your code, your dependencies
or system properties, have changed. If everything stays the same, Gradle will
skip the test run, saving you a lot of time.&lt;/p&gt;

&lt;p&gt;So you can imagine my desparation when I see snippets like this on StackOverflow:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-(groovy)&quot;&gt;tasks.withType(Test) {
    outputs.upToDateWhen { false }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Let’s talk about what this means and why it is a bad idea.&lt;/p&gt;

&lt;h2 id=&quot;communicating-intent&quot;&gt;Communicating intent&lt;/h2&gt;

&lt;p&gt;The above snippet just says “Never reuse this test’s output”. 
But why? Is it because there is some hidden input that Gradle doesn’t know about?
Or is it because the test produces random outputs? The reader can’t tell.&lt;/p&gt;

&lt;h2 id=&quot;deterministic-tests-dont-need-reruns&quot;&gt;Deterministic tests don’t need reruns&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;“Insanity is doing the same thing over and over and expecting different results”&lt;/p&gt;

  &lt;p&gt;- Not Albert Einstein&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The vast majority of your tests should be deterministic, i.e. given the same inputs they should produce the same result.
If this is not the case, your project is in serious trouble. Stop reading this post and start fixing your code!&lt;/p&gt;

&lt;p&gt;Rerunning deterministic tests is a waste of your team’s time.&lt;/p&gt;

&lt;h2 id=&quot;non-deterministic-tests&quot;&gt;Non-deterministic tests&lt;/h2&gt;

&lt;p&gt;There are a few reasons why you might want to rerun &lt;em&gt;some&lt;/em&gt; tests in &lt;em&gt;some&lt;/em&gt; cases even though none of the code changed.
In these cases, you should model the additional input properly. 
Tell Gradle what exactly makes your tests non-deterministic.&lt;/p&gt;

&lt;h3 id=&quot;randomized-tests&quot;&gt;Randomized tests&lt;/h3&gt;

&lt;p&gt;Some tests use randomization to improve the quality of your software.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Random_testing&quot;&gt;Random testing&lt;/a&gt; can be used to ensure that the production code can handle all kinds of inputs,
not just the ones that the developer came up with.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Mutation_testing&quot;&gt;Mutation testing&lt;/a&gt; changes the production code in subtle way (e.g. introducing off-by-one errors)
and checks whether your test suite catches these mistakes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Make this randomization explicit by making the random seed an input to your task:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-(groovy)&quot;&gt;task randomizedTest(type: Test) {
    systemProperty &quot;random.testing.seed&quot;, new Random().nextInt()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This will force Gradle to always rerun that test, because it will always have a different seed.
Even better, you could make the seed user-configurable to locally reproduce bugs that were found on your build server.&lt;/p&gt;

&lt;h3 id=&quot;system-integration-tests&quot;&gt;System integration tests&lt;/h3&gt;

&lt;p&gt;System integration tests verify your application against realistic versions of other applications that are controlled by other teams.
They may fail even if you didn’t change anything, e.g. because another team broke an API.
They also tend to be among the slowest tests in your code base, so you don’t want to rerun them just because you changed some documentation.
A good compromise may be to check integration at least once a day, even if nothing on your side has changed.&lt;/p&gt;

&lt;p&gt;Make this interval part of your test inputs:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-(groovy)&quot;&gt;task systemIntegrationTest(type: Test) {
    inputs.property &quot;integration.date&quot;, LocalDate.now()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can then set up an automated build that runs this test in the morning before everyone starts working and let it push the result to a shared build cache.
When your team comes to work, the test result will be downloaded from the cache and the tests won’t have to rerun for that day.
They can then use the saved time on more productive tasks like fixing bugs or developing new features.&lt;/p&gt;

&lt;h3 id=&quot;flaky-tests&quot;&gt;Flaky tests&lt;/h3&gt;

&lt;p&gt;Sometimes you encounter a bug that only makes a test fail 1 out of 10 times.
In order to analyze the situation, you want Gradle to rerun the test even if it was successful before.
In this case it is reasonable to use the random input approach I showed above.
However, I find it more productive to wrap the flaky test in an endless loop.
This way I can keep the debugger in the IDE running and even make small changes on the fly without having to restart.&lt;/p&gt;

&lt;h2 id=&quot;model-your-tests-properly&quot;&gt;Model your tests properly&lt;/h2&gt;

&lt;p&gt;Properly modeling your requirements is just as important for your build logic as it is for your production code.
Doing it right will make your builds faster and your team more productive.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;java&lt;/code&gt; plugin’s built-in &lt;a href=&quot;https://docs.gradle.org/current/userguide/java_plugin.html#sec:java_test&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test&lt;/code&gt;&lt;/a&gt; task is meant for deterministic and fast unit tests.
It may seem convenient to put all your tests into this one task and rerun them on every build, because a few of them are non-deterministic.
This lazy approach will save you a few minutes of thinking and coding, but the long term costs are huge, slowing everyone on your team down every day.&lt;/p&gt;

&lt;p&gt;Instead, create additional &lt;a href=&quot;https://docs.gradle.org/current/dsl/org.gradle.api.tasks.testing.Test.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Test&lt;/code&gt;&lt;/a&gt; tasks for different types of tests like functional, performance, or randomized tests. For each one of them, consider when it needs to rerun and model that as an input to the task.&lt;/p&gt;

&lt;p&gt;Don’t waste your time.&lt;/p&gt;</content><author><name>Stefan Oehme</name></author><summary type="html">Tests are usually the longest running operation in your development process. Running them unnecessarily is the ultimate time waster. Gradle helps you avoid this cost with its build cache and incremental build features. It knows when any of your test inputs, like your code, your dependencies or system properties, have changed. If everything stays the same, Gradle will skip the test run, saving you a lot of time.</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://oehme.github.io/images/me.png" /><media:content medium="image" url="https://oehme.github.io/images/me.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Performance testing at Gradle</title><link href="https://oehme.github.io/performance-testing-at-gradle" rel="alternate" type="text/html" title="Performance testing at Gradle" /><published>2018-05-10T00:00:00+00:00</published><updated>2018-05-10T00:00:00+00:00</updated><id>https://oehme.github.io/performance-testing-at-gradle</id><content type="html" xml:base="https://oehme.github.io/performance-testing-at-gradle">&lt;p&gt;Performance is one of Gradle’s most important features. This is why we go to great lengths to ensure that Gradle stays fast and improves with every release.
I’m proud of the performance testing infrastructure that we’ve built over the last two years and would like to give you a tour of it.&lt;/p&gt;

&lt;h2 id=&quot;overview&quot;&gt;Overview&lt;/h2&gt;

&lt;p&gt;We have about 150 performance tests in two categories: Regression tests and experiments.&lt;/p&gt;

&lt;p&gt;Regression tests compare the current code against a previous version of Gradle.
They fail as soon as a statistically relevant difference is detected. 
Most regression tests are run on every commit and need to pass before a branch can be merged.&lt;/p&gt;

&lt;p&gt;Experiments compare different ways of using Gradle, e.g. comparing parallel vs. non-parallel builds or measuring the overhead of the build cache. 
They can either have hand-picked thresholds like “the cache overhead should be no more than X percent” or no threshold at all, 
serving only an informational purpose. 
Most experiments are run once a day, as they usually excercise more complex scenarios that take longer to run.&lt;/p&gt;

&lt;p&gt;The tests are distributed over a farm of build agents to achieve an acceptable execution time.&lt;/p&gt;

&lt;p&gt;The test results are stored in a database so we can render historical reports and see both positive and negative trends.
The following graph shows the recent history of how long it takes to run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gradle idea&lt;/code&gt; on a 500-subproject Java build.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/history-idea-large-java-project.png&quot; alt=&quot;performance test history&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Once a week, we run a historical build that compares the current version of Gradle against a wide selection of older versions.
This shows us the bigger picture and allows us to measure how much we’ve improved, not just in the last release, but over the last year or more.&lt;/p&gt;

&lt;h2 id=&quot;scenarios&quot;&gt;Scenarios&lt;/h2&gt;

&lt;p&gt;Every scenario consists of a test project and some tasks to run. 
Some scenarios also do intermediate steps like changing a source file or cleaning the cache.
A typical performance test definition might look like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/performance-scenario-definition.png&quot; alt=&quot;performance test scenario&quot; /&gt;&lt;/p&gt;

&lt;p&gt;There are two kinds of test projects: Fully synthetic and representative.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fully synthetic projects&lt;/strong&gt; are generated to show how fast an ideal usage of Gradle can be.
They are usually very big and used to explore scaling bottlenecks in Gradle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Representative projects&lt;/strong&gt; try to mimic some of the real-world builds we’ve seen at our customers,
without exposing any of their proprietary details.
These builds give us a reality check on which non-ideal usage patterns we should speed up or prevent.&lt;/p&gt;

&lt;h2 id=&quot;distributed-execution&quot;&gt;Distributed execution&lt;/h2&gt;

&lt;p&gt;Running all our performance scenarios on one machine would take more than half a day.
We want to run most of them on every commit, so we need to go much faster.
We do this using a “coordinator” build, which discovers all the scenarios and schedules individual builds for each of them.
The coordinator uses TeamCity’s REST API to schedule and wait for the scenarios.
Once they are finished, the coordinator gets the scenario results and synthesizes a test report.
From the TeamCity UI it looks just as if all the tests had run on the same machine. 
You can browse executed tests, see their execution time and how often they’ve failed in the past.&lt;/p&gt;

&lt;p&gt;All the build agents are automatically managed, dedicated servers with exactly the same hardware and software configuration.
This ensures that the results are reproducible. 
The full regression test suite takes about an hour to run, though the target is to get it down to 30 minutes.&lt;/p&gt;

&lt;h2 id=&quot;regression-detection&quot;&gt;Regression detection&lt;/h2&gt;

&lt;p&gt;For regression tests, we run the current code against a baseline, usually some recent nightly build that is known to be well-performing.
Each version gets its own user home, temporary directory etc. to ensure isolation.
We even go so far as to pad folder names so that each version under test needs to use the same amount of memory for its file system cache keys.&lt;/p&gt;

&lt;p&gt;Each version is given ample warmup time and is then usually measured for 40 iterations. 
Afterwards, we use a &lt;a href=&quot;https://en.wikipedia.org/wiki/Mann%E2%80%93Whitney_U_test&quot;&gt;Mann-Whitney u test&lt;/a&gt; to determine whether the current version is significantly slower than the baseline.
If so, the test fails and someone has to investigate.&lt;/p&gt;

&lt;p&gt;We require a confidence of 99%, so there is still a 1% chance that a test may fail due to pure bad luck.
This is the price we’re ready to pay in order to detect even the smallest regressions.
The infrastructure reliably detects performance regressions of as little as 1%. 
That means if a commit makes a 5s build just 50ms slower, the performance test will already sound the alarm.&lt;/p&gt;

&lt;h2 id=&quot;profiling&quot;&gt;Profiling&lt;/h2&gt;

&lt;p&gt;It is not always obvious what caused a performance regression.
A commit may look unsuspicious at first, but have some unintended side effect.
A regression may have gone undetected because it was very small and now another, equally small regression pushed the test over the edge.
In both cases, the committer needs more data to find out what the problem is.&lt;/p&gt;

&lt;p&gt;For this purpose, we can run any scenario with the &lt;a href=&quot;http://www.oracle.com/technetwork/java/javaseproducts/mission-control/java-mission-control-1998576.html&quot;&gt;Java Flight Recorder&lt;/a&gt; attached.
This gives us highly accurate data on CPU usage, memory allocation, file and network IO, and blocked threads.
The data is visualized using the &lt;a href=&quot;https://github.com/chrishantha/jfr-flame-graph&quot;&gt;jfr-flamegraph converter&lt;/a&gt; 
and the &lt;a href=&quot;https://github.com/brendangregg/FlameGraph&quot;&gt;FlameGraph tool&lt;/a&gt;.
You can zoom and search the stacks and quickly explore both the top-down (flame graph) and bottom-up (icicle graph) resource usage of the version under test.
For instance, here is an icicle graph showing the allocation hot spots when calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gradle help&lt;/code&gt; on our own build.
It is a simplified version since the &lt;a href=&quot;/images/icicles-kotlin-script-caching-detailed.svg&quot;&gt;fully detailed&lt;/a&gt; one is over 2MB large.&lt;/p&gt;

&lt;p&gt;
    &lt;object data=&quot;/images/icicles-kotlin-script-caching-simple.svg&quot; type=&quot;image/svg+xml&quot;&gt;&lt;/object&gt;
&lt;/p&gt;

&lt;p&gt;We had recently migrated most of our scripts from Groovy to Kotlin and didn’t see the full performance benefit we expected.
A look at the graph quickly shows that inefficient classloader caching was to blame.
This analysis took no more than a few minutes and uncovered an issue that may otherwise have led to disappointed users.&lt;/p&gt;

&lt;p&gt;For each tested version, we also create a diff against every other tested version. 
This usually makes it pretty easy to spot where things got slower or faster.
For instance, this flame graph diff shows what happened when we removed the rule-based model bridging from projects that aren’t using the rule-based model.&lt;/p&gt;

&lt;p&gt;
    &lt;object data=&quot;/images/diff-flamegraph-model-bridging-simple.svg&quot; type=&quot;image/svg+xml&quot;&gt;&lt;/object&gt;
&lt;/p&gt;

&lt;p&gt;You can clearly see task creation in dark blue, meaning it is much faster now.
Red, on the other hand, would mean that something got significantly slower.
You can also look at the &lt;a href=&quot;/images/diff-flamegraph-model-bridging-detailed.svg&quot;&gt;fully detailed version&lt;/a&gt; (1 MB).&lt;/p&gt;

&lt;h2 id=&quot;coming-to-you-soon&quot;&gt;Coming to you soon&lt;/h2&gt;

&lt;p&gt;The flame graphs you saw above will soon be added to the &lt;a href=&quot;https://github.com/gradle/gradle-profiler&quot;&gt;Gradle Profiler&lt;/a&gt;, a tool that deserves its own blog post in the future.
With it, you’ll be able to easily create the same visualizations for your own performance scenarios.
This will allow both plugin authors and build engineers optimize their to algorithms for the fastest possible builds.&lt;/p&gt;

&lt;p&gt;The Gradle team will, of course, keep pushing for ever-faster builds. 
Gradle 4.8 will contain some nice configuration time improvements for large Android builds. 
These improvements were only possible thanks to JFR snapshots generously provided by our customers and the great work on flame graphs done by &lt;a href=&quot;https://github.com/brendangregg&quot;&gt;Brendan Gregg&lt;/a&gt; and &lt;a href=&quot;https://github.com/chrishantha&quot;&gt;Chrishantha Perera&lt;/a&gt;. Thank you so much!&lt;/p&gt;</content><author><name>Stefan Oehme</name></author><summary type="html">Performance is one of Gradle’s most important features. This is why we go to great lengths to ensure that Gradle stays fast and improves with every release. I’m proud of the performance testing infrastructure that we’ve built over the last two years and would like to give you a tour of it.</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://oehme.github.io/images/me.png" /><media:content medium="image" url="https://oehme.github.io/images/me.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">The build-changing magic of tidying up</title><link href="https://oehme.github.io/use-buildSrc" rel="alternate" type="text/html" title="The build-changing magic of tidying up" /><published>2018-03-21T00:00:00+00:00</published><updated>2018-03-21T00:00:00+00:00</updated><id>https://oehme.github.io/use-buildSrc</id><content type="html" xml:base="https://oehme.github.io/use-buildSrc">&lt;p&gt;Organizing your build logic properly can make the difference between a tool that your developers will love or a mess of spaghetti code that no one dares to touch.&lt;/p&gt;

&lt;p&gt;Gradle offers different ways to organize build logic:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Scripting in a project’s build script&lt;/li&gt;
  &lt;li&gt;Writing scripts that are applied to multiple projects&lt;/li&gt;
  &lt;li&gt;Writing binary plugins in &lt;a href=&quot;https://docs.gradle.org/current/userguide/organizing_build_logic.html#sec:build_sources&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;buildSrc&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Writing binary plugins shared through a repository with other teams&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Unfortunately, I often see users not making the leap from step 2 to step 3 as early as they should.
Scripts should only contain declarative configuration.
As soon as you need control flow, complex computations or external dependencies, you should move that logic to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;buildSrc&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;buildSrc&lt;/code&gt; is a special project which is built before your settings and build scripts are executed.
This means it can contribute additional plugins and tasks that your build scripts can use.
You can write these types in any JVM language you like.
The dependencies of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;buildSrc&lt;/code&gt; project are visible to all build scripts in your project.&lt;/p&gt;

&lt;p&gt;Let’s see how these properties make &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;buildSrc&lt;/code&gt; such a great tool.&lt;/p&gt;

&lt;h2 id=&quot;plugin-classpath-management&quot;&gt;Plugin classpath management&lt;/h2&gt;

&lt;h3 id=&quot;without-buildsrc&quot;&gt;Without buildSrc&lt;/h3&gt;

&lt;p&gt;If you have ever tried splitting a complex build script into multiple smaller ones you may have run into &lt;a href=&quot;https://github.com/gradle/gradle/issues/1262&quot;&gt;classloading problems&lt;/a&gt;.
Project build scripts cannot see classes loaded by scripts that they apply.&lt;/p&gt;

&lt;p&gt;For instance, you may have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spring-boot.gradle&lt;/code&gt; script that loads the Spring Boot plugin and configures some defaults.
Already there are some oddities - you have to apply the plugin by its class name instead of id.&lt;/p&gt;

&lt;figure&gt;
  &lt;figcaption&gt;spring-boot.gradle&lt;/figcaption&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;&lt;span class=&quot;n&quot;&gt;buildscript&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;repositories&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;gradlePluginPortal&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dependencies&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;classpath&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'org.springframework.boot:spring-boot-gradle-plugin:2.0.1.RELEASE'&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;apply&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;plugin:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'org.springframework.boot.gradle.plugin.SpringBootPlugin'&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;springBoot&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;//set some defaults&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;/figure&gt;

&lt;p&gt;But even worse - when you try referencing any of the Spring Boot plugin’s types in a project script, you’ll
be greeted with script compilation errors.&lt;/p&gt;

&lt;figure&gt;
  &lt;figcaption&gt;some-project.gradle&lt;/figcaption&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;&lt;span class=&quot;n&quot;&gt;apply&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;from:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'spring-boot.gradle'&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;withType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BootRun&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;//won't compile :(&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;//some project-specific config&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;/figure&gt;

&lt;h3 id=&quot;with-buildsrc&quot;&gt;With buildSrc&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;buildSrc&lt;/code&gt; on the other hand exposes all its types and dependencies to all scripts in your project.
This means that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;buildSrc&lt;/code&gt; is the perfect place to centrally manage all your plugin dependencies.&lt;/p&gt;

&lt;figure&gt;
  &lt;figcaption&gt;buildSrc/build.gradle&lt;/figcaption&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;&lt;span class=&quot;n&quot;&gt;apply&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;plugin:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'java-library'&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;apply&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;plugin:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'java-gradle-plugin'&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;repositories&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;gradlePluginPortal&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;dependencies&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;api&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'org.springframework.boot:spring-boot-gradle-plugin:2.0.1.RELEASE'&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;/figure&gt;

&lt;p&gt;The two scripts from earlier can now be simplified and will work just as you’d expect.&lt;/p&gt;

&lt;figure&gt;
  &lt;figcaption&gt;spring-boot.gradle&lt;/figcaption&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;&lt;span class=&quot;n&quot;&gt;apply&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;plugin:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'org.springframework.boot'&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;springBoot&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;//set some defaults&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;/figure&gt;

&lt;figure&gt;
  &lt;figcaption&gt;some-project.gradle&lt;/figcaption&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;&lt;span class=&quot;n&quot;&gt;apply&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;from:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'spring-boot.gradle'&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;withType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BootRun&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;//some project-specific config&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;/figure&gt;

&lt;h2 id=&quot;ide-support&quot;&gt;IDE support&lt;/h2&gt;

&lt;p&gt;The example above still uses a script for configuring the Spring Boot defaults.
I highly recommend moving this logic into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;buildSrc&lt;/code&gt; as soon as it gets more complex.
Writing plugins in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;buildSrc&lt;/code&gt; gives you much better IDE support than writing scripts.
Your IDE will offer you syntax highlighting, auto completion, source navigation and refactoring.&lt;/p&gt;

&lt;p&gt;Don’t be afraid to make the move. Both scripts and plugins use the same Gradle API.
A script is just a plugin without the class/method boilerplate.
Here is how that plugin could look like when copy-pasted to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;buildSrc&lt;/code&gt;.&lt;/p&gt;

&lt;figure&gt;
  &lt;figcaption&gt;buildSrc/src/main/groovy/MySpringBootPlugin.groovy&lt;/figcaption&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MySpringBootPlugin&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Plugin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Project&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Project&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;apply&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;plugin:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'org.springframework.boot'&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;springBoot&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//some complex team/company-specific defaults&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;/figure&gt;

&lt;p&gt;You can then give this plugin an id and use the much nicer plugin DSL to apply it.&lt;/p&gt;

&lt;figure&gt;
  &lt;figcaption&gt;buildSrc/build.gradle&lt;/figcaption&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;&lt;span class=&quot;n&quot;&gt;gradlePlugin&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;plugins&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;springBoot&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'spring-boot'&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;implementationClass&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'MySpringBootPlugin'&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;/figure&gt;

&lt;figure&gt;
  &lt;figcaption&gt;some-project.gradle&lt;/figcaption&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-groovy&quot; data-lang=&quot;groovy&quot;&gt;&lt;span class=&quot;n&quot;&gt;plugins&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'spring-boot'&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;withType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BootRun&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;//some project-specific config&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;/figure&gt;

&lt;h2 id=&quot;testing&quot;&gt;Testing&lt;/h2&gt;

&lt;p&gt;Another reason to keep complex logic out of scripts is that scripts cannot be tested.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;buildSrc&lt;/code&gt; allows you to write unit tests for low-level implementation details and functional &lt;a href=&quot;https://guides.gradle.org/testing-gradle-plugins/&quot;&gt;tests&lt;/a&gt; for the user-facing behavior
of your plugins and tasks. This is essential as your build grows more complex, as a good test suite
allows you to refactor without fear of breaking your developers’ workflow.&lt;/p&gt;

&lt;h2 id=&quot;performance&quot;&gt;Performance&lt;/h2&gt;

&lt;p&gt;While dynamic Groovy allows you to build very succinct and readable DSLs, it also has a rather high execution overhead.
If you have a large project and scripts that you apply to all subprojects, consider using statically compiled &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;buildSrc&lt;/code&gt; plugins instead.
Of course, you could just switch the script over to the statically compiled &lt;a href=&quot;https://github.com/gradle/kotlin-dsl&quot;&gt;Kotlin DSL&lt;/a&gt;.
But moving it to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;buildSrc&lt;/code&gt; and turning it into a proper plugin gives you all the other benefits mentioned above.
And if neither Kotlin nor Groovy are your cup of tea when it comes to writing complex logic,
you can write it in plain old Java too. Any statically compiled language is great for performance.&lt;/p&gt;

&lt;p&gt;For instance, here is the plugin from earlier, translated to Java.&lt;/p&gt;
&lt;figure&gt;
  &lt;figcaption&gt;buildSrc/src/main/groovy/MySpringBootPlugin.java&lt;/figcaption&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MySpringBootPlugin&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Plugin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Project&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Project&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;org.springframework.boot&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;extensions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;configure&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;SpringBootExtension&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;//some complex team/company-specific defaults&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;});&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;/figure&gt;

&lt;h2 id=&quot;sharing&quot;&gt;Sharing&lt;/h2&gt;

&lt;p&gt;Once you have extracted plugins to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;buildSrc&lt;/code&gt;, the next step towards making them a standalone project shared with other
teams is much simpler. You’ll already have the project structure in place and a good suite of tests to show others what
to expect of your plugin.&lt;/p&gt;

&lt;h2 id=&quot;readability&quot;&gt;Readability&lt;/h2&gt;

&lt;p&gt;Extracting complex logic into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;buildSrc&lt;/code&gt; and only leaving declarative logic in your build scripts makes it much easier
for others to customize the few details they care about.
Don’t make your team members read a bunch of unrelated code just to change a dependency or add another subproject.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Properly structuring your build takes some discipline.
You need to treat your build logic the same way you would write your production code.
Complex control flow should be well-tested and remain separate from declarative configuration.
In return you don’t just get a more manageable build, but better IDE support, performance and convenience.
I hope I could inspire you to give your build a fresh look and ask yourself: “What will I extract today?”&lt;/p&gt;</content><author><name>Stefan Oehme</name></author><summary type="html">Organizing your build logic properly can make the difference between a tool that your developers will love or a mess of spaghetti code that no one dares to touch.</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://oehme.github.io/images/me.png" /><media:content medium="image" url="https://oehme.github.io/images/me.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>