tpkg 1.16.2

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.
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require 'rake/gempackagetask'
2
+ spec = Gem::Specification.new do |s|
3
+ s.name = 'tpkg'
4
+ s.summary = 'tpkg Application Packaging & Deployment'
5
+ s.add_dependency('facter')
6
+ s.add_dependency('net-ssh')
7
+ s.version = '1.16.2'
8
+ s.authors = ['Darren Dao', 'Jason Heiss']
9
+ s.email = 'tpkg-users@lists.sourceforge.net'
10
+ s.homepage = 'http://tpkg.sourceforge.net'
11
+ s.rubyforge_project = 'tpkg'
12
+ s.platform = Gem::Platform::RUBY
13
+ s.required_ruby_version = '>=1.8'
14
+ s.files = Dir['**/**']
15
+ s.executables = [ 'tpkg', 'cpan2tpkg', 'gem2tpkg' ]
16
+ end
17
+ Rake::GemPackageTask.new(spec).define
18
+
data/bin/cpan2tpkg ADDED
@@ -0,0 +1,348 @@
1
+ #!/home/t/bin/perl
2
+
3
+ use warnings;
4
+ use strict;
5
+
6
+ use Getopt::Long;
7
+ use CPAN;
8
+ use Config;
9
+ # According to the Changes file for EU::I this is the earliest version
10
+ # that supports all of the ExtUtils::Installed->new() parameters we use.
11
+ use ExtUtils::Installed 1.41_03;
12
+ use File::Temp qw(tempdir);
13
+ use File::Find; # find
14
+ use File::Path; # mkpath
15
+ use File::Basename; # basename, dirname
16
+ use File::Copy; # copy
17
+ use File::Spec; # catdir, catfile, etc.
18
+ use Storable qw(dclone);
19
+
20
+ sub usage
21
+ {
22
+ die <<EOF;
23
+ Usage: cpan2tpkg
24
+ [--version version]
25
+ Version of module to install
26
+ [--package-version version]
27
+ Package version to use in tpkg, defaults to 1
28
+ [--extra-deps foo,1.0,1.9999,bar,4.5,4.5,blah,,,]
29
+ Extra dependencies to add to the package
30
+ [--native-deps foo,1.0,1.9999,bar,4.5,4.5,blah,,,]
31
+ Native dependencies to add to the package
32
+ [--help]
33
+ Show this message
34
+
35
+ All options can be shortened to anything that's unique.
36
+ EOF
37
+ }
38
+
39
+ # FIXME: we're not actually using $modver, we're just letting CPAN installed the latest available version
40
+ my $modver;
41
+ my $pkgver = 1;
42
+ my %extradeps = ();
43
+ my $extradepsopts = '';
44
+ my %nativedeps = ();
45
+ my $nativedepsopts = '';
46
+ my $help;
47
+
48
+ my $getopt = GetOptions(
49
+ 'version=s' => \$modver,
50
+ 'package-version=s' => \$pkgver,
51
+ 'extra-deps=s' => \$extradepsopts,
52
+ 'native-deps=s' => \$nativedepsopts,
53
+ 'help' => \$help);
54
+
55
+ my $module = shift @ARGV;
56
+
57
+ usage() if (!$getopt);
58
+ usage() if ($help);
59
+ usage() if (!$module);
60
+
61
+ my @extradepsopts = split(/,/, $extradepsopts);
62
+ while (scalar @extradepsopts)
63
+ {
64
+ my $dep = shift @extradepsopts;
65
+ # These are optional, shift will return undef if they aren't present
66
+ # and we'll handle that properly when creating tpkg.xml
67
+ my $depminver = shift @extradepsopts;
68
+ my $depmaxver = shift @extradepsopts;
69
+ $extradeps{$dep} = {};
70
+ $extradeps{$dep}{minimum_version} = $depminver;
71
+ $extradeps{$dep}{maximum_version} = $depmaxver;
72
+ }
73
+ my @nativedepsopts = split(/,/, $nativedepsopts);
74
+ while (scalar @nativedepsopts)
75
+ {
76
+ my $dep = shift @nativedepsopts;
77
+ # These are optional, shift will return undef if they aren't present
78
+ # and we'll handle that properly when creating tpkg.xml
79
+ my $depminver = shift @nativedepsopts;
80
+ my $depmaxver = shift @nativedepsopts;
81
+ $nativedeps{$dep} = {};
82
+ $nativedeps{$dep}{minimum_version} = $depminver;
83
+ $nativedeps{$dep}{maximum_version} = $depmaxver;
84
+ }
85
+
86
+ # FIXME, pause for confirmation?
87
+ printf "Running under $^X, version %vd\n", $^V;
88
+ # Getting the major and minor versions out of $^X looks like it should be
89
+ # more straighforward according to the documentation, but the structure of
90
+ # $^X is different between Perl 5.8 and 5.10 and despite a few hours of
91
+ # trying I'm not smart enough to sort it out.
92
+ # Example $] from perl 5.8.8: 5.008008
93
+ my ($major, my $minorpatch) = split(/\./, $]);
94
+ my ($minor, $patch) = unpack('A3A3', $minorpatch);
95
+ # Force conversion to number so that 010 becomes 10, etc.
96
+ my $majornum = $major + 0;
97
+ my $minornum = $minor + 0;
98
+ my $majorminor = "$majornum$minornum";
99
+ my $majordotminor = "$majornum.$minornum";
100
+
101
+ # FIXME: distribute MyConfig.pm?
102
+ # MyConfig.pm needs all sorts of OS-specific stuff (paths to make, gzip,
103
+ # etc.) so distributing it would be a pain. For now we don't, and thus
104
+ # force the user to do their own CPAN configuration. In Perl 5.10
105
+ # that's fairly painless.
106
+
107
+ my $modobj = CPAN::Shell->expand('Module', $module);
108
+ if (!$modobj)
109
+ {
110
+ die "Unable to find $module or something went wrong\n";
111
+ }
112
+
113
+ # Set install location to temp directory
114
+ # There are two installers that modules might use to install themselves
115
+ # (ExtUtils::MakeMaker or Module::Build), so we have to set two different
116
+ # options to cover both possiblities.
117
+ my $workdir = tempdir(CLEANUP => 1);
118
+ print "Working directory: $workdir\n";
119
+ # http://perldoc.perl.org/ExtUtils/MakeMaker.html#DESTDIR
120
+ CPAN::Shell->o('conf', 'makepl_arg', "DESTDIR=$workdir");
121
+ # http://perldoc.perl.org/Module/Build.html#INSTALL-PATHS
122
+ CPAN::Shell->o('conf', 'mbuildpl_arg', "--destdir=$workdir");
123
+
124
+ # FIXME: desired?
125
+ #CPAN::Shell->o('conf', 'prerequisites_policy', "ask");
126
+
127
+ # Install the module
128
+ $modobj->install;
129
+
130
+ # It is not nearly as straightforward as one might wish to get
131
+ # ExtUtils::Installed to inspect only a specified directory structure and not
132
+ # include system library directories. EU::I wants to look at a couple of
133
+ # system directories from $Config, any directories in the PERL5LIB environment
134
+ # variable, and any extra directories you pass it. You can work around the
135
+ # system directories from $Config by passing EU::I your own modified $Config
136
+ # via the config_override parameter. You can also empty out $ENV[PERL5LIB].
137
+ # And that just leaves it searching the extra directories you pass it via the
138
+ # extra_libs parameter. To further complicate matters EU::I searches the
139
+ # directories in @INC when asked for the version of an installed module, but
140
+ # allows you to customize that via the inc_override parameter.
141
+ my @incwork;
142
+ foreach my $incdir (@INC)
143
+ {
144
+ push(@incwork, File::Spec->catdir($workdir, $incdir));
145
+ }
146
+ @incwork = sort(@incwork);
147
+ # Lie to ExtUtils::Installed about the two directories from %Config it
148
+ # cares about so that it only searches our working directory and no
149
+ # system directories.
150
+ my %myconfig = %{dclone(\%Config)};
151
+ $myconfig{archlibexp} = File::Spec->catdir($workdir, $Config{archlibexp});
152
+ $myconfig{sitearchexp} = File::Spec->catdir($workdir, $Config{sitearchexp});
153
+ # EU::I searches PERL5LIB as well, so clear that out
154
+ $ENV{PERL5LIB} = '';
155
+ my $extinst = ExtUtils::Installed->new(
156
+ config_override=>\%myconfig,
157
+ inc_override=>\@incwork,
158
+ extra_libs=>\@incwork);
159
+
160
+ print "Installed modules to be packaged (except Perl):\n";
161
+ print join(', ', $extinst->modules), "\n";
162
+
163
+ # Inspect temp directory and package each installed module
164
+ foreach my $name ($extinst->modules)
165
+ {
166
+ next if ($name eq 'Perl');
167
+ print "Packaging $name\n";
168
+ my $dashname = $name;
169
+ $dashname =~ s/::/-/g;
170
+ my $ver = $extinst->version($name);
171
+ print "Module version is $ver\n";
172
+ my %deps = ();
173
+ my $mod = CPAN::Shell->expand('Module', $name);
174
+ # Looks like newer versions of CPAN.pm have a CPAN::Module::distribution()
175
+ # method. What I'm using here is a little hacky but works, so I
176
+ # don't think it is worth forcing users to use a newer version.
177
+ my $dist = CPAN::Shell->expand('Distribution', $mod->{RO}{CPAN_FILE});
178
+ # The docs say you have to call make before prereq_pm is available
179
+ $dist->make;
180
+ # If there are any pre-reqs capture those
181
+ if ($dist->prereq_pm)
182
+ {
183
+ # Older versions return a basic {module name => min version} structure
184
+ # Newer versions seperate build-time dependencies (build_requires)
185
+ # from run-time dependencies (requires), each of which has the
186
+ # {name=>minver} format.
187
+ my %prereqs = %{$dist->prereq_pm};
188
+ if (exists $prereqs{requires})
189
+ {
190
+ # If we got back the newer format we want to pull out the run-time
191
+ # requirements. If there are no run-time requirements then
192
+ # $prereqs{requires} will be undef.
193
+ if ($prereqs{requires})
194
+ {
195
+ %prereqs = %{$prereqs{requires}};
196
+ }
197
+ else
198
+ {
199
+ %prereqs = ();
200
+ }
201
+ }
202
+ foreach my $dep (keys %prereqs)
203
+ {
204
+ # Skip dependencies on core modules
205
+ my $depmod = CPAN::Shell->expand('Module', $dep);
206
+ # This is a bit of an indirect way to identify core modules
207
+ # but the only way I can figure out. Core stuff gets
208
+ # installed with perl into /home/t/perl-version, CPAN
209
+ # modules into /home/t/lib/perl5/site_perl. The "d" in
210
+ # dslip_status has "s" as one of it's possible values,
211
+ # meaning "Standard, supplied with Perl 5" according to the
212
+ # docs. However, that doesn't seem to be set reliably.
213
+ if ($depmod->inst_file =~ m,/home/t/perl,)
214
+ {
215
+ print "Prereq $dep is a core module, skipping\n";
216
+ next;
217
+ }
218
+
219
+ my $dashdep = $dep;
220
+ $dashdep =~ s/::/-/g;
221
+ $deps{$dashdep} = {};
222
+ if ($prereqs{$dep} ne '0')
223
+ {
224
+ $deps{$dashdep}{minimum_version} = $prereqs{$dep};
225
+ }
226
+ }
227
+ }
228
+ my $tpkgdir = tempdir(CLEANUP =>1);
229
+ print "Packaging into $tpkgdir\n";
230
+ mkdir("$tpkgdir/root");
231
+ my $nativefile;
232
+ foreach my $packlistfile ($extinst->files($name))
233
+ {
234
+ chomp($packlistfile);
235
+ # Chop off $workdir
236
+ $packlistfile =~ s/^$workdir//;
237
+ # Make directory tree in $tpkgdir
238
+ mkpath("$tpkgdir/root/" . dirname($packlistfile));
239
+ # Copy file
240
+ copy("$workdir/$packlistfile", "$tpkgdir/root/$packlistfile");
241
+ if ($packlistfile =~ quotemeta($Config{archname}))
242
+ {
243
+ $nativefile = 1;
244
+ }
245
+ }
246
+ # Create tpkg.xml
247
+ open(my $xmlfh, '>', "$tpkgdir/tpkg.xml") or die;
248
+ print $xmlfh '<?xml version="1.0" encoding="UTF-8"?>', "\n";
249
+ print $xmlfh '<!DOCTYPE tpkg SYSTEM "http://tpkg.sourceforge.net/tpkg-1.0.dtd">', "\n";
250
+ print $xmlfh '<tpkg>', "\n";
251
+ print $xmlfh " <name>cpan-perl$majorminor-$dashname</name>", "\n";
252
+ print $xmlfh " <version>$ver</version>", "\n";
253
+ print $xmlfh " <package_version>$pkgver</package_version>", "\n";
254
+ print $xmlfh ' <maintainer>cpan2tpkg</maintainer>', "\n";
255
+ # If the package has native code then it needs to be flagged as
256
+ # specific to the OS and architecture
257
+ if ($nativefile)
258
+ {
259
+ my $os;
260
+ my $arch;
261
+ open(my $tpkgqefh, '-|', 'tpkg --qe') or die;
262
+ while(my $tpkgqeline = <$tpkgqefh>)
263
+ {
264
+ chomp($tpkgqeline);
265
+ if ($tpkgqeline =~ /^Operating System: (.*)/)
266
+ {
267
+ $os = $1;
268
+ }
269
+ elsif ($tpkgqeline =~ /^Architecture: (.*)/)
270
+ {
271
+ $arch = $1;
272
+ }
273
+ }
274
+ close($tpkgqefh);
275
+ if (!$os || !$arch)
276
+ {
277
+ die "Unable to read OS and architecture from tpkg --qe";
278
+ }
279
+ # Packages built on Red Hat should work on CentOS and
280
+ # vice-versa
281
+ if ($os =~ /RedHat-(.*)/)
282
+ {
283
+ $os = $os . ",CentOS-$1";
284
+ }
285
+ elsif ($os =~ /CentOS-(.*)/)
286
+ {
287
+ $os = $os . ",RedHat-$1";
288
+ }
289
+ print $xmlfh " <operatingsystem>$os</operatingsystem>", "\n";
290
+ print $xmlfh " <architecture>$arch</architecture>", "\n";
291
+ }
292
+ # Insert appropriate dependencies into the package
293
+ print $xmlfh ' <dependencies>', "\n";
294
+ foreach my $dep (keys %deps)
295
+ {
296
+ print $xmlfh ' <dependency>', "\n";
297
+ print $xmlfh " <name>cpan-perl$majorminor-$dep</name>", "\n";
298
+ if ($deps{$dep}{minimum_version})
299
+ {
300
+ print $xmlfh " <minimum_version>$deps{$dep}{minimum_version}</minimum_version>", "\n";
301
+ }
302
+ if ($deps{$dep}{maximum_version})
303
+ {
304
+ print $xmlfh " <maximum_version>$deps{$dep}{maximum_version}</maximum_version>", "\n";
305
+ }
306
+ print $xmlfh ' </dependency>', "\n";
307
+ }
308
+ foreach my $extradep (keys %extradeps)
309
+ {
310
+ print $xmlfh ' <dependency>', "\n";
311
+ print $xmlfh " <name>$extradep</name>", "\n";
312
+ if ($extradeps{$extradep}{minimum_version})
313
+ {
314
+ print $xmlfh " <minimum_version>$extradeps{$extradep}{minimum_version}</minimum_version>", "\n";
315
+ }
316
+ if ($extradeps{$extradep}{maximum_version})
317
+ {
318
+ print $xmlfh " <maximum_version>$extradeps{$extradep}{maximum_version}</maximum_version>", "\n";
319
+ }
320
+ print $xmlfh ' </dependency>', "\n";
321
+ }
322
+ foreach my $nativedep (keys %nativedeps)
323
+ {
324
+ print $xmlfh ' <dependency>', "\n";
325
+ print $xmlfh " <name>$nativedep</name>", "\n";
326
+ if ($nativedeps{$nativedep}{minimum_version})
327
+ {
328
+ print $xmlfh " <minimum_version>$nativedeps{$nativedep}{minimum_version}</minimum_version>", "\n";
329
+ }
330
+ if ($nativedeps{$nativedep}{maximum_version})
331
+ {
332
+ print $xmlfh " <maximum_version>$nativedeps{$nativedep}{maximum_version}</maximum_version>", "\n";
333
+ }
334
+ print $xmlfh ' <native/>', "\n";
335
+ print $xmlfh ' </dependency>', "\n";
336
+ }
337
+ # Insert an appropriate dependency on Perl itself
338
+ print $xmlfh ' <dependency>', "\n";
339
+ print $xmlfh " <name>perl</name>", "\n";
340
+ print $xmlfh " <minimum_version>", sprintf("%vd", $^V), "</minimum_version>", "\n";
341
+ print $xmlfh " <maximum_version>$majordotminor.9999</maximum_version>", "\n";
342
+ print $xmlfh ' </dependency>', "\n";
343
+ print $xmlfh ' </dependencies>', "\n";
344
+ print $xmlfh '</tpkg>', "\n";
345
+ # Build package
346
+ system("tpkg --make $tpkgdir");
347
+ }
348
+
data/bin/gem2tpkg ADDED
@@ -0,0 +1,445 @@
1
+ #!/usr/bin/ruby -w
2
+
3
+ # Ensure we can find tpkg.rb
4
+ $:.unshift File.dirname(__FILE__)
5
+
6
+ require 'fileutils' # FileUtils.cp, rm, etc.
7
+ require 'tempfile' # Tempfile
8
+ require 'optparse'
9
+ require 'shellwords'
10
+ require 'tpkg'
11
+ require 'facter'
12
+
13
+ # Names of packages containing ruby and gems. Dependencies on these
14
+ # will be added to generated packages, so that if a user installs a gem
15
+ # then tpkg will pull in ruby and gems.
16
+ RUBYDEPS = ['ruby']
17
+ # T_RUBY_BASE = "/home/t/ruby"
18
+ T_RUBY_BASE = "#{Tpkg::DEFAULT_BASE}/#{RUBYDEPS.first}".freeze
19
+ DEFAULT_GEM_COMMAND = "#{T_RUBY_BASE}/bin/gem"
20
+
21
+ # Figure out rubygems library from ruby.
22
+ # There might be several versions so get the latest one. We're assuming that
23
+ # the version directories are named appropriately under lib/ruby/site_ruby.
24
+ versions = Dir.glob(File.join("#{T_RUBY_BASE}", "lib", "ruby", "site_ruby", "*"))
25
+ versions.sort!
26
+ DEFAULT_RUBYGEMS_PATH = versions[-1]
27
+
28
+ # Haven't found a Ruby method for creating temporary directories,
29
+ # so create a temporary file and replace it with a directory.
30
+ def tempdir(basename, tmpdir=Dir::tmpdir)
31
+ tmpfile = Tempfile.new(basename, tmpdir)
32
+ tmpdir = tmpfile.path
33
+ tmpfile.close!
34
+ Dir.mkdir(tmpdir)
35
+ tmpdir
36
+ end
37
+
38
+ # Handle command line arguments
39
+ @gems = nil
40
+ @gemver = nil
41
+ @pkgver = 1
42
+ @extradeps = {}
43
+ @nativedeps = {}
44
+ @installopts = []
45
+ @buildopts = []
46
+ @gemcmd = DEFAULT_GEM_COMMAND
47
+ @rubygemspath = nil
48
+
49
+ opts = OptionParser.new
50
+ opts.banner = 'Usage: gem2tpkg [options] GEMNAME1 GEMNAME2 ...'
51
+ opts.on('--version', '-v', '=VERSION', 'Version of gem to install') do |opt|
52
+ @gemver = "-v #{opt}"
53
+ end
54
+ opts.on('--package-version', '--pv', '=PKGVER', 'Package version to use in tpkg, defaults to 1') do |opt|
55
+ @pkgver = opt
56
+ end
57
+ opts.on('--extra-deps', '=EXTRADEPS', Array, 'Extra dependencies to add to the package') do |opt|
58
+ # Example: --extra-deps=foo,1.0,1.9999,bar,4.5,4.5,blah,,
59
+ while !opt.empty?
60
+ dep = opt.shift
61
+ # These are optional, shift will return nil if they aren't present
62
+ # and we'll handle that properly when creating tpkg.xml
63
+ depminver = opt.shift
64
+ depmaxver = opt.shift
65
+ @extradeps[dep] = {}
66
+ @extradeps[dep][:minimum_version] = depminver
67
+ @extradeps[dep][:maximum_version] = depmaxver
68
+ end
69
+ end
70
+ opts.on('--native-deps', '=NATIVEDEPS', Array, 'Native dependencies to add to the package') do |opt|
71
+ # Example: --native-deps=foo,1.0,1.9999,bar,4.5,4.5,blah,,
72
+ while !opt.empty?
73
+ dep = opt.shift
74
+ # These are optional, shift will return nil if they aren't present
75
+ # and we'll handle that properly when creating tpkg.xml
76
+ depminver = opt.shift
77
+ depmaxver = opt.shift
78
+ @nativedeps[dep] = {}
79
+ @nativedeps[dep][:minimum_version] = depminver
80
+ @nativedeps[dep][:maximum_version] = depmaxver
81
+ end
82
+ end
83
+ opts.on('--install-options', '=INSTALLOPTS', 'Extra options to gem install') do |opt|
84
+ @installopts = Shellwords.shellwords(opt)
85
+ end
86
+ opts.on('--build-options', '=BUILDOPTS', 'Extra options to gem build') do |opt|
87
+ @buildopts = Shellwords.shellwords(opt)
88
+ end
89
+ opts.on('--gem-cmd', '=GEMCMD', 'Path to gem command') do |opt|
90
+ @gemcmd = opt
91
+ end
92
+ opts.on('--rubygems-path', '=PATH', 'Path to rubygems library') do |opt|
93
+ @rubygemspath = opt
94
+ end
95
+ opts.on_tail("-h", "--help", "Show this message") do
96
+ puts opts
97
+ exit
98
+ end
99
+
100
+ # we now allow user to specifies multiple gems at the end of the command line arguments
101
+ leftover = opts.parse(ARGV)
102
+ @gems = leftover
103
+ #if leftover.length == 1
104
+ # @gem = leftover.shift
105
+ #else
106
+ # puts opts
107
+ # exit
108
+ #end
109
+
110
+ # require the correct rubygems based on what the user specifies
111
+ @rubygemspath ||= DEFAULT_RUBYGEMS_PATH
112
+ $:.unshift @rubygemspath unless @rubygemspath.nil?
113
+ require 'rubygems'
114
+
115
+ # Create the directory we want gem to install into
116
+ @gemdir = tempdir('gem2tpkg')
117
+ ENV['GEM_HOME'] = @gemdir
118
+ ENV['GEM_PATH'] = @gemdir
119
+
120
+ # Install the gem
121
+ #geminst = [@gemcmd, 'install', @gems.join(" "), '--no-rdoc', '--no-ri']
122
+ geminst = [@gemcmd, 'install', '--no-rdoc', '--no-ri'] | @gems
123
+ if @gemver
124
+ geminst << @gemver
125
+ end
126
+ # Pass through any options the user specified, might be necessary for
127
+ # finding libraries, etc.
128
+ if !@installopts.empty?
129
+ geminst.concat(@installopts)
130
+ end
131
+ if !@buildopts.empty?
132
+ geminst << '--'
133
+ geminst.concat(@buildopts)
134
+ end
135
+
136
+ r = system(*geminst)
137
+ if !r
138
+ abort('gem install failed')
139
+ end
140
+
141
+ @already_packaged = []
142
+ def package(gem)
143
+ pkgfiles = []
144
+
145
+ return pkgfiles if @already_packaged.include?(gem)
146
+
147
+ puts "Packaging #{gem}"
148
+
149
+ gemsdir = nil
150
+ #globdirs = Dir.glob(File.join(@gemdir, 'gems', "#{gem}-*"))
151
+ globdirs = Dir.glob(File.join(@gemdir, 'gems', "#{gem}-[0-9]*"))
152
+ if globdirs.length == 1
153
+ gemsdir = globdirs[0]
154
+ else
155
+ # I don't expect this to happen in the real world, if it does we'll
156
+ # have to figure out a better mechanism of isolating the correct
157
+ # directory. I guess we could load all of the gemspecs in the
158
+ # specifications directory until we find one with the correct gem
159
+ # name in it.
160
+ abort File.join(@gemdir, 'gems', "#{gem}-*") + ' is abiguous'
161
+ end
162
+
163
+ # gemsdir will be something like /tmp/gem2tpkg.5833.0/gems/rake-0.8.4
164
+ # and the gemspec will be something like
165
+ # /tmp/gem2tpkg.5833.0/specifications/rake-0.8.4.gemspec. So we
166
+ # use the basename of gemsdir as the core of the gemspec
167
+ # filename.
168
+ gemspecfile = File.join(@gemdir,
169
+ 'specifications',
170
+ File.basename(gemsdir) + '.gemspec')
171
+
172
+ # Load the gemspec
173
+ gemspec = Gem::Specification.load(gemspecfile)
174
+
175
+ # Package any dependencies
176
+ # Example output:
177
+ # Gem rails-2.3.2
178
+ # rake (>= 0.8.3)
179
+ # activesupport (= 2.3.2)
180
+ # activerecord (= 2.3.2)
181
+ # actionpack (= 2.3.2)
182
+ # actionmailer (= 2.3.2)
183
+ # activeresource (= 2.3.2)
184
+ # Now that we're loading the gemspec we could read this out of the
185
+ # gemspec instead, but this is already written and working so I'm
186
+ # leaving it alone for now.
187
+ deps = {}
188
+ # gem dependency does something equivalent to /^gemname/ by default,
189
+ # i.e. matches against anything that starts with the specified name.
190
+ # We want to exactly match the gem in question. Turns out that if you
191
+ # pass something that looks like a regex then gem dependency will use
192
+ # that instead. See rubygems/commands/dependency_command.rb
193
+ IO.popen("#{@gemcmd} dependency /^#{Regexp.escape(gem)}$/") do |pipe|
194
+ pipe.each_line do |line|
195
+ next if line =~ /^Gem / # Skip header line
196
+ next if line =~ /^\s*$/ # Skip blank lines
197
+
198
+ # Skip development dependencies for now. We don't need them for
199
+ # running production code.
200
+ next if line =~ /, development\)$/
201
+ line.chomp!
202
+ # Example lines:
203
+ # These two are for the same installed state but different
204
+ # versions of gem:
205
+ # activesupport (= 2.3.2)
206
+ # activesupport (= 2.3.2, runtime)
207
+ depgem, operator, depver = line.split
208
+ operator.sub!(/^\(/, '')
209
+ depver.sub!(/\)$/, '')
210
+ depver.sub!(/,$/, '')
211
+ # Save the dependency info for tpkg.xml
212
+ # http://rubygems.org/read/chapter/16
213
+ deps[depgem] = {}
214
+ case operator
215
+ when '='
216
+ deps[depgem][:minimum_version] = depver
217
+ deps[depgem][:maximum_version] = depver
218
+ when '!='
219
+ # Not quite sure what to do with this one
220
+ abort "Unknown version dependency: #{line}"
221
+ when '>='
222
+ deps[depgem][:minimum_version] = depver
223
+ when '>'
224
+ # If the gem seems to follow the standard gem version convention
225
+ # we can achieve this by incrementing the build number
226
+ # http://rubygems.org/read/chapter/7
227
+ verparts = depver.split('.')
228
+ if depver =~ /^[\d\.]+/ && verparts.length <=3
229
+ verparts.map! { |v| v.to_i }
230
+ # Pad with zeros if necessary
231
+ (verparts.length...3).each { verparts << 0 }
232
+ verparts[2] += 1
233
+ minver = "#{verparts[0]}.#{verparts[1]}.#{verparts[3]}"
234
+ deps[depgem][:minimum_version] = minver
235
+ else
236
+ # Fall back to this, which isn't exactly correct but
237
+ # hopefully better than nothing
238
+ warn "Can't parse depver #{depver}, falling back to approximation"
239
+ deps[depgem][:minimum_version] = depver
240
+ end
241
+ when '<='
242
+ deps[depgem][:maximum_version] = depver
243
+ when '<'
244
+ # If the gem seems to follow the standard gem version convention
245
+ # we can achieve this by decrementing the build number
246
+ # http://rubygems.org/read/chapter/7
247
+ verparts = depver.split('.')
248
+ if depver =~ /^[\d\.]+/ && verparts.length <=3
249
+ verparts.map! { |v| v.to_i }
250
+ # Pad with zeros if necessary
251
+ (verparts.length...3).each { verparts << 0 }
252
+ if verparts[2] == 0
253
+ if verparts[1] == 0 # 1.0.0 -> 0.9999.9999
254
+ verparts[0] -= 1
255
+ verparts[1] = 9999
256
+ verparts[2] = 9999
257
+ else # 1.1.0 -> 1.0.9999
258
+ verparts[1] -= 1
259
+ verparts[2] = 9999
260
+ end
261
+ else # 1.1.1 -> 1.1.0
262
+ verparts[2] -= 1
263
+ end
264
+ maxver = "#{verparts[0]}.#{verparts[1]}.#{verparts[3]}"
265
+ deps[depgem][:maximum_version] = maxver
266
+ else
267
+ # Fall back to this, which isn't exactly correct but
268
+ # hopefully better than nothing
269
+ warn "Can't parse depver #{depver}, falling back to approximation"
270
+ deps[depgem][:maximum_version] = depver
271
+ end
272
+ when '~>'
273
+ # ~> 2.2 is equivalent to >= 2.2 and < 3
274
+ deps[depgem][:minimum_version] = depver
275
+ majorver = depver.split('.').first.to_i
276
+ maxver = "#{majorver}.9999.9999"
277
+ deps[depgem][:maximum_version] = maxver
278
+ end
279
+ # Package the dependency
280
+ pkgfiles.concat(package(depgem))
281
+ end
282
+ end
283
+ if !$?.success?
284
+ abort 'gem dependency failed'
285
+ end
286
+
287
+ # The directory where we make our package
288
+ pkgdir = tempdir('gem2tpkg')
289
+ pkgbasedir = File.join(pkgdir, "/root/#{T_RUBY_BASE}/lib/ruby/gems/1.8")
290
+ FileUtils.mkdir_p(pkgbasedir)
291
+ pkggemdir = File.join(pkgbasedir, 'gems')
292
+ FileUtils.mkdir_p(pkggemdir)
293
+ pkgspecdir = File.join(pkgbasedir, 'specifications')
294
+ FileUtils.mkdir_p(pkgspecdir)
295
+
296
+ # Copy the gems directory over
297
+ system("#{Tpkg::find_tar} -C #{File.dirname(gemsdir)} -cf - #{File.basename(gemsdir)} | #{Tpkg::find_tar} -C #{pkggemdir} -xpf -")
298
+
299
+ # If a gem flags any files as executables a wrapper script is
300
+ # created by gem for each executable in a bin directory at
301
+ # the top level of the gem directory structure. We want to
302
+ # copy those wrapper scripts into the package.
303
+ binfiles = []
304
+ gemspec.executables.each do |exec|
305
+ binfiles << File.join(@gemdir, 'bin', exec)
306
+ end
307
+ if !binfiles.empty?
308
+ bindir = "#{pkgdir}/root/#{T_RUBY_BASE}/bin"
309
+ FileUtils.mkdir_p(bindir)
310
+ binfiles.each do |binfile|
311
+ FileUtils.cp(binfile, bindir, :preserve => true)
312
+ end
313
+ end
314
+
315
+ # Copy over the gemspec file
316
+ FileUtils.cp(gemspecfile, pkgspecdir, :preserve => true)
317
+
318
+ # Add tpkg.xml
319
+ os = nil
320
+ arch = nil
321
+ File.open(File.join(pkgdir, 'tpkg.xml'), 'w') do |file|
322
+ file.puts '<?xml version="1.0" encoding="UTF-8"?>'
323
+ file.puts '<!DOCTYPE tpkg SYSTEM "http://tpkg.sourceforge.net/tpkg-1.0.dtd">'
324
+ file.puts '<tpkg>'
325
+ file.puts " <name>gem-#{gem}</name>"
326
+ file.puts " <version>#{gemspec.version.to_s}</version>"
327
+ file.puts " <package_version>#{@pkgver}</package_version>"
328
+ file.puts ' <maintainer>gem2tpkg</maintainer>'
329
+ # If the gemspec lists any extensions then the package has native
330
+ # code and needs to be flagged as specific to the OS and architecture
331
+ if gemspec.extensions && !gemspec.extensions.empty?
332
+ os = Tpkg::get_os
333
+ if os =~ /RedHat-(.*)/
334
+ os = os + ",CentOS-#{$1}"
335
+ elsif os =~ /CentOS-(.*)/
336
+ os = os + ",RedHat-#{$1}"
337
+ end
338
+ arch = Facter['hardwaremodel'].value
339
+ file.puts " <operatingsystem>#{os}</operatingsystem>"
340
+ file.puts " <architecture>#{arch}</architecture>"
341
+ end
342
+ if !deps.empty? ||
343
+ !@extradeps.empty? || !@nativedeps.empty? ||
344
+ !RUBYDEPS.empty?
345
+ file.puts ' <dependencies>'
346
+ deps.each do |depgem, depvers|
347
+ file.puts ' <dependency>'
348
+ file.puts " <name>gem-#{depgem}</name>"
349
+ if depvers[:minimum_version]
350
+ file.puts " <minimum_version>#{depvers[:minimum_version]}</minimum_version>"
351
+ end
352
+ if depvers[:maximum_version]
353
+ file.puts " <maximum_version>#{depvers[:maximum_version]}</maximum_version>"
354
+ end
355
+ file.puts ' </dependency>'
356
+ end
357
+ @extradeps.each do |extradep, depvers|
358
+ file.puts ' <dependency>'
359
+ file.puts " <name>#{extradep}</name>"
360
+ if depvers[:minimum_version]
361
+ file.puts " <minimum_version>#{depvers[:minimum_version]}</minimum_version>"
362
+ end
363
+ if depvers[:maximum_version]
364
+ file.puts " <maximum_version>#{depvers[:maximum_version]}</maximum_version>"
365
+ end
366
+ file.puts ' </dependency>'
367
+ end
368
+ @nativedeps.each do |nativedep, depvers|
369
+ file.puts ' <dependency>'
370
+ file.puts " <name>#{nativedep}</name>"
371
+ if depvers[:minimum_version]
372
+ file.puts " <minimum_version>#{depvers[:minimum_version]}</minimum_version>"
373
+ end
374
+ if depvers[:maximum_version]
375
+ file.puts " <maximum_version>#{depvers[:maximum_version]}</maximum_version>"
376
+ end
377
+ file.puts ' <native/>'
378
+ file.puts ' </dependency>'
379
+ end
380
+ RUBYDEPS.each do |rubydep|
381
+ file.puts ' <dependency>'
382
+ file.puts " <name>#{rubydep}</name>"
383
+ file.puts ' </dependency>'
384
+ end
385
+ file.puts ' </dependencies>'
386
+ end
387
+ file.puts '</tpkg>'
388
+ end
389
+
390
+ # Make package
391
+ pkgfile = Tpkg::make_package(pkgdir)
392
+ # If the package is OS-specific then rename the file to reflect that
393
+ if os
394
+ # Examples:
395
+ # FreeBSD-7 -> freebsd7
396
+ # RedHat-5 -> redhat5
397
+ # CentOS-5 -> redhat5
398
+ fileos = Tpkg::get_os.sub('CentOS', 'RedHat').downcase.sub('-', '')
399
+ newpkgfile = File.join(
400
+ File.dirname(pkgfile),
401
+ "#{File.basename(pkgfile, '.tpkg')}-#{fileos}-#{arch}.tpkg")
402
+ File.rename(pkgfile, newpkgfile)
403
+ pkgfile = newpkgfile
404
+ end
405
+ pkgfiles << pkgfile
406
+
407
+ # Cleanup
408
+ FileUtils.rm_rf(pkgdir)
409
+
410
+ @already_packaged << gem
411
+ pkgfiles
412
+ end
413
+
414
+ # Count the number of gems installed
415
+ gemcount = 0
416
+ IO.popen("#{@gemcmd} list") do |pipe|
417
+ pipe.each_line do |line|
418
+ next if line.include?('***') # Skip header line
419
+ next if line =~ /^\s*$/ # Skip blank lines
420
+ gemcount += 1
421
+ end
422
+ end
423
+ if gemcount == 0
424
+ abort "Zero gems installed according to gem list?"
425
+ end
426
+
427
+ pkgfiles = []
428
+ @gems.each do | gem |
429
+ pkgfiles |= package(gem)
430
+ end
431
+ # Make sure the package method made as many packages as there were gems
432
+ # installed
433
+ if pkgfiles.length != gemcount
434
+ abort "gem count (#{gemcount}) vs pkg count (#{pkgfiles.length}) mismatch"
435
+ end
436
+
437
+ # Tell the user what packages were created
438
+ puts 'The following packages were created:'
439
+ pkgfiles.each do |pkgfile|
440
+ puts pkgfile
441
+ end
442
+
443
+ # Cleanup
444
+ FileUtils.rm_rf(@gemdir)
445
+