fpm-aeppert 1.6.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.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELIST +661 -0
  3. data/CONTRIBUTORS +26 -0
  4. data/LICENSE +21 -0
  5. data/bin/fpm +8 -0
  6. data/lib/fpm.rb +20 -0
  7. data/lib/fpm/command.rb +648 -0
  8. data/lib/fpm/errors.rb +4 -0
  9. data/lib/fpm/namespace.rb +4 -0
  10. data/lib/fpm/package.rb +539 -0
  11. data/lib/fpm/package/apk.rb +510 -0
  12. data/lib/fpm/package/cpan.rb +405 -0
  13. data/lib/fpm/package/deb.rb +935 -0
  14. data/lib/fpm/package/dir.rb +221 -0
  15. data/lib/fpm/package/empty.rb +13 -0
  16. data/lib/fpm/package/freebsd.rb +147 -0
  17. data/lib/fpm/package/gem.rb +243 -0
  18. data/lib/fpm/package/npm.rb +120 -0
  19. data/lib/fpm/package/osxpkg.rb +165 -0
  20. data/lib/fpm/package/p5p.rb +124 -0
  21. data/lib/fpm/package/pacman.rb +403 -0
  22. data/lib/fpm/package/pear.rb +117 -0
  23. data/lib/fpm/package/pkgin.rb +35 -0
  24. data/lib/fpm/package/pleaserun.rb +63 -0
  25. data/lib/fpm/package/puppet.rb +120 -0
  26. data/lib/fpm/package/pyfpm/__init__.py +1 -0
  27. data/lib/fpm/package/pyfpm/get_metadata.py +104 -0
  28. data/lib/fpm/package/python.rb +318 -0
  29. data/lib/fpm/package/rpm.rb +593 -0
  30. data/lib/fpm/package/sh.rb +69 -0
  31. data/lib/fpm/package/solaris.rb +95 -0
  32. data/lib/fpm/package/tar.rb +86 -0
  33. data/lib/fpm/package/virtualenv.rb +164 -0
  34. data/lib/fpm/package/zip.rb +63 -0
  35. data/lib/fpm/rake_task.rb +60 -0
  36. data/lib/fpm/util.rb +358 -0
  37. data/lib/fpm/util/tar_writer.rb +80 -0
  38. data/lib/fpm/version.rb +3 -0
  39. data/templates/deb.erb +52 -0
  40. data/templates/deb/changelog.erb +5 -0
  41. data/templates/deb/ldconfig.sh.erb +13 -0
  42. data/templates/deb/postinst_upgrade.sh.erb +62 -0
  43. data/templates/deb/postrm_upgrade.sh.erb +46 -0
  44. data/templates/deb/preinst_upgrade.sh.erb +41 -0
  45. data/templates/deb/prerm_upgrade.sh.erb +39 -0
  46. data/templates/osxpkg.erb +11 -0
  47. data/templates/p5p_metadata.erb +12 -0
  48. data/templates/pacman.erb +47 -0
  49. data/templates/pacman/INSTALL.erb +41 -0
  50. data/templates/pleaserun/generate-cleanup.sh +17 -0
  51. data/templates/pleaserun/install-path.sh +17 -0
  52. data/templates/pleaserun/install.sh +117 -0
  53. data/templates/pleaserun/scripts/after-install.sh +4 -0
  54. data/templates/pleaserun/scripts/before-remove.sh +12 -0
  55. data/templates/puppet/package.pp.erb +34 -0
  56. data/templates/puppet/package/remove.pp.erb +13 -0
  57. data/templates/rpm.erb +260 -0
  58. data/templates/rpm/filesystem_list +14514 -0
  59. data/templates/sh.erb +369 -0
  60. data/templates/solaris.erb +15 -0
  61. metadata +322 -0
