This message was deleted.
# dependency-management
s
This message was deleted.
👀 1
➕ 1
m
The advantage is that attributes are then strongly typed, and that you can define constants on the attribute type for its values for example (similarly to the
<http://Usage.XXX|Usage.XXX>
constants.
You cannot use a
Bundling
attribute where it would expect a
Usage
. If both were stringly typed then you could mix things up.
v
But nothing prevents me to do
attribute(Usage.USAGE_ATTRIBUTE, objects.named(Bundling.EXTERNAL))
or with static imports
attribute(USAGE_ATTRIBUTE, objects.named(EXTERNAL))
m
In practice, strongly typed attributes are a PITA (because of classloading and such).
this would fail compilation (
attribute(Usage.USAGE_ATTRIBUTE, objects.named(Bundling.EXTERNAL))
)
(or fail at runtime in Groovy ;))
v
How should it fail compilation?
Bundling.EXTERNAL
is just a
String
m
because it expects a
Bundling
as the 2d argument
not a string
v
Same as
attribute(Usage.USAGE_ATTRIBUTE, objects.named("external")
You mean it expects a
Usage
and it is a
Usage
, the type is inferred from the first argument
m
(your code is valid kotlin code, but in java,
named
takes the
Named
type as the 1st arg)
v
Same as
attribute(Usage.USAGE_ATTRIBUTE, objects.named<Usage>(Bundling.EXTERNAL))
m
yes, that's the Kotlin DSL doing that
v
Yeah, in Java it would be
attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.class, Bundling.EXTERNAL))
m
correct
which would make it more obvious you make a mistake
v
A slight bit, yeah, but I can also do
attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.class, "external"))
which then is not so obvious and sometimes is even handy
m
there are other supposedly advantages of using
Named
, which is that the objects can have more methods to be used in disambiguation rules for example
v
For example if you need a
MachineArchitecture
for which no constant exists like
arm32
m
but really the idea was that you should avoid stringly typed attributes, and use strongly typed versions. The implementation of it, though, is really not great.
v
Ah, so if you have more methods or properties and then use
objects.newInstance
or whatever instead.
That could make sense, I see
m
correct
I think there are a few examples like that in Gradle's codebase
but in the end, it's dehydrated when going to the module files
and effectively, everything becomes strings.
v
Yeah, that's why I wondered
Also created https://github.com/gradle/gradle/issues/27816 to hopefully get some explanatory docs about it 🙂
⭐ 1
m
if you will, the questions are basically the same as "should I pass a boolean as a method argument, or an enum which has explicit values?"
v
Practically my next question if I have you around. 😄
How would you go about a boolean attribute? Would you make it a
Boolean
attribute or not?
m
in one version you have "simpler" code with
false, true, false, false
and the other you get
Awesome.FALSE, etc...
I have used both. In lots of cases I used a
Boolean
type because it's much easier to deal with
and then I cross fingers that I don't need a 3rd value in the future
v
Actually, the documentation uses
String
for a custom attribute 😄
m
depends if the value set is open or not I guess
v
Effectively it is always open as we have seen 😄
m
I'm not judging. Personally I would have dropped strongly typed attributes because they are a pain to deal with, and also source of performance issues 🙂
v
👌
Ah, there is indeed one attribute with added methods
OperatingSystemFamily has isWindows and isLinux and isMacOs
Thanks for your insights, I guess for now I go with
String
and
Boolean
. At least the
String
can probably anytime enhanced to a full type as it is a
String
in the GMM anyway
m
to me this is a tradeoff between the "pureness" of the model, which should be strongly typed, and the implementation costs. In this case I think the costs are vastly superior to the benefits, so I would have dropped that, but it's too late now 🙂
v
Never too late, major version => breaking change 😄
➖ 1
This is Gradle, not Maven 😄
m
don't hate your users 😄
👏 1
v
They are not mine 🤷‍♂️ 🙂
Oh, my, f***ing god. This typed vs. primitive-typed just cost me another 10 hours of headache or so. If attributes like for example
OPERATING_SYSTEM_ATTRIBUTE
are set by a non-cached component metadata rule or on the first run with a cached rule, they are properly typed
OperatingSystemFamily
. But if the type is coming from the GMM, or from the cached state of a cached rule, it is just
String
typed, which makes it significantly harder to get the value from an attributes container for example when filtering on resolved artifacts to find those that do not have a value for an attribute or have a specific value but not none. 🤯
m
yes. That's a nightmare to deal with.
and you've probably not faced the case where you have 2 different classes for a custom type, because if the plugin is declared in each script (as it should), then the types are different (Foo cannot be cast to Foo).
v
Oh boy, well that's the typical problem you have. Same when in the past you applied a plugin in one legacy script plugin and tried to configure it from the other, not finding the tasks by type. So another point for not using custom types, but primitives if you can. 😄
m
hahahahahahahah!!! (not) Just got caught too. Code as simple as
attributes.getAttribute(Category.CATEGORY_ATTRIBUTE)
just returns
null
when working on
ResolvedVariantResult
, because what you get there is a stringly typed attribute.
v
No, what you get there can be either
If you have a component metadata rule (either non-cached or cached but actually run) you get the strongly typed attribute.
m
yes, depends on whether the dependency was fetched from a repo, or a local project too
v
Only if it is coming from GMM or from cache, then it is stringly tpyed