This sums it up pretty well. Coming from a background of using Maven purely for dependency management and basic compilation, existing Gradle projects are incredibly unapproachable and slow. And don’t get me started on the background daemons to speed things up either.
My experience with JenkinsFile has improved once we setup library functions for it to leverage on. Only downside is that you need to write them in Groovy but the payoff is that the JenkinsFile reduces to basically a function call or two per stage which makes it super clean + it makes JenkinsFiles more or less "standard" (because there is practically no project-specific stuff in it - the complexity is managed by the library functions). This approach has made fixing issues or enhancements so much more easier for us.
At the end of the day, its just the shell executing the commands :) sure, you can always distil it down to a script (or a set of scripts) and nothing wrong with it either - as always, depends on the context.
Yup. Gradle is by far the worst-designed technology I've encountered in ~30 years of programming. Using it not only made me not want to use Gradle, it made me not want to use Java.
Same for Maven. Common reason: the mere presence of a "leftover after refactoring" file beneath target/ breaks the build because it is still included in the classpath.
FWIW: I've used Maven for years and I don't have these problems.
When I am tasked with fixong other peoples config I usually move things to the correct location, delete the corresponding configuration qnd then everything works (better).
i took up flutter a couple of years ago, which means i am now saddled with (ugh) gradle for android builds. i am used to being able to intuit the underlying architecture of various tech things and fix them, which is utterly impossible as far as gradle goes.
not only are the error messages unhelpful, they are usually confidently incorrect. they tell you the problem is likely thing X, which in fact has nothing to do with the problem. (to be fair, this seems to be coming from whatever stuff google has added to the android build process, rather than gradle itself.)
these days, once gradle starts spitting errors, i know better than to read them. instead, i have to start thinking about when the build last worked, and what might have changed. if that path proves not to bear fruit, then it is time to trash that project completely and recreate it from scratch, which experience has taught me will take a lot less time than trying to “fix” the gradle build errors.
> Gradle is by far the worst-designed technology I've encountered in ~30 years of programming.
I nominate Groovy. Many of gradle's failures are just idiomatic groovy. The entire mindset of "friendly simplifications" that work 80% of the time, and if they give pretend that you don't notice, look, there's pretty code over there!
Even kotlin suffers from some of that (you can override a val x:Int with a val x:Int get() unless it's tied down otherwise, talk about principle of maximum surprise) but the genes from the other parent avoid the worst.
I think I conflate the two, and I'm not sure that's wrong. I've never seen anyone use Groovy outside Gradle. I mean why would you, if you had a choice?
Back in the days when Kotlin didn't exist and even Scala was in its infancy, some people genuinely liked the JVM ecosystem and even aspects of Java itself, but were turned off by the verbosity of Java. Those were the people that started using Groovy and also projects such as Grails (especially since they saw the success of Rails and Django and wanted to emulate that).
Also, I guess some people genuinely like gradual typing, although I've never found much use for it.
Nowadays, I wouldn't pick up Groovy anymore, but there was a time where it was basically that, or Java (or a different ecosystem entirely; or something like JRuby / Jython).
There are worse things than using Grails. For example using straight up Spring. Grails is a sane wrapper around Spring so you can easily bring regular Java developers into a sane ecosystem. For example Grails' Gorm is the only sane way of using Hibernate in the entire JVM ecosystem. Using Hibernate directly will drain your sanity very quickly.
Especially before Java 8, writing Java code was very painful compared with Groovy, even though Groovy was fairly slower, and its compiler buggy. Groovy has closures, AST transformations builtin, much nicer syntax for properties (backed by getters/setters), ability to sort-of change classes at runtime (without instrumentation), multiple dispatch (i.e. dispatch based on the runtime type of the arguments), the ability to use reflection without the awful reflection API and so on.
Groovy is fantastic for quickly creating domain specific languages. I've done a bunch of them over the years for different problem domains and have yet to find another solution that yields effective custom languages that are as readable and easy to implement.
I have an old java (7, 8) app I maintain that whenever possible I move classes to Groovy as I am making other changes in them.
I would probably do Kotlin, now, but that wasn't an option when I started this.
The amount of boilerplate removed is one big win, but moreover the more functionally oriented aspects of most anything you'd do with a for-loop in java is wonderful.
And with annotations such as @CompileStatic and @TypeChecked, I am able to forgo SOME of the syntactic niceties for the compile-time binding java-comparable speed.
I've had to use it to configure Jenkins.
In Groovy, variables are global if you forget to declare them with 'var'. This has caused so many concurrency bugs and so much sadness.
Groovy is an amazing language. Don't judge it based on Gradle. It is used for all kinds of things. Gradle sort of interacts with it in a highly toxic way and brings out all its bad parts. If you use it the opposite way - as a better version of Java with mostly structured code, plenty of explicit typing and avoiding the highly "magic" aspects that Gradle focuses on, then its a great language for application development. Certainly better for high level things than Java - but completely 100% compatible with the Java ecosystem.
Kotlin is playing it fast and loose with blurring the line between fields and bean-style properties. In Java you could have a public final field and it was obvious that it was immutable (unless referencing a mutable object). Kotlin, in an attempt to match Groovy in syntax cuteness, allows to declare a val (supposedly the equivalent of a java final field) that is actually not backed by memory at all but a getter method in disguise.
val x: Int // this is an immutable value
val y: Int // this is an immutable Function0<Int> that is executed each time and but pretends to be a val
get() = System.currentTimeMillis()
Supporting properties is one thing, having assignments copy the backing property as a lazy evaluated function-pointer another, even if value is supposedly a int.
Python getter properties are evaluated instantly on assignment. To convert them back into function-pointers like the Kotlin example above you really have to go out of your way by deep diving into inspect module. The result is a property-object which is not assignable to a int-variable, but type checking probably wouldn't go this deep as inspect naturally is very dynamic in its nature.
My guess is Kotlin does this as a way of structural typing, if it walks like a int and talks like an int it is an int.
Do those others heavily advertise the difference between var and val as a core feature and then sabotage it by dressing up getters (that are evaluated on each call) as immutable values?
I think the main thing to understanding this is that Kotlin tries really really hard to not have fields. They're all properties, similar to get and set calls in Java (and if you have a feild in an interface it's basically that).
I think the reason for that is that Kotlin doesn't necessarily aim for immutability as much as it aims for read-only values. This is also why there is no truly immutable implementation of `List` in the stdlib, although the interface itself it read-only (but it can still point to a value that is being mutated somewhere else).
I agree that true immutability - or code that is more obvious about avoiding side-effects - could be useful, but I don't think that was ever the goal.
That's certainly an accurate description of the cause. But I don't think that it's a valid excuse: not supporting immutability well is fine, pretending that you do when actually you don't is not.
The kotlin defense to this surely is that immutability happily exists in val constructor args (with the usual exception: not transitive through references), but that's purely contextual and a difference that is very hard to represent at usage site (e.g. they look exactly the same in the intellij "javadoc" view). If for some reason bean-style methods had to be represented through var/val syntax, I think that it would have been much better if read-only calculated getters were represented by a var without set(), leaving val to cases where you're guaranteed to get the same when referenced twice (value set once in constructor or some lazyness shenanigans).
Can anybody explain to me why it ever seemed like a good idea to make both: (1) Semicolons at the ends of lines and (2) Function-call parentheses, not required, and not illegal, but optional? It strikes me as an abdication of the responsibility of a designer, like making both String::substring and String::substr available as aliases one to the other. Who cares? Choose one and stick with it! Otherwise it becomes a race between linter-writers and community programmers to see if the entire ecosystem will become polluted with an ugly mix of both styles. A race which the linter-writers always lose.
Groovy was created during the time when Ruby was taking off and was dramatically improving productivity of creating basic CRUD type web sites. So Ruby had a beautiful simple syntax, and I think Groovy was an experiment to see how close you could get to that while maintaining compatibility with Java syntax. It's actually a remarkable success as far as it goes - most Java code can be copy and pasted in and is valid Groovy. But of course it only works because it optionally accepts parentheses, explicit vs implicit arguments etc.
It does lead though to one of the biggest lies which is "if you know Java then you already know Groovy!" - that's just a mean trick to play on people.
And to make things more interesting at some point the nearly-frozen-in-time Java started evolving again so Groovy is stuck in some sort of parallel universe because Ruby failed to get mass traction and most people are not familiar with it while Java 8+ has its own concepts that don't translate as neatly as Java 6-7 concepts translated to Groovy at the time.
Maybe the rest of Ruby is more "beautiful" and "simple", but those adjectives don't apply to the inconsistency caused by optional code punctuation. I think Ruby users even realized this, and have since imposed strict requirements on their projects that only one style be followed.
Groovy is all the more guilty for carrying the bad tradition forward. It's akin to somebody looking at the success of C and deciding that its macro system must be perfectly preserved going forward.
> I think Ruby users even realized this, and have since imposed strict requirements on their projects that only one style be followed.
A common set of conventions in the Ruby community uses both options for optional punctiation, with context controlling which is used for a particular case. E.g.,
1. Use a single expression per line, and don't combine the line starting or ending a block with the contents, and don't use semicolons where they are unnecessary (so, semicolons are used only to separate the opening and “end” of single-line blocks.)
2. Use parens among function calls, except omit them in calls that are part of internal DSLs.
For Groovy the simple answer is they claimed that every piece of Java code would also work in Groovy. This wasn't ever true (well, maybe before someone decided that the equals() method is confusing), but it didn't matter, because of the "80% is plenty" mindset that groovy appears to be built on.
That would justify requiring semicolons/parentheses. It doesn't justify making them optional. The real explanation is that they were mimicking another language, Ruby, that had made that mistake.
Writing Groovy scripts in JMeter (for some custom requests) when doing distributed performance testing was a good way to enjoy pretty much anything else afterwards.
I suppose I should more accurately call that thing JSR223 + Groovy, all stored in an extensive XML file and edited through the JMeter UI..
Off topic, but if you plan on coding your load tests I think Gatling is a better choice, you get to program in Scala which so far looks clean and readable to me.
Come on. Gradle might have many features you might not need, but IMHO the one popular alternative maven is one of the worst designed build tools.
Dependency resolution is a joke just taking the shortest path in the tree in case of conflicts. No good support for incremental compiles etc
Sorry that you feel this way, but this is simply not true. If it were the case, why would all these companies [0] waste their time with Gradle? Bad technologies tend do die out. Not the case here.
It isn't really the case that poorly designed technologies die out, necessarily. They just need to have one killer advantage. Javascript, as the only option, PHP prior to getting its act together won with its extremely low barrier to entry both in terms of learning the language and getting it running somewhere. So gradle having users doesn't convey to me that it's necessarily well-designed. I think it's "killer advantage" was that it's not xml.
I do not work on Android apps, but I use Gradle in semi-large web app and lots of fat jar projects. Do you happen to know if there are any perf measurements that outline Gradle is slower?
Basically every Android conference has a set of talks on how to make Gradle perform in usable ways, including setting up the background daemon to use as much RAM as it can, RAM disks, SSDs.
Tricks that Maven, or the other build systems never required.
Well, in all fairness I switched from Maven to Gradle exactly due to low performance. Gradle gave my projects a boost of 3 to 7 times faster build speeds. RAM is not an issue for my setups, although I can’t recall how Maven held there against Gradle.
Buck looks quite interesting. Thanks for pointing it out!
I tried to say in the last sentence that I think most people who abandoned maven for Gradle mostly just hate xml, sorry if you missed the sentence saying that.
I have the same impression of gradle. If I have to learn another language (groovy) and a whole new platform infrastructure and APIs just to understand what a build does, it's definitely not worth the effort. Most of my peers have the same views.
This argument I don't really get. You're going to learn a "language" anyway - whatever build system you employ. It may or may not be Turing complete, but you're going to end up learning it once you get to any level of complexity in what you are doing.
Why not have it be something with more general usefulness as well?
Many people dont appreciate Maven because they get hung up on XML. If you need to do anything in Maven which isnt dead easy to do in XML you just write a plugin in Java which is the language you are coding in anyway.
I wonder if this is a reflection of enterprise processes. Writing a plugin in a previous employer meant creating a new repo, a new CI/CD pipeline, getting it pushed to artifactory, making sure people had the inhouse artifactory set up in their settings.xml, and all that had internal overhead. Also learning the maven plugin API. And nobody on your team would want to, so you'd be stuck answering every question about this feature forever more. Compared to gradle where the same tasks just require a few if statements in a scripting language.
A long time ago I wrote a Maven plugin and decided to clean it up for open source. It got absolutely nowhere because after everything you describe—setting up a repo; learning the Maven API, which is one of those expansive APIs that has a lot of metaphoric semantic constructs—distributing it involved getting it into Maven Central by which point I had run out of time/mental bandwidth and never made it happen.
Compare if I had been using Gradle at the time: I would have just coded it up in Groovy, a language I thoroughly enjoy, right there in setup. There are some things I don't like about Gradle, but there's a lot it has going for it, too. Personally, I like XML and enjoy Maven for situations where existing plugins are enough—which is 99% of the time—but that lack of flexibility can be constraining when existing plugins aren't enough.
I find that little hurdle actually improves the quality a lot. It means people don't write maven plugins for every single conceivable scenario; usually there will be at most one or two plugins for whatever you wanted to do, properly documented and maintained, whereas with gradle there will be four or five different plugins with no documentation each of which only covers half of the task. And forcing people to put logic in actual released plugins rather than ad-hoc one-liners in the middle of the build definition means you avoid ending up with a bunch of ad-hoc one-liners scattered throughout your build definition, which is much better for long-term maintenance.
Yeah for sure, and that's what makes Maven a great tool at the end of the day. It's just those instances you encounter rarely where existing Maven plugins are not enough that I've wished I was using Gradle so I could drop a few one-liners and be done with it. I really like Groovy so any excuse to write some Groovy is always tempting. As you say, a few one liners here and a few one liners there can make a build incomprehensible. So people need to recognize that and keep their Gradle builds approachable if they're going to be doing that. It's pros and cons.
if you had made the groovy script in gradle, the code/capability would not have been sharable to anyone else but this single build (other than copy/paste sharing).
if you had the same code as a maven plugin, it would've been instantly sharable, and easily updatable centrally via a release process (as maven plugins tend to also be built with maven). Upfront work, but for long term reward.
>Writing a plugin in a previous employer meant creating a new >repo [..]
I just used the maven groovy plugin to invoke a groovy script containing the functionality instead of a maven plugin (a few lines of code). the groovy script was part of the source code and therefore easy to tweak. A lot of maven variables from the project are accessible from the script. Yeah, it's not really nice.
This is the crux of the problem, and is a problem with many libraries, configuration systems, languages, etc.
The author of said tool wants it to be as widely used as possible, so he optimizes for the 100%, going so far up the flexibility curve that it becomes incredibly difficult to do 80% of the things you want.
What these authors should be doing is optimizing for the 80%, which means an opinionated top layer that offers a single way to do everything that 80% of your users would want to do. Then if you're smart, you'll have a layered architecture (which the 80% layer rests upon) that allows the other 20% to learn about how the sausage is made in order to do their complex tasks.
With this, you have a common tool that is at least 80% easily understandable with minimal time investment.
Failure to do this gives you unfriendly systems like CMake, Gradle, OpenSSL, Postfix, and Java's time libraries.
I agree. When I was a kid I had a camera that had an 'easy mode' in software. If you turned it on, the software would disable most advanced features and the UI would be something incredibly simple. This allowed me a complete novice to get started taking pictures. After some experience I turned the setting off and started to experiment with more advanced features without getting overwhelmed.
I always wondered why not more products adopted this type of UX.
Also nice how the built-in Iphone calculator app is plain and simple in portrait mode, and rivals a scientific calculator when rotated to landscape mode.
Apple did the same thing with undo. Such a commonly used feature, hidden behind shaking your phone? That’s not even an interaction. They’ve slightly improved it with three-finger swipe to the side, which at least is a bit like a gesture, but again completely undiscoverable. Apparently under no circumstances will we just be getting an undo button.
It’s not just Apple, though: there’s a whole page of useful settings for Instagram’s Hyperlapse app. Guess how you open it? You tap the screen four times with four fingers. Why on earth did they build and maintain a settings page and then hide it like that?
They do kinds of have an Expert mode though; all of the original menus and options that they hide behind frustratingly glib, to the point of being useless, "modern" menus, with plenty of whitespace.
A recent Windows update even went so far as to proudly boast about how they were adding back menu items that had disappeared in this drive for stupidification of the menus. Hardly progress.
It's an old post, they have in Windows 10. Kinda. Many sections in Settings have options for advanced settings that would take you to the old Control Panel.
That's a bit different though. They're rebuilding the airplane mid flight. The long term goal is to replace the Control Panel entirely, it's just a multi-year and possibly multi-decade effort.
That's why they keep linking to the Control Panel, they're not there yet. I think they'll soon be done with the System page, for example.
> What these authors should be doing is optimizing for the 80%, which means an opinionated top layer that offers a single way to do everything that 80% of your users would want to do. Then if you're smart, you'll have a layered architecture (which the 80% layer rests upon) that allows the other 20% to learn about how the sausage is made in order to do their complex tasks.
A good example of this approach is MSBuild + Nuke [1]. You can have as much complexity as you want in your Nuke build script (which is plain C# in a normal, debuggable C# project), but MSBuild has to exist to bootstrap the Nuke build script.
The java.time.* are the new classes (IIRC, based on joda-time), but there are also the old time libraries in java.util.* and java.text.*. I don't know which ones the parent poster was talking about, but it might be the old ones.
Yes I know they're "new", but the Java time API was released 7 years ago with JDK 8. I don't think its fair to be critical of something that was fixed almost a decade ago.
Basically, yes. Google has been awfully slow at implementing some Java 8+ features. Their current solution is D8 desugaring, which basically rewites all the APIs into JDK8 compatible code.
The problem is that many Java projects in the wild are... well, old. Some of them haven't migrated to Java 8 yet and even then you can stuck with a lot of code using the old API.
I wonder though, how much is some extra complexity good for the bottom line of companies that make their money on consulting and supporting the tools that they create/maintain?
When someone does not understand basic tool which is used by most Java developers and that person writes book on that language, that's makes me very surprised. Obviously that person does not have any practical experience with Java, because there's no way you wouldn't stumble onto Gradle or Maven if you're writing real world code in Java.
I guess, that book writing skill is more important in writing good books, than programming skill.
If you work on any seriously big Java/Kotlin/Android project, and value your time, I recommend looking at Buck (or Bazel or other tools) for building instead of Gradle if you haven’t done so yet.
It would be annoying to move build tools, and plenty more of hookups are not as convenient in a bunch if cases, but when you get it going things will run faster. (So again, the bigger the project, the more likely you would like that)
A big part of the reason is that tools suck as Buck (which, like Bazel, uses Skylark as the language, which is a very limited python in essence) avoid the general programming patterns to avoid the issues described here.
I concur. I worked on a java project at a previous job and Bazel was instrumental in getting fast deterministic builds. There were some growing pains as the bazel docs weren't great back then (this was before bazel reached 1.0), but once we got things to work it was nice to work with. We were able to leverage Bazel for our Angular application as well so it was nice to have everything building with one tool.
> A big part of the reason is that tools suck as Buck (which, like Bazel, uses Skylark as the language, which is a very limited python in essence) avoid the general programming patterns to avoid the issues described here.
I also agree with this. My current language of choice is OCaml and the most popular build tool is dune [1], which uses an s-expression based configuration language, and its nice to have a more restrictive DSL that avoids some pitfalls of using a full blown programming language for configuring builds.
Bruce Eckel's book "Thinking in C++ Vols 1 and 2"[1][2] was possibly my favorite book ever read. Bruce is (in my personal opinion as an observer of people with zero professional training in psychology) a depth-first learner and naturally seeks to understand how things work by looking at the layers. Building up from there allows true understanding and even mastery of the material.
This is exactly how I am as well, and the result is often that it can take a while for me to pick things up and really understand them (since I have to cover so much more material than a typical person) but my level of mastery afterward is typically very high.
My experience now from well over a decade in the industry is that most people are not this way, and also that many people who are this way don't necessarily realize it. If you feel like understanding history, layers, etc helps you understand things much better, do yourself a favor and follow Bruce Eckel's blog and read his books. Thinking in C++ 2nd edition is 20 years old now. A third edition would be wonderful. Or maybe even better, "Thinking in Rust" :-D
Interesting observation. I feel like I am like that - I hate doing anything that requires me to develop only a superficial understanding (e.g. messing around with Kubernetes configuration without understanding how it all works on a fundamental level). I also don't like systems that are hard to debug - I want to be able to make assumptions and test them, and that can sometimes be notoriously difficult with cloud-based infrastructure.
Interesting you mention K8s, I had that same problem! It took me 6 months to get up to speed on K8s because I wanted to what everything meant, and those are all deep rabbit holes (like, try explaining the `apiVersion` in every object without going down a rabbit hole of how K8s works for example).
Making that even worse there wasn't much out there to cover internals. The situation is much better now though. I guess that's part of price you pay to be an early adopter of the latest hotness.
I still don't really know how Kubernetes works deep down, although I have learned enough to at least debug some of the most common issues I encounter at work, so if you have any very good resources (don't have to be free), I'd be happy to hear of them (the resources I've tried weren't very good, unfortunately).
I work a lot with gradle and had the same problems in the beginning. I overcome most problems by extending gradle through custom plugins and reading a lot of code. The documentation became clearer and clearer. Granted that is obviously not the desired way of learning a tool. Whenever I talk about Gradle and what it’s biggest problems are it’s exactly that. The introduction docs glance over so many aspects. It gets worse if you are completely new to groovy, Java and gradle. The idea to hide everything behind conventions is smart to keep the amount of configuration low. But if one has no idea about these conventions it becomes insanely hard to figure out how this black box works. One thing I have to disagree is the way he describes how to create tasks and that there are multiple ways of doing it. What he shows is the internal gradle API mainly targeted for plugin authors or gradle itself. There is no barrier. Build script authors should stay away from these APIs to stay forwards compatible with future versions. That’s also why gradle has 3 types of documentation: DSL, API and the raw Java Docs. Again nothing that makes the Tool easier to use for beginners.
Recently I had the pleasure of learning Ruby and then suddenly Gradle started making sense. Things like having more than 1 way to do something, or the magic DSL syntax achieved by using the Ruby unary block syntax - it's a bit more understandable once you learn Ruby.
> Commands are indented (horribly, by tabs, because make was created in the early days of Unix when they were still obsessed with saving bytes)
See, to me this is exactly not simple (a reference to make’s “simplicity” appears shortly after this). It’s a form of complexity because every time something is wrong with the build, this is one of the things I will need to check.
Moving the complexity elsewhere so that it becomes someone else’s problem just does not work for me as a definition of simplicity. You see this pattern a lot. Golang is pretty much an entire object lesson in this dynamic. Everything is “simple”, aka, “your problem”.
Ohohohoh Gradle. Making Android software turns this into a form of Stockholm syndrome, where you just get used to it. (And as some colleagues go, even pretend to like it. That is, until the next time it does something utterly stupid.) I have spent half a day trying to get a set of tasks to run depending on files that were changed since the original commit that started the git branch. This ought to be simple. Get all the files changed since the start, figure out their modules, figure out modules that have a dependency on those, run the tests only on those modules!
In Gradle-insanity land, you'll need a whole plugin for that. That you have to register in the buildScript.classpath closure. Wait, is it also in the plugins block of the root file ? But maybe also the modules themselves, as an apply. Or is it a plugin block too ? Oh wait, you're using buildSrc ? Well that's a whole other problem. Don't forget you need a basic .properties file in META-INF that just gives out the plugin's name so it can discover it through reflection instead of having a proper API to do so.
You would think the Kotlin scripts instead would fix it, but oh no. Not content with adding a solid 10 seconds every time gradle builds your model (which it rebuilds... Kind of when it feels like it), half of the pretended benefits of it simply do not exist. Almost all the documentation online is for the Groovy scripts, which is, obviously, incompatible with the .kts scripts. And I'm not just talking about syntax, no! Entire APIs are different. Setting a flag like `enabled = true` in groovy ? Have fun, we've added an isEnabled instead, because why not. Registering tasks? Wait, hold on, we've added new solutions in Kotlin! The existing APIs weren't batshit insane already, so let's abuse kotlin delegation. Kotlin's `by lazy {}` is nice and simple, why do we not make your write `val taskName by creating { }` ? And through some horrible logic, the name of your variable is what is exposed to Gradle. Or abusing the same thing for you to read things from a .properties file, because what is more clear than `val isFlagEnabled by project` ? Isn't it obvious that it's going to read a .properties file ? Not the values given through the -P flag when building though, because that would actually make sense. But wait! Let the gradle website explain to you the logic of lazy registered tasks. Why do you need these, you ask? Because Gradle is so absolutely terrible that having a large amount of tasks means they all get created when your daemon starts. Or when it performs anything.
Oh, right, you need a daemon because why would you not need to eat 1GB of RAM just to not have horrible startup times for a build tool? Load all the tasks, at all the times! Add a development flavor to all your modules ? lol you've just doubled your amount of tasks.
Let's add to this of course the absolute insanity that are the Android build tools. Making those work in a Makefile would be hell. aapt2, dexing... An absolute abomination of an amalgamation of bad tools (lol aapt2 silently ignoring your vector drawable overrides because it doesn't like the android:fillType attribute.) and horrible performance (aapt2 taking over a minute to process files on a medium sized project, or kapt (but that's a Kotlin problem) being awful in terms of performance.) All this to output a glorified .zip in the end. Or whatever new format's Google drug addled minds have invented to lock you in further to the Play Store and to make you give them your signing keys.
I am not a fan of Gradle. Or Android's build tools.
Not only has the Android team managed to push Gradle no matter what, they sell Android Java as the real Java.
They purposely use Android Java samples, with its half broken support for modern Java, as means to sell Kotlin in the platform, and when one points out the real differences, radio silence.
While Gradle does have a lot of issues, some of the things you mentioned are not correct.
> Don't forget you need a basic .properties file in META-INF that just gives out the plugin's name so it can discover it through reflection instead of having a proper API to do so.
Not really clear what do you mean, but declaring plugin names should not be done in properties, Gradle has syntax for declaring plugin ids inside its build config. If the plugin author haven’t used it, well, that’s on their conscience. Plugin users should never ever declare anything plugin-related in meta-inf.
> And through some horrible logic, the name of your variable is what is exposed to Gradle.
This is a feature of Kotlin as a language - in it, delegates know the names of the properties they are used for. `lazy` is just the simplest use of the delegates feature, which happens not to care about property names.
> Isn't it obvious that it's going to read a .properties file ? Not the values given through the -P flag when building though, because that would actually make sense.
This is simply untrue. Extracting properties this way most certainly will read them from .properties files and from all other sources of properties on a project, including the -P arguments.
Gradle does have a lot of issues, I can talk about them for hours (there are really nasty ones where there are no non-awful workarounds, for example, the built-in `tarTree` does not support symlinks in TAR archives, and there is no way to rebase the archive contents when extracting, i.e. to specify which directory of the archive to extract from, and these issues are very unlikely to be fixed, and these two are just the surface), but too many of your examples are somewhat wrong.
Make is still popular (not necessarily loved), because it still largely adheres to the Unix philosophy of doing one thing and one thing only. Anything more complex, make it executable and put it on the path and then call it from make.
Gradle. It's a full programming language. And let's not forget there's a ray tracer in pure cmake doing the rounds.
Being cross-platform without having to drop out of the main build tool isn't necessarily a benefit, and that's part of the reason for it's complexity.
The article seems to boil to down to: you need to understand Groovy to effectively use Gradle.
It's impossible to disagree with this, but this is a realization you should have after interacting with it after 10 minutes.
I may be biased because I'd been using Groovy (Grails apps) for years before I used Gradle so the syntax was not a problem at all for me. For me, the problem with Gradle is the very poor documentation. If I want to do something more complex, I either find the answer in Stackoverflow/the Gradle message board, or I will have to look at the source code of the plugins. Creating a task for scratch is fairly simple, it's interacting with the plugins that's difficult, and while I agree with the author that excessive abstraction through DSLs doesn't help, if these DSLs didn't leak and were properly documented it wouldn't be a problem.
Another problem is that it's clear what the right conventions are. Say you want some classes to some exec task. Where should these classes live? Do you use a configuration? Create a sourceSet? Add a directory to an existing sourceSet? You just edit the classpath property in the task to point to a directory? I miss some of the rigidity of Maven, though not its XML syntax. The ability to write actual functions in Groovy (e.g. a closure implementing a file filter) is very much welcome.
Having used Gradle, I disagree. Even if you know Groovy, declarations in Gradle are executed in a certain order and you don't really know what a closure does or what it's side effects are. Sometimes you have to read the source code because the documentation is insufficient. Gradle is not a simple tool and the documentation (last time I was using it) isn't helping.
I agree that order of execution is sometimes problematic in Groovy and you have to use evaluationDependsOn() or afterEvaluate {}. I don't think the DSLs are per se particularly problematic; they generally just set some properties of the task (when they're more complex and yet the abstraction leaks, then they do more harm than do). But yes, the documentation is awful, and is IMO the biggest problem with Gradle.
While I agree with you that it should be obvious that Groovy knowledge is necessary to operate Gradle, I think it's important to note that the intersect between people building things in Groovy and people building things in Java/Kotlin is quite small. And for that reason Gradle, with Groovy as its star feature, will always be a fraught choice for a build system.
Developers simply have to know too much Groovy to operate Gradle effectively.
I entirely agree. But i also don't know what the Gradle authors could have done instead. Writing build scripts in Java 1.6 (the latest version at the time Gradle was written) would have been a showstopper. Kotlin didn't exist. XML alone isn't expressive enough, so you would have needed some sort of Ant-like crummy language expressed in XML.
Buildr, which i think predated Gradle, used Ruby. I'm not sure Ruby is any better than Groovy here.
It's interesting because plenty of build systems exist written in Python but they don't get much used because Python itself is not really flexible or expressive enough for writing good DSLs - and build systems are just specialised enough that a good DSL is actually required to do them efficiently.
So we seem to be on a never-ending treadmill of wheel reinvention searching for the right compromise between these things.
> It's interesting because plenty of build systems exist written in Python but they don't get much used because Python itself is not really flexible or expressive enough for writing good DSLs - and build systems are just specialised enough that a good DSL is actually required to do them efficiently.
Scons is another example of Python as the config language. In fact most of the times you don't need a comlicated DSL, or any language at all. Look at Webpack, npm, Android Blueprint or Bazel which more or less is just JSON-flavor, or Rusts Cargo.toml which is just toml markup.
You can get very very far with just a list of files and some config properties, if there is something complicated needed in the configuration the markup can refer to an external script.
In fact not accidentally mixing the configuration step with the execution step, as Gradle often does, can be considered a feature.
Cargo isn't just TOML, it's TOML, plus a build.rs script, written in Rust, for everything you can't express in the TOML.
I agree that a lot of the time, you don't need Gradle's sophistication. But large, complex projects, in my experience, inevitably develop strange and unusual build needs. At that point, if your tool doesn't support them naturally, the complexity ends up going somewhere else - you have to write plugins, or standalone tools, or build script generators, or manage a lot of repetition in the build script, or multiply subprojects, etc, so you can do what you need to do.
> Cargo isn't just TOML, it's TOML, plus a build.rs script, written in Rust, for everything you can't express in the TOML.
Cargo is nice--until you have to coexist with something else that also wants to own the world of the build process.
And that build.rs script causes a LOT of heartburn when your needs get larger or you need repeatable builds.
I don't know what the right answer is, but cargo is exactly the kind of system you describe that you have to start working around when you have more sophisticated needs.
> this is a realization you should have after interacting with it after 10 minutes
The problem is, it is presented as if you don't need to know Groovy. And its not surprising because, if everybody contemplating using Gradle was first told "You should only do this if most of your team is willing to learn Groovy to good proficiency" - hardly anybody would do that.
Now groovy is a great language IMHO but I think the way this goes down results in people hating it because they get handed a build as if they should understand it easily and then experience hours and hours of frustration as it exhibits unexpected behavior as they learn all the surprising things about Groovy (and not just Groovy but the Groovy+Gradle DSL dialect that is much weirder than plain Groovy).
The problems match my experience with Gradle pretty well, and they point to a bigger issue in build systems: you usually can't easily debug the build system. Most build systems try to be declarative, but can't help to have imperative parts where order of execution matters or when a certain environment variable is set. You have these kind of problems in Gradle and I experienced them in Cmake, Maven and Make as well. At some point you just want to step-debug or have the environment variables print at a certain point during the build. All I want is a build tool where I declare my build by simply calling functions (or extend with custom functions) and if there's an issue I want to be able to step through the script and check what's wrong.
You very much can debug Gradle. It's just like any other Java/Groovy application. You set a breakpoint in the build file or some Gradle source file and you attach a debugger. In this respect, it's probably more debug-friendly than, say Makefiles where all you can do is add some print statements or go through megabytes of debug output.
Adding to that, in IntelliJ it's as simple as running the task from the 'run Gradle task' window (not sure how it's called) with Shift pressed. All the breakpoints in the build scripts will now work.
If you also use wrapper with sources (`-all` instead of `-bin` suffix in the `gradle/wrapper/gradle-wrapper.properties`) you can step into the entire Gradle source code as well
Sometimes builds are complex. You have several deployment paths, platforms, modules, features, signing and what not. Then the build is akin to a small program. That’s still reasonable. The problem is that all build tools that allow this make it more complex than simply writing a small script that does exactly what you need. Add to that that some dependemcies use a different build tool that you use. Adding a cmake dependency to a make project makes everything even more complex.
The problem with Gradle is that people try to use it, and so try to understand it, and then treat the sunk cost to understand it as a reason to keep using it. Bruce correctly sees being locked into Gradle as a way to need his expertise, but that is not a reason to follow him down the rabbit hole.
The correct response to discovering the nature of Gradle is to abandon it, immediately. (Similarly Waf, and Scons.) The world does not need yet another one-or-two-language-target build system, or another build system as full general scripting language. Things work better when you don't need expertise. A build system should be just barely powerful enough to do builds, but not powerful enough to mystify. A build system should work equally well for all the languages you might find you need to use. A build system should not need to be studied to be understood: isolated examples should suffice for each need.
Similarly, the build for a project should not need to be understood: everything you need to know about what you are looking at should be right there, or (failing that) in a single, central place for the project. The build should be the least interesting or engaging part of the project; the project itself inevitably demands more attention than you can spare. A build system that demands attention is a child screaming when you need to work.
I agree with you very much. Since gradle supported kotlin script(typed), I seem to have a little understanding of it. I have used it for a long time. Besides the treat the sunk cost, Maven makes people less willing to use it than gradle.
Reading this, I'm reminded in many (not good) ways of the .NET build system. Where trivial projects look simple, but once you pull back the curtains there are horrors to behold.
- Scoping is mysterious: things that you expect to be in scope AREN'T, and lots of things that you can't see ARE in scope. This makes it impossible for a beginner to look up the source code (or even, often, the documentation) that corresponds to a given thing in a Gradle file: you might realize that "ext" exists, but there's no way to tell what scope it comes from. That defeats most of the usual approaches that programmers have learned for exploring a new system. To use the UX lingo, Gradle is undiscoverable.
- Similarly, your Gradle script is interacting with things you can't see. When you write "sourceSets { ... }" you're actually calling a method on an object. What object? You can't see it, or even a reference to it, in your Gradle file.
- I came to Gradle with the definition in my head that a build tool is a thing that tracks dependencies so that it only has to rebuild things whose inputs have changed. Gradle doesn't do that at all. Many hours of searching for how Gradle detects changes were fruitless, because it doesn't even try, really. Gradle basically always runs everything, which is why it's so goddamn slow. This is because the entire dependency graph is constructed dynamically on every run, and that prevents the system from effectively retaining from run to run which steps have already been done.
> I came to Gradle with the definition in my head that a build tool is a thing that tracks dependencies so that it only has to rebuild things whose inputs have changed. Gradle doesn't do that at all. Many hours of searching for how Gradle detects changes were fruitless, because it doesn't even try, really. Gradle basically always runs everything, which is why it's so goddamn slow.
Gradle has build cache since version 3.5 [0]. You probably mean the configuration phase for which caching was introduced in version 6.6. [1]
Gradle has been consistently improving the last couple of years. I’m quite happy with its speed since the v6.5 upgrade.
> Many hours of searching for how Gradle detects changes were fruitless, because it doesn't even try, really. Gradle basically always runs everything, which is why it's so goddamn slow. This is because the entire dependency graph is constructed dynamically on every run,
Something that is not clear from the Gradle documentation is that every task must have its input and output defined (inputFile, inputDirectory, outputFile, outputDitectory property). It is absolutely essential so Gradle can infer which task has to be rerun when, for instance, a build is run. This is usually done for the ‘basic’ task (like build, test etc..) but you have to specify it when you have custom task. This can really make a difference in speed. Another trick is to use the console option (gradle build —console plain) so you will see each task that have been run and their status (if they have be runned, or if they have been skipped due to dependencies)
I’ve tried to improve build times on a large Android project, and failed because basically every dependency gets this wrong and it’s impossible to fix them all. This includes things like Google frameworks that you might expect to be well-written.
It’s not well-documented, as you say, and beyond that the tool doesn’t do anything to enforce or encourage correct behavior, or offer you any way to fix incorrect behavior in code you depend on.
As others mentioned gradle has a build cache. It works different to make as it doesn't take the mime timestamp of the input/output but rather creates hashes. In the early versions these caches were project local only. Later the cache was system wide. Now you can setup a gradle cache-server so the whole team can use the same cache. But this all comes of course with some restrictions. Gradle needs full control over the inputs/outputs. If another tool makes a change to an output gradle will mark this as dirty and will rebuild it from scratch. Same when you update java, gradle or plugins since gradle can't be certain that a an update of the mentioned parts would result in a different output. On paper this all makes sense. But if you start to write custom tasks which depend on each other or work on lists of input files this becomes increasingly difficult. Luckily writing tests for custom tasks is easy if one finds the documentation and muse to set this up.
It definitely is confusing, but while the rules to scoping are pretty extensive, they're also well defined. The trick is to know how Gradle "travels" through objects and looks for the property in class properties, extensions, extras, etc. I wrote about it [1] if you're interested.
> Gradle script is interacting with things you can't see
Not necessarily -- yes, the receiver for top-level calls (Project instance) is implicit, but you can access it via `project` property. You could prefix each top-level call in your Gradle script with `project.`, making all calls explicit.
Btw `sourceSets { }` calls the lambda on a `SourceSetContainer` instance, accessible via `Project#sourceSets` property [2], so you can get an instance of it. I suppose there might some objects that are configurable but not accessible, but nothing comes to my mind right away, actually.
> a build tool is a thing that tracks dependencies so that it only has to rebuild things whose inputs have changed. Gradle doesn't do that at all.
You might've had a bad experience with a misconfigured plugin/build or a bug. Gradle does track dependencies, and while it doesn't do that perfectly always, it avoids a lot of unnecessary work, on many layers (incremental compilation, task avoidance, task caching). As a general rule, when running the same task(s) twice, the second invocation shouldn't execute any taks.
> This is because the entire dependency graph is constructed dynamically on every run
It's true that configuration/task graph creation is still happening on every invocation (this will change with Configuration Cache [3]). But it's definitely not accurate that this prevents Gradle from knowing which steps have already been done. Again, I fully believe it didn't work for you, but it doesn't mean Gradle doesn't do that.
I'm not trying to say Gradle doesn't have flaws. In fact, I think your comment proves the points from the article. Unless one invests in learning how Gradle works, it's impossible to spot e.g. why the build isn't doing any task avoidance, or understand scoping. It's rather unfortunate, because after that initial investment Gradle is a pretty great tool to work with.
That initial investment is non-trivial, though. It's at least tens of hours of study in my experience. And they keep adding concepts and deprecating old ones.
And keep in mind this is just for the build tool, someone using Gradle probably already has to juggle a million other details, most likely much more important than Gradle (business concerns, main tech stack concerns, etc.).
I am beginning to think that a lot of these problems stem from Gradle's frameworkyness. You don't call it, it calls you. Whatever it's doing in between when it calls into the bits and pieces of code that you wrote is a mystery. That, in turn, makes it almost impossible to intuit how things in a Gradle script relate to each other. The relationships aren't lexical, and you can't figure out how things work by tracing through the code. Instead, its inner workings feel like spooky action at a distance.
If Gradle were more of a library, and a Gradle build script were just a Groovy or Kotlin script that calls into that library, everything might be easier to understand.
Exactly how I’ve felt about Ruby or Rails for the last 12 years. There’s always something magic in scope you can’t see.
And duck typing all the things means you just have to trace through the code to actually find out what that <thing> parameter really is and what the function is expecting.
If I had to go back to Java tomorrow I would use Gradle hands down. I don't know why the author didn't grok it too well, but for me it has tons of plugins, the scriptability when a plugin is slightly off from what you need, and can be extremely tiny when you just need to build a simple jar.
Both Maven and Gradle suffer from bad user/developer experience. I think it's partly due to the culture and mindset that Java dictates to its ecosystem. The languages determines our experience, and the experience influences the culture.
Yeah, I've never used the combination professionally but Any wity Ivy always struck me as the happy zone. Although, over time, Maven's lack of explicit flow control has bothered me less and less.
I forked a project [1] in 2015 to remove Gradle, and it then quickly subsumed the original project, remaining under active development to this day.
There were other reasons, but de-gradling was one of the main motivations for my fork, and among the first of the major changes I made. The project is an implementation of an API which was discontinued by the original developers, but initially was built using Maven.
After switching from Gradle (which the project switched to in 2014) back to Maven, build times significantly decreased and development became much more pleasant. I found Gradle to be like a speed bump slowing down development, and reverting back to Maven was like a breath of fresh air. Simple, straightforward, and fast. Maven may not be perfect, but it does the job well.
Android is the only reason I put up with Gradle, for anything else Java related, I turn to Maven and happily forget that Gradle even exists.
It is Ant all over again, with Groovy slowness and game rig hardware requirements to perform in a sane way. There is hardly any Android conference without build related performance improvement talks.
Gradle was the excuse ant writers/maintainers wanted not to migrate to Maven, because the latter forces you to be cleaner with your project organization.
Maybe this is a dumb question but I thought gradle kts was released a while ago. Why does everyone in this thread still complains about groovy? Could they not just use kotlin?
It's not an easy switch. Still most of the examples and articles are in Groovy. Until recently, IDE (IntelliJ) support for Gradle Kotlin DSL was abysmal (like analysing stuff on the main thread). Only recently it's getting more traction as the worst issues have been resolved.
> There is hardly any Android conference without build related performance improvement talks.
fwiw I think the Android toolchain being slow doesn't help. On the same conferences someone will also speak about how the new Android Gradle Plugin is X% faster, so it's not all Gradle's fault :) That said there's plenty more Gradle can do (and is doing) to improve performance. The startup cost to configure all tasks is definitely high on my annoyances list
Kotlin is great, gradle has improved a lot in the past two years, especially since it supports .kts scripts, but it's still shit. Now, I'm very happy write rust
229 comments
[ 3.4 ms ] story [ 220 ms ] thread> To do anything you have to know everything
This sums it up pretty well. Coming from a background of using Maven purely for dependency management and basic compilation, existing Gradle projects are incredibly unapproachable and slow. And don’t get me started on the background daemons to speed things up either.
Eventually we'll get to the GitLab CI/CD, but the existing disaster supports the status quo, so it's not a priority.
That said, learning Yet Another Language that only deals with a niche requirement is a total bore.
If we can cook it down to executing some garden-variety bash in a special context, along with some spiffy pipeline visualization, that is enough.
If Clean Project is required to build the project, why isn't that part of the build process?
When I am tasked with fixong other peoples config I usually move things to the correct location, delete the corresponding configuration qnd then everything works (better).
i took up flutter a couple of years ago, which means i am now saddled with (ugh) gradle for android builds. i am used to being able to intuit the underlying architecture of various tech things and fix them, which is utterly impossible as far as gradle goes.
not only are the error messages unhelpful, they are usually confidently incorrect. they tell you the problem is likely thing X, which in fact has nothing to do with the problem. (to be fair, this seems to be coming from whatever stuff google has added to the android build process, rather than gradle itself.)
these days, once gradle starts spitting errors, i know better than to read them. instead, i have to start thinking about when the build last worked, and what might have changed. if that path proves not to bear fruit, then it is time to trash that project completely and recreate it from scratch, which experience has taught me will take a lot less time than trying to “fix” the gradle build errors.
Compared to Cargo (Rust) and Cabal or Stack in Haskell, it's constantly painful to use.
I nominate Groovy. Many of gradle's failures are just idiomatic groovy. The entire mindset of "friendly simplifications" that work 80% of the time, and if they give pretend that you don't notice, look, there's pretty code over there!
Even kotlin suffers from some of that (you can override a val x:Int with a val x:Int get() unless it's tied down otherwise, talk about principle of maximum surprise) but the genes from the other parent avoid the worst.
Also, I guess some people genuinely like gradual typing, although I've never found much use for it.
Nowadays, I wouldn't pick up Groovy anymore, but there was a time where it was basically that, or Java (or a different ecosystem entirely; or something like JRuby / Jython).
I would probably do Kotlin, now, but that wasn't an option when I started this.
The amount of boilerplate removed is one big win, but moreover the more functionally oriented aspects of most anything you'd do with a for-loop in java is wonderful.
And with annotations such as @CompileStatic and @TypeChecked, I am able to forgo SOME of the syntactic niceties for the compile-time binding java-comparable speed.
https://github.com/geb/geb/blob/master/module/geb-core/src/m...
So it looks mostly like Java, its just making use of Groovy's niceties and more powerful constructs in various parts to make the coding nicer.
Could you expand on this? I don't follow what you mean. Are you talking about some form of shadowing?
Python getter properties are evaluated instantly on assignment. To convert them back into function-pointers like the Kotlin example above you really have to go out of your way by deep diving into inspect module. The result is a property-object which is not assignable to a int-variable, but type checking probably wouldn't go this deep as inspect naturally is very dynamic in its nature.
My guess is Kotlin does this as a way of structural typing, if it walks like a int and talks like an int it is an int.
I agree that true immutability - or code that is more obvious about avoiding side-effects - could be useful, but I don't think that was ever the goal.
The kotlin defense to this surely is that immutability happily exists in val constructor args (with the usual exception: not transitive through references), but that's purely contextual and a difference that is very hard to represent at usage site (e.g. they look exactly the same in the intellij "javadoc" view). If for some reason bean-style methods had to be represented through var/val syntax, I think that it would have been much better if read-only calculated getters were represented by a var without set(), leaving val to cases where you're guaranteed to get the same when referenced twice (value set once in constructor or some lazyness shenanigans).
It does lead though to one of the biggest lies which is "if you know Java then you already know Groovy!" - that's just a mean trick to play on people.
Groovy is all the more guilty for carrying the bad tradition forward. It's akin to somebody looking at the success of C and deciding that its macro system must be perfectly preserved going forward.
A common set of conventions in the Ruby community uses both options for optional punctiation, with context controlling which is used for a particular case. E.g.,
1. Use a single expression per line, and don't combine the line starting or ending a block with the contents, and don't use semicolons where they are unnecessary (so, semicolons are used only to separate the opening and “end” of single-line blocks.)
2. Use parens among function calls, except omit them in calls that are part of internal DSLs.
I suppose I should more accurately call that thing JSR223 + Groovy, all stored in an extensive XML file and edited through the JMeter UI..
0 - https://gradle.com/customers
(Edit: Heh, TIL rage downvotes are a thing in HN as well.)
Or Facebook's Buck.
I do not work on Android apps, but I use Gradle in semi-large web app and lots of fat jar projects. Do you happen to know if there are any perf measurements that outline Gradle is slower?
Tricks that Maven, or the other build systems never required.
Buck looks quite interesting. Thanks for pointing it out!
Gradle has Android giving it wind on its sails, because a couple of people there are really into it.
Yet even Google's own teams rather use Basel and Soong instead.
Fun fact: look up gradle in urban dictionary.
Why not have it be something with more general usefulness as well?
So increasing the default max heap size of the gradle build helped fix the issue https://github.com/quarkusio/quarkus/pull/10508/files.
A long time ago I wrote a Maven plugin and decided to clean it up for open source. It got absolutely nowhere because after everything you describe—setting up a repo; learning the Maven API, which is one of those expansive APIs that has a lot of metaphoric semantic constructs—distributing it involved getting it into Maven Central by which point I had run out of time/mental bandwidth and never made it happen.
Compare if I had been using Gradle at the time: I would have just coded it up in Groovy, a language I thoroughly enjoy, right there in setup. There are some things I don't like about Gradle, but there's a lot it has going for it, too. Personally, I like XML and enjoy Maven for situations where existing plugins are enough—which is 99% of the time—but that lack of flexibility can be constraining when existing plugins aren't enough.
if you had the same code as a maven plugin, it would've been instantly sharable, and easily updatable centrally via a release process (as maven plugins tend to also be built with maven). Upfront work, but for long term reward.
I just used the maven groovy plugin to invoke a groovy script containing the functionality instead of a maven plugin (a few lines of code). the groovy script was part of the source code and therefore easy to tweak. A lot of maven variables from the project are accessible from the script. Yeah, it's not really nice.
Gradle also sucks, but at least you can script it much more easily.
You should not need to write a plugin!
This is the crux of the problem, and is a problem with many libraries, configuration systems, languages, etc.
The author of said tool wants it to be as widely used as possible, so he optimizes for the 100%, going so far up the flexibility curve that it becomes incredibly difficult to do 80% of the things you want.
What these authors should be doing is optimizing for the 80%, which means an opinionated top layer that offers a single way to do everything that 80% of your users would want to do. Then if you're smart, you'll have a layered architecture (which the 80% layer rests upon) that allows the other 20% to learn about how the sausage is made in order to do their complex tasks.
With this, you have a common tool that is at least 80% easily understandable with minimal time investment.
Failure to do this gives you unfriendly systems like CMake, Gradle, OpenSSL, Postfix, and Java's time libraries.
U/X applies to code as well.
I almost never hold my phone in landscape.
It’s not just Apple, though: there’s a whole page of useful settings for Instagram’s Hyperlapse app. Guess how you open it? You tap the screen four times with four fingers. Why on earth did they build and maintain a settings page and then hide it like that?
[0] https://devblogs.microsoft.com/oldnewthing/20030728-00/?p=43...
A recent Windows update even went so far as to proudly boast about how they were adding back menu items that had disappeared in this drive for stupidification of the menus. Hardly progress.
That's why they keep linking to the Control Panel, they're not there yet. I think they'll soon be done with the System page, for example.
A good example of this approach is MSBuild + Nuke [1]. You can have as much complexity as you want in your Nuke build script (which is plain C# in a normal, debuggable C# project), but MSBuild has to exist to bootstrap the Nuke build script.
[1] http://www.nuke.build/
[0] https://en.wikipedia.org/wiki/Turing_tarpit
The java.time libraries are available on android starting API level 26 https://developer.android.com/reference/java/time/package-su...
That is android 11, released in September of 2020 which is not widely distributed.
Does anyone know why the delay? It looks like Java 8 language features finally became compatible through some compatibility layer in Gradle 4.
Was it because of the lawsuits?
Instead, in overthought out systems, complex things are simple, and simple things are possible with unnecessary effort.
Trying to do everything for everyone makes for very complex systems that are too abstract to be easily used.
I guess, that book writing skill is more important in writing good books, than programming skill.
It would be annoying to move build tools, and plenty more of hookups are not as convenient in a bunch if cases, but when you get it going things will run faster. (So again, the bigger the project, the more likely you would like that)
A big part of the reason is that tools suck as Buck (which, like Bazel, uses Skylark as the language, which is a very limited python in essence) avoid the general programming patterns to avoid the issues described here.
> A big part of the reason is that tools suck as Buck (which, like Bazel, uses Skylark as the language, which is a very limited python in essence) avoid the general programming patterns to avoid the issues described here.
I also agree with this. My current language of choice is OCaml and the most popular build tool is dune [1], which uses an s-expression based configuration language, and its nice to have a more restrictive DSL that avoids some pitfalls of using a full blown programming language for configuring builds.
[1] https://dune.build/
Bruce Eckel's book "Thinking in C++ Vols 1 and 2"[1][2] was possibly my favorite book ever read. Bruce is (in my personal opinion as an observer of people with zero professional training in psychology) a depth-first learner and naturally seeks to understand how things work by looking at the layers. Building up from there allows true understanding and even mastery of the material.
This is exactly how I am as well, and the result is often that it can take a while for me to pick things up and really understand them (since I have to cover so much more material than a typical person) but my level of mastery afterward is typically very high.
My experience now from well over a decade in the industry is that most people are not this way, and also that many people who are this way don't necessarily realize it. If you feel like understanding history, layers, etc helps you understand things much better, do yourself a favor and follow Bruce Eckel's blog and read his books. Thinking in C++ 2nd edition is 20 years old now. A third edition would be wonderful. Or maybe even better, "Thinking in Rust" :-D
[1] Volume 1 available for free on Archive.org: https://archive.org/details/TICPP2ndEdVolOne
[2] Volume 2 available for free on Archive.org: https://archive.org/details/TICPP2ndEdVolTwo
Making that even worse there wasn't much out there to cover internals. The situation is much better now though. I guess that's part of price you pay to be an early adopter of the latest hotness.
See, to me this is exactly not simple (a reference to make’s “simplicity” appears shortly after this). It’s a form of complexity because every time something is wrong with the build, this is one of the things I will need to check.
Moving the complexity elsewhere so that it becomes someone else’s problem just does not work for me as a definition of simplicity. You see this pattern a lot. Golang is pretty much an entire object lesson in this dynamic. Everything is “simple”, aka, “your problem”.
In Gradle-insanity land, you'll need a whole plugin for that. That you have to register in the buildScript.classpath closure. Wait, is it also in the plugins block of the root file ? But maybe also the modules themselves, as an apply. Or is it a plugin block too ? Oh wait, you're using buildSrc ? Well that's a whole other problem. Don't forget you need a basic .properties file in META-INF that just gives out the plugin's name so it can discover it through reflection instead of having a proper API to do so.
You would think the Kotlin scripts instead would fix it, but oh no. Not content with adding a solid 10 seconds every time gradle builds your model (which it rebuilds... Kind of when it feels like it), half of the pretended benefits of it simply do not exist. Almost all the documentation online is for the Groovy scripts, which is, obviously, incompatible with the .kts scripts. And I'm not just talking about syntax, no! Entire APIs are different. Setting a flag like `enabled = true` in groovy ? Have fun, we've added an isEnabled instead, because why not. Registering tasks? Wait, hold on, we've added new solutions in Kotlin! The existing APIs weren't batshit insane already, so let's abuse kotlin delegation. Kotlin's `by lazy {}` is nice and simple, why do we not make your write `val taskName by creating { }` ? And through some horrible logic, the name of your variable is what is exposed to Gradle. Or abusing the same thing for you to read things from a .properties file, because what is more clear than `val isFlagEnabled by project` ? Isn't it obvious that it's going to read a .properties file ? Not the values given through the -P flag when building though, because that would actually make sense. But wait! Let the gradle website explain to you the logic of lazy registered tasks. Why do you need these, you ask? Because Gradle is so absolutely terrible that having a large amount of tasks means they all get created when your daemon starts. Or when it performs anything.
Oh, right, you need a daemon because why would you not need to eat 1GB of RAM just to not have horrible startup times for a build tool? Load all the tasks, at all the times! Add a development flavor to all your modules ? lol you've just doubled your amount of tasks.
Let's add to this of course the absolute insanity that are the Android build tools. Making those work in a Makefile would be hell. aapt2, dexing... An absolute abomination of an amalgamation of bad tools (lol aapt2 silently ignoring your vector drawable overrides because it doesn't like the android:fillType attribute.) and horrible performance (aapt2 taking over a minute to process files on a medium sized project, or kapt (but that's a Kotlin problem) being awful in terms of performance.) All this to output a glorified .zip in the end. Or whatever new format's Google drug addled minds have invented to lock you in further to the Play Store and to make you give them your signing keys.
I am not a fan of Gradle. Or Android's build tools.
Not only has the Android team managed to push Gradle no matter what, they sell Android Java as the real Java.
They purposely use Android Java samples, with its half broken support for modern Java, as means to sell Kotlin in the platform, and when one points out the real differences, radio silence.
> Don't forget you need a basic .properties file in META-INF that just gives out the plugin's name so it can discover it through reflection instead of having a proper API to do so.
Not really clear what do you mean, but declaring plugin names should not be done in properties, Gradle has syntax for declaring plugin ids inside its build config. If the plugin author haven’t used it, well, that’s on their conscience. Plugin users should never ever declare anything plugin-related in meta-inf.
> And through some horrible logic, the name of your variable is what is exposed to Gradle.
This is a feature of Kotlin as a language - in it, delegates know the names of the properties they are used for. `lazy` is just the simplest use of the delegates feature, which happens not to care about property names.
> Isn't it obvious that it's going to read a .properties file ? Not the values given through the -P flag when building though, because that would actually make sense.
This is simply untrue. Extracting properties this way most certainly will read them from .properties files and from all other sources of properties on a project, including the -P arguments.
Gradle does have a lot of issues, I can talk about them for hours (there are really nasty ones where there are no non-awful workarounds, for example, the built-in `tarTree` does not support symlinks in TAR archives, and there is no way to rebase the archive contents when extracting, i.e. to specify which directory of the archive to extract from, and these issues are very unlikely to be fixed, and these two are just the surface), but too many of your examples are somewhat wrong.
Gradle. It's a full programming language. And let's not forget there's a ray tracer in pure cmake doing the rounds.
Being cross-platform without having to drop out of the main build tool isn't necessarily a benefit, and that's part of the reason for it's complexity.
It's impossible to disagree with this, but this is a realization you should have after interacting with it after 10 minutes.
I may be biased because I'd been using Groovy (Grails apps) for years before I used Gradle so the syntax was not a problem at all for me. For me, the problem with Gradle is the very poor documentation. If I want to do something more complex, I either find the answer in Stackoverflow/the Gradle message board, or I will have to look at the source code of the plugins. Creating a task for scratch is fairly simple, it's interacting with the plugins that's difficult, and while I agree with the author that excessive abstraction through DSLs doesn't help, if these DSLs didn't leak and were properly documented it wouldn't be a problem.
Another problem is that it's clear what the right conventions are. Say you want some classes to some exec task. Where should these classes live? Do you use a configuration? Create a sourceSet? Add a directory to an existing sourceSet? You just edit the classpath property in the task to point to a directory? I miss some of the rigidity of Maven, though not its XML syntax. The ability to write actual functions in Groovy (e.g. a closure implementing a file filter) is very much welcome.
Developers simply have to know too much Groovy to operate Gradle effectively.
Buildr, which i think predated Gradle, used Ruby. I'm not sure Ruby is any better than Groovy here.
Perhaps Jython would have been a good choice.
It's interesting because plenty of build systems exist written in Python but they don't get much used because Python itself is not really flexible or expressive enough for writing good DSLs - and build systems are just specialised enough that a good DSL is actually required to do them efficiently.
So we seem to be on a never-ending treadmill of wheel reinvention searching for the right compromise between these things.
Meson seems to be a counterexample.
You can get very very far with just a list of files and some config properties, if there is something complicated needed in the configuration the markup can refer to an external script.
In fact not accidentally mixing the configuration step with the execution step, as Gradle often does, can be considered a feature.
I agree that a lot of the time, you don't need Gradle's sophistication. But large, complex projects, in my experience, inevitably develop strange and unusual build needs. At that point, if your tool doesn't support them naturally, the complexity ends up going somewhere else - you have to write plugins, or standalone tools, or build script generators, or manage a lot of repetition in the build script, or multiply subprojects, etc, so you can do what you need to do.
Cargo is nice--until you have to coexist with something else that also wants to own the world of the build process.
And that build.rs script causes a LOT of heartburn when your needs get larger or you need repeatable builds.
I don't know what the right answer is, but cargo is exactly the kind of system you describe that you have to start working around when you have more sophisticated needs.
The problem is, it is presented as if you don't need to know Groovy. And its not surprising because, if everybody contemplating using Gradle was first told "You should only do this if most of your team is willing to learn Groovy to good proficiency" - hardly anybody would do that.
Now groovy is a great language IMHO but I think the way this goes down results in people hating it because they get handed a build as if they should understand it easily and then experience hours and hours of frustration as it exhibits unexpected behavior as they learn all the surprising things about Groovy (and not just Groovy but the Groovy+Gradle DSL dialect that is much weirder than plain Groovy).
If you also use wrapper with sources (`-all` instead of `-bin` suffix in the `gradle/wrapper/gradle-wrapper.properties`) you can step into the entire Gradle source code as well
The correct response to discovering the nature of Gradle is to abandon it, immediately. (Similarly Waf, and Scons.) The world does not need yet another one-or-two-language-target build system, or another build system as full general scripting language. Things work better when you don't need expertise. A build system should be just barely powerful enough to do builds, but not powerful enough to mystify. A build system should work equally well for all the languages you might find you need to use. A build system should not need to be studied to be understood: isolated examples should suffice for each need.
Similarly, the build for a project should not need to be understood: everything you need to know about what you are looking at should be right there, or (failing that) in a single, central place for the project. The build should be the least interesting or engaging part of the project; the project itself inevitably demands more attention than you can spare. A build system that demands attention is a child screaming when you need to work.
- Scoping is mysterious: things that you expect to be in scope AREN'T, and lots of things that you can't see ARE in scope. This makes it impossible for a beginner to look up the source code (or even, often, the documentation) that corresponds to a given thing in a Gradle file: you might realize that "ext" exists, but there's no way to tell what scope it comes from. That defeats most of the usual approaches that programmers have learned for exploring a new system. To use the UX lingo, Gradle is undiscoverable.
- Similarly, your Gradle script is interacting with things you can't see. When you write "sourceSets { ... }" you're actually calling a method on an object. What object? You can't see it, or even a reference to it, in your Gradle file.
- I came to Gradle with the definition in my head that a build tool is a thing that tracks dependencies so that it only has to rebuild things whose inputs have changed. Gradle doesn't do that at all. Many hours of searching for how Gradle detects changes were fruitless, because it doesn't even try, really. Gradle basically always runs everything, which is why it's so goddamn slow. This is because the entire dependency graph is constructed dynamically on every run, and that prevents the system from effectively retaining from run to run which steps have already been done.
Gradle has build cache since version 3.5 [0]. You probably mean the configuration phase for which caching was introduced in version 6.6. [1]
Gradle has been consistently improving the last couple of years. I’m quite happy with its speed since the v6.5 upgrade.
0 - https://docs.gradle.org/current/userguide/build_cache.html
1 - https://docs.gradle.org/6.6/release-notes.html
- daemon
- build cache
- config cache
for a build tool! And it only took them more a full decade to implement them (the config cache is still experimental).
And even all those things only take it from "molasses slow" to "bearable".
Something that is not clear from the Gradle documentation is that every task must have its input and output defined (inputFile, inputDirectory, outputFile, outputDitectory property). It is absolutely essential so Gradle can infer which task has to be rerun when, for instance, a build is run. This is usually done for the ‘basic’ task (like build, test etc..) but you have to specify it when you have custom task. This can really make a difference in speed. Another trick is to use the console option (gradle build —console plain) so you will see each task that have been run and their status (if they have be runned, or if they have been skipped due to dependencies)
It’s not well-documented, as you say, and beyond that the tool doesn’t do anything to enforce or encourage correct behavior, or offer you any way to fix incorrect behavior in code you depend on.
It definitely is confusing, but while the rules to scoping are pretty extensive, they're also well defined. The trick is to know how Gradle "travels" through objects and looks for the property in class properties, extensions, extras, etc. I wrote about it [1] if you're interested.
> Gradle script is interacting with things you can't see
Not necessarily -- yes, the receiver for top-level calls (Project instance) is implicit, but you can access it via `project` property. You could prefix each top-level call in your Gradle script with `project.`, making all calls explicit.
Btw `sourceSets { }` calls the lambda on a `SourceSetContainer` instance, accessible via `Project#sourceSets` property [2], so you can get an instance of it. I suppose there might some objects that are configurable but not accessible, but nothing comes to my mind right away, actually.
> a build tool is a thing that tracks dependencies so that it only has to rebuild things whose inputs have changed. Gradle doesn't do that at all.
You might've had a bad experience with a misconfigured plugin/build or a bug. Gradle does track dependencies, and while it doesn't do that perfectly always, it avoids a lot of unnecessary work, on many layers (incremental compilation, task avoidance, task caching). As a general rule, when running the same task(s) twice, the second invocation shouldn't execute any taks.
> This is because the entire dependency graph is constructed dynamically on every run It's true that configuration/task graph creation is still happening on every invocation (this will change with Configuration Cache [3]). But it's definitely not accurate that this prevents Gradle from knowing which steps have already been done. Again, I fully believe it didn't work for you, but it doesn't mean Gradle doesn't do that.
I'm not trying to say Gradle doesn't have flaws. In fact, I think your comment proves the points from the article. Unless one invests in learning how Gradle works, it's impossible to spot e.g. why the build isn't doing any task avoidance, or understand scoping. It's rather unfortunate, because after that initial investment Gradle is a pretty great tool to work with.
[1] https://medium.com/tooploox/where-do-gradle-properties-come-... [2] https://docs.gradle.org/current/dsl/org.gradle.api.Project.h... [3] https://docs.gradle.org/current/userguide/configuration_cach...
And keep in mind this is just for the build tool, someone using Gradle probably already has to juggle a million other details, most likely much more important than Gradle (business concerns, main tech stack concerns, etc.).
Gradle is practically consultingware.
Btw, when was the last time you used Gradle? For what type of projects and are you currently using it in prod?
Yes. Maven.
If Gradle were more of a library, and a Gradle build script were just a Groovy or Kotlin script that calls into that library, everything might be easier to understand.
Exactly how I’ve felt about Ruby or Rails for the last 12 years. There’s always something magic in scope you can’t see.
And duck typing all the things means you just have to trace through the code to actually find out what that <thing> parameter really is and what the function is expecting.
There were other reasons, but de-gradling was one of the main motivations for my fork, and among the first of the major changes I made. The project is an implementation of an API which was discontinued by the original developers, but initially was built using Maven.
After switching from Gradle (which the project switched to in 2014) back to Maven, build times significantly decreased and development became much more pleasant. I found Gradle to be like a speed bump slowing down development, and reverting back to Maven was like a breath of fresh air. Simple, straightforward, and fast. Maven may not be perfect, but it does the job well.
[1] An open source Bukkit server implementation, https://github.com/GlowstoneMC/Glowstone -> http://github.com/GlowstonePlusPlus/GlowstonePlusPlus
It is Ant all over again, with Groovy slowness and game rig hardware requirements to perform in a sane way. There is hardly any Android conference without build related performance improvement talks.
fwiw I think the Android toolchain being slow doesn't help. On the same conferences someone will also speak about how the new Android Gradle Plugin is X% faster, so it's not all Gradle's fault :) That said there's plenty more Gradle can do (and is doing) to improve performance. The startup cost to configure all tasks is definitely high on my annoyances list