Skip to content

Commit e1ab08e

Browse files
committed
early draft
1 parent 68b8b98 commit e1ab08e

File tree

3 files changed

+376
-2
lines changed

3 files changed

+376
-2
lines changed

build.gradle

+3
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@ dependencies {
104104
grapesImplementation "org.apache.ivy:ivy:${versions.ivy}", {
105105
transitive = false
106106
}
107+
grapesImplementation 'org.apache.maven:maven-resolver-provider:4.0.0-rc-2'
108+
grapesImplementation "org.apache.maven.resolver:maven-resolver-supplier-mvn4:2.0.5"
109+
grapesImplementation "org.slf4j:slf4j-simple:2.0.16"
107110

108111
loggingImplementation "org.fusesource.jansi:jansi:${versions.jansi}"
109112

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,368 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package groovy.grape
20+
21+
import groovy.transform.AutoFinal
22+
import groovy.transform.CompileDynamic
23+
import groovy.transform.CompileStatic
24+
import groovy.transform.EqualsAndHashCode
25+
import groovy.transform.NamedParam
26+
import groovy.transform.NamedParams
27+
import org.codehaus.groovy.reflection.ReflectionUtils
28+
import org.eclipse.aether.RepositorySystem
29+
import org.eclipse.aether.RepositorySystemSession
30+
import org.eclipse.aether.artifact.Artifact
31+
import org.eclipse.aether.artifact.DefaultArtifact
32+
import org.eclipse.aether.collection.CollectRequest
33+
import org.eclipse.aether.graph.Dependency
34+
import org.eclipse.aether.graph.DependencyFilter
35+
import org.eclipse.aether.repository.LocalRepository
36+
import org.eclipse.aether.repository.RemoteRepository
37+
import org.eclipse.aether.repository.RepositoryPolicy
38+
import org.eclipse.aether.resolution.ArtifactResult
39+
import org.eclipse.aether.resolution.DependencyRequest
40+
import org.eclipse.aether.supplier.RepositorySystemSupplier
41+
import org.eclipse.aether.util.artifact.JavaScopes
42+
import org.eclipse.aether.util.filter.DependencyFilterUtils
43+
44+
/**
45+
* Implementation supporting {@code @Grape} and {@code @Grab} annotations based on Maven.
46+
*/
47+
@AutoFinal
48+
@CompileStatic
49+
class GrapeMaven implements GrapeEngine {
50+
// weak hash map so we don't leak loaders directly
51+
final Map<ClassLoader, Set<MavenGrabRecord>> loadedDeps = [] as WeakHashMap
52+
/** Stores the MavenGrabRecord(s) for all dependencies in each grab() call. */
53+
final Set<MavenGrabRecord> grabRecordsForCurrDependencies = [] as LinkedHashSet
54+
boolean enableGrapes = true
55+
final List<RemoteRepository> repos = [
56+
new RemoteRepository.Builder("central", "default", "https://repo.maven.apache.org/maven2/").build()
57+
]
58+
59+
@Override
60+
grab(String endorsedModule) {
61+
grab(group: 'groovy.endorsed', module: endorsedModule, version: GroovySystem.getVersion())
62+
}
63+
64+
@Override
65+
grab(Map args) {
66+
args.calleeDepth = args.calleeDepth ?: DEFAULT_CALLEE_DEPTH + 1
67+
grab(args, args)
68+
}
69+
70+
@Override
71+
grab(Map args, Map... dependencies) {
72+
println "GrapeMaven.grab" // TODO remove debug
73+
try (RepositorySystem system = new RepositorySystemSupplier().get()) {
74+
def localRepo = new LocalRepository(getGrapeCacheDir().toURI())
75+
dependencies.each { Map dep ->
76+
try (RepositorySystemSession.CloseableSession session = system
77+
.createSessionBuilder()
78+
.withLocalRepositories(localRepo)
79+
.setChecksumPolicy(RepositoryPolicy.CHECKSUM_POLICY_FAIL)
80+
.build()) {
81+
println "GrapeMaven.grab $dep" // TODO remove debug
82+
String module = dep.module ?: dep.artifactId ?: dep.artifact
83+
if (!module) {
84+
throw new RuntimeException('grab requires at least a module: or artifactId: or artifact: argument')
85+
}
86+
String groupId = dep.group ?: dep.groupId ?: dep.organisation ?: dep.organization ?: dep.org ?: ''
87+
String version = dep.version ?: dep.revision ?: dep.rev ?: '*'
88+
if (version == '*') version = 'latest.default'
89+
String classifier = dep.classifier ?: ''
90+
String ext = dep.ext ?: dep.type ?: 'jar'
91+
String type = dep.type ?: ''
92+
println "GrapeMaven.grab $groupId $module $classifier $ext $version"
93+
94+
Artifact artifact = new DefaultArtifact(groupId, module, classifier, ext, version)
95+
96+
DependencyFilter classpathFilter = DependencyFilterUtils.classpathFilter(JavaScopes.COMPILE)
97+
98+
CollectRequest collectRequest = new CollectRequest(root: new Dependency(artifact, JavaScopes.COMPILE), repositories: repos)
99+
100+
DependencyRequest dependencyRequest = new DependencyRequest(collectRequest, classpathFilter)
101+
102+
List<ArtifactResult> artifactResults =
103+
system.resolveDependencies(session, dependencyRequest).getArtifactResults()
104+
105+
for (ArtifactResult found : artifactResults) {
106+
println "$found resolved to $found.localArtifactResult.path"
107+
}
108+
}
109+
}
110+
}
111+
}
112+
113+
@Override
114+
@CompileDynamic
115+
Map<String, Map<String, List<String>>> enumerateGrapes() {
116+
Map<String, Map<String, List<String>>> bunches = [:].withDefault { [:].withDefault { [] } }
117+
String grapeCacheBase = grapeCacheDir.canonicalPath + File.separator
118+
grapeCacheDir.eachFileRecurse { File f ->
119+
if (f.name.endsWith('.jar')) {
120+
def version = f.parentFile.name
121+
def module = f.parentFile.parentFile.name
122+
def group = f.parentFile.parentFile.parentFile.canonicalPath - grapeCacheBase
123+
if (f.baseName == "$module-${version}") {
124+
bunches[group.replace(File.separatorChar, '.' as char)][module] << version
125+
}
126+
}
127+
}
128+
bunches
129+
}
130+
131+
void uninstallArtifact(String group, String module, String rev) {
132+
String groupPath = group.replace('.' as char, File.separatorChar)
133+
def artifactDir = new File(grapeCacheDir, groupPath + File.separator + module + File.separator + rev)
134+
if (artifactDir.exists()) {
135+
artifactDir.deleteDir()
136+
}
137+
}
138+
139+
@Override
140+
URI[] resolve(Map args, Map... dependencies) {
141+
resolve(args, null, dependencies)
142+
}
143+
144+
@Override
145+
URI[] resolve(Map args, List depsInfo, Map... dependencies) {
146+
// identify the target classloader early, so we fail before checking repositories
147+
ClassLoader loader = chooseClassLoader(
148+
refObject: args.remove('refObject'),
149+
classLoader: args.remove('classLoader'),
150+
calleeDepth: args.calleeDepth ?: DEFAULT_CALLEE_DEPTH,
151+
)
152+
153+
// check for non-fail null
154+
// if we were in fail mode we would have already thrown an exception
155+
if (!loader) {
156+
return new URI[0]
157+
}
158+
159+
resolve(loader, args, depsInfo, dependencies)
160+
}
161+
162+
private Set<MavenGrabRecord> getLoadedDepsForLoader(ClassLoader loader) {
163+
// use a LinkedHashSet to preserve the initial insertion order
164+
loadedDeps.computeIfAbsent(loader, k -> [] as LinkedHashSet)
165+
}
166+
167+
URI[] resolve(ClassLoader loader, Map args, Map... dependencies) {
168+
resolve(loader, args, null, dependencies)
169+
}
170+
171+
URI[] resolve(ClassLoader loader, Map args, List depsInfo, Map... dependencies) {
172+
if (!enableGrapes) {
173+
return new URI[0]
174+
}
175+
176+
boolean populateDepsInfo = (depsInfo != null)
177+
Set<MavenGrabRecord> localDeps = getLoadedDepsForLoader(loader)
178+
dependencies.each { Map dep ->
179+
MavenGrabRecord mgr = createGrabRecord(dep)
180+
grabRecordsForCurrDependencies.add(mgr)
181+
localDeps.add(mgr)
182+
}
183+
184+
List<URI> results = []
185+
results as URI[]
186+
}
187+
188+
MavenGrabRecord createGrabRecord(Map dep) {
189+
String module = dep.module ?: dep.artifactId ?: dep.artifact
190+
if (!module) {
191+
throw new RuntimeException('grab requires at least a module: or artifactId: or artifact: argument')
192+
}
193+
194+
// check for malformed components of the coordinates
195+
dep.each { k, v ->
196+
if (v instanceof CharSequence) {
197+
if (k.toString().contains('v')) { // revision, version, rev
198+
if (!(v ==~ '[^\\/:"<>|]*')) {
199+
throw new RuntimeException("Grab: invalid value of '$v' for $k: should not contain any of / \\ : \" < > |")
200+
}
201+
} else {
202+
if (!(v ==~ '[-._a-zA-Z0-9]*')) {
203+
throw new RuntimeException("Grab: invalid value of '$v' for $k: should only contain - . _ a-z A-Z 0-9")
204+
}
205+
}
206+
}
207+
}
208+
209+
// check for mutually exclusive arguments
210+
Set<String> keys = (Set<String>) dep.keySet()
211+
keys.each { key ->
212+
// Set<String> badArgs = MUTUALLY_EXCLUSIVE_KEYS[key]
213+
// if (badArgs && !badArgs.disjoint(keys)) {
214+
// throw new RuntimeException("Grab: mutually exclusive arguments: ${keys.intersect(badArgs) + key}")
215+
// }
216+
}
217+
218+
String groupId = dep.group ?: dep.groupId ?: dep.organisation ?: dep.organization ?: dep.org ?: ''
219+
String version = dep.version ?: dep.revision ?: dep.rev ?: '*'
220+
if (version == '*') version = 'latest.default'
221+
String classifier = dep.classifier ?: null
222+
String ext = dep.ext ?: dep.type ?: ''
223+
String type = dep.type ?: ''
224+
225+
// ModuleRevisionId mrid = ModuleRevisionId.newInstance(groupId, module, version)
226+
227+
boolean force = dep.containsKey('force') ? dep.force : true
228+
boolean changing = dep.containsKey('changing') ? dep.changing : false
229+
boolean transitive = dep.containsKey('transitive') ? dep.transitive : true
230+
231+
new MavenGrabRecord(/*mrid: mrid, conf: getConfList(dep), force: force, changing: changing, transitive: transitive,*/ ext: ext, type: type, classifier: classifier)
232+
}
233+
234+
@Override
235+
Map[] listDependencies(ClassLoader classLoader) {
236+
List<? extends Map> results = loadedDeps[classLoader]?.collect { MavenGrabRecord grabbed ->
237+
238+
def dep = [:
239+
// group : grabbed.mrid.getOrganisation(),
240+
// module : grabbed.mrid.getName(),
241+
// version: grabbed.mrid.getRevision()
242+
]
243+
/*
244+
if (grabbed.conf != DEFAULT_CONF) {
245+
dep.conf = grabbed.conf
246+
}
247+
if (grabbed.changing) {
248+
dep.changing = grabbed.changing
249+
}
250+
if (!grabbed.transitive) {
251+
dep.transitive = grabbed.transitive
252+
}
253+
if (!grabbed.force) {
254+
dep.force = grabbed.force
255+
}
256+
*/
257+
if (grabbed.classifier) {
258+
dep.classifier = grabbed.classifier
259+
}
260+
if (grabbed.ext) {
261+
dep.ext = grabbed.ext
262+
}
263+
if (grabbed.type) {
264+
dep.type = grabbed.type
265+
}
266+
dep
267+
}
268+
results as Map[]
269+
}
270+
271+
@Override
272+
void addResolver(@NamedParams([
273+
@NamedParam(value = 'name', type = String, required = true),
274+
@NamedParam(value = 'root', type = String, required = true),
275+
@NamedParam(value = 'm2Compatible', type = Boolean, required = false)
276+
]) Map<String, Object> args) {
277+
repos << new RemoteRepository.Builder(
278+
(String) args.name,
279+
"default",
280+
(String) args.root
281+
// settings: (ResolverSettings) settings,
282+
// m2compatible: (boolean) args.getOrDefault('m2Compatible', Boolean.TRUE)
283+
).build()
284+
}
285+
286+
static File getGroovyRoot() {
287+
println "GrapeMaven.getGroovyRoot" // TODO remove debug
288+
String root = System.getProperty('groovy.root')
289+
def groovyRoot
290+
if (root == null) {
291+
groovyRoot = new File(System.getProperty('user.home'), '.groovy')
292+
} else {
293+
groovyRoot = new File(root)
294+
}
295+
try {
296+
groovyRoot = groovyRoot.getCanonicalFile()
297+
} catch (IOException ignore) {
298+
// skip canonicalization then, it may not exist yet
299+
}
300+
groovyRoot
301+
}
302+
303+
static File getGrapeDir() {
304+
println "GrapeMaven.getGrapeDir" // TODO remove debug
305+
String root = System.getProperty('grape.root')
306+
if (root == null) {
307+
return getGroovyRoot()
308+
}
309+
File grapeRoot = new File(root)
310+
try {
311+
grapeRoot = grapeRoot.getCanonicalFile()
312+
} catch (IOException ignore) {
313+
// skip canonicalization then, it may not exist yet
314+
}
315+
grapeRoot
316+
}
317+
318+
static File getGrapeCacheDir() {
319+
File cache = new File(getGrapeDir(), 'grapesM2')
320+
if (!cache.exists()) {
321+
cache.mkdirs()
322+
} else if (!cache.isDirectory()) {
323+
throw new RuntimeException("The grape cache dir $cache is not a directory")
324+
}
325+
cache
326+
}
327+
328+
private ClassLoader chooseClassLoader(Map args) {
329+
ClassLoader loader = (ClassLoader) args.classLoader
330+
if (!isValidTargetClassLoader(loader)) {
331+
Class caller = args.refObject?.getClass() ?:
332+
ReflectionUtils.getCallingClass((int) args.calleeDepth ?: 1)
333+
loader = caller?.getClassLoader()
334+
while (loader && !isValidTargetClassLoader(loader)) {
335+
loader = loader.getParent()
336+
}
337+
if (!isValidTargetClassLoader(loader)) {
338+
throw new RuntimeException('No suitable ClassLoader found for grab')
339+
}
340+
}
341+
loader
342+
}
343+
344+
private boolean isValidTargetClassLoader(ClassLoader loader) {
345+
isValidTargetClassLoaderClass(loader?.getClass())
346+
}
347+
348+
private boolean isValidTargetClassLoaderClass(Class loaderClass) {
349+
loaderClass != null && (loaderClass.getName() == 'groovy.lang.GroovyClassLoader'
350+
|| loaderClass.getName() == 'org.codehaus.groovy.tools.RootLoader'
351+
|| isValidTargetClassLoaderClass(loaderClass.getSuperclass()))
352+
}
353+
354+
}
355+
356+
@CompileStatic
357+
@EqualsAndHashCode
358+
class MavenGrabRecord {
359+
// ModuleRevisionId mrid
360+
// List<String> conf
361+
// ArtifactType type
362+
String ext
363+
String type
364+
String classifier
365+
// boolean force
366+
// boolean changing
367+
// boolean transitive
368+
}

src/main/java/groovy/grape/Grape.java

+5-2
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,12 @@ public static void setDisableChecksums(boolean disableChecksums) {
119119
public static synchronized GrapeEngine getInstance() {
120120
if (instance == null) {
121121
try {
122-
// by default use GrapeIvy
122+
String grapeClass = System.getProperty("groovy.grape.impl");
123+
if (grapeClass == null) {
124+
grapeClass = "groovy.grape.GrapeIvy"; // by default use GrapeIvy
125+
}
123126
// TODO: META-INF/services resolver?
124-
instance = (GrapeEngine) Class.forName("groovy.grape.GrapeIvy").getDeclaredConstructor().newInstance();
127+
instance = (GrapeEngine) Class.forName(grapeClass).getDeclaredConstructor().newInstance();
125128
} catch (ReflectiveOperationException ignore) {
126129
}
127130
}

0 commit comments

Comments
 (0)