# 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 'xmlsimple' require 'buildr/packaging' 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}" pom = POM.new(IO.read(filename)) 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 # # 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). def dependencies(scopes = SCOPES_WE_USE) #try to cache dependencies also @depends_for_scopes ||= {} unless depends = @depends_for_scopes[scopes] declared = project["dependencies"].first["dependency"] rescue nil depends = (declared || []).reject { |dep| value_of(dep["optional"]) =~ /true/ }. map { |dep| spec = pom_to_hash(dep, properties) apply = managed(spec) spec = apply.merge(spec) if apply #calculate transitive dependencies if scopes.include?(spec[:scope]) spec.delete(:scope) exclusions = dep["exclusions"]["exclusion"] rescue nil transitive_deps = POM.load(spec).dependencies(SCOPES_TRANSITIVE) transitive_deps = transitive_deps.reject{|dep| exclusions.find {|ex| dep.index("#{dep['groupdId'].first}:#{dep['artifactId'].first}:") == 0} } if exclusions [Artifact.to_spec(spec)] + transitive_deps end }.flatten.compact #.uniq_by{|spec| art = spec.split(':'); "#{art[0]}:#{art[1]}"} @depends_for_scopes[scopes] = 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 = ["groupId", "artifactId", "version", "packaging"].inject({}) { |hash, key| value = project[key] || (parent ? parent.project[key] : nil) hash["pom.#{key}"] = hash["project.#{key}"] = value_of(value) if value hash } props = project["properties"].first rescue {} props = props.inject({}) { |mapped, pair| mapped[pair.first] = value_of(pair.last, pom) ; 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 substitute ? value.gsub(/\$\{([^}]+)\}/) { |key| substitute[$1] } : 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 } { :type=>"jar" }.merge(hash) end end end