You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@buildr.apache.org by vb...@apache.org on 2008/03/26 01:08:49 UTC

svn commit: r641083 - in /incubator/buildr/trunk: Rakefile lib/buildr/xmlbeans.rb lib/java/artifact.rb lib/java/artifact_namespace.rb lib/java/artifact_search.rb lib/java/groovyc.rb spec/artifact_namespace_spec.rb

Author: vborja
Date: Tue Mar 25 17:08:47 2008
New Revision: 641083

URL: http://svn.apache.org/viewvc?rev=641083&view=rev
Log:
ArtifactNamespace

Added:
    incubator/buildr/trunk/lib/java/artifact_namespace.rb
    incubator/buildr/trunk/spec/artifact_namespace_spec.rb
Removed:
    incubator/buildr/trunk/lib/java/artifact_search.rb
Modified:
    incubator/buildr/trunk/Rakefile
    incubator/buildr/trunk/lib/buildr/xmlbeans.rb
    incubator/buildr/trunk/lib/java/artifact.rb
    incubator/buildr/trunk/lib/java/groovyc.rb

Modified: incubator/buildr/trunk/Rakefile
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/Rakefile?rev=641083&r1=641082&r2=641083&view=diff
==============================================================================
--- incubator/buildr/trunk/Rakefile (original)
+++ incubator/buildr/trunk/Rakefile Tue Mar 25 17:08:47 2008
@@ -155,7 +155,6 @@
 
   desc 'Run all specs specifically with JRuby'
   task('jruby') { system 'jruby -S rake spec' }
-
 end
 
 

Modified: incubator/buildr/trunk/lib/buildr/xmlbeans.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/lib/buildr/xmlbeans.rb?rev=641083&r1=641082&r2=641083&view=diff
==============================================================================
--- incubator/buildr/trunk/lib/buildr/xmlbeans.rb (original)
+++ incubator/buildr/trunk/lib/buildr/xmlbeans.rb Tue Mar 25 17:08:47 2008
@@ -22,10 +22,12 @@
   # Provides XMLBeans schema compiler. Require explicitly using <code>require "buildr/xmlbeans"</code>.
   module XMLBeans
 
-    STAX = "stax:stax-api:jar:1.0"
-    XMLBEANS = "org.apache.xmlbeans:xmlbeans:jar:2.3.0"
-    REQUIRES = [ STAX, XMLBEANS ]
-
+    Buildr.artifacts(self) do
+      need :stax => 'stax:stax-api:jar:>=1.0',
+           :xmlbeans => 'org.apache.xmlbeans:xmlbeans:jar:>=2.2'
+      default :stax => '1.0', :xmlbeans => '2.3.0'
+    end
+    
     class << self
 
       def compile(*args)
@@ -45,12 +47,9 @@
         touch options[:output].to_s, :verbose=>false
       end
 
-    private
-
       def requires()
-        @requires ||= Buildr.artifacts(REQUIRES).each { |artifact| artifact.invoke }.map(&:to_s)
+        Buildr.artifacts[XMLBeans].used.each { |artifact| artifact.invoke }.map(&:to_s)
       end
-    
     end
 
     def compile_xml_beans(*args)
@@ -62,7 +61,7 @@
         XMLBeans.compile args.flatten, :output=>task.name,
           :javasource=>compile.options.source, :xsb=>compile.target
       end
-      compile.using(:javac).from(generated).with(STAX, XMLBEANS)
+      compile.using(:javac).from(generated).with(*XMLBeans.requires)
       # Once compiled, we need to copy the generated XSB/XSD and one (magical?) class file
       # into the target directory, or the rest is useless.
       compile do |task|

Modified: incubator/buildr/trunk/lib/java/artifact.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/lib/java/artifact.rb?rev=641083&r1=641082&r2=641083&view=diff
==============================================================================
--- incubator/buildr/trunk/lib/java/artifact.rb (original)
+++ incubator/buildr/trunk/lib/java/artifact.rb Tue Mar 25 17:08:47 2008
@@ -17,7 +17,7 @@
 require 'core/project'
 require 'core/transports'
 require 'builder'
-require 'java/artifact_search'
+require 'java/artifact_namespace'
 
 module Buildr
 
@@ -558,9 +558,6 @@
   def artifact(spec, &block) #:yields:task
     spec = Artifact.to_hash(spec)
     unless task = Artifact.lookup(spec)
-      if ArtifactSearch.enabled? && ArtifactSearch.requirement?(spec)
-        spec = ArtifactSearch.best_version(spec)
-      end
       task = Artifact.define_task(repositories.locate(spec))
       task.send :apply_spec, spec
       Rake::Task['rake:artifacts'].enhance [task]
@@ -571,11 +568,15 @@
 
   # :call-seq:
   #   artifacts(*spec) => artifacts
