Skip to content
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

Lazy exact dependencies #2639

Merged
merged 11 commits into from
Oct 12, 2023
Merged

Conversation

CRogers
Copy link
Contributor

@CRogers CRogers commented Oct 10, 2023

Before this PR

Repo upgrades to Gradle 8 are failing because of errors when using baseline-java-versions and baseline-exact-dependencies together:

> Failed to apply plugin class ‘com.palantir.baseline.plugins.javaversions.BaselineJavaVersion’.
The value for property ‘languageVersion’ is final and cannot be changed any further.

The source of the problem is that in Gradle 8.3, the values of various Java toolchain related attributes are finalised for a Configuration the moment that Configuration is created. baseline-exact-dependencies was using getConfigurations().getByName(...) when configuring source sets, which means that it was forcing the creation of Configurations and so finalising the values of these attributes before baseline-java-versions had a chance to configure the languageVersion etc.

After this PR

==COMMIT_MSG==
baseline-exact-dependencies is now far more lazy around Configuration creation in order to support Gradle 8.
==COMMIT_MSG==

Possible downsides?

@CRogers CRogers requested a review from felixdesouza October 10, 2023 16:05
@changelog-app
Copy link

changelog-app bot commented Oct 10, 2023

Generate changelog in changelog/@unreleased

What do the change types mean?
  • feature: A new feature of the service.
  • improvement: An incremental improvement in the functionality or operation of the service.
  • fix: Remedies the incorrect behaviour of a component of the service in a backwards-compatible way.
  • break: Has the potential to break consumers of this service's API, inclusive of both Palantir services
    and external consumers of the service's API (e.g. customer-written software or integrations).
  • deprecation: Advertises the intention to remove service functionality without any change to the
    operation of the service itself.
  • manualTask: Requires the possibility of manual intervention (running a script, eyeballing configuration,
    performing database surgery, ...) at the time of upgrade for it to succeed.
  • migration: A fully automatic upgrade migration task with no engineer input required.

Note: only one type should be chosen.

How are new versions calculated?
  • ❗The break and manual task changelog types will result in a major release!
  • 🐛 The fix changelog type will result in a minor release in most cases, and a patch release version for patch branches. This behaviour is configurable in autorelease.
  • ✨ All others will result in a minor version release.

Type

  • Feature
  • Improvement
  • Fix
  • Break
  • Deprecation
  • Manual task
  • Migration

Description

baseline-exact-dependencies is now far more lazy around Configuration creation in order to support Gradle 8.

Check the box to generate changelog(s)

  • Generate changelog entry

// \-- testCompile extendsFrom(compile)
// We therefore want to look at only the dependencies _directly_ declared in the implementation
// and compile configurations (belonging to our source set)
Configuration implCopy = project.getConfigurations()
Copy link
Contributor Author

@CRogers CRogers Oct 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had originally tried to put this in a beforeResolve, where it makes most sense to live. But this causes GCV to have ConcurrentModificationExceptions, as it iterates over all the Configurations and then resolves them while this section of code creates more Configurations, meaning I had to keep it in afterEvaluate

Object value =
compileClasspath.get().getAttributes().getAttribute(attribute);
conf.getAttributes().attribute((Attribute<Object>) attribute, value);
});
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no way to lazily add a Map of attributes (aka lazily conditionally add an attribute). So it too must unfortunately live in an afterEvaluate.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can get around this particular usage of afterEvaluate since we don't actually need to copy the map, just the attribute from the compileClasspath => we know the attribute we want and can do use the attribute provider method for a single attribute key.

Additionally, I think this code is only really effective for gradle versions < 5.6? => if our min version is high enough, this bit of code is redundant as it's already covered by

if (GradleVersion.current().compareTo(GradleVersion.version("5.6")) >= 0) {
attributes.attribute(
LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE,
project.getObjects().named(LibraryElements.class, LibraryElements.CLASSES));
}
?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was the first thing I tried and it didn't work. Sometimes the library elements attribute does not exist on source configuration. This then causes attribute(Attribute, Provider<T>) to throw as per the javadoc:

This method can NOT be used to discard the value of an property. Specifying a null provider will result in an IllegalArgumentException being thrown. When the provider has no value at finalization time, an IllegalStateException - regardless of whether or not a convention has been set.

Copy link
Contributor

@felixdesouza felixdesouza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not everything is actionable, or necessarily for this PR, but would be interesting to see, as it could simplify the class a lot

@@ -21,7 +21,7 @@

