Skip to content

Conversation

wolfs
Copy link
Member

@wolfs wolfs commented Apr 5, 2022

This PRs explores what would be necessary to compile build scripts against a different Gradle API. It allows passing the Gradle API version via -Dorg.gradle.api.version=6.9 to the Gradle installation. Note that you also need a repository which contains an artifact for the Gradle API of this version with the coordinates org.gradle:gradle-api:<version>. You can specify the URL of the repository via -Dgradle.api.repository.url=....

Currently this works for Kotlin and Groovy build scripts. It also should work for Kotlin pre-compiled scripts and Kotlin and Groovy build logic. Though it doesn't work for Groovy pre-compiled build scripts. I suppose we should be able to make this work with Groovy pre-compiled build scripts as well, though I did run into some classloader problems there.

You can also declare a dependency in your build script on the Gradle API with a version by using gradleApi(<version>) instead of gradleApi().

Exploration doc: https://docs.google.com/document/d/1JB75X_Lt5BlubszqD0l10rHPqL89B2yd-yD39qizyDA/edit

@wolfs wolfs self-assigned this Apr 5, 2022
@wolfs wolfs force-pushed the wolfs/provider/compile-groovy-scripts-against-gradle-api branch from 1cb5d97 to aa9a6be Compare April 5, 2022 12:17
@wolfs wolfs force-pushed the wolfs/provider/compile-groovy-scripts-against-gradle-api branch from aa9a6be to 600bab3 Compare April 5, 2022 12:18
/**
* Creates a dependency on the API of the current version of Gradle.
*
* @return The dependency.
Copy link
Member

Choose a reason for hiding this comment

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

It would be good to add some Javadoc on the expected format of the parameter. Are snapshots accepted?

I think we need to mention that only older versions are accepted, right? What happens if I give it a newer or non-existent version?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, probably. Though I wouldn't go into polishing the Javadoc for this spike.

return new GroovyScriptClasspathProvider(
classLoaderScopeRegistry.getCoreAndPluginsScope(),
() -> {
String version = System.getProperty("org.gradle.api.version");
Copy link
Member

Choose a reason for hiding this comment

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

We should qualify what API version this is. I'd go with org.gradle.api.target-version or something.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I think we should qualify the property name. It feels like target doesn't really capture it. For me, target sounds more about the target runtime. How about org.gradle.api.source-version, since this is the version of the Gradle API we use for sources.

Copy link
Member Author

Choose a reason for hiding this comment

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

Renamed.

private void applyDependencies(Project project) {
DependencyHandler dependencies = project.getDependencies();
dependencies.add(API_CONFIGURATION, dependencies.gradleApi());
String apiVersion = System.getProperty("org.gradle.api.version");
Copy link
Member

Choose a reason for hiding this comment

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

We query this property in six different places by name. Instead it would be good to have a shared mechanism to expose this as an Optional<VersionNumber> instead. An @Inject-able service would be nice.

Copy link
Member Author

Choose a reason for hiding this comment

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

I extracted the common logic to a single class. I didn't make it a service, yet, since I am not sure what scope this would need to go.

repository.setUrl(repositoryUrl);
});
dependencies.add(API_CONFIGURATION, dependencies.gradleApi(apiVersion));
}
Copy link
Member

Choose a reason for hiding this comment

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

I think it would be good if the user could declare the API version they want to use via a gradleApi("6.9") call in the build script of the plugin. The above mechanism does not allow that.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, this can be improved. Note that with this solution you can already specify a different version of the gradle API by adding a constraint to the configuration, since the gradleApi(version) is a regular dependency and participates in dependency resolution. For the final solution, I am not sure what the right thing from dependency management is. defaultDependencies sounds it may be something in the right direction.

repository.setUrl(repositoryUrl);
});
dependencies.add(JavaPlugin.API_CONFIGURATION_NAME, dependencies.gradleApi(apiVersion));
}
Copy link
Member

Choose a reason for hiding this comment

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

This code is repeated, and should be abstracted/shared.

Copy link
Member Author

Choose a reason for hiding this comment

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

I extracted the common code.

? gradleApisFromCurrentGradle(dependencyFactory)
: gradleApisFromRepository(resolutionServices, dependencyFactory, version);
},
() -> ((SelfResolvingDependency) dependencyFactory.createDependency(DependencyFactory.ClassPathNotation.LOCAL_GROOVY)).resolve());
Copy link
Member

Choose a reason for hiding this comment

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

Looks like we are using local Groovy regardless of the Gradle version, but that will change. Shouldn't we resolve the Groovy version via the repository instead if the target API version is specified?

Copy link
Member Author

Choose a reason for hiding this comment

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

I wouldn't resolve a different Groovy/Kotlin version per API. If we'd do this, then we'd also need to upgrade the Kotlin/Groovy code and use a different compiler as well. I don't see why we can't compile the old code with the newer Kotlin/Groovy version.

Copy link
Member

Choose a reason for hiding this comment

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

I think that reasoning makes sense. Let's mention it here in a comment.


@Override
public Dependency gradleApi() {
return dependencyFactory.createDependency(DependencyFactory.ClassPathNotation.GRADLE_API);
Copy link
Member

Choose a reason for hiding this comment

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

Should we qualify the name of this constant now? Like, CURRENT_GRADLE_API? Or BUNDLED_GRALDE_API?

Copy link
Member Author

Choose a reason for hiding this comment

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

I wouldn't do this now. I would like to get rid of this notation and always use a real dependency.

…l/DependencyHandler.java

Co-authored-by: Lóránt Pintér <[email protected]>
succeeds("help")
}

@IgnoreRest
Copy link
Member

Choose a reason for hiding this comment

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

Is @IgnoreRest here for prototyping reasons?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I removed it now.

@wolfs wolfs requested review from asodja and lptr April 13, 2022 14:14
url.getProtocol(),
url.getHost(),
url.getPort(),
url.getFile().replace(" ", "%20")
Copy link
Member

Choose a reason for hiding this comment

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

Do we need to encode any other character here?

Copy link
Member Author

Choose a reason for hiding this comment

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

I copied that from the Kotlin version of this class which seems to work. I suppose we should have only one classpath provider and not two separate implementations doing mostly the same things.

abstract val sourceGradleApiVersion: Property<String>

init {
sourceGradleApiVersion.set(GradleApiVersionProvider.getGradleApiSourceVersion().orElse(null))
Copy link
Member

Choose a reason for hiding this comment

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

There are two init blocks in this class. Is that ok?

});
}

public static void addToConfiguration(Configuration configuration, DependencyHandler repositoryHandler) {
Copy link
Member

Choose a reason for hiding this comment

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

It's maybe not that important here, but could we return dependency here and the caller would do whatever it wants? Similar also for the repository.

For this case:

GradleApiVersionProvider.addToConfiguration(project.getConfigurations().getByName(API_CONFIGURATION), dependencies);
GradleApiVersionProvider.addGradleSourceApiRepository(project.getRepositories());

It feels a bit weird to me that we accept something as a parameter and then we add something to it, instead of that we provide a dependency/repository and the caller would do with it whatever it wants.

if (config.hasDeclarationDeprecations()) publicFunctionWithAnnotationsFlags to config.getDeclarationDeprecationBlock()
else publicFunctionFlags to ""
val sourceApiGradleVersion = GradleVersion.version(
GradleApiVersionProvider.getGradleApiSourceVersion().orElse(GradleVersion.current().version)
Copy link
Member

Choose a reason for hiding this comment

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

It seems GradleApiVersionProvider.getGradleApiSourceVersion().orElse(GradleVersion.current().version) is repeated multiple times, would it make sense to have methods on GradleApiVersionProvider provider like
GradleApiVersionProvider.getGradleApiSourceVersionOrCurrent()?

import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderConvertible
${if (supportsProviderConvertible) "import org.gradle.api.provider.ProviderConvertible" else ""}
Copy link
Member

Choose a reason for hiding this comment

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

Dang, I hope ifs for different source versions won't be too annoying in the future.

For this particular case, can we maybe just do: org.gradle.api.provider.* or are there too many types in that package?

@lptr lptr removed their request for review April 20, 2022 09:45
@lptr lptr added the in:api-evolution Bytecode upgrades, multi-gradle-version support, gradle-api label Jun 14, 2022
@lptr
Copy link
Member

lptr commented Jun 14, 2022

This has been merged into #20818.

@lptr lptr closed this Jun 14, 2022
@lptr lptr deleted the wolfs/provider/compile-groovy-scripts-against-gradle-api branch June 14, 2022 13:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

in:api-evolution Bytecode upgrades, multi-gradle-version support, gradle-api

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants