pompompom 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +5 -0
- data/Gemfile +11 -0
- data/LICENSE +20 -0
- data/README.mdown +32 -0
- data/Rakefile +7 -0
- data/TODO +0 -0
- data/bin/pompompom +17 -0
- data/lib/pom_pom_pom.rb +1 -0
- data/lib/pompompom/cli.rb +87 -0
- data/lib/pompompom/dependency.rb +55 -0
- data/lib/pompompom/downloader.rb +15 -0
- data/lib/pompompom/metadata.rb +26 -0
- data/lib/pompompom/pom.rb +234 -0
- data/lib/pompompom/resolver.rb +149 -0
- data/lib/pompompom/url_builder.rb +37 -0
- data/lib/pompompom.rb +10 -0
- data/pompompom.gemspec +116 -0
- data/spec/pompompom/cli_spec.rb +93 -0
- data/spec/pompompom/dependency_spec.rb +144 -0
- data/spec/pompompom/metadata_spec.rb +38 -0
- data/spec/pompompom/pom_spec.rb +221 -0
- data/spec/pompompom/resolver_spec.rb +205 -0
- data/spec/pompompom/url_builders_shared.rb +33 -0
- data/spec/resources/repository/aopalliance/aopalliance/1.0/aopalliance-1.0.jar +0 -0
- data/spec/resources/repository/aopalliance/aopalliance/1.0/aopalliance-1.0.pom +15 -0
- data/spec/resources/repository/aopalliance/aopalliance/maven-metadata.xml +5 -0
- data/spec/resources/repository/com/example/test/8.8/test-8.8.jar +0 -0
- data/spec/resources/repository/com/example/test/8.8/test-8.8.pom +11 -0
- data/spec/resources/repository/com/example/test/9.9/test-9.9.jar +0 -0
- data/spec/resources/repository/com/example/test/9.9/test-9.9.pom +11 -0
- data/spec/resources/repository/com/example/test/maven-metadata.xml +11 -0
- data/spec/resources/repository/com/example/test-abc/1.0/test-abc-1.0.jar +0 -0
- data/spec/resources/repository/com/example/test-abc/1.0/test-abc-1.0.pom +19 -0
- data/spec/resources/repository/com/example/test-abc/maven-metadata.xml +13 -0
- data/spec/resources/repository/com/example/test-child/1.0/test-child-1.0.pom +16 -0
- data/spec/resources/repository/com/example/test-def/1.0/test-def-1.0.jar +0 -0
- data/spec/resources/repository/com/example/test-def/1.0/test-def-1.0.pom +19 -0
- data/spec/resources/repository/com/example/test-exclusions/1.0/test-exclusions-1.0.jar +1 -0
- data/spec/resources/repository/com/example/test-exclusions/1.0/test-exclusions-1.0.pom +25 -0
- data/spec/resources/repository/com/example/test-optional/1.0/test-optional-1.0.jar +1 -0
- data/spec/resources/repository/com/example/test-optional/1.0/test-optional-1.0.pom +20 -0
- data/spec/resources/repository/com/example/test-parent/1.0/test-parent-1.0.pom +16 -0
- data/spec/resources/repository/com/google/google/1/google-1.pom +37 -0
- data/spec/resources/repository/com/google/inject/guice/2.0/guice-2.0.jar +1 -0
- data/spec/resources/repository/com/google/inject/guice/2.0/guice-2.0.pom +20 -0
- data/spec/resources/repository/com/google/inject/guice-parent/2.0/guice-parent-2.0.pom +70 -0
- data/spec/resources/repository/com/rabbitmq/amqp-client/1.8.0/amqp-client-1.8.0.jar +1 -0
- data/spec/resources/repository/com/rabbitmq/amqp-client/1.8.0/amqp-client-1.8.0.pom +98 -0
- data/spec/resources/repository/commons-cli/commons-cli/1.1/commons-cli-1.1.jar +1 -0
- data/spec/resources/repository/commons-cli/commons-cli/1.1/commons-cli-1.1.pom +165 -0
- data/spec/resources/repository/commons-io/commons-io/1.2/commons-io-1.2.jar +1 -0
- data/spec/resources/repository/commons-io/commons-io/1.2/commons-io-1.2.pom +235 -0
- data/spec/resources/repository/javax/servlet/servlet-api/2.5/servlet-api-2.5.jar +0 -0
- data/spec/resources/repository/javax/servlet/servlet-api/2.5/servlet-api-2.5.pom +6 -0
- data/spec/resources/repository/net/iconara/pompompom/1.0/pompompom-1.0.jar +0 -0
- data/spec/resources/repository/net/iconara/pompompom/1.0/pompompom-1.0.pom +11 -0
- data/spec/resources/repository/org/eclipse/jetty/jetty-continuation/7.1.4.v20100610/jetty-continuation-7.1.4.v20100610.pom +73 -0
- data/spec/resources/repository/org/eclipse/jetty/jetty-http/7.1.4.v20100610/jetty-http-7.1.4.v20100610.pom +73 -0
- data/spec/resources/repository/org/eclipse/jetty/jetty-io/7.1.4.v20100610/jetty-io-7.1.4.v20100610.pom +62 -0
- data/spec/resources/repository/org/eclipse/jetty/jetty-parent/15/jetty-parent-15.pom +419 -0
- data/spec/resources/repository/org/eclipse/jetty/jetty-project/7.1.4.v20100610/jetty-project-7.1.4.v20100610.pom +368 -0
- data/spec/resources/repository/org/eclipse/jetty/jetty-server/7.1.4.v20100610/jetty-server-7.1.4.v20100610.jar +0 -0
- data/spec/resources/repository/org/eclipse/jetty/jetty-server/7.1.4.v20100610/jetty-server-7.1.4.v20100610.pom +101 -0
- data/spec/resources/repository/org/eclipse/jetty/jetty-util/7.1.4.v20100610/jetty-util-7.1.4.v20100610.pom +80 -0
- data/spec/spec_helper.rb +5 -0
- data/tasks/gem.rake +18 -0
- data/tasks/rdoc.rake +13 -0
- data/tasks/spec.rake +17 -0
- metadata +142 -0
data/Gemfile
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2010 Theo Hultberg
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.mdown
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Pom Pom Pom
|
|
2
|
+
|
|
3
|
+
Pom Pom Pom is a basic dependency manager for Maven repository artifacts.
|
|
4
|
+
|
|
5
|
+
The following code would download `amqp-client-1.8.0.jar` and its dependencies, `commons-cli-1.1.jar` and `commons-io-1.1.jar`, to the directory `lib`:
|
|
6
|
+
|
|
7
|
+
dependencies = %w(com.rabbitmq:amqp-client:1.8.0 com.google.inject:guice:2.0)
|
|
8
|
+
dependencies = dependencies.map { |d| Dependency.parse(d) }
|
|
9
|
+
repositories = %w(http://repo1.maven.org/maven2)
|
|
10
|
+
resolver = Resolver.new(repositories)
|
|
11
|
+
resolver.download('lib', true, *dependencies)
|
|
12
|
+
|
|
13
|
+
There will be less verbose ways of doing this in the future. The goal is to be able to use this with rake, like this:
|
|
14
|
+
|
|
15
|
+
task :dependencies do
|
|
16
|
+
pompompom :artifacts => %w(com.rabbitmq:amqp-client:1.8.0 com.google.inject:guice:2.0)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
Which would do the same as the code above.
|
|
20
|
+
|
|
21
|
+
There is a command line tool that can be used to install artifacts:
|
|
22
|
+
|
|
23
|
+
pompompom com.rabbitmq:amqp-client:1.8.0 com.google.inject:guice:2.0
|
|
24
|
+
|
|
25
|
+
It will create a directory called `lib` and download JARs into it. In the future things may be configurable, and POMs will be cached to make things quicker.
|
|
26
|
+
|
|
27
|
+
## Why another dependency management tool, why not use Maven, Buildr, sbt or Ivy?
|
|
28
|
+
|
|
29
|
+
Every time I look at Maven I cringe. Buildr is almost as bad, but hides some of the complexity. Ivy is better, but still too much noisy XML.
|
|
30
|
+
|
|
31
|
+
Pom Pom Pom is meant to do more or less what Ivy does, but in a way that doesn't make you want to tear out your eyes.
|
|
32
|
+
|
data/Rakefile
ADDED
data/TODO
ADDED
|
File without changes
|
data/bin/pompompom
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
if File.symlink?(__FILE__)
|
|
4
|
+
$: << File.expand_path('../../lib', File.readlink(__FILE__))
|
|
5
|
+
else
|
|
6
|
+
$: << File.expand_path('../../lib', __FILE__)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
require 'rubygems'
|
|
10
|
+
|
|
11
|
+
gem 'hpricot'
|
|
12
|
+
gem 'immutable_struct'
|
|
13
|
+
|
|
14
|
+
require 'pompompom'
|
|
15
|
+
require 'pompompom/cli'
|
|
16
|
+
|
|
17
|
+
exit PomPomPom::Cli.new(STDIN, STDOUT, STDERR).run!(*ARGV)
|
data/lib/pom_pom_pom.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'pompompom'
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
module PomPomPom
|
|
2
|
+
class Cli
|
|
3
|
+
STANDARD_REPOSITORIES = [
|
|
4
|
+
'http://repo1.maven.org/maven2/'
|
|
5
|
+
]
|
|
6
|
+
|
|
7
|
+
DEFAULT_DESTINATION_DIR = 'lib'
|
|
8
|
+
|
|
9
|
+
def initialize(stdin, stdout, stderr)
|
|
10
|
+
@stdin, @stdout, @stderr = stdin, stdout, stderr
|
|
11
|
+
@status_logger = EchoLogger.new(@stderr)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def run!(*args)
|
|
15
|
+
if args.empty?
|
|
16
|
+
print_usage
|
|
17
|
+
return 1
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
resolver = create_resolver
|
|
21
|
+
|
|
22
|
+
create_lib_directory!
|
|
23
|
+
|
|
24
|
+
@status_logger.info("Determining transitive dependencies...")
|
|
25
|
+
|
|
26
|
+
dependencies = parse_dependencies(args)
|
|
27
|
+
dependencies = resolver.find_transitive_dependencies(*dependencies)
|
|
28
|
+
dependencies = dependencies.reject { |d| File.exists?(File.join(DEFAULT_DESTINATION_DIR, d.jar_file_name)) }
|
|
29
|
+
|
|
30
|
+
if dependencies.empty?
|
|
31
|
+
@status_logger.info('All dependencies are met')
|
|
32
|
+
else
|
|
33
|
+
dependencies.each do |dependency|
|
|
34
|
+
@status_logger.info(%(Downloading "#{dependency.to_dependency.to_s}"))
|
|
35
|
+
resolver.download!(DEFAULT_DESTINATION_DIR, false, dependency)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
return 0
|
|
40
|
+
rescue => e
|
|
41
|
+
@status_logger.warn(%(Warning: #{e.message}))
|
|
42
|
+
return 1
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def create_lib_directory!
|
|
48
|
+
return if File.directory?(DEFAULT_DESTINATION_DIR)
|
|
49
|
+
raise %(Cannot create destination, "#{DEFAULT_DESTINATION_DIR}" is a file!) if File.exists?(DEFAULT_DESTINATION_DIR)
|
|
50
|
+
Dir.mkdir(DEFAULT_DESTINATION_DIR)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def create_resolver
|
|
54
|
+
Resolver.new(STANDARD_REPOSITORIES, :logger => @status_logger)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def parse_dependencies(args)
|
|
58
|
+
args.map do |coordinate|
|
|
59
|
+
begin
|
|
60
|
+
Dependency.parse(coordinate)
|
|
61
|
+
rescue ArgumentError => e
|
|
62
|
+
@status_logger.warn(%(Warning: "#{coordinate}" is not a valid artifact coordinate))
|
|
63
|
+
nil
|
|
64
|
+
end
|
|
65
|
+
end.compact
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def print_usage
|
|
69
|
+
@status_logger.info('Usage: pompompom <group_id:artifact_id:version> [<group_id:artifact_id:version>]')
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
class EchoLogger
|
|
73
|
+
def initialize(io)
|
|
74
|
+
@io = io
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def debug(msg); end
|
|
78
|
+
|
|
79
|
+
def info(msg)
|
|
80
|
+
@io.puts(msg)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
alias_method :warn, :info
|
|
84
|
+
alias_method :fatal, :info
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
require 'immutable_struct'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module PomPomPom
|
|
5
|
+
class Dependency < ImmutableStruct.new(:group_id, :artifact_id, :version, :packaging, :classifier, :optional, :exclusions)
|
|
6
|
+
include UrlBuilder
|
|
7
|
+
|
|
8
|
+
def self.parse(artifact_coordinates)
|
|
9
|
+
raise ArgumentError, %(Malformed artifact coordinate: "#{artifact_coordinates}") if artifact_coordinates.nil? || artifact_coordinates.strip.length == 0
|
|
10
|
+
components = artifact_coordinates.strip.split(':')
|
|
11
|
+
raise ArgumentError, %(Malformed artifact coordinate: "#{artifact_coordinates}") if components.size < 3 || components.size > 5
|
|
12
|
+
case components.size
|
|
13
|
+
when 3
|
|
14
|
+
group_id, artifact_id, version = components
|
|
15
|
+
when 4
|
|
16
|
+
group_id, artifact_id, packaging, version = components
|
|
17
|
+
when 5
|
|
18
|
+
group_id, artifact_id, packaging, classifier, version = components
|
|
19
|
+
end
|
|
20
|
+
Dependency.new(group_id, artifact_id, version, packaging, classifier)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def exclusions
|
|
24
|
+
self[:exclusions] || []
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def optional?
|
|
28
|
+
optional
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def has_version?
|
|
32
|
+
!version.nil?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def same_artifact?(o)
|
|
36
|
+
o.artifact_id == self.artifact_id && o.group_id == self.group_id
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def eql?(o)
|
|
40
|
+
o.to_s == to_s
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def hash
|
|
44
|
+
to_s.hash
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def clone(overrides={})
|
|
48
|
+
self.class.new(self.to_h.merge(overrides))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def to_s
|
|
52
|
+
"#{group_id}:#{artifact_id}:#{version}"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module PomPomPom
|
|
2
|
+
class Metadata
|
|
3
|
+
def initialize(io)
|
|
4
|
+
@io = io
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def parse!
|
|
8
|
+
doc = Hpricot.XML(@io)
|
|
9
|
+
versioning = doc.at('/metadata/versioning')
|
|
10
|
+
if versioning
|
|
11
|
+
release = versioning.at('release/text()')
|
|
12
|
+
if release
|
|
13
|
+
@latest_version = release.to_s
|
|
14
|
+
else
|
|
15
|
+
@latest_version = versioning.search('versions/version/text()').map(&:to_s).sort.last
|
|
16
|
+
end
|
|
17
|
+
else
|
|
18
|
+
@latest_version = doc.at('/metadata/version/text()').to_s
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def latest_version
|
|
23
|
+
@latest_version
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
require 'hpricot'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module PomPomPom
|
|
5
|
+
class Pom
|
|
6
|
+
include UrlBuilder
|
|
7
|
+
|
|
8
|
+
PROPERTIES = [:group_id, :artifact_id, :version, :name, :description, :url, :packaging]
|
|
9
|
+
|
|
10
|
+
attr_reader *PROPERTIES
|
|
11
|
+
attr_reader :parent
|
|
12
|
+
|
|
13
|
+
def initialize(io, defaults={})
|
|
14
|
+
@io = io
|
|
15
|
+
merge_defaults!(defaults)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def parse!
|
|
19
|
+
doc = Hpricot.XML(@io)
|
|
20
|
+
parse_properties!(doc)
|
|
21
|
+
parse_meta!(doc)
|
|
22
|
+
parse_parent!(doc)
|
|
23
|
+
parse_dependency_management!(doc)
|
|
24
|
+
parse_dependencies!(doc)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def dependencies(scope = :default)
|
|
28
|
+
@dependencies.fetch(scope, []).dup
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def exclusions
|
|
32
|
+
[]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def has_parent?
|
|
36
|
+
!!parent
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def group_id
|
|
40
|
+
@group_id || parent.group_id
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def artifact_id
|
|
44
|
+
@artifact_id || parent.artifact_id
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def version
|
|
48
|
+
@version || parent.version
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def properties
|
|
52
|
+
if has_parent? && @parent.respond_to?(:properties)
|
|
53
|
+
@parent.properties.merge(@properties)
|
|
54
|
+
else
|
|
55
|
+
@properties || {}
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def to_dependency
|
|
60
|
+
Dependency.new(
|
|
61
|
+
:group_id => group_id,
|
|
62
|
+
:artifact_id => artifact_id,
|
|
63
|
+
:version => version,
|
|
64
|
+
:packaging => packaging,
|
|
65
|
+
:dependencies => dependencies
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def merge(parent)
|
|
70
|
+
defaults = Hash[*PROPERTIES.map { |p| [p, self.send(p) || parent.send(p)] }.flatten]
|
|
71
|
+
defaults = defaults.merge(
|
|
72
|
+
:parent => parent,
|
|
73
|
+
:dependencies => @dependencies.dup,
|
|
74
|
+
:dependency_management => @dependency_management.dup,
|
|
75
|
+
:properties => @properties.dup
|
|
76
|
+
)
|
|
77
|
+
self.class.new(nil, defaults)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def to_s
|
|
81
|
+
to_dependency.to_s
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
protected
|
|
85
|
+
|
|
86
|
+
def resolve_version(dependency)
|
|
87
|
+
@dependency_management.find { |d| d.same_artifact?(dependency) } || dependency
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def camelize(str)
|
|
93
|
+
if str.include?('_')
|
|
94
|
+
components = str.split('_')
|
|
95
|
+
components.first + components[1..-1].map { |s| s.capitalize }.join('')
|
|
96
|
+
else
|
|
97
|
+
str
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def snakeify(str)
|
|
102
|
+
str.gsub(/([a-z])([A-Z])/) do |match|
|
|
103
|
+
"#{$1}_#{$2.downcase}"
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def merge_defaults!(defaults)
|
|
108
|
+
@parent = defaults[:parent]
|
|
109
|
+
@dependency_management = defaults.fetch(:dependency_management, [])
|
|
110
|
+
@properties = defaults.fetch(:properties, [])
|
|
111
|
+
|
|
112
|
+
PROPERTIES.each do |p|
|
|
113
|
+
if defaults.has_key?(p)
|
|
114
|
+
value = defaults[p]
|
|
115
|
+
value = expand_properties(value) if value
|
|
116
|
+
instance_variable_set('@' + p.to_s, value)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
default_dependencies = defaults.fetch(:dependencies, {})
|
|
121
|
+
|
|
122
|
+
@dependencies = default_dependencies.keys.inject({}) do |deps, scope|
|
|
123
|
+
deps[scope] = default_dependencies[scope].map do |dependency|
|
|
124
|
+
dependency = dependency.clone(
|
|
125
|
+
:group_id => expand_properties(dependency.group_id),
|
|
126
|
+
:artifact_id => expand_properties(dependency.artifact_id),
|
|
127
|
+
:version => expand_properties(dependency.version)
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
if dependency.has_version?
|
|
131
|
+
dependency
|
|
132
|
+
elsif @parent
|
|
133
|
+
@parent.resolve_version(dependency)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
deps
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def parse_properties!(doc)
|
|
141
|
+
@properties = {}
|
|
142
|
+
doc.search('/project/properties/*').each do |property_node|
|
|
143
|
+
if property_node.elem?
|
|
144
|
+
@properties[property_node.name] = property_node.inner_text.to_s
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def parse_meta!(doc)
|
|
150
|
+
properties = PROPERTIES.map { |p| [p.to_s, camelize(p.to_s)] }
|
|
151
|
+
properties.each do |property, tag_name|
|
|
152
|
+
val = doc.at("/project/#{tag_name}/text()")
|
|
153
|
+
instance_variable_set('@' + property, expand_properties(val.to_s)) if val
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def parse_parent!(doc)
|
|
158
|
+
parent_node = doc.at("/project/parent")
|
|
159
|
+
@parent = parse_dependency(parent_node) if parent_node
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def parse_dependencies!(doc)
|
|
163
|
+
doc.search('/project/dependencies/dependency').each do |dep_node|
|
|
164
|
+
scope = parse_scope(dep_node)
|
|
165
|
+
@dependencies[scope] ||= []
|
|
166
|
+
@dependencies[scope] << parse_dependency(dep_node)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def parse_dependency_management!(doc)
|
|
171
|
+
doc.search('/project/dependencyManagement/dependencies/dependency').each do |dep_node|
|
|
172
|
+
@dependency_management << parse_dependency(dep_node)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def parse_dependency(dep_node)
|
|
177
|
+
Dependency.new(
|
|
178
|
+
:group_id => parse_attr(dep_node, 'groupId'),
|
|
179
|
+
:artifact_id => parse_attr(dep_node, 'artifactId'),
|
|
180
|
+
:version => parse_version(dep_node),
|
|
181
|
+
:optional => parse_attr(dep_node, 'optional').downcase == 'true',
|
|
182
|
+
:exclusions => parse_exclusions(dep_node)
|
|
183
|
+
)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def parse_attr(dep_node, attr_name)
|
|
187
|
+
str = expand_properties(dep_node.at("#{attr_name}/text()").to_s)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def expand_properties(str)
|
|
191
|
+
return nil if str.nil?
|
|
192
|
+
str.gsub(/\$\{([.\w]+)\}/) do |match|
|
|
193
|
+
property_value($1)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def property_value(property_name)
|
|
198
|
+
custom_properties = {'version' => @version}.merge(properties)
|
|
199
|
+
if custom_properties.has_key?(property_name)
|
|
200
|
+
custom_properties[property_name]
|
|
201
|
+
else
|
|
202
|
+
components = property_name.split('.')
|
|
203
|
+
if components.first == 'project' && components.size == 2 && self.respond_to?(snakeify(components[1]))
|
|
204
|
+
self.send(snakeify(components[1]))
|
|
205
|
+
elsif components.first == 'env' && components.size == 2
|
|
206
|
+
ENV[components[1]]
|
|
207
|
+
#elsif components.first == 'settings' # not yet supported
|
|
208
|
+
else
|
|
209
|
+
"${#{property_name}}"
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def parse_version(dep_node)
|
|
215
|
+
v = parse_attr(dep_node, 'version')
|
|
216
|
+
if v.length == 0 then nil else v end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def parse_scope(dep_node)
|
|
220
|
+
scope = parse_attr(dep_node, 'scope')
|
|
221
|
+
scope = 'default' if scope.nil? || scope.strip.length == 0
|
|
222
|
+
scope.to_sym
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def parse_exclusions(dep_node)
|
|
226
|
+
dep_node.search('exclusions/exclusion').map do |excl_node|
|
|
227
|
+
Dependency.new(
|
|
228
|
+
parse_attr(excl_node, 'groupId'),
|
|
229
|
+
parse_attr(excl_node, 'artifactId')
|
|
230
|
+
)
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
module PomPomPom
|
|
2
|
+
class Resolver
|
|
3
|
+
class JarNotFoundError < StandardError; end
|
|
4
|
+
class DependencyNotFoundError < StandardError; end
|
|
5
|
+
|
|
6
|
+
def initialize(repositories, options={})
|
|
7
|
+
@repositories, @logger, @downloader = repositories, (options[:logger] || NullLogger.new), (options[:downloader] || Downloader.new)
|
|
8
|
+
raise ArgumentError, 'No repositories given!' if repositories.nil? || repositories.empty?
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def download!(target_dir, transitive, *dependencies)
|
|
12
|
+
create_target_directory(target_dir)
|
|
13
|
+
all_dependencies = if transitive then find_transitive_dependencies(*dependencies) else dependencies end
|
|
14
|
+
all_dependencies.each do |pom|
|
|
15
|
+
destination = File.join(target_dir, "#{pom.artifact_id}-#{pom.version}.jar")
|
|
16
|
+
JarDownloader.new(pom, destination, @repositories, @downloader, @logger).download!
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def find_transitive_dependencies(*dependencies)
|
|
21
|
+
resolver = PomResolver.new(dependencies, @repositories, @downloader, @logger)
|
|
22
|
+
filter_newest(group_by_artifact(resolver.all_poms))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def filter_newest(pom_groups)
|
|
28
|
+
pom_groups.map do |id, poms|
|
|
29
|
+
if poms.size > 1
|
|
30
|
+
newest = poms.sort { |a, b| a.version <=> b.version }.reverse.first
|
|
31
|
+
@logger.warn(%(Warning: multiple versions of #{id} were required, using the newest required version (#{newest.version})))
|
|
32
|
+
newest
|
|
33
|
+
else
|
|
34
|
+
poms.first
|
|
35
|
+
end
|
|
36
|
+
end.flatten
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def group_by_artifact(poms)
|
|
40
|
+
poms.inject({}) do |acc, pom|
|
|
41
|
+
id = "#{pom.group_id}:#{pom.artifact_id}"
|
|
42
|
+
acc[id] ||= []
|
|
43
|
+
acc[id] << pom
|
|
44
|
+
acc
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def create_target_directory(target_dir)
|
|
49
|
+
return if File.directory?(target_dir)
|
|
50
|
+
raise 'Cannot create target directory because it already exists (but is not a directory)' if File.exists?(target_dir)
|
|
51
|
+
Dir.mkdir(target_dir)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def args_and_options(*args)
|
|
55
|
+
if Hash === args.last
|
|
56
|
+
options = args.pop
|
|
57
|
+
end
|
|
58
|
+
[args, options || {}]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
class NullLogger
|
|
62
|
+
def debug(msg); end
|
|
63
|
+
def info(msg); end
|
|
64
|
+
def warn(msg); end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
class PomResolver
|
|
68
|
+
def initialize(dependencies, repositories, downloader, logger)
|
|
69
|
+
@dependencies, @repositories, @downloader, @logger = dependencies, repositories, downloader, logger
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def all_poms
|
|
73
|
+
@dependencies.map { |d| resolve_dependencies(d) }.flatten
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def resolve_dependencies(dependency)
|
|
79
|
+
pom = nil
|
|
80
|
+
@repositories.detect do |repository|
|
|
81
|
+
if dependency.has_version?
|
|
82
|
+
d = dependency
|
|
83
|
+
else
|
|
84
|
+
d = find_latest(dependency, repository)
|
|
85
|
+
end
|
|
86
|
+
pom = get_pom(repository, d)
|
|
87
|
+
pom
|
|
88
|
+
end
|
|
89
|
+
raise DependencyNotFoundError, "Could not find POM for #{dependency} in any repository" unless pom
|
|
90
|
+
transitive_dependencies = pom.dependencies.reject do |d|
|
|
91
|
+
d.optional? || dependency.exclusions.any? { |dd| dd.artifact_id == d.artifact_id }
|
|
92
|
+
end
|
|
93
|
+
[pom] + transitive_dependencies.map { |d| resolve_dependencies(d) }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def find_latest(dependency, repository)
|
|
97
|
+
@logger.info(%(Finding latest version for #{dependency.group_id}:#{dependency.artifact_id}))
|
|
98
|
+
url = dependency.metadata_url(repository)
|
|
99
|
+
metadata = Metadata.new(@downloader.get(url))
|
|
100
|
+
metadata.parse!
|
|
101
|
+
dependency.clone(:version => metadata.latest_version)
|
|
102
|
+
rescue => e
|
|
103
|
+
@logger.warn(%(Could not donwload "#{url}": #{e.message}))
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def get_pom(repository, dependency)
|
|
107
|
+
url = dependency.pom_url(repository)
|
|
108
|
+
@logger.debug(%(Loading POM from "#{url}"))
|
|
109
|
+
data = @downloader.get(url)
|
|
110
|
+
if data
|
|
111
|
+
pom = Pom.new(StringIO.new(data))
|
|
112
|
+
pom.parse!
|
|
113
|
+
if pom.has_parent?
|
|
114
|
+
parent = get_pom(repository, pom.parent)
|
|
115
|
+
pom = pom.merge(parent)
|
|
116
|
+
end
|
|
117
|
+
pom
|
|
118
|
+
else
|
|
119
|
+
nil
|
|
120
|
+
end
|
|
121
|
+
rescue => e
|
|
122
|
+
@logger.debug(%(Could not download "#{url}": #{e.message}))
|
|
123
|
+
nil
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
class JarDownloader
|
|
128
|
+
def initialize(pom, local_path, repositories, downloader, logger)
|
|
129
|
+
@pom, @local_path, @repositories, @downloader, @logger = pom, local_path, repositories, downloader, logger
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def download!
|
|
133
|
+
data = nil
|
|
134
|
+
@repositories.detect do |repository|
|
|
135
|
+
url = @pom.jar_url(repository)
|
|
136
|
+
@logger.debug(%(Loading JAR from "#{url}"))
|
|
137
|
+
begin
|
|
138
|
+
data = @downloader.get(url)
|
|
139
|
+
rescue => e
|
|
140
|
+
@logger.debug(%(Could not download "#{url}": #{e.message}))
|
|
141
|
+
end
|
|
142
|
+
data
|
|
143
|
+
end
|
|
144
|
+
raise JarNotFoundError, "Could not download JAR for #{@pom.to_dependency} in any repository" unless data
|
|
145
|
+
File.open(@local_path, 'w') { |f| f.write(data) }
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|