+  #   artifacts { |namespace| ... } => namespace
+  #   artifacts(scope) { |namespace| ... } => namespace
+  #   artifacts[scope] => namespace
   #
   # Handles multiple artifacts at a time. This method is the plural equivalent of
   # #artifacts, but can do more things.
   #
-  # You can pass any number of arguments, each of which can be:
+  # The first form returns an array of artifacts built using the supplied
+  # specifications, each of which can be:
   # * An artifact specification (String or Hash). Returns the appropriate Artifact task.
   # * An artifact of any other task. Returns the task as is.
   # * A project. Returns all artifacts created (packaged) by that project.
@@ -589,10 +590,19 @@
   #   artifacts(xml, ws, db)
   #
   # Using artifacts created by a project:
-  #   artifact project('my-app')               # All packages
-  #   artifact project('mu-app').package(:war) # Only the WAR
-  def artifacts(*specs)
-    specs.flatten.inject([]) do |set, spec|
+  #   artifacts project('my-app')               # All packages
+  #   artifacts project('my-app').package(:war) # Only the WAR
+  #
+  # Last three forms return an ArtifactNamespace instead of an array
+  # and are only useful to work with the resulting namespace. See the
+  # documentation for ArtifactNamespace for more info.
+  def artifacts(*specs, &block)
+    specs.flatten!
+    if block
+      scope = Rake.application.current_scope
+      return ArtifactNamespace.instance(specs.first || scope, &block)
+    end
+    specs.inject([]) do |set, spec|
       case spec
       when Hash
         set |= [artifact(spec)]
@@ -606,10 +616,15 @@
         set |= [spec]
       when Struct
         set |= artifacts(spec.values)
+      when Symbol
+        name = spec
+        spec = ArtifactNamespace.instance.spec(name)
+        raise "No artifact found by name #{name.inspect}" unless spec
+        set |= [artifact(spec)]
       else
-        fail "Invalid artifact specification in: #{specs.to_s}"
+        fail "Invalid artifact specification in #{specs.inspect}"
       end
-    end
+    end.extend ArtifactNamespace::AryMixin
   end
 
   def transitive(*specs)

