jar-dependencies 0.6.0.pre3-java

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.
@@ -0,0 +1,211 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jars
4
+ module MavenVersion
5
+ class << self
6
+ def resolve(*args)
7
+ return nil if args.empty? || (args.size == 1 && args[0].nil?)
8
+
9
+ low, high = convert(args[0])
10
+ low, high = convert(args[1], low, high) if /[=~><]/.match?(args[1])
11
+ (low == high) ? low : "#{low || '[0'},#{high || ')'}"
12
+ end
13
+
14
+ private
15
+
16
+ def convert(arg, low = nil, high = nil)
17
+ if arg.include?('~>')
18
+ val = arg.sub(/~>\s*/, '')
19
+ last = val.include?('.') ? val.sub(/\.[0-9]*[a-z]+.*$/, '').sub(/\.[^.]+$/, '.99999') : '99999'
20
+ ["[#{snapshot_version(val)}", "#{snapshot_version(last)}]"]
21
+ elsif arg.include?('>=')
22
+ val = arg.sub(/>=\s*/, '')
23
+ ["[#{snapshot_version(val)}", high]
24
+ elsif arg.include?('<=')
25
+ val = arg.sub(/<=\s*/, '')
26
+ [low, "#{snapshot_version(val)}]"]
27
+ # treat '!' the same way as '>' since maven cannot describe such range
28
+ elsif /[!>]/.match?(arg)
29
+ val = arg.sub(/[!>]\s*/, '')
30
+ ["(#{snapshot_version(val)}", high]
31
+ elsif arg.include?('<')
32
+ val = arg.sub(/<\s*/, '')
33
+ [low, "#{snapshot_version(val)})"]
34
+ elsif arg.include?('=')
35
+ val = arg.sub(/=\s*/, '')
36
+ # for prereleased version pick the maven version (no version range)
37
+ if /[a-z]|[A-Z]/.match?(val)
38
+ [val, val]
39
+ else
40
+ ["[#{val}", "#{val}.0.0.0.0.1)"]
41
+ end
42
+ else
43
+ # no conversion here, i.e. assume maven version
44
+ [arg, arg]
45
+ end
46
+ end
47
+
48
+ def snapshot_version(val)
49
+ if val.match(/[a-z]|[A-Z]/) && !val.match(/-SNAPSHOT|[${}]/)
50
+ "#{val}-SNAPSHOT"
51
+ else
52
+ val
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ class GemspecArtifacts
59
+ class Exclusion
60
+ attr_reader :group_id, :artifact_id
61
+
62
+ def initialize(line)
63
+ @group_id, @artifact_id = line.gsub(/['"]/, '').strip.split(':')
64
+ @artifact_id.strip!
65
+ end
66
+
67
+ def to_s
68
+ "#{@group_id}:#{@artifact_id}"
69
+ end
70
+ end
71
+
72
+ class Exclusions < Array
73
+ def to_s
74
+ "[#{join(', ')}]"
75
+ end
76
+
77
+ def initialize(line)
78
+ super()
79
+ line.gsub(/'"|^\s*\[|\]\s*$/, '').split(/,\s*/).each do |exclusion|
80
+ self << Exclusion.new(exclusion)
81
+ end
82
+ freeze
83
+ end
84
+ end
85
+
86
+ class Artifact
87
+ attr_reader :type, :group_id, :artifact_id, :classifier, :version, :scope, :exclusions
88
+
89
+ ALLOWED_TYPES = %w[jar pom].freeze
90
+
91
+ def initialize(type, group_id, artifact_id, classifier, version, exclusions, **options)
92
+ @type = type
93
+ @group_id = group_id
94
+ @artifact_id = artifact_id
95
+ @classifier = classifier
96
+ @version = version
97
+ @exclusions = exclusions
98
+ options.each do |k, v|
99
+ instance_variable_set(:"@#{k}", v)
100
+ end
101
+ end
102
+
103
+ def self.parse(line)
104
+ line = line.strip
105
+ index = line.index(/\s/)
106
+ return nil if index.nil?
107
+
108
+ type = line[0..index].strip
109
+ return nil unless ALLOWED_TYPES.member?(type)
110
+
111
+ line = line[index..]
112
+ line.gsub!(/['"]/, '')
113
+ line.strip!
114
+
115
+ options = {}
116
+ line.sub!(/,\s*:exclusions\s*(:|=>)\s*(\[[^\]]+\])/) do
117
+ options[:exclusions] = Exclusions.new(Regexp.last_match(2).strip)
118
+ ''
119
+ end
120
+ line.sub!(/,\s*:([a-z]+)\s*(:|=>)\s*(:?[a-zA-Z0-9_]+)/) do
121
+ options[Regexp.last_match(1).to_sym] = Regexp.last_match(3).sub(/^:/, '')
122
+ ''
123
+ end
124
+ exclusions = nil
125
+ line.sub!(/[,:]\s*\[(.+:.+,?\s*)+\]$/) do |a|
126
+ exclusions = Exclusions.new(a[1..].strip)
127
+ ''
128
+ end
129
+
130
+ line.strip!
131
+ line.gsub!(/,\s*/, ':')
132
+
133
+ if /[\[()\]]/.match?(line)
134
+ index = line.index(/[\[(].+$/)
135
+ version = line[index..].sub(':', ', ')
136
+ line = line[0..(index - 1)].strip.sub(/:$/, '')
137
+ else
138
+ index = line.index(/:[^:]+$/)
139
+ version = line[(index + 1)..]
140
+ line = line[0..(index - 1)].strip
141
+ end
142
+
143
+ case line.count(':')
144
+ when 2
145
+ group_id, artifact_id, classifier = line.split(':')
146
+ when 1
147
+ group_id, artifact_id = line.split(':')
148
+ classifier = nil
149
+ else
150
+ warn line
151
+ return nil
152
+ end
153
+ new(type, group_id, artifact_id, classifier, version, exclusions, **options)
154
+ end
155
+
156
+ def to_s
157
+ args = [@group_id, @artifact_id]
158
+ args << @classifier if @classifier
159
+ args << @version
160
+ args << @exclusions.to_s if @exclusions
161
+ "#{@type} #{group_id}:#{args[1..].join(', ')}"
162
+ end
163
+
164
+ def to_gacv
165
+ args = [@group_id, @artifact_id]
166
+ args << @classifier if @classifier
167
+ args << @version
168
+ args.join(':')
169
+ end
170
+
171
+ def to_coord(classifier: true)
172
+ args = [@group_id, @artifact_id]
173
+ args << @classifier if classifier && @classifier
174
+ args << @type
175
+ args << MavenVersion.resolve(@version)
176
+ args.join(':')
177
+ end
178
+
179
+ def key
180
+ args = [@group_id, @artifact_id]
181
+ args << @classifier if @classifier
182
+ args.join(':')
183
+ end
184
+ end
185
+
186
+ attr_reader :artifacts
187
+
188
+ def initialize(spec)
189
+ @artifacts = []
190
+ spec.requirements.each do |req|
191
+ req.split("\n").each do |line|
192
+ artifact = Artifact.parse(line)
193
+ @artifacts << artifact if artifact
194
+ end
195
+ end
196
+ @artifacts.freeze
197
+ end
198
+
199
+ def [](index)
200
+ @artifacts[index]
201
+ end
202
+
203
+ def each(&block)
204
+ @artifacts.each(&block)
205
+ end
206
+
207
+ def size
208
+ @artifacts.size
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,226 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jar_dependencies'
4
+ require 'jars/maven_exec'
5
+
6
+ module Jars
7
+ class Installer
8
+ class Dependency
9
+ EMPTY = ""
10
+ # the trailing ":<file>" of a dependency line; the file may itself contain
11
+ # a ':' on Windows (C:\...), hence the optional drive-letter prefix
12
+ TRAILING_FILE = /:(?<file>(?:[A-Z]:\\)?[^:]+)\z/.freeze
13
+
14
+ # A dependency:list line has the form
15
+ # groupId:artifactId:type[:classifier]:version:scope:/path/to/file
16
+ def self.parse(line)
17
+ return unless /:jar:|:pom:/.match?(line)
18
+
19
+ line = line.dup
20
+ # remove ANSI escape sequences and module section (https://issues.apache.org/jira/browse/MDEP-974)
21
+ line.gsub!(/\e\[\d*m/, EMPTY)
22
+ line.gsub!(/ -- module.*/, EMPTY)
23
+ line.strip!
24
+
25
+ # Peel off the trailing file path first: since it may contain a ':' it
26
+ # cannot take part in the split below. What is left (pre_match) is
27
+ # colon-clean, so every field is addressed by position -- never by
28
+ # matching a keyword, which breaks when an artifact id is itself a scope
29
+ # or type keyword (e.g. "com.dylibso.chicory:runtime:jar:1.7.5").
30
+ tail = line.match(TRAILING_FILE)
31
+ file = tail[:file]
32
+
33
+ # classifier is the only optional field, so there are exactly 5 or 6
34
+ components = tail.pre_match.split(':')
35
+ if components.size == 6
36
+ group_id, artifact_id, type, classifier, version, scope_name = components
37
+ else
38
+ group_id, artifact_id, type, version, scope_name = components
39
+ end
40
+
41
+ coord = [group_id, artifact_id, type, classifier, version].compact.join(':')
42
+ gav = [group_id, artifact_id, classifier, version].compact.join(':')
43
+ path = File.join(*group_id.split('.'), artifact_id, version, file[%r{[^\\/]+\z}])
44
+
45
+ scope =
46
+ case scope_name
47
+ when 'provided'
48
+ :provided
49
+ when 'test'
50
+ :test
51
+ else
52
+ :runtime
53
+ end
54
+
55
+ new(type.to_sym, scope, coord, gav, file, path, scope_name == 'system')
56
+ end
57
+
58
+ attr_reader :path, :file, :gav, :scope, :type, :coord
59
+
60
+ def initialize(type, scope, coord, gav, file, path, system)
61
+ @type = type
62
+ @scope = scope
63
+ @coord = coord
64
+ @gav = gav
65
+ @file = file
66
+ @path = path
67
+ @system = system
68
+ end
69
+
70
+ def system?
71
+ @system
72
+ end
73
+ end
74
+
75
+ def self.install_jars(write_require_file: false)
76
+ new.install_jars(write_require_file: write_require_file)
77
+ end
78
+
79
+ def self.load_from_maven(file)
80
+ result = []
81
+ File.read(file).each_line do |line|
82
+ dep = Dependency.parse(line)
83
+ result << dep if dep && dep.scope == :runtime
84
+ end
85
+ result
86
+ end
87
+
88
+ def self.vendor_file(dir, dep)
89
+ return unless !dep.system? && dep.type == :jar && dep.scope == :runtime
90
+
91
+ vendored = File.join(dir, dep.path)
92
+ FileUtils.mkdir_p(File.dirname(vendored))
93
+ FileUtils.cp(dep.file, vendored)
94
+ end
95
+
96
+ def self.print_require_jar(file, dep, fallback: false)
97
+ return if dep.type != :jar || dep.scope != :runtime
98
+
99
+ if dep.system?
100
+ file&.puts("require '#{dep.file}'")
101
+ elsif dep.scope == :runtime
102
+ if fallback
103
+ file&.puts(" require '#{dep.path}'")
104
+ else
105
+ file&.puts(" require_jar '#{dep.gav.gsub(':', "', '")}'")
106
+ end
107
+ end
108
+ end
109
+
110
+ COMMENT = '# this is a generated file, to avoid over-writing it just delete this comment'
111
+
112
+ def self.write_require_jars(deps, require_filename)
113
+ return if !require_filename || (
114
+ File.exist?(require_filename) && !File.read(require_filename).index(COMMENT)
115
+ )
116
+
117
+ FileUtils.mkdir_p(File.dirname(require_filename))
118
+ File.open(require_filename, 'w') do |f|
119
+ f.puts COMMENT
120
+ f.puts 'begin'
121
+ f.puts " require 'jar_dependencies'"
122
+ f.puts 'rescue LoadError'
123
+ deps.each do |dep|
124
+ # do not use require_jar method
125
+ print_require_jar(f, dep, fallback: true)
126
+ end
127
+ f.puts 'end'
128
+ f.puts
129
+ f.puts 'if defined? Jars'
130
+ deps.each do |dep|
131
+ print_require_jar(f, dep)
132
+ end
133
+ f.puts 'end'
134
+ end
135
+ end
136
+
137
+ def self.vendor_jars(deps, dir)
138
+ deps.each do |dep|
139
+ vendor_file(dir, dep)
140
+ end
141
+ end
142
+
143
+ def initialize(spec = nil)
144
+ @mvn = MavenExec.new(spec)
145
+ end
146
+
147
+ def spec
148
+ @mvn.spec
149
+ end
150
+
151
+ def vendor_jars(vendor_dir = nil, write_require_file: true)
152
+ return unless jars?
153
+
154
+ if Jars.to_prop(Jars::VENDOR) == 'false'
155
+ vendor_dir = nil
156
+ else
157
+ vendor_dir ||= spec.require_path
158
+ end
159
+ do_install(vendor_dir, write_require_file)
160
+ end
161
+
162
+ def self.vendor_jars!(vendor_dir = nil)
163
+ new.vendor_jars!(vendor_dir)
164
+ end
165
+
166
+ def vendor_jars!(vendor_dir = nil, write_require_file: true)
167
+ vendor_dir ||= spec.require_path
168
+ do_install(vendor_dir, write_require_file)
169
+ end
170
+
171
+ def install_jars(write_require_file: true)
172
+ return unless jars?
173
+
174
+ do_install(nil, write_require_file)
175
+ end
176
+
177
+ def ruby_maven_install_options=(options)
178
+ # no-op: kept for backward compatibility with post_install_hook
179
+ end
180
+
181
+ def jars?
182
+ spec = self.spec
183
+ return if spec.nil? # rubocop:disable Style/ReturnNilInPredicateMethodDefinition
184
+ return false unless spec.requirements&.any?
185
+ return false unless spec.runtime_dependencies.find { |s| s.name == 'jar-dependencies' }
186
+
187
+ return true if spec.platform.to_s == 'java'
188
+
189
+ Jars.warn "jar-dependencies found on non-java platform gem; skipping jar installation"
190
+ nil # rubocop:disable Style/ReturnNilInPredicateMethodDefinition
191
+ end
192
+
193
+ private
194
+
195
+ def do_install(vendor_dir, write_require_file)
196
+ require_paths = spec.require_paths
197
+ if vendor_dir && !require_paths.include?(vendor_dir)
198
+ raise "vendor dir #{vendor_dir} not in require_paths of gemspec #{require_paths}"
199
+ end
200
+
201
+ target_dir = File.join(@mvn.basedir, vendor_dir || spec.require_path)
202
+ jars_file = File.join(target_dir, "#{spec.name}_jars.rb")
203
+
204
+ # write out new jars_file if write_require_file is true or check timestamps:
205
+ # do not generate file if specfile is older than the generated file
206
+ if !write_require_file && File.exist?(jars_file) && File.mtime(@mvn.specfile) < File.mtime(jars_file)
207
+ jars_file = nil # leave jars_file as is
208
+ end
209
+ deps = install_dependencies
210
+ self.class.write_require_jars(deps, jars_file)
211
+ self.class.vendor_jars(deps, target_dir) if vendor_dir
212
+ end
213
+
214
+ def install_dependencies
215
+ deps = File.join(@mvn.basedir, 'deps.lst')
216
+
217
+ Jars.info("jar dependencies for #{spec.spec_name} ", newline: false)
218
+ @mvn.resolve_dependencies_list(deps)
219
+ Jars.info("") # newline after progress dots
220
+
221
+ self.class.load_from_maven(deps)
222
+ ensure
223
+ FileUtils.rm_f(deps) if deps
224
+ end
225
+ end
226
+ end
data/lib/jars/lock.rb ADDED
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jars/maven_exec'
4
+
5
+ module Jars
6
+ class JarDetails < Array
7
+ def scope
8
+ self[-2].to_sym
9
+ end
10
+
11
+ def file
12
+ file = self[-1].strip
13
+ file.empty? ? path : file
14
+ end
15
+
16
+ def group_id
17
+ self[0]
18
+ end
19
+
20
+ def artifact_id
21
+ self[1]
22
+ end
23
+
24
+ def version
25
+ self[-3]
26
+ end
27
+
28
+ def classifier
29
+ return nil if size == 5
30
+
31
+ self[2]
32
+ end
33
+
34
+ def gacv
35
+ classifier ? self[0..3] : self[0..2]
36
+ end
37
+
38
+ def path
39
+ if scope == :system
40
+ # replace maven like system properties embedded into the string
41
+ self[-1].gsub(/\$\{[a-zA-Z.]+\}/) do |a|
42
+ ENV_JAVA[a[2..-2]] || a
43
+ end
44
+ else
45
+ File.join(Jars.home, group_id.gsub(/[.]/, '/'), artifact_id, version, "#{gacv[1..].join('-')}.jar")
46
+ end
47
+ end
48
+ end
49
+
50
+ class Lock
51
+ def initialize(file)
52
+ @file = file
53
+ end
54
+
55
+ def process(scope)
56
+ scope ||= :runtime
57
+ File.read(@file).each_line do |line|
58
+ next unless /:.+:/.match?(line)
59
+
60
+ jar = JarDetails.new(line.strip.sub(':jar:', ':').sub(/:$/, ': ').split(':'))
61
+ case scope
62
+ when :all, :test
63
+ yield jar
64
+ when :compile
65
+ # jar.scope is maven scope
66
+ yield jar if jar.scope != :test
67
+ when :provided
68
+ # jar.scope is maven scope
69
+ yield jar if jar.scope == :provided
70
+ when :runtime
71
+ # jar.scope is maven scope
72
+ yield jar if (jar.scope != :test) && (jar.scope != :provided)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jar_dependencies'
4
+ require 'jars/version'
5
+ require 'jars/gemspec_artifacts'
6
+
7
+ module Jars
8
+ class LockDown
9
+ def basedir
10
+ File.expand_path('.')
11
+ end
12
+
13
+ def collect_artifacts
14
+ artifacts = []
15
+ done = []
16
+
17
+ attach_jar_coordinates_from_bundler_dependencies(artifacts, done)
18
+
19
+ # Also collect from local gemspec if present
20
+ specs = Dir['*.gemspec']
21
+ if specs.size == 1
22
+ spec = eval(File.read(specs.first), TOPLEVEL_BINDING, specs.first) # rubocop:disable Security/Eval
23
+ ga = GemspecArtifacts.new(spec)
24
+ ga.artifacts.each do |a|
25
+ unless done.include?(a.key)
26
+ artifacts << a
27
+ done << a.key
28
+ end
29
+ end
30
+ end
31
+
32
+ artifacts
33
+ end
34
+ private :collect_artifacts
35
+
36
+ def attach_jar_coordinates_from_bundler_dependencies(artifacts, done)
37
+ load_path = $LOAD_PATH.dup
38
+ require 'bundler'
39
+ # TODO: make this group a commandline option
40
+ Bundler.setup('default')
41
+ cwd = File.expand_path('.')
42
+ Gem.loaded_specs.each_value do |spec|
43
+ all = cwd == spec.full_gem_path # if gemspec is local then include all dependencies
44
+ ga = GemspecArtifacts.new(spec)
45
+ ga.artifacts.each do |a|
46
+ next if done.include?(a.key)
47
+ next unless all || (a.scope != 'provided' && a.scope != 'test')
48
+
49
+ artifacts << a
50
+ done << a.key
51
+ end
52
+ end
53
+ rescue LoadError => e
54
+ Jars.warn "bundler unavailable (#{e.message}); skipping dependency discovery" if Jars.verbose?
55
+ rescue Bundler::GemfileNotFound
56
+ # bundler is available, but there is no Gemfile to inspect
57
+ rescue Bundler::GemNotFound
58
+ Jars.warn "cannot set up bundler with #{Bundler.default_lockfile}"
59
+ raise
60
+ ensure
61
+ $LOAD_PATH.replace(load_path)
62
+ end
63
+
64
+ def lock_down(vendor_dir = nil, force: false, update: false, tree: nil) # rubocop:disable Lint/UnusedMethodArgument
65
+ lock_file = File.expand_path(Jars.lock)
66
+
67
+ if !force && File.exist?(lock_file)
68
+ puts 'Jars.lock already exists, use --force to overwrite'
69
+ return
70
+ end
71
+
72
+ artifacts = collect_artifacts
73
+
74
+ if artifacts.empty?
75
+ puts 'no jar dependencies found'
76
+ return
77
+ end
78
+
79
+ puts
80
+ puts '-- jar root dependencies --'
81
+ puts
82
+ artifacts.each do |a|
83
+ puts " #{a.to_gacv}:#{a.scope || 'compile'}"
84
+ puts " exclusions: #{a.exclusions}" if a.exclusions && !a.exclusions.empty?
85
+ end
86
+
87
+ context = Jars::Mima.create_context
88
+ begin
89
+ resolved = Jars::Mima.resolve_with_context(context, artifacts, all_dependencies: true)
90
+ ensure
91
+ context.close
92
+ end
93
+
94
+ # Write Jars.lock
95
+ File.open(lock_file, 'w') do |f|
96
+ resolved.each do |dep|
97
+ next unless dep.type == 'jar'
98
+
99
+ f.puts dep.to_lock_entry
100
+ end
101
+ end
102
+
103
+ # Optionally vendor jars
104
+ if vendor_dir
105
+ require 'fileutils'
106
+ vendor_path = File.expand_path(vendor_dir)
107
+ resolved.each do |dep|
108
+ next unless dep.type == 'jar' && dep.runtime? && !dep.system?
109
+
110
+ target = File.join(vendor_path, dep.jar_path)
111
+ FileUtils.mkdir_p(File.dirname(target))
112
+ FileUtils.cp(dep.file, target)
113
+ end
114
+ end
115
+
116
+ if tree
117
+ puts
118
+ puts '-- jar dependency tree --'
119
+ puts
120
+ resolved.each do |dep|
121
+ prefix = dep.classifier ? "#{dep.classifier}:" : ''
122
+ puts " #{dep.group_id}:#{dep.artifact_id}:#{prefix}#{dep.version}:#{dep.scope}"
123
+ end
124
+ puts
125
+ end
126
+
127
+ puts
128
+ puts File.read(lock_file)
129
+ puts
130
+ end
131
+ end
132
+ end