gjp 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # See gjp.gemspec
4
+ gemspec
@@ -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
@@ -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
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new
5
+
6
+ task :default => :test
7
+ task :test => :spec
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
@@ -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
@@ -0,0 +1,7 @@
1
+ require "gjp/version"
2
+ require "gjp/logger"
3
+ require "gjp/cli"
4
+ require "gjp/pom"
5
+ require "gjp/get_pom"
6
+ require "gjp/get_source_address"
7
+ require "gjp/get_source"
@@ -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
@@ -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