| # 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. |
| |
| # |
| module Buildr |
| class POM |
| |
| POM_TO_SPEC_MAP = { :group=> 'groupId', :id=> 'artifactId', :type=> 'type', |
| :version=> 'version', :classifier=> 'classifier', :scope=> 'scope'} |
| SCOPES_TRANSITIVE = [nil, 'compile', 'runtime'] |
| SCOPES_WE_USE = SCOPES_TRANSITIVE + ['provided'] |
| |
| # POM project as Hash (using XmlSimple). |
| attr_reader :project |
| # Parent POM if referenced by this POM. |
| attr_reader :parent |
| |
| class << self |
| |
| # :call-seq: |
| # POM.load(arg) |
| # |
| # Load new POM object form various kind of sources such as artifact, hash representing spec, filename, XML. |
| def load(source) |
| case source |
| when Hash |
| load(Buildr.artifact(source).pom) |
| when Artifact |
| pom = source.pom |
| pom.invoke |
| load(pom.to_s) |
| when Rake::FileTask |
| source.invoke |
| load(source.to_s) |
| when String |
| filename = File.expand_path(source) |
| unless pom = cache[filename] |
| trace "Loading m2 pom file from #{filename}" |
| begin |
| pom = POM.new(IO.read(filename)) |
| rescue REXML::ParseException => e |
| fail "Could not parse #{filename}, #{e.continued_exception}" |
| end |
| cache[filename] = pom |
| end |
| pom |
| else |
| raise ArgumentError, 'Expecting Hash spec, Artifact, file name or file task' |
| end |
| end |
| |
| private |
| |
| def cache() |
| @cache ||= {} |
| end |
| |
| end |
| |
| def initialize(xml) #:nodoc: |
| @project = XmlSimple.xml_in(xml) |
| @parent = POM.load(pom_to_hash(project["parent"].first).merge(:type=>'pom')) if project['parent'] |
| end |
| |
| # :call-seq: |
| # dependencies(scopes?) => artifacts |
| # dependencies(:scopes = [:runtime, :test, ...], :optional = true) => artifacts |
| # |
| # Returns list of required dependencies as specified by the POM. You can specify which scopes |
| # to use (e.g. "compile", "runtime"); use +nil+ for dependencies with unspecified scope. |
| # The default scopes are +nil+, "compile" and "runtime" (aka SCOPES_WE_USE) and no optional dependencies. |
| # Specifying optional = true will return all optional dependencies matching the given scopes. |
| def dependencies(options = {}) |
| # backward compatibility |
| options = { :scopes => options } if Array === options |
| |
| # support symbols, but don't fidget with nil |
| options[:scopes] = (options[:scopes] || SCOPES_WE_USE).map { |s| s.to_s if s } |
| |
| # try to cache dependencies also |
| @depends_for_scopes ||= {} |
| unless depends = @depends_for_scopes[options] |
| declared = project['dependencies'].first['dependency'] rescue nil |
| depends = (declared || []) |
| depends = depends.reject { |dep| value_of(dep['optional']) =~ /true/ } unless options[:optional] |
| depends = depends.map { |dep| |
| spec = pom_to_hash(dep, properties) |
| apply = managed(spec) |
| spec = apply.merge(spec) if apply |
| |
| next if options[:exclusions] && options[:exclusions].any? { |ex| dep['groupId'] == ex['groupId'] && dep['artifactId'] == ex['artifactId'] } |
| |
| # calculate transitive dependencies |
| if options[:scopes].include?(spec[:scope]) |
| spec.delete(:scope) |
| |
| exclusions = dep['exclusions'].first['exclusion'] rescue nil |
| transitive_deps = POM.load(spec).dependencies(:exclusions => exclusions, :scopes => (options[:scopes_transitive] || SCOPES_TRANSITIVE) ) rescue [] |
| |
| [Artifact.to_spec(spec)] + transitive_deps |
| end |
| }.flatten.compact #.uniq_by{|spec| art = spec.split(':'); "#{art[0]}:#{art[1]}"} |
| @depends_for_scopes[options] = depends |
| end |
| depends |
| end |
| |
| # :call-seq: |
| # properties() => hash |
| # |
| # Returns properties available to this POM as hash. Includes explicit properties and pom.xxx/project.xxx |
| # properties for groupId, artifactId, version and packaging. |
| def properties() |
| @properties ||= begin |
| pom = %w(groupId artifactId version packaging).inject({}) { |hash, key| |
| value = project[key] || (parent ? parent.project[key] : nil) |
| hash[key] = hash["pom.#{key}"] = hash["project.#{key}"] = value_of(value) if value |
| hash |
| } |
| pom = %w(groupId artifactId version).inject(pom) { |hash, key| |
| value = parent.project[key] |
| hash[key] = hash["pom.parent.#{key}"] = hash["project.parent.#{key}"] = value_of(value) if value |
| hash |
| } if parent |
| props = project['properties'].first rescue {} |
| props = props.inject({}) { |mapped, pair| mapped[pair.first] = value_of(pair.last, props) ; mapped } |
| (parent ? parent.properties.merge(props) : props).merge(pom) |
| end |
| end |
| |
| # :call-seq: |
| # managed() => hash |
| # managed(hash) => hash |
| # |
| # The first form returns all the managed dependencies specified by this POM in dependencyManagement. |
| # The second form uses a single spec hash and expands it from the current/parent POM. Used to determine |
| # the version number if specified in dependencyManagement instead of dependencies. |
| def managed(spec = nil) |
| if spec |
| managed.detect { |dep| [:group, :id, :type, :classifier].all? { |key| spec[key] == dep[key] } } || |
| (parent ? parent.managed(spec) : nil) |
| else |
| @managed ||= begin |
| managed = project['dependencyManagement'].first['dependencies'].first['dependency'] rescue nil |
| managed ? managed.map { |dep| pom_to_hash(dep, properties) } : [] |
| end |
| end |
| end |
| |
| private |
| |
| # :call-seq: |
| # value_of(element) => string |
| # value_of(element, true) => string |
| # |
| # Returns the normalized text value of an element from its XmlSimple value. The second form performs |
| # property substitution. |
| def value_of(element, substitute = nil) |
| value = element.to_a.join.strip |
| value = value.gsub(/\$\{([^}]+)\}/) { |key| Array(substitute[$1]).join.strip } if substitute |
| value |
| end |
| |
| # :call-seq: |
| # pom_to_hash(element) => hash |
| # pom_to_hash(element, true) => hash |
| # |
| # Return the spec hash from an XmlSimple POM referencing element (e.g. project, parent, dependency). |
| # The second form performs property substitution. |
| def pom_to_hash(element, substitute = nil) |
| hash = POM_TO_SPEC_MAP.inject({}) { |spec, pair| |
| spec[pair.first] = value_of(element[pair.last], substitute) if element[pair.last] |
| spec |
| } |
| {:scope => 'compile', :type => 'jar'}.merge(hash) |
| end |
| |
| end |
| end |