gembuild 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +26 -0
- data/.rspec +2 -0
- data/.travis.yml +15 -0
- data/CHANGELOG.md +15 -0
- data/Gemfile +3 -0
- data/LICENSE +675 -0
- data/README.md +80 -0
- data/Rakefile +14 -0
- data/bin/gembuild +10 -0
- data/gembuild.gemspec +45 -0
- data/lib/gembuild.rb +142 -0
- data/lib/gembuild/aur_scraper.rb +159 -0
- data/lib/gembuild/exceptions.rb +31 -0
- data/lib/gembuild/gem_scraper.rb +205 -0
- data/lib/gembuild/pkgbuild.erb +32 -0
- data/lib/gembuild/pkgbuild.rb +283 -0
- data/lib/gembuild/project.rb +167 -0
- data/lib/gembuild/version.rb +23 -0
- metadata +301 -0
data/README.md
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# Gembuild
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/mfinelli/gembuild.svg?branch=master)](https://travis-ci.org/mfinelli/gembuild)
|
4
|
+
[![Coverage Status](https://coveralls.io/repos/mfinelli/gembuild/badge.svg?branch=master&service=github)](https://coveralls.io/github/mfinelli/gembuild?branch=master)
|
5
|
+
[![Code Climate](https://codeclimate.com/github/mfinelli/gembuild/badges/gpa.svg)](https://codeclimate.com/github/mfinelli/gembuild)
|
6
|
+
[![Inline Documentation](https://inch-ci.org/github/mfinelli/gembuild.svg)](https://inch-ci.org/github/mfinelli/gembuild)
|
7
|
+
|
8
|
+
Create PKGBUILDs for ruby gems.
|
9
|
+
|
10
|
+
## Configuration
|
11
|
+
|
12
|
+
Upon first run the gem will prompt for your name and email address to use in
|
13
|
+
the maintainer field in PKGBUILDs and to use as the git author. If you have
|
14
|
+
already configured git it will confirm the values that it finds. It will also
|
15
|
+
ask where to store package repositories. All information is then saved in
|
16
|
+
`~/.gembuild` which is just a simple YAML file and can be easily changed
|
17
|
+
using your favorite text editor.
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
Simple usage to create/update a package for a gem:
|
22
|
+
|
23
|
+
```shell
|
24
|
+
$ bundle exec bin/gembuild mina
|
25
|
+
```
|
26
|
+
|
27
|
+
This will checkout the AUR package for `ruby-mina`, fetch the latest version
|
28
|
+
of the gem and update the PKGBUILD accordingly, and then regenerate the
|
29
|
+
metadata using `mksrcinfo` and commit all changes.
|
30
|
+
|
31
|
+
### Advanced Usage
|
32
|
+
|
33
|
+
There are four main parts: the AUR scraper, the rubygems scraper, the
|
34
|
+
PKGBUILD and the project.
|
35
|
+
|
36
|
+
#### AUR Scraper
|
37
|
+
|
38
|
+
The AUR scraper is used to get version information about a package currently
|
39
|
+
on the AUR.
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
s = Gembuild::AurScraper.new('ruby-mina')
|
43
|
+
s.scrape!
|
44
|
+
```
|
45
|
+
|
46
|
+
#### RubyGems Scraper
|
47
|
+
|
48
|
+
The RubyGems scraper gets the rest of the information needed for the PKGBUILD
|
49
|
+
by making several queries to rubygems.org. **N.B. that it skips any version
|
50
|
+
marked as a "prerelease".**
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
s = Gembuild::GemScraper.neW('mina')
|
54
|
+
s.scrape!
|
55
|
+
```
|
56
|
+
|
57
|
+
#### PKGBUILD
|
58
|
+
|
59
|
+
The `Pkgbuild` class is actually responsible for creating a PKGBUILD for a
|
60
|
+
gem.
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
Gembuild::Pkgbuild.create('mina')
|
64
|
+
```
|
65
|
+
|
66
|
+
#### Project
|
67
|
+
|
68
|
+
The `Project` class is what checks out the repository from the AUR,
|
69
|
+
configures git for it, makes sure there is a `.gitignore` and commits all
|
70
|
+
changes after creating or updating the PKGBUILD.
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
Gembuild::Project.new('mina').clone_and_commit!
|
74
|
+
```
|
75
|
+
|
76
|
+
## License
|
77
|
+
|
78
|
+
This project is licensed under the GPLv3 or any later version. For more
|
79
|
+
information please see the LICENSE file included with the project or
|
80
|
+
[https://www.gnu.org/licenses](https://www.gnu.org/licenses/gpl.html).
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
require 'rubocop/rake_task'
|
4
|
+
require 'yard'
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
7
|
+
RuboCop::RakeTask.new(:rubocop) do |task|
|
8
|
+
task.requires << 'rubocop-rspec'
|
9
|
+
end
|
10
|
+
YARD::Rake::YardocTask.new(:yard)
|
11
|
+
|
12
|
+
# Checking the spec files on travis causes an error and the build to fail.
|
13
|
+
# task default: [:rubocop, :spec]
|
14
|
+
task default: :spec
|
data/bin/gembuild
ADDED
data/gembuild.gemspec
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'gembuild/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'gembuild'
|
9
|
+
spec.version = Gembuild::VERSION
|
10
|
+
spec.authors = ['Mario Finelli']
|
11
|
+
spec.email = ['mario@finel.li']
|
12
|
+
|
13
|
+
spec.summary = 'Generate PKGBUILDs for ruby gems.'
|
14
|
+
spec.description = 'Generate PKGBUILDs for ruby gems.'
|
15
|
+
spec.homepage = 'https://github.com/mfinelli/gembuild'
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
|
21
|
+
spec.bindir = 'bin'
|
22
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
23
|
+
spec.require_paths = ['lib']
|
24
|
+
|
25
|
+
spec.licenses = ['GPL-3.0+']
|
26
|
+
|
27
|
+
spec.add_dependency 'mechanize'
|
28
|
+
spec.add_dependency 'nokogiri'
|
29
|
+
|
30
|
+
spec.add_development_dependency 'bundler'
|
31
|
+
spec.add_development_dependency 'coveralls'
|
32
|
+
spec.add_development_dependency 'mutant-rspec'
|
33
|
+
spec.add_development_dependency 'pry'
|
34
|
+
spec.add_development_dependency 'rake'
|
35
|
+
spec.add_development_dependency 'redcarpet'
|
36
|
+
spec.add_development_dependency 'rspec'
|
37
|
+
spec.add_development_dependency 'rubocop'
|
38
|
+
spec.add_development_dependency 'rubocop-rspec'
|
39
|
+
spec.add_development_dependency 'rubycritic'
|
40
|
+
spec.add_development_dependency 'ruby-lint'
|
41
|
+
spec.add_development_dependency 'simplecov'
|
42
|
+
spec.add_development_dependency 'vcr'
|
43
|
+
spec.add_development_dependency 'webmock'
|
44
|
+
spec.add_development_dependency 'yard'
|
45
|
+
end
|
data/lib/gembuild.rb
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# Gembuild: create Arch Linux PKGBUILDs for ruby gems.
|
4
|
+
# Copyright (C) 2015 Mario Finelli <mario@finel.li>
|
5
|
+
#
|
6
|
+
# This program is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
require 'English'
|
20
|
+
|
21
|
+
require 'gembuild/aur_scraper'
|
22
|
+
require 'gembuild/exceptions'
|
23
|
+
require 'gembuild/gem_scraper'
|
24
|
+
require 'gembuild/pkgbuild'
|
25
|
+
require 'gembuild/project'
|
26
|
+
require 'gembuild/version'
|
27
|
+
|
28
|
+
# Create Arch Linux PKGBUILDs for ruby gems.
|
29
|
+
module Gembuild
|
30
|
+
class << self
|
31
|
+
# The path to the gembuild configuration file.
|
32
|
+
#
|
33
|
+
# @return [String] real path to the configuration file
|
34
|
+
def conf_file
|
35
|
+
File.expand_path(File.join('~', '.gembuild'))
|
36
|
+
end
|
37
|
+
|
38
|
+
# Read from the configuration file if it exists, otherwise prompt for the
|
39
|
+
# configuration and save it to file.
|
40
|
+
#
|
41
|
+
# @return [Hash] the configuration options: maintainer name and email
|
42
|
+
# address and where to checkout packages
|
43
|
+
def configure
|
44
|
+
unless File.file?(conf_file)
|
45
|
+
name = fetch_git_global_name
|
46
|
+
email = fetch_git_global_email
|
47
|
+
pkgdir = fetch_pkgdir
|
48
|
+
|
49
|
+
File.write(
|
50
|
+
conf_file,
|
51
|
+
{ name: name, email: email, pkgdir: pkgdir }.to_yaml
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
YAML.load_file(conf_file)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Attempt to read the global git name, prompting the user for the name if
|
59
|
+
# unsuccessful.
|
60
|
+
#
|
61
|
+
# @return [String] the name to use as package maintainer
|
62
|
+
def fetch_git_global_name
|
63
|
+
name = `git config --global user.name`.strip
|
64
|
+
|
65
|
+
if $CHILD_STATUS.success?
|
66
|
+
if prompt_for_confirmation(name)
|
67
|
+
return name
|
68
|
+
else
|
69
|
+
prompt_for_git_name
|
70
|
+
end
|
71
|
+
else
|
72
|
+
prompt_for_git_name('Could not detect name from git configuration.')
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Attempt to read the global git email, prompting the user for the email
|
77
|
+
# if unsuccessful.
|
78
|
+
#
|
79
|
+
# @return [String] the email to use as package maintainer
|
80
|
+
def fetch_git_global_email
|
81
|
+
email = `git config --global user.email`.strip
|
82
|
+
|
83
|
+
if $CHILD_STATUS.success?
|
84
|
+
if prompt_for_confirmation(email)
|
85
|
+
return email
|
86
|
+
else
|
87
|
+
prompt_for_git_email
|
88
|
+
end
|
89
|
+
else
|
90
|
+
prompt_for_git_email('Could not detect email from git configuration.')
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Prompt the user for the name to use.
|
95
|
+
#
|
96
|
+
# This method is only called if reading the global git configuration was
|
97
|
+
# unsuccessful or the user specified that it was incorrect.
|
98
|
+
#
|
99
|
+
# @param msg [String, nil] An optional message to display before
|
100
|
+
# prompting.
|
101
|
+
# @return [String] the name to use as package maintainer
|
102
|
+
def prompt_for_git_name(msg = nil)
|
103
|
+
puts msg unless msg.nil? || msg.empty?
|
104
|
+
puts 'Please enter desired name: '
|
105
|
+
gets.chomp
|
106
|
+
end
|
107
|
+
|
108
|
+
# Prompt the user for the email to use.
|
109
|
+
#
|
110
|
+
# This method is only called if reading the global git configuration was
|
111
|
+
# unsuccessful or the user specified that it was incorrect.
|
112
|
+
#
|
113
|
+
# @param msg [String, nil] An optional message to display before
|
114
|
+
# prompting.
|
115
|
+
# @return [String] the email address to use as package maintainer
|
116
|
+
def prompt_for_git_email(msg = nil)
|
117
|
+
puts msg unless msg.nil? || msg.empty?
|
118
|
+
puts 'Please enter desired email: '
|
119
|
+
gets.chomp
|
120
|
+
end
|
121
|
+
|
122
|
+
# Ask the user to confirm the detected value.
|
123
|
+
#
|
124
|
+
# @param detected [String] The value that was detected.
|
125
|
+
# @return [Boolean] whether or not the value is correct
|
126
|
+
def prompt_for_confirmation(detected)
|
127
|
+
puts "Detected \"#{detected}\", is this correct? (y/n)"
|
128
|
+
response = gets.chomp.downcase[0, 1]
|
129
|
+
|
130
|
+
response == 'y'
|
131
|
+
end
|
132
|
+
|
133
|
+
# Prompt the user for the location where they would like to store
|
134
|
+
# checked-out packages.
|
135
|
+
#
|
136
|
+
# @return [String] the filepath where to store package repositories
|
137
|
+
def fetch_pkgdir
|
138
|
+
puts 'Where should projects be checked out?'
|
139
|
+
File.expand_path(gets.chomp)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# Gembuild: create Arch Linux PKGBUILDs for ruby gems.
|
4
|
+
# Copyright (C) 2015 Mario Finelli <mario@finel.li>
|
5
|
+
#
|
6
|
+
# This program is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
require 'mechanize'
|
20
|
+
|
21
|
+
module Gembuild
|
22
|
+
# This class is used to query the AUR for information about a package.
|
23
|
+
#
|
24
|
+
# @!attribute [r] agent
|
25
|
+
# @return [Mechanize] the Mechanize agent
|
26
|
+
# @!attribute [r] pkgname
|
27
|
+
# @return [String] the package about which to query the AUR
|
28
|
+
# @!attribute [r] url
|
29
|
+
# @return [String] the AUR url for the package
|
30
|
+
class AurScraper
|
31
|
+
attr_reader :agent, :pkgname, :url
|
32
|
+
|
33
|
+
# Creates a new AurScraper instance.
|
34
|
+
#
|
35
|
+
# @raise [Gembuild::UndefinedPkgnameError] if the pkgname is nil or empty
|
36
|
+
#
|
37
|
+
# @example Create new AurScraper object
|
38
|
+
# Gembuild::AurScraper.new('ruby-mina')
|
39
|
+
# # => #<Gembuild::AurScraper:0x000000040659b0
|
40
|
+
# # @agent=
|
41
|
+
# # #<Mechanize
|
42
|
+
# # #<Mechanize::CookieJar:0x000000040658c0
|
43
|
+
# # @store=
|
44
|
+
# # #<HTTP::CookieJar::HashStore:0x000000040813b8
|
45
|
+
# # @gc_index=0,
|
46
|
+
# # @gc_threshold=150,
|
47
|
+
# # @jar={},
|
48
|
+
# # @logger=nil,
|
49
|
+
# # @mon_count=0,
|
50
|
+
# # @mon_mutex=#<Mutex:0x00000004081368>,
|
51
|
+
# # @mon_owner=nil>>
|
52
|
+
# # nil>,
|
53
|
+
# # @pkgname="ruby-mina",
|
54
|
+
# # @url="https://aur.archlinux.org/rpc.php?type=info&arg=ruby-mina">
|
55
|
+
#
|
56
|
+
# @param pkgname [String] The name of the package about which to query.
|
57
|
+
# @return [Gembuild::AurScraper] a new AurScraper instance
|
58
|
+
def initialize(pkgname)
|
59
|
+
fail Gembuild::UndefinedPkgnameError if pkgname.nil? || pkgname.empty?
|
60
|
+
|
61
|
+
@agent = Mechanize.new
|
62
|
+
@pkgname = pkgname
|
63
|
+
|
64
|
+
@url = "https://aur.archlinux.org/rpc.php?type=info&arg=#{pkgname}"
|
65
|
+
end
|
66
|
+
|
67
|
+
# Query the AUR for information about a package and then parse the JSON
|
68
|
+
# results.
|
69
|
+
#
|
70
|
+
# @example Query the AUR about a package
|
71
|
+
# s = Gembuild::AurScraper.new('ruby-mina')
|
72
|
+
# r = s.query_aur
|
73
|
+
# #=> {:version=>1,
|
74
|
+
# # :type=>"info",
|
75
|
+
# # :resultcount=>1,
|
76
|
+
# # :results=>
|
77
|
+
# # {:ID=>238062,
|
78
|
+
# # :Name=>"ruby-mina",
|
79
|
+
# # :PackageBaseID=>101492,
|
80
|
+
# # :PackageBase=>"ruby-mina",
|
81
|
+
# # :Version=>"0.3.7-1",
|
82
|
+
# # :Description=>"Really fast deployer and server automation tool.",
|
83
|
+
# # :URL=>"http://github.com/nadarei/mina",
|
84
|
+
# # :NumVotes=>0,
|
85
|
+
# # :OutOfDate=>nil,
|
86
|
+
# # :Maintainer=>"supermario",
|
87
|
+
# # :FirstSubmitted=>1444354070,
|
88
|
+
# # :LastModified=>1444354135,
|
89
|
+
# # :License=>"MIT",
|
90
|
+
# # :URLPath=>"/cgit/aur.git/snapshot/ruby-mina.tar.gz",
|
91
|
+
# # :CategoryID=>1}}
|
92
|
+
#
|
93
|
+
# @return [Hash] the information about the package
|
94
|
+
def query_aur
|
95
|
+
JSON.parse(agent.get(url).body, symbolize_names: true)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Determine whether the package already exists on the AUR by the number of
|
99
|
+
# results returned.
|
100
|
+
#
|
101
|
+
# @example Check if package exists on the AUR
|
102
|
+
# s = Gembuild::AurScraper.new('ruby-mina')
|
103
|
+
# r = s.query_aur
|
104
|
+
# s.package_exists?(r) #=> true
|
105
|
+
#
|
106
|
+
# @param response [Hash] The JSON parsed response from the AUR.
|
107
|
+
# @return [Boolean] whether or not the package exists already on the AUR
|
108
|
+
def package_exists?(response)
|
109
|
+
response[:results].count.zero? ? false : true
|
110
|
+
end
|
111
|
+
|
112
|
+
# Parse the version from the AUR response.
|
113
|
+
#
|
114
|
+
# A version string is expected to either look like 0.1.2-3 or like
|
115
|
+
# 1:2.3.4-5. So the strategy is to first split on the dash to get the
|
116
|
+
# package release number. Then with the remaining string attempt a split
|
117
|
+
# on the colon. If there is only one part then it means that there is no
|
118
|
+
# epoch (or rather that the epoch is zero). If there are two parts then we
|
119
|
+
# use the first as the epoch value. Finally, whatever is left is the
|
120
|
+
# actual version of the gem.
|
121
|
+
#
|
122
|
+
# @example Get package version from the AUR
|
123
|
+
# s = Gembuild::AurScraper.new('ruby-mina')
|
124
|
+
# r = s.query_aur
|
125
|
+
# s.get_version_hash(r)
|
126
|
+
# #=> {:epoch=>0, :pkgver=>Gem::Version.new("0.3.7"), :pkgrel=>1}
|
127
|
+
#
|
128
|
+
# @param response [Hash] The JSON parsed response from the AUR.
|
129
|
+
# @return [Hash] a hash of the different version parts
|
130
|
+
def get_version_hash(response)
|
131
|
+
version = response[:results][:Version].split('-')
|
132
|
+
|
133
|
+
pkgrel = version.pop.to_i
|
134
|
+
version = version.join
|
135
|
+
|
136
|
+
version = version.split(':')
|
137
|
+
epoch = version.count == 1 ? 0 : version.shift.to_i
|
138
|
+
version = Gem::Version.new(version.join)
|
139
|
+
|
140
|
+
{ epoch: epoch, pkgver: version, pkgrel: pkgrel }
|
141
|
+
end
|
142
|
+
|
143
|
+
# Query the AUR and returned the parsed results.
|
144
|
+
#
|
145
|
+
# @example Query the AUR for information about a package
|
146
|
+
# s = Gembuild::AurScraper.new('ruby-mina')
|
147
|
+
# s.scrape!
|
148
|
+
# #=> {:epoch=>0, :pkgver=>Gem::Version.new("0.3.7"), :pkgrel=>1}
|
149
|
+
#
|
150
|
+
# @return [nil, Hash] the version hash or nil if the package doesn't exist
|
151
|
+
def scrape!
|
152
|
+
response = query_aur
|
153
|
+
|
154
|
+
return nil unless package_exists?(response)
|
155
|
+
|
156
|
+
get_version_hash(response)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|