fpm 0.3.11 → 0.4.0pre1
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/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
|