fpm 0.3.11 → 0.4.0pre1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELIST +15 -0
- data/bin/fpm +2 -15
- data/lib/fpm.rb +5 -12
- data/lib/fpm/command.rb +323 -0
- data/lib/fpm/errors.rb +1 -0
- data/lib/fpm/namespace.rb +2 -11
- data/lib/fpm/package.rb +255 -100
- data/lib/fpm/package/deb.rb +367 -0
- data/lib/fpm/package/dir.rb +86 -0
- data/lib/fpm/package/gem.rb +162 -0
- data/lib/fpm/{source → package}/npm.rb +3 -3
- data/lib/fpm/package/pear.rb +41 -0
- data/lib/fpm/{target → package}/puppet.rb +1 -3
- data/lib/fpm/{source → package}/pyfpm/__init__.py +0 -0
- data/lib/fpm/package/pyfpm/__init__.pyc +0 -0
- data/lib/fpm/{source → package}/pyfpm/get_metadata.py +15 -3
- data/lib/fpm/package/pyfpm/get_metadata.pyc +0 -0
- data/lib/fpm/package/python.rb +125 -0
- data/lib/fpm/package/rpm.rb +132 -0
- data/lib/fpm/{target → package}/solaris.rb +3 -2
- data/lib/fpm/package/tar.rb +62 -0
- data/lib/fpm/util.rb +56 -7
- data/templates/deb.erb +12 -12
- data/templates/rpm.erb +32 -38
- data/templates/solaris.erb +1 -1
- metadata +119 -78
- data/lib/fpm/builder.rb +0 -220
- data/lib/fpm/flags.rb +0 -20
- data/lib/fpm/program.rb +0 -273
- data/lib/fpm/rubyfixes.rb +0 -11
- data/lib/fpm/source.rb +0 -155
- data/lib/fpm/source/dir.rb +0 -59
- data/lib/fpm/source/gem.rb +0 -162
- data/lib/fpm/source/python.rb +0 -137
- data/lib/fpm/source/rpm.rb +0 -28
- data/lib/fpm/source/tar.rb +0 -50
- data/lib/fpm/target/deb.rb +0 -184
- data/lib/fpm/target/rpm.rb +0 -68
- data/lib/rpm/header.rb +0 -89
- data/lib/rpm/lead.rb +0 -48
- data/lib/rpm/namespace.rb +0 -1
- data/lib/rpm/rpmfile.rb +0 -81
- data/lib/rpm/tag.rb +0 -304
@@ -0,0 +1,367 @@
|
|
1
|
+
require "erb"
|
2
|
+
require "fpm/namespace"
|
3
|
+
require "fpm/package"
|
4
|
+
require "fpm/errors"
|
5
|
+
require "fpm/util"
|
6
|
+
require "fileutils"
|
7
|
+
require "insist"
|
8
|
+
|
9
|
+
class FPM::Package::Deb < FPM::Package
|
10
|
+
# Map of what scripts are named.
|
11
|
+
SCRIPT_MAP = {
|
12
|
+
:before_install => "preinst",
|
13
|
+
:after_install => "postinst",
|
14
|
+
:before_remove => "prerm",
|
15
|
+
:after_remove => "postrm",
|
16
|
+
}
|
17
|
+
|
18
|
+
option "--ignore-iteration-in-dependencies", :flag,
|
19
|
+
"For '=' (equal) dependencies, allow iterations on the specified " \
|
20
|
+
"version. Default is to be specific. This option allows the same " \
|
21
|
+
"version of a package but any iteration is permitted"
|
22
|
+
|
23
|
+
option "--pre-depends", "DEPENDENCY",
|
24
|
+
"Add DEPENDENCY as a Pre-Depends" do |val|
|
25
|
+
@pre_depends ||= []
|
26
|
+
@pre_depends << dep
|
27
|
+
end
|
28
|
+
|
29
|
+
# Take care about the case when we want custom control file but still use fpm ...
|
30
|
+
option "--custom-control", "FILEPATH",
|
31
|
+
"Custom version of the Debian control file." do |control|
|
32
|
+
File.expand_path(control)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Add custom debconf config file
|
36
|
+
option "--config", "SCRIPTPATH",
|
37
|
+
"Add SCRIPTPATH as debconf config file." do |config|
|
38
|
+
File.expand_path(config)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Add custom debconf templates file
|
42
|
+
option "--templates", "FILEPATH",
|
43
|
+
"Add FILEPATH as debconf templates file." do |templates|
|
44
|
+
File.expand_path(templates)
|
45
|
+
end
|
46
|
+
|
47
|
+
option "--installed-size", "KILOBYTES",
|
48
|
+
"The installed size, in kilobytes. If omitted, this will be calculated " \
|
49
|
+
"automatically" do |value|
|
50
|
+
value.to_i
|
51
|
+
end
|
52
|
+
|
53
|
+
# Return the architecture. This will default to native if not yet set.
|
54
|
+
# It will also try to use dpkg and 'uname -m' to figure out what the
|
55
|
+
# native 'architecture' value should be.
|
56
|
+
def architecture
|
57
|
+
if @architecture.nil? or @architecture == "native"
|
58
|
+
# Default architecture should be 'native' which we'll need to ask the
|
59
|
+
# system about.
|
60
|
+
if program_in_path?("dpkg")
|
61
|
+
@architecture = %x{dpkg --print-architecture 2> /dev/null}.chomp
|
62
|
+
@architecture = %{uname -m}.chomp if $?.exitstatus != 0
|
63
|
+
else
|
64
|
+
@architecture = %x{uname -m}.chomp
|
65
|
+
end
|
66
|
+
elsif @architecture == "x86_64"
|
67
|
+
# Debian calls x86_64 "amd64"
|
68
|
+
@architecture = "amd64"
|
69
|
+
end
|
70
|
+
|
71
|
+
return @architecture
|
72
|
+
end # def architecture
|
73
|
+
|
74
|
+
# Get the name of this package. See also FPM::Package#name
|
75
|
+
#
|
76
|
+
# This accessor actually modifies the name if it has some invalid or unwise
|
77
|
+
# characters.
|
78
|
+
def name
|
79
|
+
if @name =~ /[A-Z]/
|
80
|
+
@logger.warn("Debian tools (dpkg/apt) don't do well with packages " \
|
81
|
+
"that use capital letters in the name. In some cases it will " \
|
82
|
+
"automatically downcase them, in others it will not. It is confusing." \
|
83
|
+
"Best to not use any capital letters at all.",
|
84
|
+
:oldname => @name, :fixedname => @name.downcase)
|
85
|
+
@name = @name.downcase
|
86
|
+
end
|
87
|
+
|
88
|
+
if @name.include?("_")
|
89
|
+
@logger.info("Package name includes underscores, converting to dashes",
|
90
|
+
:name => @name)
|
91
|
+
@name = @name.gsub(/[_]/, "-")
|
92
|
+
end
|
93
|
+
|
94
|
+
return @name
|
95
|
+
end # def name
|
96
|
+
|
97
|
+
def input(input_path)
|
98
|
+
extract_info(input_path)
|
99
|
+
extract_files(input_path)
|
100
|
+
end # def input
|
101
|
+
|
102
|
+
def extract_info(package)
|
103
|
+
with(build_path("control")) do |path|
|
104
|
+
FileUtils.mkdir(path) if !File.directory?(path)
|
105
|
+
# Unpack the control tarball
|
106
|
+
safesystem("ar p #{package} control.tar.gz | tar -zxf - -C #{path}")
|
107
|
+
|
108
|
+
control = File.read(File.join(path, "control"))
|
109
|
+
|
110
|
+
parse = lambda do |field|
|
111
|
+
value = control[/^#{field.capitalize}: .*/]
|
112
|
+
if value.nil?
|
113
|
+
return nil
|
114
|
+
else
|
115
|
+
value.split(": ",2).last
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Parse 'epoch:version-iteration' in the version string
|
120
|
+
version_re = /^(?:([0-9]+):)?(.+?)(?:-(.*))?$/
|
121
|
+
m = version_re.match(parse.call("Version"))
|
122
|
+
if !m
|
123
|
+
raise "Unsupported version string '#{parse.call("Version")}'"
|
124
|
+
end
|
125
|
+
self.epoch, self.version, self.iteration = m.captures
|
126
|
+
|
127
|
+
self.architecture = parse.call("Architecture")
|
128
|
+
self.category = parse.call("Section")
|
129
|
+
self.license = parse.call("License") || self.license
|
130
|
+
self.maintainer = parse.call("Maintainer")
|
131
|
+
self.name = parse.call("Package")
|
132
|
+
self.url = parse.call("Homepage")
|
133
|
+
self.vendor = parse.call("Vendor") || self.vendor
|
134
|
+
|
135
|
+
# The description field is a special flower, parse it that way.
|
136
|
+
# The description is the first line as a normal Description field, but also continues
|
137
|
+
# on future lines indented by one space, until the end of the file. Blank
|
138
|
+
# lines are marked as ' .'
|
139
|
+
description = control[/^Description: .*/m].split(": ", 2).last
|
140
|
+
self.description = description.gsub(/^ /, "").gsub(/^\.$/, "")
|
141
|
+
|
142
|
+
#self.config_files = config_files
|
143
|
+
|
144
|
+
self.dependencies += parse_depends(parse.call("Depends"))
|
145
|
+
end
|
146
|
+
end # def extract_info
|
147
|
+
|
148
|
+
# Parse a 'depends' line from a debian control file.
|
149
|
+
#
|
150
|
+
# The expected input 'data' should be everything after the 'Depends: ' string
|
151
|
+
#
|
152
|
+
# Example:
|
153
|
+
#
|
154
|
+
# parse_depends("foo (>= 3), bar (= 5), baz")
|
155
|
+
def parse_depends(data)
|
156
|
+
# parse dependencies. Debian dependencies come in one of two forms:
|
157
|
+
# * name
|
158
|
+
# * name (op version)
|
159
|
+
# They are all on one line, separated by ", "
|
160
|
+
|
161
|
+
dep_re = /^([^ ]+)(?: \(([>=<]+) ([^)]+)\))?$/
|
162
|
+
return data.split(/, */).collect do |dep|
|
163
|
+
m = dep_re.match(dep)
|
164
|
+
if m
|
165
|
+
name, op, version = m.captures
|
166
|
+
# deb uses ">>" and "<<" for greater and less than respectively.
|
167
|
+
# fpm wants just ">" and "<"
|
168
|
+
op = "<" if op == "<<"
|
169
|
+
op = ">" if op == ">>"
|
170
|
+
# this is the proper form of dependency
|
171
|
+
"#{name} #{op} #{version}"
|
172
|
+
else
|
173
|
+
# Assume normal form dependency, "name op version".
|
174
|
+
dep
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end # def parse_depends
|
178
|
+
|
179
|
+
def extract_files(package)
|
180
|
+
# unpack the data.tar.gz from the deb package into staging_path
|
181
|
+
safesystem("ar p #{package} data.tar.gz | tar -zxf - -C #{staging_path}")
|
182
|
+
end # def extract_files
|
183
|
+
|
184
|
+
def output(output_path)
|
185
|
+
insist { File.directory?(build_path) } == true
|
186
|
+
|
187
|
+
# create 'debian-binary' file, required to make a valid debian package
|
188
|
+
File.write(build_path("debian-binary"), "2.0")
|
189
|
+
|
190
|
+
write_control_tarball
|
191
|
+
|
192
|
+
# Tar up the staging_path and call it 'data.tar.gz'
|
193
|
+
datatar = build_path("data.tar.gz")
|
194
|
+
safesystem(tar_cmd, "-C", staging_path, "-zcf", datatar, ".")
|
195
|
+
|
196
|
+
# pack up the .deb, which is just an 'ar' archive with 3 files
|
197
|
+
# the 'debian-binary' file has to be first
|
198
|
+
with(File.expand_path(output_path)) do |output_path|
|
199
|
+
::Dir.chdir(build_path) do
|
200
|
+
safesystem("ar", "-qc", output_path, "debian-binary", "control.tar.gz", datatar)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
@logger.log("Created deb package", :path => output_path)
|
204
|
+
end # def output
|
205
|
+
|
206
|
+
def default_output
|
207
|
+
if iteration
|
208
|
+
"#{name}_#{version}-#{iteration}_#{architecture}.#{type}"
|
209
|
+
else
|
210
|
+
"#{name}_#{version}_#{architecture}.#{type}"
|
211
|
+
end
|
212
|
+
end # def default_output
|
213
|
+
|
214
|
+
def converted_from(origin)
|
215
|
+
self.dependencies = self.dependencies.collect do |dep|
|
216
|
+
fix_dependency(dep)
|
217
|
+
end.flatten
|
218
|
+
end # def converted_from
|
219
|
+
|
220
|
+
def fix_dependency(dep)
|
221
|
+
# Deb dependencies are: NAME (OP VERSION), like "zsh (> 3.0)"
|
222
|
+
# Convert anything that looks like 'NAME OP VERSION' to this format.
|
223
|
+
if dep =~ /[\(,\|]/
|
224
|
+
# Don't "fix" ones that could appear well formed already.
|
225
|
+
else
|
226
|
+
# Convert ones that appear to be 'name op version'
|
227
|
+
name, op, version = dep.split(/ +/)
|
228
|
+
if !version.nil?
|
229
|
+
# Convert strings 'foo >= bar' to 'foo (>= bar)'
|
230
|
+
dep = "#{name} (#{op} #{version})"
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
name_re = /^[^ \(]+/
|
235
|
+
name = dep[name_re]
|
236
|
+
if name =~ /[A-Z]/
|
237
|
+
@logger.warn("Downcasing dependency '#{name}' because deb packages " \
|
238
|
+
" don't work so good with uppercase names")
|
239
|
+
dep.gsub!(name_re) { |n| n.downcase }
|
240
|
+
end
|
241
|
+
|
242
|
+
if dep.include?("_")
|
243
|
+
@logger.warn("Replacing underscores with dashes in '#{dep}' because " \
|
244
|
+
"debs don't like underscores")
|
245
|
+
dep.gsub!("_", "-")
|
246
|
+
end
|
247
|
+
|
248
|
+
# Convert gem ~> X.Y.Z to '>= X.Y.Z' and << X.Y+1.0
|
249
|
+
if dep =~ /\(~>/
|
250
|
+
name, version = dep.gsub(/[()~>]/, "").split(/ +/)[0..1]
|
251
|
+
nextversion = version.split(".").collect { |v| v.to_i }
|
252
|
+
l = nextversion.length
|
253
|
+
nextversion[l-2] += 1
|
254
|
+
nextversion[l-1] = 0
|
255
|
+
nextversion = nextversion.join(".")
|
256
|
+
return ["#{name} (>= #{version})", "#{name} (<< #{nextversion})"]
|
257
|
+
elsif (m = dep.match(/(\S+)\s+\(= (.+)\)/))
|
258
|
+
# Convert 'foo (= x)' to 'foo (>= x)' and 'foo (<< x+1)'
|
259
|
+
name, version = m[1..2]
|
260
|
+
nextversion = version.split('.').collect { |v| v.to_i }
|
261
|
+
nextversion[-1] += 1
|
262
|
+
nextversion = nextversion.join(".")
|
263
|
+
return ["#{name} (>= #{version})", "#{name} (<< #{nextversion})"]
|
264
|
+
else
|
265
|
+
# otherwise the dep is probably fine
|
266
|
+
return dep
|
267
|
+
end
|
268
|
+
end # def fix_dependency
|
269
|
+
|
270
|
+
def control_path(path=nil)
|
271
|
+
@control_path ||= build_path("control")
|
272
|
+
FileUtils.mkdir(@control_path) if !File.directory?(@control_path)
|
273
|
+
|
274
|
+
if path.nil?
|
275
|
+
return @control_path
|
276
|
+
else
|
277
|
+
return File.join(@control_path, path)
|
278
|
+
end
|
279
|
+
end # def control_path
|
280
|
+
|
281
|
+
def write_control_tarball
|
282
|
+
# Use custom Debian control file when given ...
|
283
|
+
write_control # write the control file
|
284
|
+
write_scripts # write the maintainer scripts
|
285
|
+
write_conffiles # write the conffiles
|
286
|
+
write_debconf # write the debconf files
|
287
|
+
|
288
|
+
# Make the control.tar.gz
|
289
|
+
with(build_path("control.tar.gz")) do |controltar|
|
290
|
+
@logger.info("Creating", :path => controltar, :from => control_path)
|
291
|
+
safesystem(tar_cmd, "--numeric-owner", "--owner=0", "--group=0", "-zcf",
|
292
|
+
controltar, "-C", control_path, ".")
|
293
|
+
end
|
294
|
+
|
295
|
+
@logger.debug("Removing no longer needed control dir", :path => control_path)
|
296
|
+
ensure
|
297
|
+
FileUtils.rm_r(control_path)
|
298
|
+
end # def write_control_tarball
|
299
|
+
|
300
|
+
def write_control
|
301
|
+
# calculate installed-size if necessary:
|
302
|
+
if attributes[:deb_installed_size].nil?
|
303
|
+
@logger.info("No deb_installed_size set, calculating now.")
|
304
|
+
total = 0
|
305
|
+
Find.find(staging_path) do |path|
|
306
|
+
stat = File.stat(path)
|
307
|
+
next if stat.directory?
|
308
|
+
total += stat.size
|
309
|
+
end
|
310
|
+
# Per http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Installed-Size
|
311
|
+
# "The disk space is given as the integer value of the estimated
|
312
|
+
# installed size in bytes, divided by 1024 and rounded up."
|
313
|
+
attributes[:deb_installed_size] = total / 1024
|
314
|
+
end
|
315
|
+
|
316
|
+
# Write the control file
|
317
|
+
with(control_path("control")) do |control|
|
318
|
+
if attributes["deb-custom-control"]
|
319
|
+
@logger.debug("Using '#{attributes["deb-custom-control"]}' template for the control file")
|
320
|
+
control_data = File.read(attributes["deb-custom-control"])
|
321
|
+
else
|
322
|
+
@logger.debug("Using 'deb.erb' template for the control file")
|
323
|
+
control_data = template("deb.erb").result(binding)
|
324
|
+
end
|
325
|
+
|
326
|
+
@logger.debug("Writing control file", :path => control)
|
327
|
+
File.write(control, control_data)
|
328
|
+
end
|
329
|
+
end # def write_control
|
330
|
+
|
331
|
+
# Write out the maintainer scripts
|
332
|
+
#
|
333
|
+
# SCRIPT_MAP is a map from the package ':after_install' to debian
|
334
|
+
# 'post_install' names
|
335
|
+
def write_scripts
|
336
|
+
SCRIPT_MAP.each do |script, filename|
|
337
|
+
next if scripts[script].nil?
|
338
|
+
|
339
|
+
with(control_path(filename)) do |path|
|
340
|
+
FileUtils.cp(scripts[script], filename)
|
341
|
+
# deb maintainer scripts are required to be executable
|
342
|
+
File.chmod(0755, filename)
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end # def write_scripts
|
346
|
+
|
347
|
+
def write_conffiles
|
348
|
+
File.open(control_path("conffiles"), "w") do |out|
|
349
|
+
# 'config_files' comes from FPM::Package and is usually set with
|
350
|
+
# FPM::Command's --config-files flag
|
351
|
+
config_files.each { |cf| out.puts(cf) }
|
352
|
+
end
|
353
|
+
end # def write_conffiles
|
354
|
+
|
355
|
+
def write_debconf
|
356
|
+
if attributes[:deb_config]
|
357
|
+
FileUtils.cp(attributes[:deb_config], control_path("config"))
|
358
|
+
File.chmod(0755, control_path("config"))
|
359
|
+
end
|
360
|
+
|
361
|
+
if attributes[:deb_templates]
|
362
|
+
FileUtils.cp(attributes[:deb_templates], control_path("templates"))
|
363
|
+
File.chmod(0755, control_path("templates"))
|
364
|
+
end
|
365
|
+
end # def write_debconf
|
366
|
+
public(:input, :output)
|
367
|
+
end # class FPM::Target::Deb
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require "fpm/package"
|
2
|
+
require "backports"
|
3
|
+
require "fileutils"
|
4
|
+
require "find"
|
5
|
+
require "socket"
|
6
|
+
|
7
|
+
class FPM::Package::Dir < FPM::Package
|
8
|
+
private
|
9
|
+
|
10
|
+
def input(path)
|
11
|
+
@logger.debug("Copying", :input => path)
|
12
|
+
@logger["method"] = "input"
|
13
|
+
::Dir.chdir(@attributes[:chdir] || ".") do
|
14
|
+
if @attributes[:prefix]
|
15
|
+
clone(path, File.join(staging_path, @attributes[:prefix]))
|
16
|
+
else
|
17
|
+
clone(path, staging_path)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Set some defaults. This is useful because other package types
|
22
|
+
# can include license data from themselves (rpms, gems, etc),
|
23
|
+
# but to make sure a simple dir -> rpm works without having
|
24
|
+
# to specify a license.
|
25
|
+
self.license = "unknown"
|
26
|
+
self.vendor = [ENV["USER"], Socket.gethostname].join("@")
|
27
|
+
ensure
|
28
|
+
@logger.remove("method")
|
29
|
+
end # def input
|
30
|
+
|
31
|
+
def output(dir)
|
32
|
+
dir = File.expand_path(dir)
|
33
|
+
::Dir.chdir(staging_path) do
|
34
|
+
@logger["method"] = "output"
|
35
|
+
clone(".", dir)
|
36
|
+
end
|
37
|
+
ensure
|
38
|
+
@logger.remove("method")
|
39
|
+
end # def output
|
40
|
+
|
41
|
+
private
|
42
|
+
# Copy a file or directory to a destination
|
43
|
+
#
|
44
|
+
# This is special because it respects the full path of the source.
|
45
|
+
# Aditionally, hardlinks will be used instead of copies.
|
46
|
+
#
|
47
|
+
# Example:
|
48
|
+
#
|
49
|
+
# clone("/tmp/hello/world", "/tmp/example")
|
50
|
+
#
|
51
|
+
# The above will copy, recursively, /tmp/hello/world into
|
52
|
+
# /tmp/example/hello/world
|
53
|
+
def clone(source, destination)
|
54
|
+
# Copy all files from 'path' into staging_path
|
55
|
+
|
56
|
+
Find.find(source).each do |file|
|
57
|
+
next if source == file && File.directory?(file) # ignore the directory itself
|
58
|
+
target = File.join(destination, file)
|
59
|
+
copy(file, target)
|
60
|
+
end
|
61
|
+
end # def clone
|
62
|
+
|
63
|
+
def copy(source, destination)
|
64
|
+
directory = File.dirname(destination)
|
65
|
+
if !File.directory?(directory)
|
66
|
+
FileUtils.mkdir_p(directory)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Create a directory if this path is a directory
|
70
|
+
if File.directory?(source)
|
71
|
+
@logger.debug("Creating", :directory => destination)
|
72
|
+
FileUtils.mkdir(destination)
|
73
|
+
else
|
74
|
+
# Otherwise try copying the file.
|
75
|
+
@logger.debug("Copying", :source => source, :destination => destination)
|
76
|
+
begin
|
77
|
+
File.link(source, destination)
|
78
|
+
rescue Errno::EXDEV
|
79
|
+
# Hardlink attempt failed, copy it instead
|
80
|
+
FileUtils.copy(source, destination)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end # def copy
|
84
|
+
|
85
|
+
public(:input, :output)
|
86
|
+
end # class FPM::Package::Dir
|