tpkg 1.16.2

Sign up to get free protection for your applications and to get access to all the features.
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
+