gjp 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +21 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +40 -0
- data/README.md +30 -0
- data/Rakefile +7 -0
- data/bin/gjp +25 -0
- data/gjp.gemspec +30 -0
- data/lib/gjp.rb +7 -0
- data/lib/gjp/cli.rb +51 -0
- data/lib/gjp/get_pom.rb +158 -0
- data/lib/gjp/get_source.rb +29 -0
- data/lib/gjp/get_source_address.rb +48 -0
- data/lib/gjp/logger.rb +24 -0
- data/lib/gjp/pom.rb +30 -0
- data/lib/gjp/version.rb +5 -0
- data/spec/data/antlr/antlr-2.7.2.jar +0 -0
- data/spec/data/antlr/pom.xml +6 -0
- data/spec/data/commons-logging/commons-logging-1.1.1.jar +0 -0
- data/spec/data/commons-logging/pom.xml +504 -0
- data/spec/data/nailgun/nailgun-0.7.1.jar +0 -0
- data/spec/data/nailgun/pom.xml +153 -0
- data/spec/data/tomcat/pom.xml +33 -0
- data/spec/lib/get_pom_spec.rb +80 -0
- data/spec/lib/get_source_address_spec.rb +18 -0
- data/spec/lib/get_source_spec.rb +23 -0
- data/spec/lib/pom_spec.rb +32 -0
- data/spec/spec_helper.rb +6 -0
- metadata +213 -0
data/.gitignore
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
coverage
|
6
|
+
InstalledFiles
|
7
|
+
lib/bundler/man
|
8
|
+
pkg
|
9
|
+
rdoc
|
10
|
+
spec/reports
|
11
|
+
test/tmp
|
12
|
+
test/version_tmp
|
13
|
+
tmp
|
14
|
+
|
15
|
+
# SPEC generated files
|
16
|
+
spec/data/tomcat/org.apache.tomcat\:tomcat\:7.0.40
|
17
|
+
|
18
|
+
# YARD artifacts
|
19
|
+
.yardoc
|
20
|
+
_yardoc
|
21
|
+
doc/
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
gjp (0.5.0)
|
5
|
+
clamp
|
6
|
+
json
|
7
|
+
nokogiri
|
8
|
+
rest-client
|
9
|
+
rubyzip
|
10
|
+
text
|
11
|
+
|
12
|
+
GEM
|
13
|
+
remote: http://rubygems.org/
|
14
|
+
specs:
|
15
|
+
clamp (0.6.1)
|
16
|
+
diff-lcs (1.2.4)
|
17
|
+
json (1.8.0)
|
18
|
+
mime-types (1.23)
|
19
|
+
nokogiri (1.5.9)
|
20
|
+
rake (10.0.4)
|
21
|
+
rest-client (1.6.7)
|
22
|
+
mime-types (>= 1.16)
|
23
|
+
rspec (2.13.0)
|
24
|
+
rspec-core (~> 2.13.0)
|
25
|
+
rspec-expectations (~> 2.13.0)
|
26
|
+
rspec-mocks (~> 2.13.0)
|
27
|
+
rspec-core (2.13.1)
|
28
|
+
rspec-expectations (2.13.0)
|
29
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
30
|
+
rspec-mocks (2.13.1)
|
31
|
+
rubyzip (0.9.9)
|
32
|
+
text (1.2.1)
|
33
|
+
|
34
|
+
PLATFORMS
|
35
|
+
ruby
|
36
|
+
|
37
|
+
DEPENDENCIES
|
38
|
+
gjp!
|
39
|
+
rake
|
40
|
+
rspec
|
data/README.md
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
gjp – Green Java Packager's tools
|
2
|
+
===
|
3
|
+
|
4
|
+
`gjp` (pronounced _/ˈdʒiː ˈaɪ ˈd͡ʒəʊ/_) is a set of tools to ease and partially automate Linux packaging of Java projects.
|
5
|
+
|
6
|
+
The project focus is on producing rpm packages for SUSE distributions, but it is general enough to be useful even for other distributions.
|
7
|
+
|
8
|
+
|
9
|
+
## Install
|
10
|
+
|
11
|
+
Easiest install is via RubyGems:
|
12
|
+
|
13
|
+
$ gem install gjp
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
Currently available tools:
|
18
|
+
* `gjp get-pom PATH` will attempt to find an artifact's pom.xml, if it exists (from the package itself or through search.maven.org)
|
19
|
+
* `gjp get-source-address PATH` will attempt to find the SCM address of an artifact from its pom.xml (from the pom.xml itself or through api.github.com)
|
20
|
+
* `gjp get-source PATH ADDRESS` downloads the source of a pom.xml's project from its SCM at ADDRESS
|
21
|
+
|
22
|
+
## Source
|
23
|
+
|
24
|
+
`gjp`'s Git repo is available on GitHub, which can be browsed at:
|
25
|
+
|
26
|
+
https://github.com/SilvioMoioli/gjp
|
27
|
+
|
28
|
+
and cloned with:
|
29
|
+
|
30
|
+
git clone git@github.com:SilvioMoioli/gjp.git
|
data/Rakefile
ADDED
data/bin/gjp
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: UTF-8
|
3
|
+
#
|
4
|
+
# The (open)?SUSE ((Linux Enterprise) (Server|Desktop))?'s
|
5
|
+
# packager Swiss Army Knife.
|
6
|
+
#
|
7
|
+
# Copyright 2013 Silvio Moioli <smoioli@suse.de>
|
8
|
+
#
|
9
|
+
# Inspired mainly by pom2spec by:
|
10
|
+
# Pascal Bleser <pascal.bleser@opensuse.org>
|
11
|
+
# Duncan Mac-Vicar Prett <dmacvicar@suse.de>
|
12
|
+
#
|
13
|
+
# This file is licensed under the
|
14
|
+
# GNU Lesser General Public License version 2.1 or later:
|
15
|
+
# http://www.gnu.org/licenses/lgpl-2.1.html
|
16
|
+
#
|
17
|
+
|
18
|
+
begin
|
19
|
+
require 'gjp'
|
20
|
+
rescue LoadError
|
21
|
+
require 'rubygems'
|
22
|
+
require 'gjp'
|
23
|
+
end
|
24
|
+
|
25
|
+
MainCommand.run
|
data/gjp.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "gjp/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "gjp"
|
7
|
+
s.version = Gjp::VERSION
|
8
|
+
s.authors = ["Silvio Moioli"]
|
9
|
+
s.email = ["smoioli@suse.de"]
|
10
|
+
s.homepage = "https://github.com/SilvioMoioli/gjp"
|
11
|
+
s.summary = "Green Java Packager's Tools"
|
12
|
+
s.description = "A suite of tools to ease Java packaging in SUSE systems"
|
13
|
+
|
14
|
+
s.rubyforge_project = "gjp"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_development_dependency "rake"
|
22
|
+
s.add_development_dependency "rspec"
|
23
|
+
|
24
|
+
s.add_runtime_dependency "clamp"
|
25
|
+
s.add_runtime_dependency "rubyzip"
|
26
|
+
s.add_runtime_dependency "rest-client"
|
27
|
+
s.add_runtime_dependency "json"
|
28
|
+
s.add_runtime_dependency "text"
|
29
|
+
s.add_runtime_dependency "nokogiri"
|
30
|
+
end
|
data/lib/gjp.rb
ADDED
data/lib/gjp/cli.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require "clamp"
|
4
|
+
|
5
|
+
class MainCommand < Clamp::Command
|
6
|
+
subcommand "get-pom", "Retrieves a pom corresponding to a jar" do
|
7
|
+
parameter "JAR", "jar file path"
|
8
|
+
option ["-v", "--verbose"], :flag, "verbose output"
|
9
|
+
option ["--very-verbose"], :flag, "very verbose output"
|
10
|
+
option ["--very-very-verbose"], :flag, "very very verbose output"
|
11
|
+
|
12
|
+
def execute
|
13
|
+
begin
|
14
|
+
init_logger
|
15
|
+
puts PomGetter.get_pom(jar)
|
16
|
+
rescue Zip::ZipError
|
17
|
+
$stderr.puts "#{jar} does not seem to be a valid jar archive, skipping"
|
18
|
+
rescue TypeError
|
19
|
+
$stderr.puts "#{jar} seems to be a valid jar archive but is corrupt, skipping"
|
20
|
+
rescue RestClient::ResourceNotFound
|
21
|
+
$stderr.puts "Got an error while looking for #{jar} in search.maven.org"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
subcommand "get-source-address", "Retrieves a project's SCM Internet address" do
|
27
|
+
parameter "POM", "project's pom file path"
|
28
|
+
option ["-v", "--verbose"], :flag, "verbose output"
|
29
|
+
option ["--very-verbose"], :flag, "very verbose output"
|
30
|
+
option ["--very-very-verbose"], :flag, "very very verbose output"
|
31
|
+
|
32
|
+
def execute
|
33
|
+
init_logger
|
34
|
+
puts SourceAddressGetter.get_source_address(pom)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
subcommand "get-source", "Retrieves a project's source code directory" do
|
39
|
+
parameter "ADDRESS", "project's SCM Internet address"
|
40
|
+
parameter "POM", "project's pom file path"
|
41
|
+
parameter "[DIRECTORY]", "directory in which to save the source code", :default => "."
|
42
|
+
option ["-v", "--verbose"], :flag, "verbose output"
|
43
|
+
option ["--very-verbose"], :flag, "very verbose output"
|
44
|
+
option ["--very-very-verbose"], :flag, "very very verbose output"
|
45
|
+
|
46
|
+
def execute
|
47
|
+
init_logger
|
48
|
+
puts SourceGetter.get_source(address, pom, directory)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/gjp/get_pom.rb
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require "digest/sha1"
|
4
|
+
require "zip/zip"
|
5
|
+
require "rest_client"
|
6
|
+
require "json"
|
7
|
+
require "text"
|
8
|
+
require "pathname"
|
9
|
+
|
10
|
+
# implements the get-pom subcommand
|
11
|
+
class PomGetter
|
12
|
+
|
13
|
+
# returns the pom corresponding to a file or directory, if it can be found
|
14
|
+
def self.get_pom(file)
|
15
|
+
(get_pom_from_dir(file) or get_pom_from_jar(file) or get_pom_from_sha1(file) or get_pom_from_heuristic(file))
|
16
|
+
end
|
17
|
+
|
18
|
+
# returns the pom in a project directory
|
19
|
+
def self.get_pom_from_dir(dir)
|
20
|
+
if File.directory?(dir)
|
21
|
+
pom_path = File.join(dir, "pom.xml")
|
22
|
+
if File.file?(pom_path)
|
23
|
+
$log.info("pom.xml found in #{dir}/pom.xml")
|
24
|
+
return File.read(pom_path)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# returns a pom embedded in a jar file
|
30
|
+
def self.get_pom_from_jar(file)
|
31
|
+
Zip::ZipFile.foreach(file) do |entry|
|
32
|
+
if entry.name =~ /\/pom.xml$/
|
33
|
+
$log.info("pom.xml found in #{file}##{entry.name}")
|
34
|
+
return entry.get_input_stream.read
|
35
|
+
end
|
36
|
+
end
|
37
|
+
return nil
|
38
|
+
end
|
39
|
+
|
40
|
+
# returns a pom from search.maven.org with a jar sha1 search
|
41
|
+
def self.get_pom_from_sha1(file)
|
42
|
+
sha1 = Digest::SHA1.hexdigest File.read(file)
|
43
|
+
results = repository_search({:q => "1:\"#{sha1}\""}).select {|result| result["ec"].include?(".pom")}
|
44
|
+
result = results.first
|
45
|
+
if result != nil
|
46
|
+
$log.info("pom.xml for #{file} found on search.maven.org for sha1 #{sha1} (#{result["g"]}:#{result["a"]}:#{result["v"]})")
|
47
|
+
return repository_download(result)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# returns a pom from search.maven.org with a heuristic name search
|
52
|
+
def self.get_pom_from_heuristic(file)
|
53
|
+
filename = Pathname.new(file).basename.to_s
|
54
|
+
matches = filename.match(/([^\/]*?)(?:[\.\-\_ ~,]([0-9].*))?\.jar$/)
|
55
|
+
if matches != nil and matches.length > 1
|
56
|
+
my_artifact_id = matches[1]
|
57
|
+
my_version = matches[2]
|
58
|
+
result = repository_search({:q => my_artifact_id}).first
|
59
|
+
if result != nil
|
60
|
+
results = repository_search({:q => "g:\"#{result["g"]}\" AND a:\"#{result["a"]}\"", :core => "gav"})
|
61
|
+
their_versions = results.map {|doc| doc["v"]}
|
62
|
+
best_matched_version = if my_version != nil then best_match(my_version, their_versions) else their_versions.max end
|
63
|
+
best_matched_result = (results.select{|result| result["v"] == best_matched_version}).first
|
64
|
+
|
65
|
+
$log.warn("pom.xml for #{file} found on search.maven.org with heuristic search (#{best_matched_result["g"]}:#{best_matched_result["a"]}:#{best_matched_result["v"]})")
|
66
|
+
|
67
|
+
return repository_download(best_matched_result)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# returns a JSON result from search.maven.com
|
73
|
+
def self.repository_search(params)
|
74
|
+
response = RestClient.get "http://search.maven.org/solrsearch/select", {:params => params.merge({"rows" => "100", "wt" => "json"})}
|
75
|
+
json = JSON.parse(response.to_s)
|
76
|
+
return json["response"]["docs"]
|
77
|
+
end
|
78
|
+
|
79
|
+
# downloads a POM from a search.maven.com search result
|
80
|
+
def self.repository_download(result)
|
81
|
+
if result != nil
|
82
|
+
path = "#{result["g"].gsub(".", "/")}/#{result["a"]}/#{result["v"]}/#{result["a"]}-#{result["v"]}.pom"
|
83
|
+
return (RestClient.get "http://search.maven.org/remotecontent", {:params => {:filepath => path}}).to_s
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# returns the "best match" between a version number and a set of available version numbers
|
88
|
+
# using a heuristic criterion. Idea:
|
89
|
+
# - split the version number in chunks divided by ., - etc.
|
90
|
+
# - every chunk with same index is "compared", differences make up a score
|
91
|
+
# - "comparison" is a subtraction if the chunk is an integer, a string distance measure otherwise
|
92
|
+
# - score weighs differently on chunk index (first chunks are most important)
|
93
|
+
# - lowest score wins
|
94
|
+
def self.best_match(my_version, their_versions)
|
95
|
+
$log.debug("version comparison: #{my_version} vs #{their_versions.join(', ')}")
|
96
|
+
|
97
|
+
my_chunks = my_version.split /[\.\-\_ ~,]/
|
98
|
+
their_chunks_hash = Hash[
|
99
|
+
their_versions.map do |their_version|
|
100
|
+
their_chunks_for_version = their_version.split /[\.\-\_ ~,]/
|
101
|
+
their_chunks_for_version += [nil]*[my_chunks.length - their_chunks_for_version.length, 0].max
|
102
|
+
[their_version, their_chunks_for_version]
|
103
|
+
end
|
104
|
+
]
|
105
|
+
|
106
|
+
max_chunks_length = ([my_chunks.length] + their_chunks_hash.values.map {|chunk| chunk.length}).max
|
107
|
+
|
108
|
+
scoreboard = []
|
109
|
+
their_versions.each do |their_version|
|
110
|
+
their_chunks = their_chunks_hash[their_version]
|
111
|
+
score = 0
|
112
|
+
their_chunks.each_with_index do |their_chunk, i|
|
113
|
+
score_multiplier = 100**(max_chunks_length -i -1)
|
114
|
+
my_chunk = my_chunks[i]
|
115
|
+
score += chunk_distance(my_chunk, their_chunk) * score_multiplier
|
116
|
+
end
|
117
|
+
scoreboard << {:version => their_version, :score => score}
|
118
|
+
end
|
119
|
+
|
120
|
+
scoreboard = scoreboard.sort_by {|element| element[:score]}
|
121
|
+
|
122
|
+
$log.debug("scoreboard: ")
|
123
|
+
scoreboard.each_with_index do |element, i|
|
124
|
+
$log.debug(" #{i+1}. #{element[:version]} (score: #{element[:score]})")
|
125
|
+
end
|
126
|
+
|
127
|
+
winner = scoreboard.first
|
128
|
+
|
129
|
+
if winner != nil
|
130
|
+
return winner[:version]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# returns a score representing the distance between two version chunks
|
135
|
+
# for integers, the score is the difference between their values
|
136
|
+
# for strings, the score is the Levenshtein distance
|
137
|
+
# in any case score is normalized between 0 (identical) and 99 (very different/uncomparable)
|
138
|
+
def self.chunk_distance(my_chunk, their_chunk)
|
139
|
+
if my_chunk == nil
|
140
|
+
my_chunk = "0"
|
141
|
+
end
|
142
|
+
if their_chunk == nil
|
143
|
+
their_chunk = "0"
|
144
|
+
end
|
145
|
+
if my_chunk.is_i? and their_chunk.is_i?
|
146
|
+
return [(my_chunk.to_i - their_chunk.to_i).abs, 99].min
|
147
|
+
else
|
148
|
+
return [Text::Levenshtein.distance(my_chunk.upcase, their_chunk.upcase), 99].min
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
class String
|
154
|
+
def is_i?
|
155
|
+
!!(self =~ /^[0-9]+$/)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require "rest_client"
|
4
|
+
|
5
|
+
# implements the get-source subcommand
|
6
|
+
class SourceGetter
|
7
|
+
|
8
|
+
# downloads a project's source into a specified directory
|
9
|
+
def self.get_source(address, pomfile, directory)
|
10
|
+
$log.info("downloading: #{address} in #{directory}, pomfile: #{pomfile}")
|
11
|
+
|
12
|
+
dummy, prefix, scm_address = address.split(/^([^:]+):(.*)$/)
|
13
|
+
|
14
|
+
$log.info("prefix: #{prefix}, scm_address: #{scm_address}")
|
15
|
+
|
16
|
+
if prefix == "git"
|
17
|
+
get_source_from_git(scm_address, pomfile, directory)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# checks out from git
|
22
|
+
def self.get_source_from_git(scm_address, pomfile, directory)
|
23
|
+
pom = Pom.new(pomfile)
|
24
|
+
dir = File.join(directory, "#{pom.group_id}:#{pom.artifact_id}:#{pom.version}")
|
25
|
+
|
26
|
+
Dir::mkdir(dir)
|
27
|
+
`git clone #{scm_address} #{dir}`
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require "rest_client"
|
4
|
+
require "json"
|
5
|
+
require "open-uri"
|
6
|
+
|
7
|
+
# implements the get-source-address subcommand
|
8
|
+
class SourceAddressGetter
|
9
|
+
|
10
|
+
# returns the pom corresponding to a file or directory, if it can be found
|
11
|
+
def self.get_source_address(file)
|
12
|
+
$log.info("looking for source address for: #{file}")
|
13
|
+
(get_source_address_from_pom(file) or get_source_address_from_github(file))
|
14
|
+
end
|
15
|
+
|
16
|
+
# returns an scm address in a pom file
|
17
|
+
def self.get_source_address_from_pom(file)
|
18
|
+
pom = Pom.new(file)
|
19
|
+
result = pom.connection_address
|
20
|
+
|
21
|
+
if result != nil
|
22
|
+
$log.info("address found in pom")
|
23
|
+
result
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# returns an scm address looking for it on github
|
28
|
+
def self.get_source_address_from_github(file)
|
29
|
+
pom = Pom.new(file)
|
30
|
+
|
31
|
+
result = (github_search(pom.artifact_id) or github_search(pom.artifact_id.split("-").first) or github_search(pom.group_id))
|
32
|
+
|
33
|
+
if result != nil
|
34
|
+
$log.info("address found on Github: #{result}")
|
35
|
+
result
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# returns a Giuthub repo address based on the keyword
|
40
|
+
def self.github_search(keyword)
|
41
|
+
if keyword != "" and keyword != nil
|
42
|
+
response = RestClient.get "https://api.github.com/legacy/repos/search/" + CGI::escape(keyword), :user_agent => "gjp/" + Gjp::VERSION, :language => "java", :sort => "forks"
|
43
|
+
json = JSON.parse(response.to_s)
|
44
|
+
|
45
|
+
(json["repositories"].map {|repository| "git:" + repository["url"]}).first
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|