data/lib/fpm/errors.rb ADDED
@@ -0,0 +1,4 @@
1
+ require "fpm/namespace"
2
+
3
+ # Raised if a package is configured in an unsupported way
4
+ class FPM::InvalidPackageConfiguration < StandardError; end
@@ -0,0 +1,4 @@
1
+ # The FPM namespace
2
+ module FPM
3
+ class Package; end
4
+ end
@@ -0,0 +1,539 @@
1
+ require "fpm/namespace" # local
2
+ require "fpm/util" # local
3
+ require "pathname" # stdlib
4
+ require "find"
5
+ require "tmpdir" # stdlib
6
+ require "backports" # gem 'backports'
7
+ require "socket" # stdlib, for Socket.gethostname
8
+ require "shellwords" # stdlib, for Shellwords.escape
9
+ require "erb" # stdlib, for template processing
10
+ require "cabin" # gem "cabin"
11
+ require "stud/temporary"
12
+
13
+ # This class is the parent of all packages.
14
+ # If you want to implement an FPM package type, you'll inherit from this.
15
+ class FPM::Package
16
+ include FPM::Util
17
+ include Cabin::Inspectable
18
+
19
+ # This class is raised if there's something wrong with a setting in the package.
20
+ class InvalidArgument < StandardError; end
21
+
22
+ # This class is raised when a file already exists when trying to write.
23
+ class FileAlreadyExists < StandardError
24
+ # Get a human-readable error message
25
+ def to_s
26
+ return "File already exists, refusing to continue: #{super}"
27
+ end # def to_s
28
+ end # class FileAlreadyExists
29
+
30
+ # This class is raised when you try to output a package to a path
31
+ # whose containing directory does not exist.
32
+ class ParentDirectoryMissing < StandardError
33
+ def to_s
34
+ return "Parent directory does not exist: #{File.dirname(super)} - cannot write to #{super}"
35
+ end # def to_s
36
+ end # class ParentDirectoryMissing
37
+
38
+ # The name of this package
39
+ attr_accessor :name
40
+
41
+ # The version of this package (the upstream version)
42
+ attr_accessor :version
43
+
44
+ # The epoch version of this package
45
+ # This is used most when an upstream package changes it's versioning
46
+ # style so standard comparisions wouldn't work.
47
+ attr_accessor :epoch
48
+
49
+ # The iteration of this package.
50
+ # Debian calls this 'release' and is the last '-NUMBER' in the version
51
+ # RedHat has this as 'Release' in the .spec file
52
+ # FreeBSD calls this 'PORTREVISION'
53
+ #
54
+ # Iteration can be nil. If nil, the fpm package implementation is expected
55
+ # to handle any default value that should be instead.
56
+ attr_accessor :iteration
57
+
58
+ # Who maintains this package? This could be the upstream author
59
+ # or the package maintainer. You pick.
60
+ attr_accessor :maintainer
61
+
62
+ # A identifier representing the vendor. Any string is fine.
63
+ # This is usually who produced the software.
64
+ attr_accessor :vendor
65
+
66
+ # URL for this package.
67
+ # Could be the homepage. Could be the download url. You pick.
68
+ attr_accessor :url
69
+
70
+ # The category of this package.
71
+ # RedHat calls this 'Group'
72
+ # Debian calls this 'Section'
73
+ # FreeBSD would put this in /usr/ports/<category>/...
74
+ attr_accessor :category
75
+
76
+ # A identifier representing the license. Any string is fine.
77
+ attr_accessor :license
78
+
79
+ # What architecture is this package for?
80
+ attr_accessor :architecture
81
+
82
+ # Array of dependencies.
83
+ attr_accessor :dependencies
84
+
85
+ # Array of things this package provides.
86
+ # (Not all packages support this)
87
+ attr_accessor :provides
88
+
89
+ # Array of things this package conflicts with.
90
+ # (Not all packages support this)
91
+ attr_accessor :conflicts
92
+
93
+ # Array of things this package replaces.
94
+ # (Not all packages support this)
95
+ attr_accessor :replaces
96
+
97
+ # a summary or description of the package
98
+ attr_accessor :description
99
+
100
+ # hash of scripts for maintainer/package scripts (postinstall, etc)
101
+ #
102
+ # The keys are :before_install, etc
103
+ # The values are the text to use in the script.
104
+ attr_accessor :scripts
105
+
106
+ # Array of configuration files
107
+ attr_accessor :config_files
108
+
109
+ attr_accessor :directories
110
+
111
+ # Any other attributes specific to this package.
112
+ # This is where you'd put rpm, deb, or other specific attributes.
113
+ attr_accessor :attributes
114
+
115
+ attr_accessor :attrs
116
+
117
+ private
118
+
119
+ def initialize
120
+ # Attributes for this specific package
121
+ @attributes = {}
122
+
123
+ # Reference
124
+ # http://www.debian.org/doc/manuals/maint-guide/first.en.html
125
+ # http://wiki.debian.org/DeveloperConfiguration
126
+ # https://github.com/jordansissel/fpm/issues/37
127
+ if ENV.include?("DEBEMAIL") and ENV.include?("DEBFULLNAME")
128
+ # Use DEBEMAIL and DEBFULLNAME as the default maintainer if available.
129
+ @maintainer = "#{ENV["DEBFULLNAME"]} <#{ENV["DEBEMAIL"]}>"
130
+ else
131
+ # TODO(sissel): Maybe support using 'git config' for a default as well?
132
+ # git config --get user.name, etc can be useful.
133
+ #
134
+ # Otherwise default to user@currenthost
135
+ @maintainer = "<#{ENV["USER"]}@#{Socket.gethostname}>"
136
+ end
137
+
138
+ # Set attribute defaults based on flags
139
+ # This allows you to define command line options with default values
140
+ # that also are obeyed if fpm is used programmatically.
141
+ self.class.default_attributes do |attribute, value|
142
+ attributes[attribute] = value
143
+ end
144
+
145
+ @name = nil
146
+ @architecture = "native"
147
+ @description = "no description given"
148
+ @version = nil
149
+ @epoch = nil
150
+ @iteration = nil
151
+ @url = nil
152
+ @category = "default"
153
+ @license = "unknown"
154
+ @vendor = "none"
155
+
156
+ # Iterate over all the options and set defaults
157
+ if self.class.respond_to?(:declared_options)
158
+ self.class.declared_options.each do |option|
159
+ option.attribute_name.tap do |attr|
160
+ # clamp makes option attributes available as accessor methods
161
+ # do --foo-bar is available as 'foo_bar'
162
+ # make these available as package attributes.
163
+ attr = "#{attr}?" if !respond_to?(attr)
164
+ input.attributes[attr.to_sym] = send(attr) if respond_to?(attr)
165
+ end
166
+ end
167
+ end
168
+
169
+ @provides = []
170
+ @conflicts = []
171
+ @replaces = []
172
+ @dependencies = []
173
+ @scripts = {}
174
+ @config_files = []
175
+ @directories = []
176
+ @attrs = {}
177
+
178
+ staging_path
179
+ build_path
180
+ end # def initialize
181
+
182
+ # Get the 'type' for this instance.
183
+ #
184
+ # For FPM::Package::ABC, this returns 'abc'
185
+ def type
186
+ self.class.type
187
+ end # def type
188
+
189
+ # Convert this package to a new package type
190
+ def convert(klass)
191
+ logger.info("Converting #{self.type} to #{klass.type}")
192
+
193
+ exclude
194
+
195
+ pkg = klass.new
196
+ pkg.cleanup_staging # purge any directories that may have been created by klass.new
197
+
198
+ # copy other bits
199
+ ivars = [
200
+ :@architecture, :@category, :@config_files, :@conflicts,
201
+ :@dependencies, :@description, :@epoch, :@iteration, :@license, :@maintainer,
202
+ :@name, :@provides, :@replaces, :@scripts, :@url, :@vendor, :@version,
203
+ :@directories, :@staging_path, :@attrs
204
+ ]
205
+ ivars.each do |ivar|
206
+ #logger.debug("Copying ivar", :ivar => ivar, :value => instance_variable_get(ivar),
207
+ #:from => self.type, :to => pkg.type)
208
+ pkg.instance_variable_set(ivar, instance_variable_get(ivar))
209
+ end
210
+
211
+ # Attributes are special! We do not want to remove the default values of
212
+ # the destination package type unless their value is specified on the
213
+ # source package object.
214
+ pkg.attributes.merge!(self.attributes)
215
+
216
+ pkg.converted_from(self.class)
217
+ return pkg
218
+ end # def convert
219
+
220
+ # This method is invoked on a package when it has been converted to a new
221
+ # package format. The purpose of this method is to do any extra conversion
222
+ # steps, like translating dependency conditions, etc.
223
+ def converted_from(origin)
224
+ # nothing to do by default. Subclasses may implement this.
225
+ # See the RPM package class for an example.
226
+ end # def converted
227
+
228
+ # Add a new source to this package.
229
+ # The exact behavior depends on the kind of package being managed.
230
+ #
231
+ # For instance:
232
+ #
233
+ # * for FPM::Package::Dir, << expects a path to a directory or files.
234
+ # * for FPM::Package::RPM, << expects a path to an rpm.
235
+ #
236
+ # The idea is that you can keep pumping in new things to a package
237
+ # for later conversion or output.
238
+ #
239
+ # Implementations are expected to put files relevant to the 'input' in the
240
+ # staging_path
241
+ def input(thing_to_input)
242
+ raise NotImplementedError.new("#{self.class.name} does not yet support " \
243
+ "reading #{self.type} packages")
244
+ end # def input
245
+
246
+ # Output this package to the given path.
247
+ def output(path)
248
+ raise NotImplementedError.new("#{self.class.name} does not yet support " \
249
+ "creating #{self.type} packages")
250
+ end # def output
251
+
252
+ def staging_path(path=nil)
253
+ @staging_path ||= Stud::Temporary.directory("package-#{type}-staging")
254
+
255
+ if path.nil?
256
+ return @staging_path
257
+ else
258
+ return File.join(@staging_path, path)
259
+ end
260
+ end # def staging_path
261
+
262
+ def build_path(path=nil)
263
+ @build_path ||= Stud::Temporary.directory("package-#{type}-build")
264
+
265
+ if path.nil?
266
+ return @build_path
267
+ else
268
+ return File.join(@build_path, path)
269
+ end
270
+ end # def build_path
271
+
272
+ # Clean up any temporary storage used by this class.
273
+ def cleanup
274
+ cleanup_staging
275
+ cleanup_build
276
+ end # def cleanup
277
+
278
+ def cleanup_staging
279
+ if File.directory?(staging_path)
280
+ logger.debug("Cleaning up staging path", :path => staging_path)
281
+ FileUtils.rm_r(staging_path)
282
+ end
283
+ end # def cleanup_staging
284
+
285
+ def cleanup_build
286
+ if File.directory?(build_path)
287
+ logger.debug("Cleaning up build path", :path => build_path)
288
+ FileUtils.rm_r(build_path)
289
+ end
290
+ end # def cleanup_build
291
+
292
+ # List all files in the staging_path
293
+ #
294
+ # The paths will all be relative to staging_path and will not include that
295
+ # path.
296
+ #
297
+ # This method will emit 'leaf' paths. Files, symlinks, and other file-like
298
+ # things are emitted. Intermediate directories are ignored, but
299
+ # empty directories are emitted.
300
+ def files
301
+ is_leaf = lambda do |path|
302
+ # True if this is a file/symlink/etc, but not a plain directory
303
+ return true if !(File.directory?(path) and !File.symlink?(path))
304
+ # Empty directories are leafs as well.
305
+ return true if ::Dir.entries(path).sort == [".", ".."]
306
+ # False otherwise (non-empty directory, etc)
307
+ return false
308
+ end # is_leaf
309
+
310
+ # Find all leaf-like paths (files, symlink, empty directories, etc)
311
+ # Also trim the leading path such that '#{staging_path}/' is removed from
312
+ # the path before returning.
313
+ #
314
+ # Wrapping Find.find in an Enumerator is required for sane operation in ruby 1.8.7,
315
+ # but requires the 'backports' gem (which is used in other places in fpm)
316
+ return Enumerator.new { |y| Find.find(staging_path) { |path| y << path } } \
317
+ .select { |path| path != staging_path } \
318
+ .select { |path| is_leaf.call(path) } \
319
+ .collect { |path| path[staging_path.length + 1.. -1] }
320
+ end # def files
321
+
322
+ def template_dir
323
+ File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "templates"))
324
+ end
325
+
326
+ def template(path)
327
+ template_path = File.join(template_dir, path)
328
+ template_code = File.read(template_path)
329
+ logger.info("Reading template", :path => template_path)
330
+ erb = ERB.new(template_code, nil, "-")
331
+ erb.filename = template_path
332
+ return erb
333
+ end # def template
334
+
335
+ #######################################
336
+ # The following methods are provided to
337
+ # easily override particular substitut-
338
+ # ions performed by to_s for subclasses
339
+ #######################################
340
+ def to_s_arch; architecture.to_s; end
341
+ def to_s_name; name.to_s; end
342
+ def to_s_fullversion; iteration ? "#{version}-#{iteration}" : "#{version}"; end
343
+ def to_s_version; version.to_s; end
344
+ def to_s_iteration; iteration.to_s; end
345
+ def to_s_epoch; epoch.to_s; end
346
+ def to_s_type; type.to_s; end
347
+ def to_s_extension; type.to_s; end
348
+ #######################################
349
+
350
+ def to_s(fmt=nil)
351
+ fmt = "NAME.EXTENSION" if fmt.nil?
352
+ return fmt.gsub("ARCH", to_s_arch) \
353
+ .gsub("NAME", to_s_name) \
354
+ .gsub("FULLVERSION", to_s_fullversion) \
355
+ .gsub("VERSION", to_s_version) \
356
+ .gsub("ITERATION", to_s_iteration) \
357
+ .gsub("EPOCH", to_s_epoch) \
358
+ .gsub("TYPE", to_s_type) \
359
+ .gsub("EXTENSION", to_s_extension)
360
+ end # def to_s
361
+
362
+ def edit_file(path)
363
+ editor = ENV['FPM_EDITOR'] || ENV['EDITOR'] || 'vi'
364
+ logger.info("Launching editor", :file => path)
365
+ command = "#{editor} #{Shellwords.escape(path)}"
366
+ system("#{editor} #{Shellwords.escape(path)}")
367
+ if !$?.success?
368
+ raise ProcessFailed.new("'#{editor}' failed (exit code " \
369
+ "#{$?.exitstatus}) Full command was: " \
370
+ "#{command}");
371
+ end
372
+
373
+ if File.size(path) == 0
374
+ raise "Empty file after editing: #{path.inspect}"
375
+ end
376
+ end # def edit_file
377
+
378
+ # This method removes excluded files from the staging_path. Subclasses can
379
+ # remove the files during the input phase rather than deleting them here
380
+ def exclude
381
+ return if attributes[:excludes].nil?
382
+
383
+ if @attributes.include?(:prefix)
384
+ installdir = staging_path(@attributes[:prefix])
385
+ else
386
+ installdir = staging_path
387
+ end
388
+
389
+ Find.find(staging_path) do |path|
390
+ match_path = path.sub("#{installdir.chomp('/')}/", '')
391
+
392
+ attributes[:excludes].each do |wildcard|
393
+ logger.debug("Checking path against wildcard", :path => match_path, :wildcard => wildcard)
394
+
395
+ if File.fnmatch(wildcard, match_path)
396
+ logger.info("Removing excluded path", :path => match_path, :matches => wildcard)
397
+ FileUtils.rm_r(path)
398
+ Find.prune
399
+ break
400
+ end
401
+ end
402
+ end
403
+ end # def exclude
404
+
405
+
406
+ class << self
407
+ # This method is invoked when subclass occurs.
408
+ #
409
+ # Lets us track all known FPM::Package subclasses
410
+ def inherited(klass)
411
+ @subclasses ||= {}
412
+ @subclasses[klass.name.gsub(/.*:/, "").downcase] = klass
413
+ end # def self.inherited
414
+
415
+ # Get a list of all known package subclasses
416
+ def types
417
+ return @subclasses
418
+ end # def self.types
419
+
420
+ # This allows packages to define flags for the fpm command line
421
+ def option(flag, param, help, options={}, &block)
422
+ @options ||= []
423
+ if !flag.is_a?(Array)
424
+ flag = [flag]
425
+ end
426
+
427
+ if param == :flag
428
+ # Automatically make 'flag' (boolean) options tunable with '--[no-]...'
429
+ flag = flag.collect { |f| "--[no-]#{type}-#{f.gsub(/^--/, "")}" }
430
+ else
431
+ flag = flag.collect { |f| "--#{type}-#{f.gsub(/^--/, "")}" }
432
+ end
433
+
434
+ help = "(#{type} only) #{help}"
435
+ @options << [flag, param, help, options, block]
436
+ end # def options
437
+
438
+ # Apply the options for this package on the clamp command
439
+ #
440
+ # Package flags become attributes '{type}-flag'
441
+ #
442
+ # So if you have:
443
+ #
444
+ # class Foo < FPM::Package
445
+ # option "--bar-baz" ...
446
+ # end
447
+ #
448
+ # The attribute value for --foo-bar-baz will be :foo_bar_baz"
449
+ def apply_options(clampcommand)
450
+ @options ||= []
451
+ @options.each do |args|
452
+ flag, param, help, options, block = args
453
+ clampcommand.option(flag, param, help, options, &block)
454
+ end
455
+ end # def apply_options
456
+
457
+ def default_attributes(&block)
458
+ return if @options.nil?
459
+ @options.each do |flag, param, help, options, _block|
460
+ attr = flag.first.gsub(/^-+/, "").gsub(/-/, "_").gsub("[no_]", "")
461
+ attr += "?" if param == :flag
462
+ yield attr.to_sym, options[:default]
463
+ end
464
+ end # def default_attributes
465
+
466
+ # Get the type of this package class.
467
+ #
468
+ # For "Foo::Bar::BAZ" this will return "baz"
469
+ def type
470
+ self.name.split(':').last.downcase
471
+ end # def self.type
472
+ end # class << self
473
+
474
+ # Get the version of this package
475
+ def version
476
+ if instance_variable_defined?(:@version) && !@version.nil?
477
+ return @version
478
+ elsif attributes[:version_given?]
479
+ # 'version_given?' will be true in cases where the
480
+ # fpm command-line tool has been given '-v' or '--version' settings
481
+ # We do this check because the default version is "1.0"
482
+ # on the fpm command line.
483
+ return attributes.fetch(:version)
484
+ end
485
+
486
+ # No version yet, nil.
487
+ return nil
488
+ end # def version
489
+
490
+ # Does this package have the given script?
491
+ def script?(name)
492
+ return scripts.include?(name)
493
+ end # def script?
494
+
495
+ # Get the contents of the script by a given name.
496
+ #
497
+ # If template_scripts? is set in attributes (often by the --template-scripts
498
+ # flag), then apply it as an ERB template.
499
+ def script(script_name)
500
+ if attributes[:template_scripts?]
501
+ erb = ERB.new(scripts[script_name], nil, "-")
502
+ # TODO(sissel): find the original file name for the file.
503
+ erb.filename = "script(#{script_name})"
504
+ return erb.result(binding)
505
+ else
506
+ return scripts[script_name]
507
+ end
508
+ end # def script
509
+
510
+ def output_check(output_path)
511
+ if !File.directory?(File.dirname(output_path))
512
+ raise ParentDirectoryMissing.new(output_path)
513
+ end
514
+ if File.file?(output_path)
515
+ if attributes[:force?]
516
+ logger.warn("Force flag given. Overwriting package at #{output_path}")
517
+ File.delete(output_path)
518
+ else
519
+ raise FileAlreadyExists.new(output_path)
520
+ end
521
+ end
522
+ end # def output_path
523
+
524
+ def provides=(value)
525
+ if !value.is_a?(Array)
526
+ @provides = [value]
527
+ else
528
+ @provides = value
529
+ end
530
+ end
531
+
532
+ # General public API
533
+ public(:type, :initialize, :convert, :input, :output, :to_s, :cleanup, :files,
534
+ :version, :script, :provides=)
535
+
536
+ # Package internal public api
537
+ public(:cleanup_staging, :cleanup_build, :staging_path, :converted_from,
538
+ :edit_file, :build_path)
539
+ end # class FPM::Package