Author: vborja
Date: Tue Mar 25 17:07:54 2008
New Revision: 641082
URL: http://svn.apache.org/viewvc?rev=641082&view=rev
Log:
ArtifactSearch + VersionRequirement
TODO: lots of documentation
Added:
incubator/buildr/trunk/lib/java/artifact_search.rb
incubator/buildr/trunk/spec/version_requirement_spec.rb
Modified:
incubator/buildr/trunk/lib/java/artifact.rb
Modified: incubator/buildr/trunk/lib/java/artifact.rb
URL:
http://svn.apache.org/viewvc/incubator/buildr/trunk/lib/java/artifact.rb?rev=641082&r1=641081&r2=641082&view=diff
==============================================================================
--- incubator/buildr/trunk/lib/java/artifact.rb (original)
+++ incubator/buildr/trunk/lib/java/artifact.rb Tue Mar 25 17:07:54 2008
@@ -17,6 +17,7 @@
require 'core/project'
require 'core/transports'
require 'builder'
+require 'java/artifact_search'
module Buildr
@@ -211,6 +212,7 @@
#
# Returns an array of specs for all the registered artifacts. (Anything
created from artifact, or package).
def list
+ @artifacts ||= {}
@artifacts.keys
end
@@ -556,6 +558,9 @@
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]
Added: incubator/buildr/trunk/lib/java/artifact_search.rb
URL:
http://svn.apache.org/viewvc/incubator/buildr/trunk/lib/java/artifact_search.rb?rev=641082&view=auto
==============================================================================
--- incubator/buildr/trunk/lib/java/artifact_search.rb (added)
+++ incubator/buildr/trunk/lib/java/artifact_search.rb Tue Mar 25 17:07:54 2008
@@ -0,0 +1,312 @@
+# 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 'java/artifact'
+
+module Buildr
+ 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("[EMAIL PROTECTED]").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
+
+
+ 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 requirement?(str)
+ str[/[#{BOOL_CHARS}#{CMP_CHARS}\(\)]/]
+ 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 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)
+ unless version.kind_of?(Gem::Version)
+ version = Gem::Version.new(0).tap { |v| v.version = version }
+ 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
+
+ module ArtifactSearchExtension
+ include Extension
+
+ def version_requirement(str)
+ VersionRequirement.create(str)
+ end
+
+ def artifact_search_enabled(bool)
+ if block_given?
+ old = ArtifactSearch.enabled?
+ begin
+ ArtifactSearch.enabled = bool
+ yield
+ ensure
+ ArtifactSearch.enabled = old
+ end
+ else
+ ArtifactSearch.enabled = bool
+ end
+ end
+ end
+
+ class Project
+ include ArtifactSearchExtension
+ end
+
+end
+
Added: incubator/buildr/trunk/spec/version_requirement_spec.rb
URL:
http://svn.apache.org/viewvc/incubator/buildr/trunk/spec/version_requirement_spec.rb?rev=641082&view=auto
==============================================================================
--- incubator/buildr/trunk/spec/version_requirement_spec.rb (added)
+++ incubator/buildr/trunk/spec/version_requirement_spec.rb Tue Mar 25 17:07:54
2008
@@ -0,0 +1,115 @@
+# 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::VersionRequirement, '.create' do
+ def create(str)
+ Buildr::VersionRequirement.create(str)
+ end
+
+ it 'should complain on invalid input' do
+ lambda { create }.should raise_error(Exception)
+ lambda { create('%') }.should raise_error(Exception, /invalid character/)
+ lambda { create('1#{0}') }.should raise_error(Exception, /invalid
character/)
+ lambda { create('1.0rc`exit`') }.should raise_error(Exception, /invalid
character/)
+ lambda { create(1.0) }.should raise_error(Exception)
+ lambda { create('1.0') }.should_not raise_error(Exception)
+ lambda { create('1.0rc3') }.should_not raise_error(Exception)
+ end
+
+ it 'should create a single version requirement' do
+ create('1.0').should_not have_alternatives
+ end
+end
+
+describe Buildr::VersionRequirement, '#satisfied_by?' do
+ def should_satisfy(str, valids = [], invalids = [])
+ req = Buildr::VersionRequirement.create(str)
+ valids.each { |v| req.should be_satisfied_by(v) }
+ invalids.each { |v| req.should_not be_satisfied_by(v) }
+ end
+
+ it 'should accept Gem version operators' do
+ should_satisfy '1.0', %w(1 1.0), %w(1.1 0.1)
+ should_satisfy '=1.0', %w(1 1.0), %w(1.1 0.1)
+ should_satisfy '= 1.0', %w(1 1.0), %w(1.1 0.1)
+ should_satisfy '!= 1.0', %w(0.9 1.1 2), %w(1 1.0 1.0.0)
+
+ should_satisfy '>1.0', %w(1.0.1), %w(1 1.0 0.1)
+ should_satisfy '>=1.0', %w(1.0.1 1 1.0), %w(0.9)
+
+ should_satisfy '<1.0', %w(0.9, 0.9.9), %w(1 1.0 1.1 2)
+ should_satisfy '<=1.0', %w(0.9, 0.9.9 1 1.0), %w(1.1 2)
+
+ should_satisfy '~> 1.2.3', %w(1.2.3 1.2.3.4 1.2.4), %w(1.2.1 0.9 1.4 2)
+ end
+
+ it 'should accept logic not operator' do
+ should_satisfy 'not 0.5', %w(0 1), %w(0.5)
+ should_satisfy '! 0.5', %w(0 1), %w(0.5)
+ should_satisfy '!= 0.5', %w(0 1), %w(0.5)
+ should_satisfy '!<= 0.5', %w(0.5.1 2), %w(0.5)
+ end
+
+ it 'should accept logic or operator' do
+ should_satisfy '0.5 or 2.0', %w(0.5 2.0), %w(1.0 0.5.1 2.0.9)
+ should_satisfy '0.5 | 2.0', %w(0.5 2.0), %w(1.0 0.5.1 2.0.9)
+ end
+
+ it 'should accept logic and operator' do
+ should_satisfy '>1.5 and <2.0', %w(1.6 1.9), %w(1.5 2 2.0)
+ should_satisfy '>1.5 & <2.0', %w(1.6 1.9), %w(1.5 2 2.0)
+ end
+
+ it 'should assume logic and if missing operator between expressions' do
+ should_satisfy '>1.5 <2.0', %w(1.6 1.9), %w(1.5 2 2.0)
+ end
+
+ it 'should allow combining logic operators' do
+ should_satisfy '>1.0 | <2.0 | =3.0', %w(1.5 3.0 1 2 4)
+ should_satisfy '>1.0 & <2.0 | =3.0', %w(1.3 3.0), %w(1 2)
+ should_satisfy '=1.0 | <2.0 & =0.5', %w(0.5 1.0), %w(1.1 0.1 2)
+ should_satisfy '~>1.1 | ~>1.3 | ~>1.5 | 2.0', %w(2 1.5.6 1.1.2 1.1.3),
%w(1.0.9 0.5 2.2.1)
+ should_satisfy 'not(2) | 1', %w(1 3), %w(2)
+ end
+
+ it 'should allow using parens to group logic expressions' do
+ should_satisfy '(1.0)', %w(1 1.0), %w(0.9 1.1)
+ should_satisfy '!( !(1.0) )', %w(1 1.0), %w(0.9 1.1)
+ should_satisfy '1 | !(2 | 3)', %w(1), %w(2 3)
+ should_satisfy '!(2 | 3) | 1', %w(1), %w(2 3)
+ end
+end
+
+describe Buildr::VersionRequirement, '#default' do
+ it 'should return nil if missing default requirement' do
+ Buildr::VersionRequirement.create('>1').default.should be_nil
+ Buildr::VersionRequirement.create('<1').default.should be_nil
+ Buildr::VersionRequirement.create('!1').default.should be_nil
+ Buildr::VersionRequirement.create('!<=1').default.should be_nil
+ end
+
+ it 'should return the last version with a = requirement' do
+ Buildr::VersionRequirement.create('1').default.should == '1'
+ Buildr::VersionRequirement.create('=1').default.should == '1'
+ Buildr::VersionRequirement.create('<=1').default.should == '1'
+ Buildr::VersionRequirement.create('>=1').default.should == '1'
+ Buildr::VersionRequirement.create('1 | 2 | 3').default.should == '3'
+ Buildr::VersionRequirement.create('1 2 | 3').default.should == '3'
+ Buildr::VersionRequirement.create('1 & 2 | 3').default.should == '3'
+ end
+end