fpm-itchio 1.4.0

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