-
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
Changes from all commits
a252643
2528beb
9f68fe1
79ce33f
aca870a
8611b87
600bab3
35a9481
2999a18
b5f3617
113105d
463ccc7
667aa7f
0819c7f
c3c6317
bab3dda
990e76e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,5 +38,4 @@ import org.apache.commons.math3.util.FastMath | |
args("--init-script", initScript.toString()) | ||
succeeds("help") | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
/* | ||
* Copyright 2022 the original author or authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package org.gradle.api.internal.artifacts; | ||
|
||
import org.gradle.api.artifacts.Configuration; | ||
import org.gradle.api.artifacts.Dependency; | ||
import org.gradle.api.artifacts.SelfResolvingDependency; | ||
import org.gradle.api.artifacts.dsl.DependencyHandler; | ||
import org.gradle.api.artifacts.dsl.RepositoryHandler; | ||
|
||
import java.io.File; | ||
import java.util.Collection; | ||
import java.util.Optional; | ||
import java.util.Set; | ||
|
||
public class GradleApiVersionProvider { | ||
|
||
public static final String GRADLE_API_SOURCE_VERSION_PROPERTY = "org.gradle.api.source-version"; | ||
|
||
public static Optional<String> getGradleApiSourceVersion() { | ||
return Optional.ofNullable(System.getProperty(GRADLE_API_SOURCE_VERSION_PROPERTY)); | ||
} | ||
|
||
public static void addGradleSourceApiRepository(RepositoryHandler repositoryHandler) { | ||
getGradleApiSourceVersion().ifPresent(version -> { | ||
String repositoryUrl = System.getProperty("gradle.api.repository.url", "https://repo.gradle.org/gradle/libs-releases"); | ||
repositoryHandler.maven(repo -> repo.setUrl(repositoryUrl)); | ||
}); | ||
} | ||
|
||
public static void addToConfiguration(Configuration configuration, DependencyHandler repositoryHandler) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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:
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. |
||
Dependency gradleApiDependency = getGradleApiSourceVersion() | ||
.map(repositoryHandler::gradleApi) | ||
.orElseGet(repositoryHandler::gradleApi); | ||
configuration.getDependencies().add(gradleApiDependency); | ||
} | ||
|
||
public static Collection<File> resolveGradleSourceApi(DependencyResolutionServices dependencyResolutionServices) { | ||
return getGradleApiSourceVersion() | ||
.map(version -> gradleApisFromRepository(dependencyResolutionServices, version)) | ||
.orElseGet(() -> gradleApisFromCurrentGradle(dependencyResolutionServices.getDependencyHandler())); | ||
} | ||
|
||
private static Set<File> gradleApisFromCurrentGradle(DependencyHandler dependencyHandler) { | ||
SelfResolvingDependency gradleApiDependency = (SelfResolvingDependency) dependencyHandler.gradleApi(); | ||
return gradleApiDependency.resolve(); | ||
|
||
} | ||
private static Set<File> gradleApisFromRepository(DependencyResolutionServices dependencyResolutionServices, String version) { | ||
addGradleSourceApiRepository(dependencyResolutionServices.getResolveRepositoryHandler()); | ||
Configuration detachedConfiguration = dependencyResolutionServices.getConfigurationContainer().detachedConfiguration(dependencyResolutionServices.getDependencyHandler().gradleApi(version)); | ||
return detachedConfiguration.resolve(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
/* | ||
* Copyright 2022 the original author or authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package org.gradle.groovy.scripts.internal; | ||
|
||
import org.gradle.api.internal.initialization.ClassLoaderScope; | ||
import org.gradle.internal.classloader.ClassLoaderVisitor; | ||
import org.gradle.internal.classpath.ClassPath; | ||
import org.gradle.internal.classpath.DefaultClassPath; | ||
|
||
import java.io.File; | ||
import java.net.MalformedURLException; | ||
import java.net.URI; | ||
import java.net.URISyntaxException; | ||
import java.net.URL; | ||
import java.util.Collection; | ||
import java.util.HashMap; | ||
import java.util.LinkedHashSet; | ||
import java.util.Map; | ||
import java.util.Set; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
import java.util.concurrent.ConcurrentMap; | ||
import java.util.function.Supplier; | ||
|
||
public class GroovyScriptClasspathProvider { | ||
private final ConcurrentMap<ClassLoaderScope, ClassPath> cachedScopeCompilationClasspath = new ConcurrentHashMap<>(); | ||
private final Map<ClassLoader, Set<File>> cachedClasspaths = new HashMap<>(); | ||
private final Set<File> gradleImplementationClasspath = new LinkedHashSet<>(); | ||
private ClassPath gradleApi; | ||
private ClassPath groovyJars; | ||
private final Supplier<Collection<File>> gradleApiJarsProvider; | ||
private final Supplier<Set<File>> groovyJarsProvider; | ||
|
||
public GroovyScriptClasspathProvider( | ||
ClassLoaderScope coreAndPluginsScope, | ||
Supplier<Collection<File>> gradleApiJarsProvider, | ||
Supplier<Set<File>> groovyJarsProvider | ||
) { | ||
this.gradleApiJarsProvider = gradleApiJarsProvider; | ||
this.groovyJarsProvider = groovyJarsProvider; | ||
this.gradleImplementationClasspath.addAll(classpathOf(coreAndPluginsScope.getExportClassLoader())); | ||
this.gradleImplementationClasspath.removeIf(file -> !file.getName().startsWith("gradle-")); | ||
} | ||
|
||
public ClassPath compilationClassPathOf(ClassLoaderScope scope) { | ||
return getGradleApi().plus(exportClassPathFromHierarchyOf(scope)); | ||
} | ||
|
||
private ClassPath getGradleApi() { | ||
if (gradleApi == null) { | ||
gradleApi = DefaultClassPath.of(gradleApiJarsProvider.get()); | ||
} | ||
return gradleApi; | ||
} | ||
|
||
private ClassPath getGroovy() { | ||
if (groovyJars == null) { | ||
groovyJars = DefaultClassPath.of(groovyJarsProvider.get()); | ||
} | ||
return groovyJars; | ||
} | ||
|
||
private ClassPath exportClassPathFromHierarchyOf(ClassLoaderScope scope) { | ||
Set<File> exportedClasspath = new LinkedHashSet<>(classpathOf(scope.getExportClassLoader())); | ||
exportedClasspath.removeAll(gradleImplementationClasspath); | ||
return DefaultClassPath.of(exportedClasspath); | ||
} | ||
|
||
private Set<File> classpathOf(ClassLoader classLoader) { | ||
Set<File> classpath = cachedClasspaths.get(classLoader); | ||
if (classpath == null) { | ||
Set<File> classpathFiles = new LinkedHashSet<>(); | ||
new ClassLoaderVisitor() { | ||
@Override | ||
public void visitClassPath(URL[] classPath) { | ||
for (URL url : classPath) { | ||
if (url.getProtocol().equals("file")) { | ||
classpathFiles.add(new File(toUri(url))); | ||
} | ||
} | ||
} | ||
|
||
@Override | ||
public void visitParent(ClassLoader parent) { | ||
classpathFiles.addAll(classpathOf(parent)); | ||
} | ||
|
||
private URI toUri(URL url) { | ||
try { | ||
return url.toURI(); | ||
} catch (URISyntaxException e) { | ||
try { | ||
return new URL( | ||
url.getProtocol(), | ||
url.getHost(), | ||
url.getPort(), | ||
url.getFile().replace(" ", "%20") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe 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. |
||
).toURI(); | ||
} catch (URISyntaxException | MalformedURLException ex) { | ||
throw new RuntimeException(ex); | ||
} | ||
} | ||
} | ||
}.visit(classLoader); | ||
cachedClasspaths.put(classLoader, classpathFiles); | ||
classpath = classpathFiles; | ||
} | ||
return classpath; | ||
} | ||
} |
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.