public final class GradleTestVersions {
public static final ImmutableList<String> VERSIONS =
ImmutableList.of(Baseline.MIN_GRADLE_VERSION.getVersion(), "7.1.1", "7.3");
ImmutableList.of(Baseline.MIN_GRADLE_VERSION.getVersion(), "7.6.2", "8.4");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you want this to be 8.4 or 8.3?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might as well use 8.4 as that is latest.

@@ -61,6 +61,7 @@ tasks.test.dependsOn tasks.publishToMavenLocal
test {
environment 'CIRCLE_ARTIFACTS', "${buildDir}/artifacts"
environment 'CIRCLE_TEST_REPORTS', "${buildDir}/circle-reports"
systemProperty 'ignoreDeprecations', 'true'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how come?

Copy link
Contributor Author

@CRogers CRogers Oct 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we discussed this internally some time ago - I actually want to set it on everything:

should we just ignoreDeprecations for every gradle project? Some reasons why:

  1. Deprecations often come from other repos we end up running as part of tests. For example, in [internal-repo] there are some tests that run tasks from [other-internal-repo], that then fail with deprecations warnings due to [other-internal-repo]'s tasks. Since we can’t fix them in the [internal-repo], we end up adding --warning-mode=none to every test, losing deprecation warnings anyway. Or worse, people add System.setProperty('ignoreDeprecations', 'true') to individual tests as nebula suggests, making the build flake.
  2. It’s impossible to see what caused deprecation warnings using nebula-test. The only way I found out to get good info is to add the buildscan plugin to the test.
  3. We don’t tend to fix deprecations until a major version remove the feature anyway.
  4. This leads to our gradle plugin repos being super out of date for their own gradle wrapper, meaning they get wrecked by stuff like not supporting jackson 2.15.2's java 20 code.

In this case, we get deprecation warnings about GCV not working with Gradle 9 when running in 8.3/8.4 - something we can't even fix in this repo.

// Specifically, we need to pick up the LIBRARY_ELEMENTS_ATTRIBUTE, which is being configured on
// compileClasspath in JavaBasePlugin.defineConfigurationsForSourceSet, but we can't reference
// it
// directly because that would require us to depend on Gradle 5.6.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably not for this PR, but surely we can raise the min gradle version? does the concern here still hold without it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose it just comes down to directly referencing the attribute like we did in line 117? instead of copying all the attributes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could probably aim to do some cleanup of minimum gradle versions (including the horrifying GradleWorkarounds files that exist in many repos) once we move to Gradle 8.

Object value =
compileClasspath.get().getAttributes().getAttribute(attribute);
conf.getAttributes().attribute((Attribute<Object>) attribute, value);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can get around this particular usage of afterEvaluate since we don't actually need to copy the map, just the attribute from the compileClasspath => we know the attribute we want and can do use the attribute provider method for a single attribute key.

Additionally, I think this code is only really effective for gradle versions < 5.6? => if our min version is high enough, this bit of code is redundant as it's already covered by

if (GradleVersion.current().compareTo(GradleVersion.version("5.6")) >= 0) {
attributes.attribute(
LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE,
project.getObjects().named(LibraryElements.class, LibraryElements.CLASSES));
}
?

Comment on lines 155 to 168
// For Gradle 6 and below, the compile configuration might still be used.
maybeCompile.ifPresent(compile -> {
Configuration compileCopy = compile.copy();
// Ensure it's not resolvable, otherwise plugins that resolve all configurations might have
// a bad time resolving this with GCV, if you have direct dependencies without corresponding
// entries in
// versions.props, but instead rely on getting a version for them from the lock file.
compileCopy.setCanBeResolved(false);
compileCopy.setCanBeConsumed(false);

project.getConfigurations().add(compileCopy);

conf.extendsFrom(compileCopy);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redundant? our min version is effectively 7.0 if you apply all the plugins, I think it's 6.7 if you don't apply javaversions

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, tbh I'd rather just leave it while we still have some repos on Gradle 6 (which I should burn down as part of this work).

Comment on lines +187 to +188
AtomicBoolean projectsEvaluated = new AtomicBoolean();
project.getGradle().projectsEvaluated(g -> projectsEvaluated.set(true));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

an aside, do you want some sort of similar check in the flattener?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


TaskProvider<CheckUnusedDependenciesTask> sourceSetUnusedDependencies = project.getTasks()
.register(
checkUnusedDependenciesNameForSourceSet(sourceSet), CheckUnusedDependenciesTask.class, task -> {
task.dependsOn(sourceSet.getClassesTaskName());
task.setSourceClasses(sourceSet.getOutput().getClassesDirs());
task.dependenciesConfiguration(explicitCompile);
task.getDependenciesConfigurations().add(explicitCompile);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wacky idea, not actually sure, but can't we just use the getHierarchy() method of the configuration we're initially copying? and then take the first one? do we actually need/want a copy, or did we only take a copy because that was the only way to get the first level without the other dependencies from super configurations?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because with that, you can just do a conf.map(c -> Iterables.getFirst(c.getHierarchy())), it's also a LinkedSet populated in DFS manner so in theory it should have what we want assuming we don't want/need a separate copy

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really sure I follow here, the first time I saw this plugin was this PR. I'd like to focus on getting this to work at all with Gradle 8 first, if we want to improve it/change it in some way it seems less risky to spend time on that intentionally in another change.

@felixdesouza
Copy link
Contributor

👍

@bulldozer-bot bulldozer-bot bot merged commit f6e4309 into develop Oct 12, 2023
@bulldozer-bot bulldozer-bot bot deleted the callumr/lazy-exact-dependencies branch October 12, 2023 11:38
@svc-autorelease
Copy link
Collaborator

Released 5.24.0

CRogers added a commit that referenced this pull request Oct 13, 2023
bulldozer-bot bot pushed a commit that referenced this pull request Oct 13, 2023
Revert "Lazy exact dependencies"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants