-
Notifications
You must be signed in to change notification settings - Fork 459
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal: Break existing DSL to match gradle idioms #32
Comments
This is only true because spotless does not allow them to do differently in a convenient manner ;) It's all about more incremental builds: What you really want is to apply the
I did not think about the ordering issue, please scratch this proposal of mine :)
It is the usual way to define defaults: When called, it applies to all elements already in the container, as well as all those added in the future. Similar things can be done with
Good point, that distinction is important. We can add a type and remove the magic meaning of 'java':
When no type is given, we use a standard format.
I don't understand how it would be harder to write. Also, contributors are likely to be Gradle users and they expect domain object containers. This is how all Gradle core DSLs work. You might not realize it, but |
I would still automatically add the But the type argument does add great new possibilities for extension. Someone could for instance add a special formatter for C++:
|
I still don't understand when the all block executes.
The python format will definitely end up with both
That's a cool feature. Obviously you understand gradle well, and I think you understand the issues I raised with the new DSL. If you or somebody else submits a PR with a working example of how JUnit should rewrite their build.gradle, and the new syntax is as clear or clearer, I'll merge and release. Not because I'm convinced it's a good idea, but because I trust the gradle team and sometimes I'm wrong. Just for comparison: cpp(CppFormat) {
formatIncludes()
// knowledge needed to write CppFormat:
// Spotless API
// Gradle domain object containers
} Another way to extend format 'cpp', {
CppFormat.applyTo(this, {
formatIncludes()
})
// knowledge needed to write CppFormat:
// Spotless API
// Function calls
}
I'd be curious to know what percentage of the plugins in the plugin portal define a domain object container. I know the rust community has a mechanism to survey feature usage across their ecosystem... But regardless of what plugin developers know, certainly a survey of every person who ran Every ecosystem has suppliers and consumers. Idiomatic java handles an error case by throwing a checked exception. If you're supplying an API, checked exceptions let you concisely describe what can go wrong in your code. But if you're just consuming an API to ship an application and you need to call a method that throws an exception but your superclass doesn't let you rethrow, then statistically speaking you'll probably just swallow it or log it to a mystery stream. If you're a build engineer, domain objects enable unmatched brevity. But for people who are just trying to tinker with the build to make a small change, they traded a little verbosity for a lot of magic. These are the things I like about gradle:
These are the parts of gradle that I fight with:
I see that domain objects allow another kind of convention-over-configuration, but I don't see them as an innate virtue. I still don't understand whether the "all{}" block will execute before or after the "misc{}" block. I don't understand if it applies to all blocks everywhere, or just the ones in the current project, or maybe just its children? And I don't understand how they're possibly simpler for my users than the existing DSL. But I'm wrong all the time, maybe I'm wrong here too. |
It is executed at the point where you write it. But it also adds a callback that will be executed for all future additions to the container. You generally use the all block first to set up some defaults. Or you might use it to register a callback for creating other tasks based on the configuration. Or maybe you just want to log some information. The point is you don't know what users might want to do with your configuration objects, so the domain object container provides all the flexibility they need out of the box. A normal user wouldn't have to write this:
The author of the
But for advanced users, the This is what often happens in build teams of bigger enterprises. They want to build their own conventions, without completely reinventing the wheel. Gradle makes this easy, as long as plugin authors follow the best practices.
We neeed to look at two different target audiences: Plugin authors/build engineers who build plugins and conventions. And on the other side build users who just consume those plugins/conventions. The former will have a good understanding of these concepts and they are the ones building on top of your API. The latter need one thing first and foremost: Consistency. That's why all Gradle DSLs use the same patterns like domain object containers and that's why all plugins should follow these conventions. They make it easier both for integrators as well as the end user.
I think this is where we will have to just agree to disagree :) I think Gradle's use of the same patterns everywhere makes it easier for users trying to find their way around. Build scripts are easier to read when they contain less imperative-style code. You have other preferences and that's completely fine. Gradle allows you to use a more imperative coding style if that makes it a bette experience for you. But as a plugin author, I just want to encourage you to follow the examples set by the core plugins, so users will have less mental overhead when configuring your plugin. I'm happy you are considering pull requests for this and I hope somebody will pick up the task. I will definitely provide help and feedback, so please don't hesitate to ask :) |
I agree with this principle, which is why (coupled with your experience) I'll take a PR even if I don't understand the advantages of a domain object container.
I also prefer declarative code to imperative code, same as you. But a list is just as declarative as a set. If possible, it's best to make it so order doesn't matter, but often it does. And that's not always a bug - the fact that ordering matters is the only reason that a convention plugin can setup some defaults which can later be partially overwritten by a buildscript. Domain object containers obscure ordering. Spotless has a declarative-looking DSL that's easy to read, but it is order-dependent. It is easier to write a But since Spotless behaves mostly declaratively, I'll merge and release a PR even if I still don't know what this would do.
|
It's about to be last call for this (3.0 is about ready). I don't understand the benefit, but I can be swayed by code :) |
First off, let's review how Spotless works. Each FormatTask has an
Iterable<File>
, and a list ofFormatterStep
.FormatterStep
is really just aFunction<String, String>
- take an unformatted string, return a formatted string. So to create a new task, you specify the files you want to format, then the list of steps you want to take to format them.Right now, the spotless DSL works like this:
Over in #31, @oehme made a suggestion to change the DSL to be more gradle idiomatic, which i'm moving here. A first proposal is this:
There's three changes wrapped up in this:
format 'misc', {}
, we can now just saymisc {}
, which allows us to also use theall{}
block to apply some logic to all the formats.eclipseFormatFile
if my block is calledjava
but notjavaSource
?Imo, 1 & 2 do not improve Spotless. 3 definitely does, but the tradeoff is that we have to add the Gradle-specific container concept. Gradle has done a fantastic job of taking the "convention-over-configuration" idea, mixing it with a scripting language, and fostering a vibrant plugin ecosystem. But it's also, imo, made a few mistakes. I'm skeptical about whether the container idea adds enough value to be worth learning, and asking Spotless' users to learn.
Certainly there are ways to improve Spotless' design, but I'm hesitant to break existing user's builds unless it makes a big difference for future adopters. My ears are open, but I don't see a clear enough win to justify the cost.
The text was updated successfully, but these errors were encountered: