tpkg 1.16.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +18 -0
- data/bin/cpan2tpkg +348 -0
- data/bin/gem2tpkg +445 -0
- data/bin/tpkg +560 -0
- data/lib/tpkg.rb +3966 -0
- data/lib/tpkg/deployer.rb +220 -0
- data/lib/tpkg/metadata.rb +436 -0
- data/lib/tpkg/thread_pool.rb +108 -0
- data/lib/tpkg/versiontype.rb +84 -0
- metadata +85 -0
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
|
+
|