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.
Files changed (43) hide show
  1. data/CHANGELIST +15 -0
  2. data/bin/fpm +2 -15
  3. data/lib/fpm.rb +5 -12
  4. data/lib/fpm/command.rb +323 -0
  5. data/lib/fpm/errors.rb +1 -0
  6. data/lib/fpm/namespace.rb +2 -11
  7. data/lib/fpm/package.rb +255 -100
  8. data/lib/fpm/package/deb.rb +367 -0
  9. data/lib/fpm/package/dir.rb +86 -0
  10. data/lib/fpm/package/gem.rb +162 -0
  11. data/lib/fpm/{source → package}/npm.rb +3 -3
  12. data/lib/fpm/package/pear.rb +41 -0
  13. data/lib/fpm/{target → package}/puppet.rb +1 -3
  14. data/lib/fpm/{source → package}/pyfpm/__init__.py +0 -0
  15. data/lib/fpm/package/pyfpm/__init__.pyc +0 -0
  16. data/lib/fpm/{source → package}/pyfpm/get_metadata.py +15 -3
  17. data/lib/fpm/package/pyfpm/get_metadata.pyc +0 -0
  18. data/lib/fpm/package/python.rb +125 -0
  19. data/lib/fpm/package/rpm.rb +132 -0
  20. data/lib/fpm/{target → package}/solaris.rb +3 -2
  21. data/lib/fpm/package/tar.rb +62 -0
  22. data/lib/fpm/util.rb +56 -7
  23. data/templates/deb.erb +12 -12
  24. data/templates/rpm.erb +32 -38
  25. data/templates/solaris.erb +1 -1
  26. metadata +119 -78
  27. data/lib/fpm/builder.rb +0 -220
  28. data/lib/fpm/flags.rb +0 -20
  29. data/lib/fpm/program.rb +0 -273
  30. data/lib/fpm/rubyfixes.rb +0 -11
  31. data/lib/fpm/source.rb +0 -155
  32. data/lib/fpm/source/dir.rb +0 -59
  33. data/lib/fpm/source/gem.rb +0 -162
  34. data/lib/fpm/source/python.rb +0 -137
  35. data/lib/fpm/source/rpm.rb +0 -28
  36. data/lib/fpm/source/tar.rb +0 -50
  37. data/lib/fpm/target/deb.rb +0 -184
  38. data/lib/fpm/target/rpm.rb +0 -68
  39. data/lib/rpm/header.rb +0 -89
  40. data/lib/rpm/lead.rb +0 -48
  41. data/lib/rpm/namespace.rb +0 -1
  42. data/lib/rpm/rpmfile.rb +0 -81
  43. 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