Index: src/main/groovy/lang/GrabExclude.java =================================================================== --- src/main/groovy/lang/GrabExclude.java Mon Sep 07 13:44:27 EST 2009 +++ src/main/groovy/lang/GrabExclude.java Mon Sep 07 13:44:27 EST 2009 @@ -0,0 +1,38 @@ +/* + * Copyright 2003-2009 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 groovy.lang; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Used to exclude the indirectly referenced artifact from the classpath. + */ +@Retention(RetentionPolicy.SOURCE) +@Target({ + ElementType.CONSTRUCTOR, + ElementType.FIELD, + ElementType.LOCAL_VARIABLE, + ElementType.METHOD, + ElementType.PARAMETER, + ElementType.TYPE}) +public @interface GrabExclude { + public abstract String group() default ""; + public abstract String module(); + public abstract String value() default ""; +} \ No newline at end of file Index: src/main/groovy/grape/GrabAnnotationTransformation.java =================================================================== --- src/main/groovy/grape/GrabAnnotationTransformation.java (revision 17111) +++ src/main/groovy/grape/GrabAnnotationTransformation.java Wed Sep 09 10:23:10 EST 2009 @@ -18,6 +18,8 @@ import groovy.lang.Grab; import groovy.lang.Grapes; +import groovy.lang.GrabExclude; +import groovy.lang.GrabConfig; import org.codehaus.groovy.ast.*; import org.codehaus.groovy.ast.expr.*; import org.codehaus.groovy.ast.stmt.ExpressionStatement; @@ -28,6 +30,8 @@ import org.codehaus.groovy.transform.GroovyASTTransformation; import java.util.*; +import java.util.regex.Pattern; +import java.util.regex.Matcher; /** * Transformation for declarative dependency management. @@ -38,19 +42,41 @@ private static final String GRAB_DOT_NAME = GRAB_CLASS_NAME.substring(GRAB_CLASS_NAME.lastIndexOf(".")); private static final String GRAB_SHORT_NAME = GRAB_DOT_NAME.substring(1); + private static final String GRABEXCLUDE_CLASS_NAME = GrabExclude.class.getName(); + private static final String GRABEXCLUDE_DOT_NAME = GRABEXCLUDE_CLASS_NAME.substring(GRABEXCLUDE_CLASS_NAME.lastIndexOf(".")); + private static final String GRABEXCLUDE_SHORT_NAME = GRABEXCLUDE_DOT_NAME.substring(1); + + private static final String GRABCONFIG_CLASS_NAME = GrabConfig.class.getName(); + private static final String GRABCONFIG_DOT_NAME = GRABCONFIG_CLASS_NAME.substring(GRABCONFIG_CLASS_NAME.lastIndexOf(".")); + private static final String GRABCONFIG_SHORT_NAME = GRABCONFIG_DOT_NAME.substring(1); + private static final String GRAPES_CLASS_NAME = Grapes.class.getName(); private static final String GRAPES_DOT_NAME = GRAPES_CLASS_NAME.substring(GRAPES_CLASS_NAME.lastIndexOf(".")); private static final String GRAPES_SHORT_NAME = GRAPES_DOT_NAME.substring(1); + private static final ClassNode THREAD_CLASSNODE = new ClassNode(Thread.class); + private static final List OPTIONAL = Arrays.asList("classifier", "transitive", "conf"); + private static final Pattern IVY_PATTERN = Pattern.compile("([a-zA-Z0-9-/._+=]+)#([a-zA-Z0-9-/._+=]+)(;([a-zA-Z0-9-/.\\(\\)\\[\\]\\{\\}_+=,:@][a-zA-Z0-9-/.\\(\\)\\]\\{\\}_+=,:@]*))?(\\[([a-zA-Z0-9-/._+=,]*)\\])?"); + boolean allowShortGrab; Set grabAliases; List grabAnnotations; + boolean allowShortGrabExcludes; + Set grabExcludeAliases; + List grabExcludeAnnotations; + + boolean allowShortGrabConfig; + Set grabConfigAliases; + List grabConfigAnnotations; + boolean allowShortGrapes; Set grapesAliases; List grapesAnnotations; SourceUnit sourceUnit; + ClassLoader loader; + boolean initContextClassLoader; public SourceUnit getSourceUnit() { return sourceUnit; @@ -58,12 +84,18 @@ public void visit(ASTNode[] nodes, SourceUnit source) { sourceUnit = source; + loader = null; + initContextClassLoader = false; ModuleNode mn = (ModuleNode) nodes[0]; allowShortGrab = true; + allowShortGrabExcludes = true; + allowShortGrabConfig = true; allowShortGrapes = true; grabAliases = new HashSet(); + grabExcludeAliases = new HashSet(); + grabConfigAliases = new HashSet(); grapesAliases = new HashSet(); for (ImportNode im : mn.getImports()) { String alias = im.getAlias(); @@ -85,9 +117,12 @@ } List> grabMaps = new ArrayList>(); + List> grabExcludeMaps = new ArrayList>(); for (ClassNode classNode : sourceUnit.getAST().getClasses()) { grabAnnotations = new ArrayList(); + grabExcludeAnnotations = new ArrayList(); + grabConfigAnnotations = new ArrayList(); grapesAnnotations = new ArrayList(); visitClass(classNode); @@ -113,14 +148,43 @@ } } + if (!grabConfigAnnotations.isEmpty()) { + for (AnnotationNode node : grabConfigAnnotations) { + checkForClassLoader(node); + checkForInitContextClassLoader(node); + } + addInitContextClassLoaderIfNeeded(classNode); + } + + if (!grabExcludeAnnotations.isEmpty()) { + grabExcludeAnnotationLoop: + for (AnnotationNode node : grabExcludeAnnotations) { + Map grabExcludeMap = new HashMap(); + checkForConvenienceForm(node); + for (String s : new String[]{"group", "module"}) { + Expression member = node.getMember(s); + if (member == null) { + addError("The missing attribute \"" + s + "\" is required in @" + node.getClassNode().getNameWithoutPackage() + " annotations", node); + continue grabExcludeAnnotationLoop; + } else if (member != null && !(member instanceof ConstantExpression)) { + addError("Attribute \"" + s + "\" has value " + member.getText() + " but should be an inline constant in @" + node.getClassNode().getNameWithoutPackage() + " annotations", node); + continue grabExcludeAnnotationLoop; + } + } + grabExcludeMap.put("group", ((ConstantExpression)node.getMember("group")).getValue()); + grabExcludeMap.put("module", ((ConstantExpression)node.getMember("module")).getValue()); + grabExcludeMaps.add(grabExcludeMap); + } + } + if (!grabAnnotations.isEmpty()) { grabAnnotationLoop: for (AnnotationNode node : grabAnnotations) { Map grabMap = new HashMap(); checkForConvenienceForm(node); - for (String s : new String[]{"group", "module", "version", "classifier"}) { + for (String s : new String[]{"group", "module", "version", "classifier", "transitive", "conf"}) { Expression member = node.getMember(s); - if (member == null && !s.equals("classifier")) { + if (member == null && !OPTIONAL.contains(s)) { addError("The missing attribute \"" + s + "\" is required in @" + node.getClassNode().getNameWithoutPackage() + " annotations", node); continue grabAnnotationLoop; } else if (member != null && !(member instanceof ConstantExpression)) { @@ -131,51 +195,109 @@ grabMap.put("group", ((ConstantExpression)node.getMember("group")).getValue()); grabMap.put("module", ((ConstantExpression)node.getMember("module")).getValue()); grabMap.put("version", ((ConstantExpression)node.getMember("version")).getValue()); - if (node.getMember("classifier") != null) - grabMap.put("classifier", ((ConstantExpression)node.getMember("classifier")).getValue()); + for (String s : OPTIONAL) { + if (node.getMember(s) != null) + grabMap.put(s, ((ConstantExpression)node.getMember(s)).getValue()); + } grabMaps.add(grabMap); + callGrabAsStaticInitIfNeeded(classNode, grapeClassNode, node, grabExcludeMaps); + } + } + + } + if (!grabMaps.isEmpty()) { + Map basicArgs = new HashMap(); + basicArgs.put("classLoader", loader != null ? loader : sourceUnit.getClassLoader()); + if (!grabExcludeMaps.isEmpty()) basicArgs.put("excludes", grabExcludeMaps); + + try { + Grape.grab(basicArgs, grabMaps.toArray(new Map[grabMaps.size()])); + } catch (RuntimeException re) { + // Decided against syntax exception since this is not a syntax error. + // The down side is we lose line number information for the offending + // @Grab annotation. + source.addException(re); + } + } + } + + private void callGrabAsStaticInitIfNeeded(ClassNode classNode, ClassNode grapeClassNode, AnnotationNode node, List> grabExcludeMaps) { - if ((node.getMember("initClass") == null) - || (node.getMember("initClass") == ConstantExpression.TRUE)) - { - List grabInitializers = new ArrayList(); + if ((node.getMember("initClass") == null) + || (node.getMember("initClass") == ConstantExpression.TRUE)) + { + List grabInitializers = new ArrayList(); - // add Grape.grab([group:group, module:module, version:version, classifier:classifier]) + // add Grape.grab(excludeArgs, [group:group, module:module, version:version, classifier:classifier]) + // or Grape.grab([group:group, module:module, version:version, classifier:classifier]) - MapExpression me = new MapExpression(); - me.addMapEntryExpression(new ConstantExpression("group"),node.getMember("group")); - me.addMapEntryExpression(new ConstantExpression("module"),node.getMember("module")); - me.addMapEntryExpression(new ConstantExpression("version"),node.getMember("version")); + MapExpression me = new MapExpression(); + me.addMapEntryExpression(new ConstantExpression("group"),node.getMember("group")); + me.addMapEntryExpression(new ConstantExpression("module"),node.getMember("module")); + me.addMapEntryExpression(new ConstantExpression("version"),node.getMember("version")); - if (node.getMember("classifier") != null) - me.addMapEntryExpression(new ConstantExpression("classifier"),node.getMember("classifier")); + for (String s : OPTIONAL) { + if (node.getMember(s) != null) + me.addMapEntryExpression(new ConstantExpression(s),node.getMember(s)); + } + ArgumentListExpression grabArgs; + if (grabExcludeMaps.isEmpty()) { + grabArgs = new ArgumentListExpression(me); + } else { + MapExpression args = new MapExpression(); + ListExpression list = new ListExpression(); + for (Map map : grabExcludeMaps) { + Set> entries = map.entrySet(); + MapExpression inner = new MapExpression(); + for (Map.Entry entry : entries) { + inner.addMapEntryExpression(new ConstantExpression(entry.getKey()), new ConstantExpression(entry.getValue())); + } + list.addExpression(inner); + } + args.addMapEntryExpression(new ConstantExpression("excludes"), list); + grabArgs = new ArgumentListExpression(args, me); + } - grabInitializers.add(new ExpressionStatement( + grabInitializers.add(new ExpressionStatement( - new StaticMethodCallExpression( - grapeClassNode, - "grab", - new ArgumentListExpression(me)))); + new StaticMethodCallExpression(grapeClassNode, "grab", grabArgs))); - // insert at beginning so we have the classloader set up before the class is called - classNode.addStaticInitializerStatements(grabInitializers, true); - } - } + // insert at beginning so we have the classloader set up before the class is called + classNode.addStaticInitializerStatements(grabInitializers, true); + } + } - } + private void addInitContextClassLoaderIfNeeded(ClassNode classNode) { + if (initContextClassLoader) { + Statement initStatement = new ExpressionStatement( + new MethodCallExpression( + new StaticMethodCallExpression(THREAD_CLASSNODE, "currentThread", ArgumentListExpression.EMPTY_ARGUMENTS), + "setContextClassLoader", + new MethodCallExpression( + new MethodCallExpression(VariableExpression.THIS_EXPRESSION, "getClass", MethodCallExpression.NO_ARGUMENTS), + "getClassLoader", + ArgumentListExpression.EMPTY_ARGUMENTS + ) + ) + ); + classNode.addObjectInitializerStatements(initStatement); } - if (!grabMaps.isEmpty()) { - Map basicArgs = new HashMap(); - basicArgs.put("classLoader", sourceUnit.getClassLoader()); + } - try { - Grape.grab(basicArgs, grabMaps.toArray(new Map[grabMaps.size()])); - } catch (RuntimeException re) { - // Decided against syntax exception since this is not a syntax error. - // The down side is we lose line number information for the offending - // @Grab annotation. - source.addException(re); + private void checkForClassLoader(AnnotationNode node) { + Object val = node.getMember("systemClassLoader"); + if (val == null || !(val instanceof ConstantExpression)) return; + Object systemClassLoaderObject = ((ConstantExpression)val).getValue(); + if (!(systemClassLoaderObject instanceof Boolean)) return; + Boolean systemClassLoader = (Boolean) systemClassLoaderObject; + if (systemClassLoader) loader = ClassLoader.getSystemClassLoader(); - } + } + + private void checkForInitContextClassLoader(AnnotationNode node) { + Object val = node.getMember("initContextClassLoader"); + if (val == null || !(val instanceof ConstantExpression)) return; + Object initContextClassLoaderObject = ((ConstantExpression)val).getValue(); + if (!(initContextClassLoaderObject instanceof Boolean)) return; + initContextClassLoader = (Boolean) initContextClassLoaderObject; - } + } - } private void checkForConvenienceForm(AnnotationNode node) { Object val = node.getMember("value"); @@ -183,14 +305,28 @@ Object allParts = ((ConstantExpression)val).getValue(); if (!(allParts instanceof String)) return; String allstr = (String) allParts; - if (allstr.contains(":")) { + if (allstr.contains("#")) { + Matcher m = IVY_PATTERN.matcher(allstr); + if (!m.find()) return; + if (m.group(1) == null || m.group(2) == null) return; + node.addMember("module", new ConstantExpression(m.group(2))); + node.addMember("group", new ConstantExpression(m.group(1))); + if (m.group(6) != null) node.addMember("conf", new ConstantExpression(m.group(6))); + if (m.group(4) != null) node.addMember("version", new ConstantExpression(m.group(4))); + else node.addMember("version", new ConstantExpression("*")); + // TODO: getting processed twice? Remove value to avoid problems but investigate further + node.getMembers().remove("value"); + } else if (allstr.contains(":")) { + // TODO: Handle '@' variation that gradle supports? String[] parts = allstr.split(":"); if (parts.length > 4) return; if (parts.length > 3) node.addMember("classifier", new ConstantExpression(parts[3])); if (parts.length > 2) node.addMember("version", new ConstantExpression(parts[2])); - else node.addMember("version", new ConstantExpression("*")); // TODO '*' default in @Grab not working? + else node.addMember("version", new ConstantExpression("*")); node.addMember("module", new ConstantExpression(parts[1])); node.addMember("group", new ConstantExpression(parts[0])); + // TODO: getting processed twice? Remove value to avoid problems but investigate further + node.getMembers().remove("value"); } } @@ -206,8 +342,18 @@ || (grabAliases.contains(name))) { grabAnnotations.add(annotation); } + if ((GRABEXCLUDE_CLASS_NAME.equals(name)) + || (allowShortGrabExcludes && GRABEXCLUDE_SHORT_NAME.equals(name)) + || (grabExcludeAliases.contains(name))) { + grabExcludeAnnotations.add(annotation); - } + } + if ((GRABCONFIG_CLASS_NAME.equals(name)) + || (allowShortGrabConfig && GRABCONFIG_SHORT_NAME.equals(name)) + || (grabConfigAliases.contains(name))) { + grabConfigAnnotations.add(annotation); - } + } + } + } /** * Adds the annotation to the internal target list if a match is found. @@ -223,6 +369,16 @@ || (grabAliases.contains(name))) { grabAnnotations.add(an); } + if ((GRABEXCLUDE_CLASS_NAME.equals(name)) + || (allowShortGrabExcludes && GRABEXCLUDE_SHORT_NAME.equals(name)) + || (grabExcludeAliases.contains(name))) { + grabExcludeAnnotations.add(an); + } + if ((GRABCONFIG_CLASS_NAME.equals(name)) + || (allowShortGrabConfig && GRABCONFIG_SHORT_NAME.equals(name)) + || (grabConfigAliases.contains(name))) { + grabConfigAnnotations.add(an); + } if ((GRAPES_CLASS_NAME.equals(name)) || (allowShortGrapes && GRAPES_SHORT_NAME.equals(name)) || (grapesAliases.contains(name))) { Index: src/main/groovy/lang/GrabConfig.java =================================================================== --- src/main/groovy/lang/GrabConfig.java Mon Sep 07 19:16:23 EST 2009 +++ src/main/groovy/lang/GrabConfig.java Mon Sep 07 19:16:23 EST 2009 @@ -0,0 +1,37 @@ +/* + * Copyright 2003-2009 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 groovy.lang; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Used to modify grab configuration. + */ +@Retention(RetentionPolicy.SOURCE) +@Target({ + ElementType.CONSTRUCTOR, + ElementType.FIELD, + ElementType.LOCAL_VARIABLE, + ElementType.METHOD, + ElementType.PARAMETER, + ElementType.TYPE}) +public @interface GrabConfig { + public abstract boolean systemClassLoader() default false; + public abstract boolean initContextClassLoader() default false; +} \ No newline at end of file Index: src/main/groovy/grape/GrapeIvy.groovy =================================================================== --- src/main/groovy/grape/GrapeIvy.groovy (revision 17555) +++ src/main/groovy/grape/GrapeIvy.groovy Wed Sep 09 09:27:35 EST 2009 @@ -1,5 +1,5 @@ /* - * Copyright 2003-2008 the original author or authors. + * Copyright 2003-2009 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. @@ -28,6 +28,10 @@ import org.apache.ivy.util.Message import org.codehaus.groovy.reflection.ReflectionUtils import org.codehaus.groovy.runtime.InvokerHelper +import org.apache.ivy.plugins.matcher.ExactPatternMatcher +import org.apache.ivy.plugins.matcher.PatternMatcher +import org.apache.ivy.core.module.id.ModuleId +import org.apache.ivy.core.module.id.ArtifactId /** * @author Danno Ferrin @@ -181,7 +185,8 @@ boolean transitive = deps.containsKey('transitive') ? deps.transitive : true def conf = deps.conf ?: deps.scope ?: deps.configuration ?: ['default'] if (conf instanceof String) { - conf = [conf] + if (conf.startsWith("[") && conf.endsWith("]")) conf = conf[1..-2] + conf = conf.split(",").toList() } def classifier = deps.classifier ?: null @@ -227,17 +232,20 @@ public ResolveReport getDependencies(Map args, IvyGrabRecord... grabRecords) { ResolutionCacheManager cacheManager = ivyInstance.getResolutionCacheManager() - DefaultModuleDescriptor md = new DefaultModuleDescriptor(ModuleRevisionId + def md = new DefaultModuleDescriptor(ModuleRevisionId .newInstance("caller", "all-caller", "working"), "integration", null, true) md.addConfiguration(new Configuration('default')) md.setLastModified(System.currentTimeMillis()) + + addExcludesIfNeeded(args, md) + for (IvyGrabRecord grabRecord : grabRecords) { DefaultDependencyDescriptor dd = new DefaultDependencyDescriptor(md, grabRecord.mrid, grabRecord.force, grabRecord.changing, grabRecord.transitive) def conf = grabRecord.conf ?: ['*'] conf.each {dd.addDependencyConfiguration('default', it)} if (grabRecord.classifier) { - DefaultDependencyArtifactDescriptor dad = new DefaultDependencyArtifactDescriptor(dd, + def dad = new DefaultDependencyArtifactDescriptor(dd, grabRecord.mrid.name, 'jar', 'jar', null, [classifier:grabRecord.classifier]) conf.each { dad.addConfiguration(it) } dd.addDependencyArtifact('default', dad) @@ -248,7 +256,7 @@ // resolve grab and dependencies ResolveOptions resolveOptions = new ResolveOptions()\ .setConfs(['default'] as String[])\ - .setOutputReport(false)\ + .setOutputReport(true)\ .setValidate(args.containsKey('validate') ? args.validate : false) ivyInstance.getSettings().setDefaultResolver( args.autoDownload ? 'downloadGrapes' : 'cachedGrapes' ) @@ -267,6 +275,19 @@ return report } + private addExcludesIfNeeded(Map args, DefaultModuleDescriptor md) { + if (!args.containsKey('excludes')) return + args.excludes.each{ map -> + def excludeRule = new DefaultExcludeRule(new ArtifactId( + new ModuleId(map.group, map.module), PatternMatcher.ANY_EXPRESSION, + PatternMatcher.ANY_EXPRESSION, + PatternMatcher.ANY_EXPRESSION), + ExactPatternMatcher.INSTANCE, null) + excludeRule.addConfiguration('default') + md.addExcludeRule(excludeRule) + } + } + public Map>> enumerateGrapes() { Map>> bunches = [:] Pattern ivyFilePattern = ~/ivy-(.*)\.xml/ //TODO get pattern from ivy conf @@ -285,13 +306,13 @@ return bunches } - public URI [] resolve(Map args, Map... dependencies) { + public URI[] resolve(Map args, Map ... dependencies) { // identify the target classloader early, so we fail before checking repositories def loader = chooseClassLoader( - classLoader:args.remove('classLoader'), + classLoader: args.remove('classLoader'), - refObject:args.remove('refObject'), + refObject: args.remove('refObject'), - calleeDepth:args.calleeDepth?:DEFAULT_DEPTH, + calleeDepth: args.calleeDepth ?: DEFAULT_DEPTH, - ) + ) // check for non-fail null. // If we were in fail mode we would have already thrown an exception Index: src/main/groovy/lang/Grab.java =================================================================== --- src/main/groovy/lang/Grab.java (revision 17397) +++ src/main/groovy/lang/Grab.java Tue Sep 08 20:12:32 EST 2009 @@ -33,9 +33,11 @@ ElementType.TYPE}) public @interface Grab { String group() default ""; - String module() ; + String module(); - String version() default "*"; + String version(); String classifier() default ""; + String transitive() default ""; + String conf() default ""; String value() default ""; /**