buildr-dependency-extensions 0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,30 @@
1
+ unless defined?(BuildrDepenencyExtensions::VERSION)
2
+ require File.join(File.dirname(__FILE__), 'lib', 'buildr-dependency-extensions', 'version')
3
+ end
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'buildr-dependency-extensions'
7
+ spec.version = BuildrDependencyExtensions::VERSION.dup
8
+ spec.author = 'John Shahid'
9
+ spec.email = "jvshahid@gmail.com"
10
+ spec.homepage = "https://github.com/jvshahid/transitive-buildr"
11
+ spec.summary = "A Buildr extension that enables transitive dependency resolution by default"
12
+
13
+ # Rakefile needs to create spec for both platforms (ruby and java), using the
14
+ # $platform global variable. In all other cases, we figure it out from RUBY_PLATFORM.
15
+ spec.platform = $platform || RUBY_PLATFORM[/java/] || 'ruby'
16
+
17
+ spec.files = Dir['{lib,spec}/**/*', '*.{gemspec}']
18
+ spec.require_paths = ['lib']
19
+
20
+ spec.has_rdoc = false
21
+
22
+ # Tested against these dependencies.
23
+ spec.add_dependency 'rake', '0.8.7'
24
+ spec.add_dependency 'buildr', '>= 1.4.5'
25
+ spec.add_dependency 'xml-simple', '~> 1.0.12'
26
+ spec.add_dependency 'rspec-expectations', '2.1.0'
27
+ spec.add_dependency 'rspec-mocks', '2.1.0'
28
+ spec.add_dependency 'rspec-core', '2.1.0'
29
+ spec.add_dependency 'rspec', '2.1.0'
30
+ end
@@ -0,0 +1,9 @@
1
+ require 'buildr-dependency-extensions/helper'
2
+ require 'buildr-dependency-extensions/resolver'
3
+ require 'buildr-dependency-extensions/transitive-buildr'
4
+ require 'buildr-dependency-extensions/pom-generator'
5
+ require 'buildr-dependency-extensions/artifact'
6
+ require 'buildr-dependency-extensions/pom'
7
+ require 'buildr-dependency-extensions/dependency_caching'
8
+
9
+ include BuildrDependencyExtensions
@@ -0,0 +1,9 @@
1
+ require 'buildr/packaging/artifact.rb'
2
+
3
+ class Buildr::Artifact
4
+ def excludes(*artifacts)
5
+ @excludes ||= []
6
+ @excludes = @excludes + artifacts
7
+ self
8
+ end
9
+ end
@@ -0,0 +1,48 @@
1
+ require 'yaml'
2
+
3
+ module BuildrDependencyExtensions
4
+ class DependencyCaching
5
+ def initialize project
6
+ @project = project
7
+ end
8
+
9
+ def write_cache
10
+ dependencies_cache = {}
11
+ dependencies_cache['runtime'] = @project.run.classpath.
12
+ select {|dep| HelperFunctions.is_artifact?(dep)}.
13
+ map {|dep| dep.to_spec}
14
+
15
+ dependencies_cache['compile'] = @project.compile.dependencies.to_a.
16
+ select {|dep| HelperFunctions.is_artifact?(dep)}.
17
+ map {|dep| dep.to_spec}
18
+
19
+ dependencies_cache['test'] = @project.test.dependencies.to_a.
20
+ select {|dep| HelperFunctions.is_artifact?(dep)}.
21
+ map {|dep| dep.to_spec}
22
+
23
+ dependency_caching_filename = @project.path_to('dependency.cache')
24
+ FileUtils.mkdir_p(File.dirname(dependency_caching_filename))
25
+ f = File.new(dependency_caching_filename, 'w')
26
+ f.write(dependencies_cache.to_yaml)
27
+ f.close
28
+ end
29
+
30
+ def read_cache
31
+ begin
32
+ dependency_caching_filename = @project.path_to('dependency.cache')
33
+ dependdencies_cache = YAML.load_file(dependency_caching_filename)
34
+
35
+ runtime_dependencies = dependdencies_cache['runtime']
36
+ compile_dependencies = dependdencies_cache['compile']
37
+ test_dependencies = dependdencies_cache['test']
38
+
39
+ dependdencies_cache['runtime'] = runtime_dependencies.map {|dep| @project.artifact(dep)}
40
+ dependdencies_cache['compile'] = compile_dependencies.map {|dep| @project.artifact(dep)}
41
+ dependdencies_cache['test'] = test_dependencies.map {|dep| @project.artifact(dep)}
42
+ dependdencies_cache
43
+ rescue Errno::ENOENT
44
+ nil
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,80 @@
1
+ require 'buildr/packaging/artifact'
2
+
3
+ module BuildrDependencyExtensions
4
+ module HelperFunctions
5
+ # Change the version number to 0 and invoke uniq on the resulting array to get a unique set of artifacts (ignoring the version number)
6
+ def HelperFunctions.get_unique_group_artifact set
7
+ new_set = set.map { |artifact| hash = Artifact.to_hash(artifact); hash[:version] = 0; Artifact.to_spec(hash) }
8
+ new_set.uniq
9
+ end
10
+
11
+ # returns all versions of artifact in original_set sorted using the Version sorting order
12
+ def HelperFunctions.get_all_versions artifact, original_set
13
+ original_artifact_hash = Artifact.to_hash(artifact)
14
+
15
+ new_set = original_set.select do |candidate_artifact|
16
+ candidate_hash = Artifact.to_hash(candidate_artifact)
17
+ candidate_hash[:group] == original_artifact_hash[:group] &&
18
+ candidate_hash[:id] == original_artifact_hash[:id] &&
19
+ candidate_hash[:type] == original_artifact_hash[:type] &&
20
+ candidate_hash[:classifier] == original_artifact_hash[:classifier]
21
+ end
22
+
23
+ new_set = new_set.uniq.sort.reverse
24
+ new_set.
25
+ map { |artifact| Artifact.to_hash(artifact)[:version] }.
26
+ map { |version_string| Version.new version_string}
27
+ end
28
+
29
+ def HelperFunctions.get_all_versions_sorted_by_depth artifact, original_set
30
+ original_artifact_hash = Artifact.to_hash(artifact)
31
+
32
+ new_set = original_set.select do |candidate_artifact|
33
+ candidate_hash = Artifact.to_hash(candidate_artifact)
34
+ candidate_hash[:group] == original_artifact_hash[:group] &&
35
+ candidate_hash[:id] == original_artifact_hash[:id] &&
36
+ candidate_hash[:type] == original_artifact_hash[:type] &&
37
+ candidate_hash[:classifier] == original_artifact_hash[:classifier]
38
+ end
39
+
40
+ new_set = new_set.uniq
41
+ # Sort using the version first, so in case of conflict we use the higher version
42
+ new_set = new_set.sort {|x,y| Version.new(x.to_hash[:version]) <=> Version.new(x.to_hash[:version])}.reverse
43
+ new_set = new_set.sort {|x,y| x.depth <=> y.depth}
44
+ new_set.
45
+ map { |artifact| Artifact.to_hash(artifact)[:version] }.
46
+ map { |version_string| Version.new version_string}
47
+ end
48
+
49
+ def HelperFunctions.is_artifact? task
50
+ task.respond_to?(:to_spec) && task.respond_to?(:to_hash)
51
+ end
52
+
53
+ end
54
+
55
+ # Parses the version string and provides a natural ordering for versions
56
+ class Version
57
+ include Comparable
58
+
59
+ attr_reader :version_array, :version_string
60
+
61
+ def initialize version_string
62
+ @version_string = version_string
63
+ @version_array = version_string.split('.')
64
+ end
65
+
66
+ def <=> other_version
67
+ @version_array <=> other_version.version_array
68
+ end
69
+
70
+ alias_method :eql?, :==
71
+
72
+ def hash
73
+ @version_string.hash
74
+ end
75
+
76
+ def to_s
77
+ @version_string
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,80 @@
1
+ require 'buildr/core/project'
2
+ require 'rubygems'
3
+ require 'xmlsimple'
4
+
5
+ module BuildrDependencyExtensions
6
+ module PomGenerator
7
+
8
+ include Extension
9
+
10
+ def self.extended(base)
11
+ class << base
12
+ def extra_pom_sections
13
+ @extra_pom_sections ||= {}
14
+ end
15
+ end
16
+ super
17
+ end
18
+
19
+ # We have to run the pom generator first before the dependencies are
20
+ # changed in the compile, test and run after_define
21
+ after_define(:compile => :'pom-generator')
22
+ after_define(:'pom-generator') do |project|
23
+ generate_pom project
24
+ end
25
+
26
+ module_function
27
+
28
+ def compile_dependencies project
29
+ project.compile.dependencies.select {|dep| HelperFunctions.is_artifact? dep}
30
+ end
31
+
32
+ def runtime_dependencies project
33
+ project.run.classpath.select {|dep| HelperFunctions.is_artifact? dep}
34
+ end
35
+
36
+ def test_dependencies project
37
+ project.test.dependencies.select {|dep| HelperFunctions.is_artifact? dep}
38
+ end
39
+
40
+ def generate_dependencies_hash dependencies, scope
41
+ dependencies.map do |dep|
42
+ { 'groupId' => dep.to_hash[:group],
43
+ 'artifactId' => dep.to_hash[:id],
44
+ 'version' => dep.to_hash[:version],
45
+ 'scope' => scope,
46
+ 'type' => dep.to_hash[:type]
47
+ }
48
+ end
49
+ end
50
+
51
+ def generate_pom project
52
+ compile_dependencies = compile_dependencies project
53
+ runtime_dependencies = runtime_dependencies project
54
+ test_dependencies = test_dependencies project
55
+
56
+ dependencies_hashes = generate_dependencies_hash compile_dependencies, 'compile'
57
+ dependencies_hashes += generate_dependencies_hash runtime_dependencies, 'runtime'
58
+ dependencies_hashes += generate_dependencies_hash test_dependencies, 'test'
59
+
60
+ pom_hash = {
61
+ 'modelVersion' => '4.0.0',
62
+ 'groupId' => project.group,
63
+ 'artifactId' => project.name,
64
+ 'version' => project.version,
65
+ 'dependencies' => {'dependency' => dependencies_hashes.to_a}
66
+ }
67
+
68
+ project.extra_pom_sections.each {|key, value| pom_hash[key] = value}
69
+
70
+ my_pom = file(project.path_to(:target, 'pom.xml')) do |f|
71
+ FileUtils.mkdir_p(File.dirname(f.name)) unless f.exist?
72
+ File.open(f.name, 'w') do |file|
73
+ file.write(XmlSimple.xml_out(pom_hash, {'RootName' => 'project', 'NoAttr' => true}))
74
+ end
75
+ end
76
+
77
+ project.package.pom.from my_pom
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,32 @@
1
+ require 'buildr/java/pom'
2
+
3
+ class Buildr::POM
4
+ def declared_dependencies(scopes)
5
+ #try to cache dependencies also
6
+ @declared_depends_for_scopes ||= {}
7
+ unless depends = @declared_depends_for_scopes[scopes]
8
+ declared = project["dependencies"].first["dependency"] rescue nil
9
+ depends = (declared || []).reject { |dep| value_of(dep["optional"]) =~ /true/ }
10
+ depends = depends.map do |dep|
11
+ spec = pom_to_hash(dep, properties)
12
+ apply = managed(spec)
13
+ spec = apply.merge(spec) if apply
14
+
15
+ #calculate transitive dependencies
16
+ if scopes.include?(spec[:scope])
17
+ exclusions = dep["exclusions"]["exclusion"] rescue nil
18
+ artifact = Artifact.to_spec(spec)
19
+
20
+ if exclusions
21
+ excuded_artifacts = exclusions.map {|exclusion| artifact("#{exclusion['groupId']}:#{exclusion['artifactId']}:jar:1.0")}
22
+ artifact.excludes(*excuded_artifacts)
23
+ end
24
+ end
25
+ artifact
26
+ end
27
+ depends = depends.compact
28
+ @declared_depends_for_scopes[scopes] = depends
29
+ end
30
+ depends
31
+ end
32
+ end
@@ -0,0 +1,44 @@
1
+ module BuildrDependencyExtensions
2
+ class ResolverBase
3
+ def initialize
4
+ @hash = {}
5
+ end
6
+
7
+ def resolve_from_hash artifact
8
+ @hash[artifact]
9
+ end
10
+
11
+ def resolved artifact, version
12
+ # @hash[artifact] = version
13
+ end
14
+ end
15
+
16
+ class HighestVersionConflictResolver < ResolverBase
17
+ def resolve artifact, all_artifacts
18
+ version = resolve_from_hash artifact
19
+ if version
20
+ version
21
+ else
22
+ all_versions = HelperFunctions.get_all_versions(artifact, all_artifacts).uniq
23
+ if all_versions.size > 1
24
+ puts $terminal.color("Warning: found versions #{all_versions.join(', ')} for artifact #{artifact}. Choosing #{all_versions[0]}", :yellow)
25
+ end
26
+ resolved artifact, all_versions[0]
27
+ all_versions[0]
28
+ end
29
+ end
30
+ end
31
+
32
+ class MavenVersionConflictResolver < ResolverBase
33
+ def resolve artifact, all_artifacts
34
+ version = resolve_from_hash artifact
35
+ if version
36
+ version
37
+ else
38
+ all_versions = HelperFunctions.get_all_versions_sorted_by_depth(artifact, all_artifacts).uniq
39
+ resolved artifact, all_versions[0]
40
+ all_versions[0]
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,125 @@
1
+ require 'buildr/core/project'
2
+ require 'yaml'
3
+
4
+ module BuildrDependencyExtensions
5
+ module TransitiveDependencies
6
+
7
+ @dependency_pom_cache = {}
8
+
9
+ include Extension
10
+
11
+ def self.extended(base)
12
+ class << base
13
+
14
+ attr_accessor :transitive_scopes
15
+ attr_accessor :cache_dependencies
16
+
17
+ def conflict_resolver
18
+ # @conflict_resolver ||= HighestVersionConflictResolver.new
19
+ @conflict_resolver ||= MavenVersionConflictResolver.new
20
+ end
21
+ end
22
+ super
23
+ end
24
+
25
+ after_define(:'transitive-dependencies' => :run) do |project|
26
+ now = Time.now
27
+ if project.transitive_scopes
28
+ dependency_caching = DependencyCaching.new(project)
29
+ dependency_cache = dependency_caching.read_cache
30
+ unless dependency_cache && project.cache_dependencies
31
+ resolve_compile_dependencies project if project.transitive_scopes.include? :compile
32
+ resolve_runtime_dependencies project if project.transitive_scopes.include? :run
33
+ resolve_test_dependencies project if project.transitive_scopes.include? :test
34
+ dependency_caching.write_cache
35
+ else
36
+ if project.transitive_scopes.include? :run
37
+ project.run.classpath = project.run.classpath.reject {|dependency| HelperFunctions.is_artifact?(dependency)}
38
+ project.run.classpath += dependency_cache['runtime']
39
+ end
40
+ if project.transitive_scopes.include? :compile
41
+ project.compile.dependencies = project.compile.dependencies.reject {|dependency| HelperFunctions.is_artifact?(dependency)}
42
+ project.compile.dependencies += dependency_cache['compile']
43
+ end
44
+ if project.transitive_scopes.include? :test
45
+ project.test.dependencies = project.test.dependencies.reject {|dependency| HelperFunctions.is_artifact?(dependency)}
46
+ project.test.dependencies += dependency_cache['test']
47
+
48
+ project.test.compile.dependencies = project.test.compile.dependencies.reject {|dependency| HelperFunctions.is_artifact?(dependency)}
49
+ project.test.compile.dependencies += dependency_cache['test']
50
+ end
51
+ end
52
+ end
53
+ trace "Adding transitive dependencies took #{Time.now - now} seconds"
54
+ end
55
+
56
+ module_function
57
+
58
+ def resolve_compile_dependencies project
59
+ original_file_tasks = project.compile.dependencies.reject {|dep| HelperFunctions.is_artifact? dep }
60
+ original_dependencies = project.compile.dependencies.select {|dep| HelperFunctions.is_artifact? dep }
61
+ new_dependencies = []
62
+ original_dependencies.each do |dependency|
63
+ add_dependency project, new_dependencies, dependency, [nil, "compile"]
64
+ end
65
+ new_dependencies = resolve_conflicts(project, new_dependencies.uniq)
66
+ project.compile.dependencies = new_dependencies + original_file_tasks
67
+ end
68
+
69
+ def resolve_runtime_dependencies project
70
+ original_file_tasks = project.run.classpath.reject {|dep| HelperFunctions.is_artifact? dep }
71
+ original_dependencies = project.run.classpath.select {|dep| HelperFunctions.is_artifact? dep }
72
+ new_dependencies = []
73
+ original_dependencies.each do |dependency|
74
+ add_dependency project, new_dependencies, dependency, [nil, "compile", "runtime"]
75
+ end
76
+ new_dependencies = resolve_conflicts(project, new_dependencies.uniq)
77
+ project.run.classpath = new_dependencies + original_file_tasks
78
+ end
79
+
80
+ def resolve_test_dependencies project
81
+ original_file_tasks = project.test.dependencies.reject {|dep| HelperFunctions.is_artifact? dep }
82
+ original_test_compile_file_tasks = project.test.compile.dependencies.reject {|dep| HelperFunctions.is_artifact? dep }
83
+ original_dependencies = project.test.dependencies.select {|dep| HelperFunctions.is_artifact? dep }
84
+ new_dependencies = []
85
+ original_dependencies.each do |dependency|
86
+ add_dependency project, new_dependencies, dependency, [nil, "compile", "runtime"]
87
+ end
88
+ new_dependencies = resolve_conflicts(project, new_dependencies.uniq)
89
+ project.test.dependencies = new_dependencies + original_file_tasks
90
+ project.test.compile.dependencies = new_dependencies + original_test_compile_file_tasks
91
+ end
92
+
93
+ def add_dependency project, new_dependencies, dependency, scopes, depth = 0
94
+ scopes.each do |scope|
95
+ if (!@dependency_pom_cache[dependency])
96
+ @dependency_pom_cache[dependency] = POM.load(dependency.pom)
97
+ end
98
+ @dependency_pom_cache[dependency].declared_dependencies([scope]).each do |dep|
99
+ artifact = project.artifact(dep)
100
+ excludes = dependency.instance_variable_get(:@excludes) || []
101
+ matching_dependency = excludes.select do |excluded_dep|
102
+ excluded_dep.to_hash[:id] == artifact.to_hash[:id] &&
103
+ excluded_dep.to_hash[:group] == artifact.to_hash[:group]
104
+ end
105
+ add_dependency project, new_dependencies, artifact, scopes, (depth + 1) if matching_dependency.empty?
106
+ end
107
+ end
108
+ class << dependency
109
+ attr_accessor :depth
110
+ end
111
+ dependency.depth = depth
112
+ new_dependencies << dependency
113
+ end
114
+
115
+ def resolve_conflicts project, dependencies
116
+ unique_transitive_artifacts = HelperFunctions.get_unique_group_artifact(dependencies)
117
+ new_scope_artifacts = unique_transitive_artifacts.map do |artifact|
118
+ artifact_hash = Artifact.to_hash(artifact)
119
+ artifact_hash[:version] = project.conflict_resolver.resolve(artifact, dependencies)
120
+ project.artifact(Artifact.to_spec(artifact_hash))
121
+ end
122
+ new_scope_artifacts
123
+ end
124
+ end
125
+ end