When using a BOM `implementation platform(foo)` in...
# community-support
v
When using a BOM
implementation platform(foo)
in a multi module project, do I need to add this line to every
build.gradle
where a "child" of the BOM is added? (Seems to be the case) Isn't there a central place so I don't need to spam it everywhere?
v
so just to answer, it needs to be in every build.gradle (that uses a dependency that bom versions)?
c
yes... I believe that is the case... but assuming you have more than one thing that you need standardized then you could roll all of that in to a convention plugin and reduce it all to a single plugin application.
v
I try to avoid convention plugin, seems too heavyweight of a thing & it slows down build if its built from source, just to hold constants basically
c
I mean it slows down the build if you're working on the build and constantly invalidating the compiled plugins... but otherwise 🤷
v
I read a blogpost some time ago from reputable folks saying composite builds were like 20% slower on a clean build
so I'd need to have a binary variant, and then it needs publishing etc .. overengineering at it's finest
c
I mean... I've never noticed any overhead to be a problem. I have both convention plugins and some custom plugins under build-logic and I embrace the approach because it allows me to simplify the actual build.gradle files that are touched on a more regular basis. I'll trade quite a bit of build time for maintainability in that sense - especially since most of our build time is either dominated by test execution, or erased by incremental build logic. It also allows me to have most of the complex build logic implemented in Java, which is much more our lingua-franca.
v
I thankfully dont need such thing, only thing being having the jdk version but
allprojects
seems to work fine
v
"seems" is the right word. It is highly discouraged bad practice.
allproject { ... }
and also all other forms of cross-project configuration is immediately introducing project coupling which works against or disables some of the more sophisticated Gradle features or optimizations. And they make your builds harder to understand and harder to maintain. Whether a composite build is recognizably slower on clean build, depends on the project setup. If you for example use build cache, the most time-intense tasks can be reused even on clean build. Most time until task execution you save when using configuration cache, as the previous configuration result can simply be reused. Also, how often do you do a clean build? If it is only on CI, who cares if it needs 20 seconds more for building your convention plugin? On local incremental build, you will hardly recognize any longer build time. On the other hand, you can much better maintain your build logic, even test your build logic properly and so on, and save much headache of future-you. --- Having said all that, if you anyway use that bad-practice
allprojects { ... }
just add the BOM-using there, if it is applied to projects where it is not needed, it doesn't hurt either. Or to stack more bad-practice on top, you can probably also use the BOM in your "bottom-most" project with
api
instead of
implementation
and then it should propagate to the other projects. 🤷‍♂️
v
honestly, wanting to have a global config is very normal, and forcing people to implement a whole plugin is too heavy handed, if cross project coupling is such a issue, then there should be a high level api that allows for all to be happy; thats why most are still using
allprojects { .. }
imo, not to spite gradle, but because its too much work
v
> wanting to have a global config is very normal Yes, and using a convention plugin for that is the idiomatic and really simple way to do it. Most people saying or thinking otherwise that I talked to in the past just didn't gave them any chance or try. 🙂 > forcing people to implement a whole plugin is too heavy handed You can use the almost exact same syntax if you use a precompiled script plugin. So you just move your code to a different file, and add a minimal settings script and build script. How is that heavy handed? Also, noone forces you to do so. Everyone can use whatever works best for them, but they have to live with the consequences. And discouragements and best practices do not exist for no reason. > thats why most are still using
allprojects { .. }
imo Please show the statistic that shows this. I seldomly see a build nowadays that uses
allprojects { ... }
, and if the maintainers are told that this is not a good idea, most agree and switch to convention plugins. > but because its too much work It is not 🙂 Most people using
allprojects { ... }
I talked to in the past just were not aware that it is bad practice and that there is a much better alternative.
c
Since we're on the subject I will admit to one lingering usage of
allprojects
that I cannot find an alternative for:
Copy code
allprojects {
  afterEvaluate { Project p ->
    if (p.getPlugins().hasPlugin(BasePlugin)) {
      assert p.getPlugins().hasPlugin(BaseConvention)
    }
  }
}
The main objective here is to ensure that this other bit of bad practice is executed:
Copy code
project.afterEvaluate(p -> p.getPlugins().configureEach(plugin -> {
  Collection<Class<? extends Plugin>> conventions = KNOWN_CONVENTIONS.entrySet().stream().filter(e -> e.getKey().isAssignableFrom(plugin.getClass())).flatMap(e -> e.getValue().stream()).collect(Collectors.toList());
  if (!conventions.isEmpty() && conventions.stream().noneMatch(convention -> project.getPlugins().hasPlugin(convention))) {
    throw new IllegalStateException("Cannot apply " + plugin.getClass() + " directly. You must use a registered convention plugin: " + conventions);
  }
}));
This allows me to register all my convention plugins and fail the build if a plugin for which there is a convention is applied directly. With some care I think the afterEvaluate could be eliminated... but I cannot think of a way to "force" the application of a plugin everywhere.
v
Well, the first snippet alone is already a bad-practice hattrick. 😄
allprojects
=> bad
afterEvaluate
=> bad
getPlugins()
=> bad (see its JavaDoc) 😄
A better way probably would be to use a settings plugin and either there apply the plugin to all projects, or there verify in a
gradle.projectsEvaluated { ... }
that the plugin that you expect is applied. I did an auto-apply of our "base" plugin in the settings script some time ago, but changed it to just there verify the base plugin is applied to every project, as at some point our "base" plugin started to apply a 3rd party plugin that itself uses bad-practice
afterEvaluate
and at the point where it was applied this was no longer allowed.
Something like
Copy code
gradle.projectsEvaluated {
    gradle.allprojects {
        if (pluginManager.hasPlugin("base")) {
            assert pluginManager.hasPlugin("your.base")
        }
    }
}
or similar
c
ooohh... I never thought of that approach... I should come here more often.
👌 1