-
Notifications
You must be signed in to change notification settings - Fork 5k
Allow compilation of build scripts against a different Gradle API version #20373
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
Allow compilation of build scripts against a different Gradle API version #20373
Conversation
1cb5d97
to
aa9a6be
Compare
aa9a6be
to
600bab3
Compare
subprojects/core-api/src/main/java/org/gradle/api/artifacts/dsl/DependencyHandler.java
Outdated
Show resolved
Hide resolved
/** | ||
* Creates a dependency on the API of the current version of Gradle. | ||
* | ||
* @return The dependency. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)); | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)); | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/KotlinBuildScriptIntegrationTest.kt
Outdated
Show resolved
Hide resolved
subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/accessors/AccessorFragments.kt
Outdated
Show resolved
Hide resolved
…l/DependencyHandler.java Co-authored-by: Lóránt Pintér <[email protected]>
succeeds("help") | ||
} | ||
|
||
@IgnoreRest |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
The evaluation of the source Gradle API version needs to be lazy.
depending on the source Gradle API version.
url.getProtocol(), | ||
url.getHost(), | ||
url.getPort(), | ||
url.getFile().replace(" ", "%20") |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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 ""} |
There was a problem hiding this comment.
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?
This has been merged into #20818. |
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 coordinatesorg.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 ofgradleApi()
.Exploration doc: https://docs.google.com/document/d/1JB75X_Lt5BlubszqD0l10rHPqL89B2yd-yD39qizyDA/edit