Closed hakanai closed 2 years ago
This looks indeed like a bug.
I will fix this before releasing the java 19 update.
I think the easiest fix is to modify the convention mapping to make a clone: https://github.com/policeman-tools/forbidden-apis/blob/89dd05342ee87cbb9b8721dcb9b9fe6840315443/src/main/resources/de/thetaphi/forbiddenapis/gradle/plugin-init.groovy#L48
Should be trivial to do with some instanceof check or a simple call to clone().
Hi,
it is not so easy to fix, because cloning would prevent the "add" from working, but what I figured out: If you use signaturesURLs +=
, it works correctly. But if you use "add", it will of course modify the shared one. Making a clone won't help, as you would modify the clone returned by the conventions but it is not used afterwards.
Actually there's a test for the Groovy behaviour and it works (see the test gradle project). It adds "jdk-system-out" in groovy syntax only to the main source set and the test one stays unmodified. In your example if you change the code to use +=
it works:
forbiddenApisTest {
signaturesURLs += loadResource("commons-lang3-extra.txt")
}
I have no idea about Kotlin, but in fact the Groovy DSL clones correctly. So it looks like affecting kotlin only. At moment I am not sure if this is a bug at all. Other tasks in Gradle behave the same. It also affects classpath. Modern plugins implemented after Kotlin now use the Property classes (but those wree used to prevent this kind of problems). Forbiddenapis is unfortuantley therefore not 100% compatible to the Kotlin DSL.
I can't change to SetProperty at the moment because of Gradle 3.x minimum dependency, so I would leave this to be fixed later in forbiddenapis v4.
A final fix would be to use XxxProperty for all settings, but this would require upgrading gradle, so needs to be delayed. I will put this on the backlog.
Workaround:
+=
in Groovy DSLsignaturesURLs = signaturesURLs.toMutableSet()
in Kotlin DSLI will think about it a bit more, but the change to properties requires more work - and would finally remove the (outdated) convention mapping technique.
I finally found a solution to fix in PR #209: I studied other old plugins and the trick is the following: Instead of returing a new instance in the convention, have some collection available as template. When the convention is executed, it takes the template, adds the extensions's data to it (addAll
for Collections
, source
for ConfigurableFileCollections) and returns the template.
I added a Gradle-like +=
and a Kotlin-like with add
to the sample project which gets tested. I verified that in both cases the data is kept and the extension is not modified.
I may release a bugfix version 3.4.1 this week or maybe a 3.5. I have one othe rissue to fix.
Consider the case where your test configuration has commons-lang3, but main does not. So you think you're adding the signatures only to the test task:
But then you get a build failing in
forbiddenApisMain
because it can't find commons-lang3'sStringUtils
.This is flaky behaviour, which is why we hadn't noticed it immediately: If
forbiddenApisMain
runs first, it will work fine, because the configuration forforbiddenApisTest
hasn't been evaluated yet. But ifforbiddenApisTest
runs first, it configuressignaturesURLs
, which turns out to be the same collection used byforbiddenApisMain
. Since forbidden APIs depends on the result of compilation, and main always finishes compiling before test, this would only happen in larger projects where there are so many tasks running concurrently that it's possible for the test one to run first.From quickly reading the code, it would look like each task uses its own collection, but it seems that the convention mapping then stomps all the references with a reference to the collection in the shared extension. So all tasks actually use the same collection object, and thus using
+=
/add
isn't actually safe right now.The workaround is to copy the collection first:
Odds are, the same issue exists with all other collections, including
bundledSignatures
. But withbundledSignatures
it's less noticeable because you're going to be using the same JDK on main and test.Ideally, these collections would actually be
SetProperty
, with the initial contents of the set coming from the shared extension. But of course, that changes the public API. :(