fpm 0.3.11 → 0.4.0pre1

Sign up to get free protection for your applications and to get access to all the features.
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