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 +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
|
+
|