Added: incubator/buildr/trunk/lib/java/artifact_namespace.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/lib/java/artifact_namespace.rb?rev=641083&view=auto
==============================================================================
--- incubator/buildr/trunk/lib/java/artifact_namespace.rb (added)
+++ incubator/buildr/trunk/lib/java/artifact_namespace.rb Tue Mar 25 17:08:47 2008
@@ -0,0 +1,737 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with this
+# work for additional information regarding copyright ownership.  The ASF
+# licenses this file to you 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.
+
+require 'core/environment'
+require 'java/artifact'
+
+module Buildr
+
+  #
+  # ArtifactNamespace allows users to control artifact versions to be
+  # used by their projects and Buildr modules/addons.
+  # 
+  # A namespace is a hierarchical dictionary that allows to specify
+  # artifact version requirements (see ArtifactNamespace#need).
+  #
+  # Every project can have it's own namespace inheriting the one for
+  # their parent projects. 
+  #
+  # To open the namespace for the current context just provide a block
+  # to the Buildr.artifacts method (see ArtifactNamespace.instance):
+  #
+  #    -- buildfile --
+  #    # open the root namespace, equivalent to +artifacts.namespace(nil)+
+  #    artifacts do |ns|
+  #       # later referenced by name
+  #       ns.use :spring => 'org.springframework:spring:jar:2.5'
+  #       ns.use :log4j => 'log4j:log4j:jar:1.2.15'
+  #    end
+  #
+  #    require 'buildr/xmlbeans'
+  #    # specify the xmlbeans version to use:
+  #    artifacts[Buildr::XMLBeans][:xmlbeans] = '2.2'
+  #   
+  #    # select some artifacts by their ruby symbols or spec
+  #    define 'foo_proj' { compile.with :log4j, :'asm:asm:jar:-', 'some:other:jar:2.0' }
+  #    # or get all used artifacts for the project namespace
+  #    define 'bar_proj' { compile.with artifacts[self].used }
+  # 
+  # The ArtifactNamespace.load method can be used to populate your
+  # namespaces from a hash of hashes, like your profile yaml in the
+  # following example:
+  # 
+  #   -- profiles.yaml --
+  #   development:
+  #     artifacts:
+  #       # root namespace, null name
+  #       ~:
+  #         spring:     org.springframework:spring:jar:2.5
+  #         log4j:      log4j:log4j:jar:1.2.15
+  #         groovy:     org.codehaus.groovy:groovy:jar:1.5.4
+  #       
+  #       # open Buildr::XMLBeans namespace
+  #       Buildr::XMLBeans: 
+  #         xmlbeans: 2.2
+  #
+  #       # for subproject one:oldie
+  #       one:oldie:
+  #         spring:  org.springframework:spring:jar:1.0
+  #
+  #   -- buildfile --
+  #   ArtifactNamespace.load(Buildr.profile['artifacts'])
+  #   require 'buildr/xmlbeans' # will use xmlbeans-2.2
+  #   require 'java/groovyc' # will find groovy 1.5.4 on global ns
+  #   describe 'one' do 
+  #     compile.with :spring, :log4j   # spring-2.5, log4j-1.2.15
+  #     describe 'oldie' do
+  #       compile.with :spring, :log4j # spring-1.0, log4j-1.2.15
+  #     end
+  #   end
+  # 
+  # 
+  class ArtifactNamespace
+    
+    # Mixin for arrays returned by Buildr.artifacts
+    module AryMixin
+      
+      # :call-seq:
+      #   artifacts[numeric] -> artifact
+      #   artifacts[scope] -> namespace
+      #
+      # Extends the regular Array#[] so that non numeric
+      # indices are scope names, returning the corresponding
+      # namespace
+      def [](idx)
+        if Numeric === idx
+          super
+        else
+          namespace(idx)
+        end
+      end
+      
+      # :call-seq:
+      #   artifacts.namespace -> namespace
+      #   artifacts.namespace(scope) -> namespace
+      def namespace(*a, &b)
+        ArtifactNamespace.instance(*a, &b)
+      end
+      
+      alias_method :ns, :namespace
+    end
+    
+    ROOT = :root
+
+    class << self
+      # Populate namespaces from a hash of hashes. 
+      # The following example uses the profiles yaml to achieve this.
+      #
+      #   -- profiles.yaml --
+      #   development:
+      #     artifacts:
+      #       # root namespace, null name
+      #       ~:
+      #         spring:     org.springframework:spring:jar:2.5
+      #         log4j:      log4j:log4j:jar:1.2.15
+      #         groovy:     org.codehaus.groovy:groovy:jar:1.5.4
+      #       
+      #       # open Buildr::XMLBeans namespace
+      #       Buildr::XMLBeans:
+      #         xmlbeans: 2.2
+      #
+      #       # for subproject one:oldie
+      #       one:oldie:
+      #         spring:  org.springframework:spring:jar:1.0
+      #
+      #   -- buildfile --
+      #   ArtifactNamespace.load(Buildr.profile['artifacts'])
+      def load(namespace_hash)
+        if Hash === namespace_hash
+          namespace_hash.each_pair do |name, uses|
+            instance(name).use(uses)
+          end
+        end
+      end
+      
+      # Forget all previously declared namespaces.
+      def clear 
+        @instances = nil
+      end
+
+      # :call-seq:
+      #   Buildr.artifacts { |ns| ... } -> namespace
+      #   Buildr.artifacts(scope) { |ns| ... } -> namespace
+      # 
+      # Obtain the namespace for the given +scope+ or for the currently
+      # running project. If a block is given, the namespace is yielded to it.
+      def instance(name = nil, &block)
+        case name
+        when Array then name = name.join(':')
+        when Module, Project then name = name.name
+        when nil then
+          task = Thread.current[:rake_chain]
+          task = task.instance_variable_get(:@value) if task
+          name = task ? task.scope : Rake.application.current_scope
+          name = name.join(':')
+        end
+        name = name.to_s.split(/:{2,}/).join(':')
+        name = ROOT if name.to_s.blank?
+        @instances ||= Hash.new { |h, k| h[k] = new(k) }
+        instance = @instances[name.to_sym]
+        instance.tap(&block) if block
+        instance
+      end
+      
+      alias_method :for, :instance
+    end
+    
+    # Set the parent namespace
+    def parent=(parent)
+      fail "Cannot set parent of root namespace!" if @name == ROOT
+      @parent = parent
+    end
+
+    # :call-seq:
+    #   namespace.parent { |parent_namespace| ... } -> parent_namespace
+    # 
+    # Get the parent namespace
+    def parent(&block)
+      return nil if @name == ROOT
+      if @parent
+        @parent = self.class.instance(@parent) unless @parent.kind_of?(self.class)
+      else
+        name = @name.to_s.split(':')[0...-1].join(':')
+        @parent = self.class.instance(name)
+      end
+      @parent.tap(&block)
+    end
+
+    def initialize(name) #:nodoc:
+      @name = name
+      clear
+    end
+
+    attr_reader :name
+
+    # Clear internal requirements map
+    def clear
+      @using = {}
+      @aliases = {}
+      @requires = {}
+    end
+    
+    # Return artifacts defined for use
+    def used(include_parents = false)
+      seen = {}
+      registry = self
+      while registry
+        registry.instance_variable_get(:@using).each_pair do |key, spec|
+          spec = spec(key) unless Hash == spec
+          name = Artifact.to_spec(spec.merge(:version => '-'))
+          seen[name] = Buildr.artifact(spec) unless seen.key?(name)
+        end
+        registry = include_parents ? registry.parent : nil
+      end
+      seen.values
+    end
+
+    def each(&block)
+      used(false).each(&block)
+    end
+    include Enumerable
+    
+    # Return the named artifacts from this namespace hierarchy
+    def only(*names)
+      names = @requires.keys if names.empty?
+      ary = names.map { |name| Buildr.artifact(spec(name)) }
+      ary.size == 1 ? ary.first : ary
+    end
+
+    # Test if named requirement has been satisfied
+    def satisfied?(name)
+      req, spec = requirement(name), spec(name)
+      if req && spec
+        req[:version].satisfied_by?(spec[:version])
+      else
+        false
+      end
+    end
+
+    # Return the artifact spec (a hash) for the given name
+    def spec(name)
+      name = normalize_name(name)
+      using = @using[name] || @using[@aliases[name]]
+      if using
+        if using.kind_of?(Hash)
+          spec = using.dup
+        else
+          spec = @requires[name] || @requires[@aliases[name]]
+          spec = spec ? spec.dup : {}
+          spec[:version] = using.dup
+        end
+        spec
+      elsif parent
+        parent.spec(name)
+      end
+    end
+
+    def requirement(name)
+      name = normalize_name(name)
+      req = @requires[name] || @requires[@aliases[name]]
+      if req
+        req.dup
+      elsif parent
+        parent.requirement(name)
+      end
+    end
+
+    def delete(name)
+      name = normalize_name(name)
+      [name, @aliases[name]].each do |n|
+        @requires.delete(n); @using.delete(n); @aliases.delete(n)
+      end
+      self
+    end
+
+    # :call-seq: 
+    #   artifacts do
+    #     need *specs
+    #     need name => spec
+    #   end
+    # 
+    # Establish an artifact dependency on the current namespace.
+    # A dependency is simply an artifact spec whose version part
+    # contains comparision operators.
+    #
+    # Supported comparison operators are =, !=, >, <, >=, <= and ~>.
+    # The compatible comparison (~>) matches from the specified version up one version.
+    # For example, ~> 5.3.1 will match all versions from 5.3.1 up to but excluding 5.4,
+    # while ~> 5.3 will match all versions from 5.3.0 up to but excluding 6.
+    #
+    # In adition to comparition operators, artifact requirements support logic operators
+    # 
+    # * ( expr )       -- Parenthesis group expressions
+    # * not( expr )    -- Negation
+    # * expr and expr  -- Logical and
+    # * expr or  expr  -- Logical or
+    #
+    # Requirements defined on parent namespaces, are inherited by
+    # their childs, this means that when a specific version is selected for use
+    # on a sub-namespace, validation will be performed by checking the parent requirement.
+    #
+    #   artifacts('one') do 
+    #     need :bar => 'foo:bar:jar:1.0 or ~>1.0'
+    #     need 'foo:baz:jar:>1.2 & <1.3 & not(>=1.2.5 | <=1.2.6)'
+    #   end
+    # 
+    #   artifacts('one:two') do 
+    #     use :bar => '0.9' # This wil fail because of previous requirement
+    #     use :bar => '1.1.1' # valid, selected for usage
+    #
+    #     use 'foo:baz:jar:1.2.5.1' # on invalid range
+    #     use 'foo:bar:jar:1.2.4' # valid, selected for usage
+    #
+    #     use :bat => 'foo:bat:jar:0.9' # valid, no requirement found for it
+    #   end
+    def need(*specs)
+      specs.flatten.each do |spec|
+        named = {}
+        if (Hash === spec || Struct === spec) &&
+           (spec.keys & ActsAsArtifact::ARTIFACT_ATTRIBUTES).empty?
+          spec.each_pair { |k, v| named[k] = Artifact.to_hash(v) }
+        else
+          named[nil] = Artifact.to_hash(spec)
+        end
+        named.each_pair do |name, spec|
+          spec[:version] = VersionRequirement.create(spec[:version])
+          unvers = Artifact.to_spec(spec.merge(:version => '-')).to_sym
+          @requires[unvers] = spec
+          if name
+            name = name.to_sym
+            using = @using[name] || @using[@aliases[name]]
+            using = { :version  => using } if using.kind_of?(String) && VersionRequirement.version?(using)
+            fail_unless_satisfied(spec, using)
+            @aliases[name.to_sym] = unvers
+            @aliases[unvers] = name.to_sym
+          end
+        end
+      end
+      self
+    end
+
+    # Specify default version if no previous one has been selected.
+    # This method is useful mainly for plugin/addon writers, allowing
+    # their users to override the artifact version to be used.
+    # Plugin/Addon writers need to document the +scope+ used by their
+    # addon, which can be simply an string or a module name.
+    # 
+    # Suppose we are writing the Foo::Addon module
+    #
+    #   module Foo::Addon
+    #     artifacts(self) do # scope is the module name => "Foo::Addon"
+    #       need :bar => 'foo:bar:jar:>2.0', # suppose bar is used at load time
+    #            :baz => 'foo:baz:jar:>3.0'  # used when Foo::Addon.baz called
+    #       default :bar => '2.5', :baz => '3.5'
+    #     end
+    #   end
+    #
+    #   # If the artifact is used at load time, users would
+    #   # need to select versions before loading the addon.
+    #   artifacts('Foo::Addon') do 
+    #     use :bar => '3.1'
+    #   end
+    #   # load the addon
+    #   addon 'foo_addon' # used bar-3.1 not 2.5
+    #   Foo::Addon.baz    # used baz-3.5
+    #   artifacts.namespace('Foo::Addon').use :baz => '4.0'
+    #   Foo::Addon.baz    # used baz-4.0
+    # 
+    def default(*specs)
+      @setting_defaults = true
+      begin
+        use(*specs)
+      ensure
+        @setting_defaults = false
+      end
+    end
+
+    # See examples for #need and #default methods.
+    def use(*specs)
+      set = lambda do |k, v|
+        k = k.to_sym
+        unless @setting_defaults && (@using[k] || @using[@aliases[k]])
+          @using[k] = v
+        end
+      end
+      specs.flatten.each do |spec|
+        if (Hash === spec || Struct === spec) &&
+           (spec.keys & ActsAsArtifact::ARTIFACT_ATTRIBUTES).empty?
+          spec.each_pair do |k, v|
+            if VersionRequirement.version?(v)
+              fail_unless_satisfied(requirement(k), {:version => v})
+              set.call(k, v)
+            else
+              spec = Artifact.to_hash(v)
+              fail_unless_satisfied(requirement(k), spec)
+              set.call(k, spec)
+            end
+          end
+        else
+          spec = Artifact.to_hash(spec)
+          unvers = Artifact.to_spec(spec.merge(:version => '-')).to_sym
+          fail_unless_satisfied(requirement(unvers), spec)
+          set.call(unvers, spec)
+        end
+      end
+      self
+    end
+    
+    alias_method :<<, :use
+    alias_method :[], :only
+    
+    # :call-seq:
+    #   artifacts.namespace('foo:bar')[:commons_net] = '1.0'
+    # 
+    # Selects an artifact version for usage, with hash like syntax.
+    # 
+    # Alias for #use
+    def []=(name, spec)
+      use name => spec
+    end
+
+    private
+    def normalize_name(name)
+      if name.to_s =~ /([^:]+:){2,4}/ 
+        spec = Artifact.to_hash(name.to_s)
+        Artifact.to_spec(spec.merge(:version => '-')).to_sym
+      elsif name.kind_of?(Symbol) || name.kind_of?(String)
+        name.to_sym
+      else
+        spec = Artifact.to_hash(name)
+        Artifact.to_spec(spec.merge(:version => '-')).to_sym
+      end
+    end
+    
+    def fail_unless_satisfied(req, spec)
+      if req && spec
+        spec = spec.dup
+        version = spec[:version]
+        unless req[:version].satisfied_by?(version)
+          raise "Version requirement #{Artifact.to_spec(req)} " +
+            "not met by #{version}"
+        end
+        spec.delete(:version)
+        if !spec.empty? && req.values_at(*spec.keys) != spec.values_at(*spec.keys)
+          spec[:version] = version
+          raise "Artifact attributes mismatch, " + 
+            "required #{Artifact.to_spec(req)}, got #{Artifact.to_spec(spec)}"
+        end
+      end
+    end
+
+  end # ArtifactNamespace
+
+  class VersionRequirement
+    
+    CMP_PROCS = Gem::Requirement::OPS.dup
+    CMP_REGEX = Gem::Requirement::OP_RE.dup
+    CMP_CHARS = CMP_PROCS.keys.join
+    BOOL_CHARS = '\|\&\!'
+    VER_CHARS = '\w\.'
+    
+    class << self
+      def version?(str)
+        /^\s*[#{VER_CHARS}]+\s*$/ === str
+      end
+      
+      def requirement?(str)
+        /[#{BOOL_CHARS}#{CMP_CHARS}\(\)]/ === str
+      end
+      
+      def create(str)
+        instance_eval normalize(str)
+      rescue StandardError => e
+        raise "Failed to parse #{str.inspect} due to: #{e}"
+      end
+
+      private
+      def requirement(req)
+        unless req =~ /^\s*(#{CMP_REGEX})?\s*([#{VER_CHARS}]+)\s*$/
+          raise "Invalid requirement string: #{req}"
+        end
+        comparator, version = $1, $2
+        version = Gem::Version.new(0).tap { |v| v.version = version }
+        VersionRequirement.new(nil, [$1, version])
+      end
+
+      def negate(vreq)
+        vreq.negative = !vreq.negative
+        vreq
+      end
+      
+      def normalize(str)
+        str = str.strip
+        if str[/[^\s\(\)#{BOOL_CHARS + VER_CHARS + CMP_CHARS}]/]
+          raise "version string #{str.inspect} contains invalid characters"
+        end
+        str.gsub!(/\s+(and|\&\&)\s+/, ' & ')
+        str.gsub!(/\s+(or|\|\|)\s+/, ' | ')
+        str.gsub!(/(^|\s*)not\s+/, ' ! ')
+        pattern = /(#{CMP_REGEX})?\s*[#{VER_CHARS}]+/
+        left_pattern = /[#{VER_CHARS}\)]$/
+        right_pattern = /^(#{pattern}|\()/
+        str = str.split.inject([]) do |ary, i|
+          ary << '&' if ary.last =~ left_pattern  && i =~ right_pattern
+          ary << i
+        end
+        str = str.join(' ')
+        str.gsub!('!', ' negate \1')
+        str.gsub!(pattern) do |expr|
+          case expr.strip
+          when 'not', 'negate' then 'negate '
+          else 'requirement("' + expr + '")'
+          end
+        end
+        str.gsub!(/negate\s+\(/, 'negate(')
+        str
+      end
+    end
+
+    def initialize(op, *requirements)
+      @op, @requirements = op, requirements
+    end
+
+    def has_alternatives?
+      requirements.size > 1
+    end
+
+    def default
+      default = nil
+      requirements.reverse.find do |r|
+        if Array === r
+          if !negative && (r.first.nil? || r.first.include?('='))
+            default = r.last.to_s
+          end
+        else
+          default = r.default
+        end
+      end
+      default
+    end
+
+    def satisfied_by?(version)
+      return false unless version
+      unless version.kind_of?(Gem::Version)
+        raise "Invalid version: #{version.inspect}" unless self.class.version?(version)
+        version = Gem::Version.new(0).tap { |v| v.version = version.strip }
+      end
+      message = op == :| ? :any? : :all?
+      result = requirements.send message do |req|
+        if Array === req
+          cmp, rv = *req
+          CMP_PROCS[cmp || '='].call(version, rv)
+        else
+          req.satisfied_by?(version)
+        end
+      end
+      negative ? !result : result
+    end
+
+    def |(other)
+      operation(:|, other)
+    end
+
+    def &(other)
+      operation(:&, other)
+    end
+
+    def to_s
+      str = requirements.map(&:to_s).join(" " + @op.to_s + " ").to_s
+      str = "( " + str + " )" if negative || requirements.size > 1
+      str = "!" + str if negative
+      str
+    end
+
+    attr_accessor :negative
+    protected
+    attr_reader :requirements, :op
+    def operation(op, other)
+      @op ||= op 
+      if negative == other.negative && @op == op && other.requirements.size == 1
+        @requirements << other.requirements.first
+        self
+      else
+        self.class.new(op, self, other)
+      end
+    end
+  end # VersionRequirement
+
+  module ArtifactSearch
+    extend self
+    
+    def enabled=(bool); @enabled = true & bool; end
+    def enabled?; @enabled; end
+    self.enabled = true
+    
+    def include(method = nil)
+      (@includes ||= []).tap { push method if method }
+    end
+
+    def exclude(method = nil)
+      (@excludes ||= []).tap { push method if method }
+    end
+    
+    def best_version(spec)
+      spec = Artifact.to_hash(spec)
+      spec[:version] = requirement = VersionRequirement.create(spec[:version])
+      select = lambda do |candidates|
+        candidates.find { |candidate| requirement.satisfied_by?(candidate) }
+      end
+      result = nil
+      methods = search_methods
+      if requirement.has_alternatives?
+        until result || methods.empty?
+          method = methods.shift
+          type = method.keys.first
+          from = method[type]
+          if (include.empty? || !(include & [:all, type, from]).empty?) &&
+              (exclude & [:all, type, from]).empty?
+            if from.respond_to?(:call)
+              versions = from.call(spec.dup)
+            else
+              versions = send("#{type}_versions", spec.dup, *from)
+            end
+            result = select[versions]
+          end
+        end
+      end
+      result ||= requirement.default
+      raise "Could not find #{Artifact.to_spec(spec)}"  +
+        "\n You may need to use an specific version instead of a requirement" +
+        "\n More Docu." unless result
+      spec.merge :version => result
+    end
+    
+    def requirement?(spec)
+      VersionRequirement.requirement?(spec[:version])
+    end
+    
+    private
+    def search_methods
+      [].tap do
+        push :runtime => [Artifact.list]
+        push :local => Buildr.repositories.local
+        Buildr.repositories.remote.each { |remote| push :remote => remote }
+        push :mvnrepository => []
+      end
+    end
+
+    def depend_version(spec)
+      spec[:version][/[\w\.]+/]
+    end
+
+    def runtime_versions(spec, artifacts)
+      spec_classif = spec.values_at(:group, :id, :type)
+      artifacts.inject([]) do |in_memory, str|
+        candidate = Artifact.to_hash(str)
+        if spec_classif == candidate.values_at(:group, :id, :type)
+          in_memory << candidate[:version]
+        end
+        in_memory
+      end
+    end
+    
+    def local_versions(spec, repo)
+      path = (spec[:group].split(/\./) + [spec[:id]]).flatten.join('/')
+      Dir[File.expand_path(path + "/*", repo)].map { |d| d.pathmap("%f") }.sort.reverse
+    end
+
+    def remote_versions(art, base, from = :metadata, fallback = true)
+      path = (art[:group].split(/\./) + [art[:id]]).flatten.join('/')
+      base ||= "http://mirrors.ibiblio.org/pub/mirrors/maven2"
+      uris = {:metadata => "#{base}/#{path}/maven-metadata.xml"}
+      uris[:listing] = "#{base}/#{path}/" if base =~ /^https?:/
+        xml = nil
+      until xml || uris.empty?
+        begin
+          xml = URI.read(uris.delete(from))
+        rescue URI::NotFoundError => e
+          from = fallback ? uris.keys.first : nil
+        end
+      end
+      return [] unless xml
+      doc = hpricot(xml)
+      case from
+      when :metadata then
+        doc.search("versions/version").map(&:innerHTML).reverse
+      when :listing then
+        doc.search("a[@href]").inject([]) { |vers, a|
+          vers << a.innerHTML.chop if a.innerHTML[-1..-1] == '/'
+          vers
+        }.sort.reverse
+      else 
+        fail "Don't know how to parse #{from}: \n#{xml.inspect}"
+      end
+    end
+
+    def mvnrepository_versions(art)
+      uri = "http://www.mvnrepository.com/artifact/#{art[:group]}/#{art[:id]}"
+      xml = begin
+              URI.read(uri)
+            rescue URI::NotFoundError => e
+              puts e.class, e
+              return []
+            end
+      doc = hpricot(xml)
+      doc.search("table.grid/tr/td[1]/a").map(&:innerHTML)
+    end
+
+    def hpricot(xml)
+      send :require, 'hpricot'
+    rescue LoadError
+      cmd = "gem install hpricot"
+      if PLATFORM[/java/]
+        cmd = "jruby -S " + cmd + " --source http://caldersphere.net"
+      end
+      raise <<-NOTICE
+      Your system is missing the hpricot gem, install it with:
+        #{cmd}
+      NOTICE
+    else
+      Hpricot(xml)
+    end
+  end # Search
+
+end
+

Modified: incubator/buildr/trunk/lib/java/groovyc.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/lib/java/groovyc.rb?rev=641083&r1=641082&r2=641083&view=diff
==============================================================================
--- incubator/buildr/trunk/lib/java/groovyc.rb (original)
+++ incubator/buildr/trunk/lib/java/groovyc.rb Tue Mar 25 17:08:47 2008
@@ -48,17 +48,18 @@
     # * :target            -- Bytecode compatibility.
     # * :javac             -- Hash of options passed to the ant javac task
     class Groovyc < Base
-
-      # Which groovy version to use?
-      VERSION = "1.5.3" unless const_defined?('VERSION')
-
-      REQUIRES = Buildr.struct(
-        :groovy => "org.codehaus.groovy:groovy:jar:#{VERSION}",
-        :commons_cli => 'commons-cli:commons-cli:jar:1.0',
-        :asm => 'asm:asm:jar:2.2.3',
-        :antlr => 'antlr:antlr:jar:2.7.7'
-      ) unless const_defined?('REQUIRES')
       
+      Buildr.artifacts(self) do
+        need :groovy => "org.codehaus.groovy:groovy:jar:>=1.5.3",
+             :commons_cli => 'commons-cli:commons-cli:jar:>=1.0',
+             :asm => 'asm:asm:jar:>=2.2',
+             :antlr => 'antlr:antlr:jar:>=2.7.7'
+        default :groovy => '1.5.3',
+                :commons_cli => '1.0',
+                :asm => '2.2.3',
+                :antlr => '2.7.7'
+      end
+        
       ANT_TASK = 'org.codehaus.groovy.ant.Groovyc'
       GROOVYC_OPTIONS = [:encoding, :verbose, :fork, :memoryInitialSize, :memoryMaximumSize, :listfiles, :stacktrace]
       JAVAC_OPTIONS = [:optimise, :warnings, :debug, :deprecation, :source, :target, :javac]
@@ -66,7 +67,7 @@
 
       class << self
         def dependencies #:nodoc:
-          Buildr.artifacts(REQUIRES)
+          Buildr.artifacts(Buildr.artifacts[self].used)
         end
 
         def applies_to?(project, task) #:nodoc:

Added: incubator/buildr/trunk/spec/artifact_namespace_spec.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/spec/artifact_namespace_spec.rb?rev=641083&view=auto
==============================================================================
--- incubator/buildr/trunk/spec/artifact_namespace_spec.rb (added)
+++ incubator/buildr/trunk/spec/artifact_namespace_spec.rb Tue Mar 25 17:08:47 2008
@@ -0,0 +1,127 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with this
+# work for additional information regarding copyright ownership.  The ASF
+# licenses this file to you 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.
+
+
+require File.join(File.dirname(__FILE__), 'spec_helpers')
+
+describe Buildr::ArtifactNamespace, 'obtained from Buildr#artifacts' do 
+  before :each do 
+    ArtifactNamespace.clear
+  end
+  
+  it 'should tap root namespace if called outside a project definition' do
+    expected = be_kind_of(ArtifactNamespace)
+    artifacts { |ns| ns.name.should == ArtifactNamespace::ROOT }
+    artifacts { |ns| ns.should expected }.should expected
+    artifacts { self.should expected }
+  end
+
+  it 'should tap root namespace when given nil' do
+    artifacts(nil) { |ns| ns.name.should == ArtifactNamespace::ROOT }
+  end
+
+  it 'should return an array responding to :namespace if no block given' do
+    ary = artifacts
+    ary.should be_kind_of(Array)
+    ary.should respond_to(:namespace)
+    ary.namespace.should be_kind_of(ArtifactNamespace)
+    ary.namespace.name.should === ArtifactNamespace::ROOT
+  end
+
+  it 'should return the namespace for the current project' do
+    define 'foo' do
+      artifacts { |ns| ns.name.should == name.intern }
+      define 'bar' do 
+        artifacts { |ns| ns.name.should == name.intern }
+      end
+    end
+  end
+
+  it 'should take the first argument as the scope when given a block' do 
+    artifacts('moo') { |ns| ns.name.should == :moo }
+    artifacts(:mooo) { |ns| ns.name.should == :mooo }
+    a = Module.new { def self.name; "Some::Module::A"; end }
+    artifacts(a) { |ns| ns.name.should == "Some:Module:A".intern }
+  end  
+end
+
+describe Buildr::ArtifactNamespace do 
+  before :each do 
+    ArtifactNamespace.clear
+  end
+
+  it 'should have no parent if its the root namespace' do
+    root = artifacts.namespace
+    root.parent.should be_nil
+  end
+
+  it 'should reference it\'s parent' do
+    root = artifacts.namespace
+    define 'foo' do
+      foo = artifacts { |ns| ns.parent.should == root }
+      define 'bar' do
+        artifacts { |ns| ns.parent.should == foo }
+      end
+    end
+  end
+
+  it 'should register a requirement with the #need method' do
+    artifacts do |root|
+      root.need 'foo:bar:jar:>1.0'
+      root.should_not be_satisfied('foo:bar:jar:?')
+      root.need :bat => 'foo:bat:jar:>1.0'
+      root.should_not be_satisfied(:bat)
+    end
+  end
+
+  it 'should register an artifact with the #use method' do
+    artifacts do |root|
+      root.use :bat => 'bat:bar:jar:2.0'
+      root.spec(:bat).should_not be_nil
+      root.spec(:bat)[:version].should == '2.0'
+      root.spec(:bat)[:group].should == 'bat'
+      artifacts(:bat).should_not be_empty
+      root.use 'bat:man:jar:3.0'
+      root.spec('bat:man:jar:?')[:version].should == '3.0'
+      artifacts('bat:man:jar:?').should_not be_empty
+    end
+  end
+
+  it 'should set defaults witht the #default method' do 
+    artifacts do |root|
+      root.use :bar => 'foo:bar:jar:2.0'
+      root.spec(:bar).should_not be_nil
+      root.default :bar => 'foo:bar:jar:1.9'
+      root.spec(:bar)[:version].should == '2.0'
+      root.default :baz => 'foo:baz:jar:1.8'
+      root.spec(:baz)[:version].should == '1.8'
+    end
+  end
+
+  it 'should complain if requirement is not met' do 
+    artifacts do |root|
+      root.need :foo => 'foo:bar:jar:>3.0'
+      lambda { root.use :foo => '2.0' }.should raise_error(Exception)
+      lambda { root.use :foo => 'foo:baz:jar:3.1' }.should raise_error(Exception)
+      root.use :foo => '3.2'
+      root.spec(:foo)[:version].should == '3.2'
+      root.use :bat => '2.0'
+      lambda { root.need :bat => 'foo:bat:jar:>2.0' }.should raise_error(Exception)
+      root.use :baz => 'foo:ban:jar:2.1'
+      lambda { root.need :baz => 'foo:baz:jar:>2.0' }.should raise_error(Exception)
+    end
+  end
+  
+end