bundle2rpm 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ *.gem
data/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2011 Matt Savona
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,22 @@
1
+ # bundle2rpm: a gem to package bundles
2
+
3
+ bundle2rpm is a tool designed to facilitate the packaging and deployment of Gem bundles on RedHat-like Linux systems. Leveraging the lovely [Bundler](http://gembundler.com) tool, bundle2rpm strives to produce self-contained RPMs that can be easily installed on other systems.
4
+
5
+ ### Why?
6
+
7
+ Let's talk enterprise (*gasp*). In extremely large development environments (hundreds of developers or more) it is not always possible for system administrators to accommodate requests to test, install and manage multiple versions of Ruby Gems. When development or production systems are shared between teams or projects, each with their own Gem requirements. To centrally maintain Gems across many systems that will satisfy the requirements of these many teams and projects is nearly impossible. Ideally, each project defines their own Gemfile and Bundler takes care of all the really hard work. bundle2rpm provides an elegant way to construct RPMs that can be built on development systems where compilers and header files are available, and then the binary RPM can be installed and leveraged on any other system in the organization.
8
+
9
+ ### How?
10
+
11
+ Install Bundler and bundle2rpm:
12
+ `gem install bundler bundle2rpm`
13
+
14
+ Build your RPM (options are available, see `bundle2rpm -h`):
15
+ `bundle2rpm /path/to/your/Gemfile /where/to/install`
16
+
17
+ Install the RPM produced by bundle2rpm on any RedHat system:
18
+ `rpm -ivh yourbundled.rpm`
19
+
20
+ ### Issues
21
+
22
+ bundle2rpm is still being tweaked. Documentation is still being pieced together as well. Still, please do let me know if you encounter any problems and I will be happy to take a look into them.
data/bin/bundle2rpm ADDED
@@ -0,0 +1,375 @@
1
+ #!/bin/env ruby
2
+
3
+ # bundle2rpm
4
+ # Matt Savona - 2011
5
+ #
6
+ # Leverage the lovely Gem Bundler tool to produce nicely packaged RPMs.
7
+
8
+ require "fileutils"
9
+ require "optparse"
10
+ require "time"
11
+ require "pp"
12
+
13
+ TMP_DIR = "/tmp"
14
+
15
+ @variable_map = {
16
+ :package_name => { :default => "rubygem-custom_bundle", :comment => "Name of your package (mybundle)." },
17
+ :package_version => { :default => "1.0.0", :comment => "Version number of your package (ex. 1.0.0)." },
18
+ :package_release => { :default => "1", :comment => "Release number of your package (ex. 1)." },
19
+ :author => { :default => "bundle2rpm", :comment => "Your name (ex. John Smith)." },
20
+ :email => { :default => "bundle2rpm@localhost", :comment => "Your email (ex. john@example.com)." },
21
+ :url => { :default => "http://localhost/", :comment => "Your URL (ex. http://loopforever.com)." },
22
+ :license => { :default => "GPL", :comment => "License for your package (ex. GPL)." },
23
+ :user => { :default => "root", :comment => "Username that owns the files in this RPM (ex. jsmith)." },
24
+ :group => { :default => "root", :comment => "Groupname that owns the files in this RPM (ex. developers)." },
25
+ :changelog => { :immutable => true, :deferred_default => { :stage => 0, :invoke => :changelog } }
26
+ }
27
+
28
+ @options = {}
29
+
30
+ @gemfile, @prefix, @verbose, @no_cleanup, @package_full_name = nil
31
+ @tmp_dir, @working_dir, @save_rpm_to, @rpm_spec_file = nil
32
+
33
+ module Exceptions
34
+ class ArgumentError < StandardError; end
35
+ class BuildError < StandardError; end
36
+ class CommandError < StandardError; end
37
+ end
38
+
39
+ def main
40
+ deferred_variables = [ [],[],[] ]
41
+
42
+ OptionParser.new do |o|
43
+ o.banner = "Usage: #{$0} [options] /path/to/Gemfile /path/to/install/into"
44
+
45
+ o.separator ""
46
+ o.separator "RPM spec file options:"
47
+
48
+ @variable_map.each do |k,v|
49
+ next if v[:immutable]
50
+
51
+ o.on("--#{k.to_s.gsub(/_/, "-")} VALUE", v[:comment]) do |ov|
52
+ @options[k] = ov
53
+ end
54
+ end
55
+
56
+ o.separator ""
57
+ o.separator "General options:"
58
+ o.on("--bundler VERSION", "Pull in and package this version of bundler (from Rubyforge/Gemcutter only). Defaults to the same version of bundler that is used to construct your RPM (first in your PATH).") do |ov|
59
+ @options[:bundler_version] = ov
60
+ end
61
+ o.on("--save-rpm-to DIR", "If specified, the RPM built for you will be written to this directory. Defaults to your working directory.") do |ov|
62
+ @options[:save_rpm_to] = ov
63
+ end
64
+ o.on("--no-cleanup", "Do not cleanup working directory. Useful only for debugging purposes, normally you want your transient build files cleaned up.") do |ov|
65
+ @no_cleanup = true
66
+ end
67
+ o.on("--tmp-dir DIR", "Temporary directory to use for transient `bundle install` and RPM build files.") do |ov|
68
+ @options[:tmp_dir] = ov
69
+ end
70
+ o.on("-v", "--verbose", "Print additional output.") do |ov|
71
+ @verbose = true
72
+ end
73
+ o.on_tail("-h", "--help", "Show this message.") do
74
+ puts o
75
+ exit
76
+ end
77
+ end.parse!
78
+
79
+ @gemfile = normalize_path(ARGV[0])
80
+ @prefix = normalize_path(ARGV[1])
81
+ @tmp_dir = normalize_path(@options[:tmp_dir] || TMP_DIR)
82
+ @save_rpm_to = normalize_path(@options[:save_rpm_to] || FileUtils.pwd)
83
+
84
+ raise Exceptions::ArgumentError, "You must specify the path to an existing Gemfile." if !@gemfile || !File.exists?(@gemfile)
85
+ raise Exceptions::ArgumentError, "You must specify the path where you want your RPM to install into." if !@prefix
86
+
87
+ puts "---"
88
+ puts "Gemfile: #{@gemfile}"
89
+ puts "Install Prefix: #{@prefix}"
90
+ puts "---"
91
+
92
+ # Define a value for all entries in the @variable_map, either user supplied or default:
93
+ @variable_map.each do |k,v|
94
+ if v[:deferred_default]
95
+ deferred_variables[v[:deferred_default][:stage]] << { :key => k, :invoke => v[:deferred_default][:invoke] }
96
+ end
97
+
98
+ @variable_map[k][:value] = @options[k] || v[:default]
99
+ end
100
+
101
+ # Some variables are deferred, meaning their value cannot be known until other variable's have values assigned
102
+ # to them. These can be deferred into (upto) three stages:
103
+ deferred_variables.each do |stage|
104
+ stage.each do |deferred_variable|
105
+ @variable_map[deferred_variable[:key]][:value] = self.send(deferred_variable[:invoke])
106
+ end
107
+ end
108
+
109
+ @package_full_name = []
110
+ @package_full_name << @variable_map[:package_name][:value]
111
+ @package_full_name << @variable_map[:package_version][:value]
112
+ @package_full_name << @variable_map[:package_release][:value]
113
+ @package_full_name = @package_full_name.join("-")
114
+ @working_dir = setup_working_dir
115
+ @rpm_spec_file = write_rpm_spec("#{@working_dir}/rpmbuild/SPECS/#{@package_full_name}.spec")
116
+ prepare_bundle
117
+ build_rpm
118
+ end
119
+
120
+ def write_rpm_spec(to_file)
121
+ puts "Generating RPM spec file."
122
+
123
+ spec_file = DATA.read
124
+
125
+ # Replace all |xxx| variables in our template with the mapped
126
+ # values via @variable_map:
127
+ @variable_map.each do |k,v|
128
+ spec_file.gsub!(/\|#{k.to_s}\|/, v[:value])
129
+ end
130
+
131
+ File.open(to_file, "w") do |f|
132
+ f.puts spec_file
133
+ end
134
+
135
+ return to_file
136
+ end
137
+
138
+ def prepare_bundle
139
+ puts "Bundling your gems."
140
+
141
+ # Don't let people's custom gem environments get in the way:
142
+ ENV["GEM_HOME"] = ENV["GEM_PATH"] = nil
143
+
144
+ bundler_version = @options[:bundler_version] || get_bundler_version
145
+ bundle_into = "#{@working_dir}/rpmbuild/SOURCES/#{@package_full_name}"
146
+ bundle_config_file = "#{bundle_into}/.bundle/config"
147
+
148
+ # Make ourselves a /bin directory to store our artificial ruby binaries:
149
+ FileUtils.mkdir("#{bundle_into}/bin")
150
+
151
+ # Copy over Gemfile and Gemfile.lock into our source directory:
152
+ [@gemfile, "#{@gemfile}.lock"].each do |gf|
153
+ FileUtils.cp(gf, bundle_into) if File.exists?(gf)
154
+ end
155
+
156
+ # No .lock file exists yet, so we need to bundle install first:
157
+ if !File.exists?("#{bundle_into}/Gemfile.lock")
158
+ exec_command("bundle install --gemfile #{bundle_into}/Gemfile --path #{bundle_into} --binstubs #{bundle_into}/bundle_bin")
159
+ end
160
+
161
+ # Install in "deployment" mode:
162
+ exec_command("bundle install --deployment --gemfile #{bundle_into}/Gemfile --path #{bundle_into} --binstubs #{bundle_into}/bundle_bin")
163
+
164
+ # We don't make any assumptions about the ruby version, so glob in order
165
+ # to find the paths we're looking for:
166
+ source_gem_path = source_bin_path = nil
167
+ Dir["#{bundle_into}/ruby/*/*"].each do |d|
168
+ case File.basename(d)
169
+ when "gems"
170
+ source_gem_path = d.gsub(/\/gems$/, "")
171
+ end
172
+ end
173
+
174
+ # It is permissible for a bundle to not include a bin_path, but it must contain a gem path:
175
+ raise Exceptions::BuildError, "Unable to determine appropriate Gem path value for your bundle. Unable to proceed." unless source_gem_path
176
+
177
+ source_bin_path = "#{source_gem_path}/bin"
178
+
179
+ # At this point we want all Rubygem interaction within our source directory:
180
+ ENV["GEM_HOME"] = ENV["GEM_PATH"] = source_gem_path
181
+
182
+ # Pull in bundler for packaging inside the RPM itself:
183
+ exec_command("gem install bundler -v #{bundler_version} --no-rdoc --no-ri")
184
+
185
+ # Move our Bundler binstubs to a more sensible location (adjacent to the bin directory Rubygems would create):
186
+ source_bundle_bin_path = File.expand_path("#{source_bin_path}/../bundle_bin")
187
+ FileUtils.mv("#{bundle_into}/bundle_bin", source_bundle_bin_path)
188
+
189
+ # Chop off our build prefix, so we can insert some magic (REPLACE_WITH_BUNDLE_DIR)
190
+ # that will be interpolated at RPM post-install time:
191
+ built_gem_path = source_gem_path.gsub(/^#{bundle_into}\//, "")
192
+ built_bin_path = source_bin_path.gsub(/^#{bundle_into}\//, "")
193
+ built_bundle_bin_path = source_bundle_bin_path.gsub(/^#{bundle_into}\//, "")
194
+
195
+ # Rewrite the bundler config file to normalize paths:
196
+ bundle_config = File.read(bundle_config_file)
197
+ bundle_config = bundle_config.gsub(/#{bundle_into}/, "REPLACE_WITH_BUNDLE_DIR")
198
+ bundle_config = bundle_config.gsub(/\/bundle_bin/, "/#{built_bundle_bin_path}")
199
+ File.open(bundle_config_file, "w") { |f| f.puts bundle_config }
200
+
201
+ # REPLACE_WITH_BUNDLE_DIR is interpolated at post-install time, as that is
202
+ # the only time when the value is known since this is built as a relocatable
203
+ # RPM:
204
+ File.open("#{bundle_into}/bin/env.bash", "w") do |f|
205
+ f.puts <<-EOS
206
+ #!/bin/bash
207
+ export B2R_BUNDLE_DIR="REPLACE_WITH_BUNDLE_DIR"
208
+ export GEM_HOME="${B2R_BUNDLE_DIR}/#{built_gem_path}"
209
+ export GEM_PATH="${B2R_BUNDLE_DIR}/#{built_gem_path}"
210
+ export BUNDLE_GEMFILE="${B2R_BUNDLE_DIR}/Gemfile"
211
+ export PATH="${B2R_BUNDLE_DIR}/#{built_bundle_bin_path}:${B2R_BUNDLE_DIR}/#{built_bin_path}:$PATH"
212
+ EOS
213
+ end
214
+
215
+ ["ruby", "irb"].each do |bin|
216
+ File.open("#{bundle_into}/bin/#{bin}", "w") do |f|
217
+ f.puts <<-EOS
218
+ #!/bin/bash
219
+ . "REPLACE_WITH_BUNDLE_DIR/bin/env.bash"
220
+
221
+ /bin/env #{bin} $@
222
+ EOS
223
+ end
224
+
225
+ File.chmod(0755, "#{bundle_into}/bin/#{bin}")
226
+ end
227
+ end
228
+
229
+ def get_bundler_version
230
+ bundler_version = `bundle -v 2>&1`
231
+
232
+ if $? == 0
233
+ # Bundler version 1.0.15
234
+ if bundler_version =~ /^Bundler version (.*)$/
235
+ return $1.strip
236
+ end
237
+ end
238
+
239
+ raise Exceptions::BuildError, "Unable to determine existing bundler version. You can explicitly set this with the --bundler option or try running `bundle -v` yourself and confirming a version number is returned."
240
+ end
241
+
242
+ def build_rpm
243
+ puts "Building RPM."
244
+
245
+ exec_command("rpmbuild --define '_topdir #{@working_dir}/rpmbuild' --define '_prefix #{@prefix}' -ba #{@rpm_spec_file}")
246
+
247
+ # Move any RPMs we built into @save_rpm_to:
248
+ Dir["#{@working_dir}/rpmbuild/RPMS/*/*.rpm"].each do |src_path|
249
+ filename = File.basename(src_path)
250
+ dest_path = "#{@save_rpm_to}/#{filename}"
251
+ FileUtils.mv(src_path, dest_path, :force => true)
252
+
253
+ puts "Wrote RPM: #{dest_path}"
254
+ end
255
+ end
256
+
257
+ def changelog(comment = "bundle2rpm managed build")
258
+ date = Time.now.strftime("%a %b %e %Y")
259
+
260
+ entries = []
261
+ entries << "* #{date} #{@variable_map[:author][:value]} <#{@variable_map[:email][:value]}> #{@variable_map[:package_version][:value]}-#{@variable_map[:package_release][:value]}"
262
+ entries << "- #{comment[0..79]}\n"
263
+
264
+ return entries.join("\n")
265
+ end
266
+
267
+ def normalize_path(path)
268
+ return nil if path.nil? || path.empty?
269
+ return File.expand_path(path).gsub(/\/$/, "")
270
+ end
271
+
272
+ def exec_command(command, fatal = true)
273
+ output_buffer = []
274
+ puts "+ #{command}" if @verbose
275
+
276
+ IO.popen("#{command} 2>&1") do |output|
277
+ while line = output.gets do
278
+ if @verbose
279
+ puts "> #{line}"
280
+ else
281
+ output_buffer << "> #{line}"
282
+ end
283
+ end
284
+ end
285
+
286
+ if $? != 0
287
+ puts "+ #{command}" if !@verbose
288
+ puts output_buffer
289
+ raise Exceptions::CommandError, "Failure of previous command was fatal, stopping." if fatal
290
+ end
291
+ end
292
+
293
+ def setup_working_dir
294
+ working_dir = "#{@tmp_dir}/bundle2rpm-#{@variable_map[:package_name][:value]}-#{Time.now.to_i.to_s}"
295
+
296
+ # This is the root working directory:
297
+ FileUtils.mkdir(working_dir)
298
+
299
+ # This is the rpmbuild directory structure:
300
+ %w{ BUILD RPMS SOURCES SPECS SRPMS }.each do |d|
301
+ FileUtils.mkdir_p("#{working_dir}/rpmbuild/#{d}")
302
+ end
303
+
304
+ # This is where we'll deploy via `bundle install`:
305
+ FileUtils.mkdir("#{working_dir}/rpmbuild/SOURCES/#{@package_full_name}")
306
+
307
+ # This is our transient GEM_HOME:
308
+ FileUtils.mkdir("#{working_dir}/.gems")
309
+
310
+ return working_dir
311
+ end
312
+
313
+ def cleanup_working_dir
314
+ return if !@working_dir || !File.exists?(@working_dir)
315
+ FileUtils.rm_rf(@working_dir)
316
+ end
317
+
318
+ begin
319
+ main
320
+ rescue SystemExit, Interrupt
321
+ puts "Cancelling bundle2rpm operations at your request."
322
+ rescue Exceptions::ArgumentError => e
323
+ puts "#{e.class}: #{e.message}"
324
+ rescue Exceptions::BuildError => e
325
+ puts "#{e.class}: #{e.message}"
326
+ rescue Exceptions::CommandError => e
327
+ puts "#{e.class}: #{e.message}"
328
+ ensure
329
+ cleanup_working_dir unless @no_cleanup
330
+ end
331
+
332
+ __END__
333
+ Summary: bundle2rpm Package
334
+ Name: |package_name|
335
+ Version: |package_version|
336
+ Release: |package_release|
337
+ License: |license|
338
+ Group: Applications/RubyGems
339
+ URL: |url|
340
+ BuildRoot: %{_tmppath}/%{name}-root
341
+ Prefix: %(echo %{_prefix})
342
+ AutoProv: no
343
+
344
+ %description
345
+ This package was generated by bundle2rpm. It contains a deployed Gem bundle which
346
+ may contain one or more individual Ruby Gems.
347
+
348
+ %install
349
+ [ -d ${RPM_BUILD_ROOT} ] && rm -rf ${RPM_BUILD_ROOT}
350
+ install -d ${RPM_BUILD_ROOT}%{_prefix}
351
+ cp -R ${RPM_SOURCE_DIR}/* ${RPM_BUILD_ROOT}%{_prefix}
352
+
353
+ find ${RPM_BUILD_ROOT}%{_prefix} \( \( -type f -o -type l \) -printf '"%%p"\n' \) \
354
+ -o \( -type d -a -printf '%%%%dir "%%p"\n' \) \
355
+ | sed "s@${RPM_BUILD_ROOT}@@g" \
356
+ > bundle.lst
357
+
358
+ # Remove the first line, it's the prefix-dir which we do not
359
+ # want under rpm control:
360
+ sed -i 1d bundle.lst
361
+
362
+ %post
363
+ #!/bin/bash
364
+ bundle_dir="%{_prefix}/%{name}-%{version}-%{release}"
365
+ sed -i "s@REPLACE_WITH_BUNDLE_DIR@${bundle_dir}@g" ${bundle_dir}/.bundle/*
366
+ sed -i "s@REPLACE_WITH_BUNDLE_DIR@${bundle_dir}@g" ${bundle_dir}/bin/*
367
+
368
+ %clean
369
+ [ -d ${RPM_BUILD_ROOT} ] && rm -rf ${RPM_BUILD_ROOT}
370
+
371
+ %files -f bundle.lst
372
+ %defattr(-,|user|,|group|,-)
373
+
374
+ %changelog
375
+ |changelog|
@@ -0,0 +1,24 @@
1
+ spec = Gem::Specification.new do |s|
2
+ s.name = "bundle2rpm"
3
+ s.version = "1.0.1"
4
+ s.platform = Gem::Platform::RUBY
5
+ s.date = "2011-07-12"
6
+ s.rubyforge_project = "bundle2rpm"
7
+ s.summary = "Convert Bundler gem bundles into RPMs."
8
+ s.description = "Turns your gem bundles into RPMs!"
9
+
10
+ s.homepage = "http://loopforever.com"
11
+ s.authors = ["Matt Savona"]
12
+ s.email = "matt.savona@gmail.com"
13
+
14
+ s.has_rdoc = false
15
+
16
+ s.required_rubygems_version = ">= 1.3.6"
17
+
18
+ s.bindir = "bin"
19
+ s.require_paths = ["lib"]
20
+ s.executables << "bundle2rpm"
21
+ s.files = `git ls-files`.split("\n")
22
+
23
+ s.add_dependency("bundler", "~> 1.0.10")
24
+ end
data/lib/.gitstub ADDED
File without changes
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bundle2rpm
3
+ version: !ruby/object:Gem::Version
4
+ hash: 21
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 1
10
+ version: 1.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Matt Savona
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-07-12 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: bundler
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 1
32
+ - 0
33
+ - 10
34
+ version: 1.0.10
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ description: Turns your gem bundles into RPMs!
38
+ email: matt.savona@gmail.com
39
+ executables:
40
+ - bundle2rpm
41
+ extensions: []
42
+
43
+ extra_rdoc_files: []
44
+
45
+ files:
46
+ - .gitignore
47
+ - LICENSE
48
+ - README.md
49
+ - bin/bundle2rpm
50
+ - bundle2rpm.gemspec
51
+ - lib/.gitstub
52
+ has_rdoc: true
53
+ homepage: http://loopforever.com
54
+ licenses: []
55
+
56
+ post_install_message:
57
+ rdoc_options: []
58
+
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ hash: 3
67
+ segments:
68
+ - 0
69
+ version: "0"
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ hash: 23
76
+ segments:
77
+ - 1
78
+ - 3
79
+ - 6
80
+ version: 1.3.6
81
+ requirements: []
82
+
83
+ rubyforge_project: bundle2rpm
84
+ rubygems_version: 1.3.7
85
+ signing_key:
86
+ specification_version: 3
87
+ summary: Convert Bundler gem bundles into RPMs.
88
+ test_files: []
89
+