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
@@ -0,0 +1,60 @@
1
+ require "fpm/namespace"
2
+ require "ostruct"
3
+ require "rake"
4
+ require "rake/tasklib"
5
+
6
+ class FPM::RakeTask < Rake::TaskLib
7
+ attr_reader :options
8
+
9
+ def initialize(package_name, opts = {}, &block)
10
+ @options = OpenStruct.new(:name => package_name.to_s)
11
+ @source, @target = opts.values_at(:source, :target).map(&:to_s)
12
+ @directory = File.expand_path(opts[:directory].to_s)
13
+
14
+ (@source.empty? || @target.empty? || options.name.empty?) &&
15
+ abort("Must specify package name, source and output")
16
+
17
+ desc "Package #{@name}" unless ::Rake.application.last_description
18
+
19
+ task(options.name) do |_, task_args|
20
+ block.call(*[options, task_args].first(block.arity)) if block_given?
21
+ abort("Must specify args") unless options.respond_to?(:args)
22
+ @args = options.delete_field(:args)
23
+ run_cli
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def parsed_options
30
+ options.to_h.map do |option, value|
31
+ opt = option.to_s.tr("_", "-")
32
+
33
+ case
34
+ when value.is_a?(String), value.is_a?(Symbol)
35
+ %W(--#{opt} #{value})
36
+ when value.is_a?(Array)
37
+ value.map { |v| %W(--#{opt} #{v}) }
38
+ when value.is_a?(TrueClass)
39
+ "--#{opt}"
40
+ when value.is_a?(FalseClass)
41
+ "--no-#{opt}"
42
+ else
43
+ fail TypeError, "Unexpected type: #{value.class}"
44
+ end
45
+ end
46
+ end
47
+
48
+ def run_cli
49
+ require "fpm"
50
+ require "fpm/command"
51
+
52
+ args = %W(-t #{@target} -s #{@source} -C #{@directory})
53
+ args << parsed_options
54
+ args << @args
55
+
56
+ args.flatten!.compact!
57
+
58
+ abort 'FPM failed!' unless FPM::Command.new("fpm").run(args) == 0
59
+ end
60
+ end
data/lib/fpm/util.rb ADDED
@@ -0,0 +1,358 @@
1
+ require "fpm/namespace"
2
+ require "childprocess"
3
+ require "ffi"
4
+
5
+ # Some utility functions
6
+ module FPM::Util
7
+ extend FFI::Library
8
+ ffi_lib FFI::Library::LIBC
9
+
10
+ # mknod is __xmknod in glibc a wrapper around mknod to handle
11
+ # various stat struct formats. See bits/stat.h in glibc source
12
+ begin
13
+ attach_function :mknod, :mknod, [:string, :uint, :ulong], :int
14
+ rescue FFI::NotFoundError
15
+ # glibc/io/xmknod.c int __xmknod (int vers, const char *path, mode_t mode, dev_t *dev)
16
+ attach_function :xmknod, :__xmknod, [:int, :string, :uint, :pointer], :int
17
+ end
18
+
19
+ # Raised if safesystem cannot find the program to run.
20
+ class ExecutableNotFound < StandardError; end
21
+
22
+ # Raised if a safesystem program exits nonzero
23
+ class ProcessFailed < StandardError; end
24
+
25
+ # Is the given program in the system's PATH?
26
+ def program_in_path?(program)
27
+ # return false if path is not set
28
+ return false unless ENV['PATH']
29
+ # Scan path to find the executable
30
+ # Do this to help the user get a better error message.
31
+ envpath = ENV["PATH"].split(":")
32
+ return envpath.select { |p| File.executable?(File.join(p, program)) }.any?
33
+ end # def program_in_path
34
+
35
+ def program_exists?(program)
36
+ # Scan path to find the executable
37
+ # Do this to help the user get a better error message.
38
+ return program_in_path?(program) if !program.include?("/")
39
+ return File.executable?(program)
40
+ end # def program_exists?
41
+
42
+ def default_shell
43
+ shell = ENV["SHELL"]
44
+ return "/bin/sh" if shell.nil? || shell.empty?
45
+ return shell
46
+ end
47
+
48
+ ############################################################################
49
+ # execmd([env,] cmd [,opts])
50
+ #
51
+ # Execute a command as a child process. The function allows to:
52
+ #
53
+ # - pass environment variables to child process,
54
+ # - communicate with stdin, stdout and stderr of the child process via pipes,
55
+ # - retrieve execution's status code.
56
+ #
57
+ # ---- EXAMPLE 1 (simple execution)
58
+ #
59
+ # if execmd(['which', 'python']) == 0
60
+ # p "Python is installed"
61
+ # end
62
+ #
63
+ # ---- EXAMPLE 2 (custom environment variables)
64
+ #
65
+ # execmd({:PYTHONPATH=>'/home/me/foo'}, [ 'python', '-m', 'bar'])
66
+ #
67
+ # ---- EXAMPLE 3 (communicating via stdin, stdout and stderr)
68
+ #
69
+ # script = <<PYTHON
70
+ # import sys
71
+ # sys.stdout.write("normal output\n")
72
+ # sys.stdout.write("narning or error\n")
73
+ # PYTHON
74
+ # status = execmd('python') do |stdin,stdout,stderr|
75
+ # stdin.write(script)
76
+ # stdin.close
77
+ # p "STDOUT: #{stdout.read}"
78
+ # p "STDERR: #{stderr.read}"
79
+ # end
80
+ # p "STATUS: #{status}"
81
+ #
82
+ # ---- EXAMPLE 4 (additional options)
83
+ #
84
+ # execmd(['which', 'python'], :process=>true, :stdin=>false, :stderr=>false) do |process,stdout|
85
+ # p = stdout.read.chomp
86
+ # process.wait
87
+ # if (x = process.exit_code) == 0
88
+ # p "PYTHON: #{p}"
89
+ # else
90
+ # p "ERROR: #{x}"
91
+ # end
92
+ # end
93
+ #
94
+ #
95
+ # OPTIONS:
96
+ #
97
+ # :process (default: false) -- pass process object as the first argument the to block,
98
+ # :stdin (default: true) -- pass stdin object of the child process to the block for writting,
99
+ # :stdout (default: true) -- pass stdout object of the child process to the block for reading,
100
+ # :stderr (default: true) -- pass stderr object of the child process to the block for reading,
101
+ #
102
+ def execmd(*args)
103
+ i = 0
104
+ if i < args.size
105
+ if args[i].kind_of?(Hash)
106
+ # args[0] may contain environment variables
107
+ env = args[i]
108
+ i += 1
109
+ else
110
+ env = Hash[]
111
+ end
112
+ end
113
+
114
+ if i < args.size
115
+ if args[i].kind_of?(Array)
116
+ args2 = args[i]
117
+ else
118
+ args2 = [ args[i] ]
119
+ end
120
+ program = args2[0]
121
+ i += 1
122
+ else
123
+ raise ArgumentError.new("missing argument: cmd")
124
+ end
125
+
126
+ if i < args.size
127
+ if args[i].kind_of?(Hash)
128
+ opts = Hash[args[i].map {|k,v| [k.to_sym, v]} ]
129
+ i += 1
130
+ end
131
+ else
132
+ opts = Hash[]
133
+ end
134
+
135
+ opts[:process] = false unless opts.include?(:process)
136
+ opts[:stdin] = true unless opts.include?(:stdin)
137
+ opts[:stdout] = true unless opts.include?(:stdout)
138
+ opts[:stderr] = true unless opts.include?(:stderr)
139
+
140
+ if !program.include?("/") and !program_in_path?(program)
141
+ raise ExecutableNotFound.new(program)
142
+ end
143
+
144
+ logger.debug("Running command", :args => args2)
145
+
146
+ stdout_r, stdout_w = IO.pipe
147
+ stderr_r, stderr_w = IO.pipe
148
+
149
+ process = ChildProcess.build(*args2)
150
+ process.environment.merge!(env)
151
+
152
+ process.io.stdout = stdout_w
153
+ process.io.stderr = stderr_w
154
+
155
+ if block_given? and opts[:stdin]
156
+ process.duplex = true
157
+ end
158
+
159
+ process.start
160
+
161
+ stdout_w.close; stderr_w.close
162
+ logger.debug("Process is running", :pid => process.pid)
163
+ if block_given?
164
+ args3 = []
165
+ args3.push(process) if opts[:process]
166
+ args3.push(process.io.stdin) if opts[:stdin]
167
+ args3.push(stdout_r) if opts[:stdout]
168
+ args3.push(stderr_r) if opts[:stderr]
169
+
170
+ yield(*args3)
171
+
172
+ process.io.stdin.close if opts[:stdin] and not process.io.stdin.closed?
173
+ stdout_r.close unless stdout_r.closed?
174
+ stderr_r.close unless stderr_r.closed?
175
+ else
176
+ # Log both stdout and stderr as 'info' because nobody uses stderr for
177
+ # actually reporting errors and as a result 'stderr' is a misnomer.
178
+ logger.pipe(stdout_r => :info, stderr_r => :info)
179
+ end
180
+
181
+ process.wait if process.alive?
182
+
183
+ return process.exit_code
184
+ end # def execmd
185
+
186
+ # Run a command safely in a way that gets reports useful errors.
187
+ def safesystem(*args)
188
+ # ChildProcess isn't smart enough to run a $SHELL if there's
189
+ # spaces in the first arg and there's only 1 arg.
190
+ if args.size == 1
191
+ args = [ default_shell, "-c", args[0] ]
192
+ end
193
+ program = args[0]
194
+
195
+ exit_code = execmd(args)
196
+ success = (exit_code == 0)
197
+
198
+ if !success
199
+ raise ProcessFailed.new("#{program} failed (exit code #{exit_code})" \
200
+ ". Full command was:#{args.inspect}")
201
+ end
202
+ return success
203
+ end # def safesystem
204
+
205
+ # Run a command safely in a way that captures output and status.
206
+ def safesystemout(*args)
207
+ if args.size == 1
208
+ args = [ ENV["SHELL"], "-c", args[0] ]
209
+ end
210
+ program = args[0]
211
+
212
+ if !program.include?("/") and !program_in_path?(program)
213
+ raise ExecutableNotFound.new(program)
214
+ end
215
+
216
+ stdout_r_str = nil
217
+ exit_code = execmd(args, :stdin=>false, :stderr=>false) do |stdout|
218
+ stdout_r_str = stdout.read
219
+ end
220
+ success = (exit_code == 0)
221
+
222
+ if !success
223
+ raise ProcessFailed.new("#{program} failed (exit code #{exit_code})" \
224
+ ". Full command was:#{args.inspect}")
225
+ end
226
+ return stdout_r_str
227
+ end # def safesystemout
228
+
229
+ # Get the recommended 'tar' command for this platform.
230
+ def tar_cmd
231
+ # Rely on gnu tar for solaris and OSX.
232
+ case %x{uname -s}.chomp
233
+ when "SunOS"
234
+ return "gtar"
235
+ when "Darwin"
236
+ # Try running gnutar, it was renamed(??) in homebrew to 'gtar' at some point, I guess? I don't know.
237
+ ["gnutar", "gtar"].each do |tar|
238
+ system("#{tar} > /dev/null 2> /dev/null")
239
+ return tar unless $?.exitstatus == 127
240
+ end
241
+ when "FreeBSD"
242
+ # use gnutar instead
243
+ return "gtar"
244
+ else
245
+ return "tar"
246
+ end
247
+ end # def tar_cmd
248
+
249
+ # wrapper around mknod ffi calls
250
+ def mknod_w(path, mode, dev)
251
+ rc = -1
252
+ case %x{uname -s}.chomp
253
+ when 'Linux'
254
+ # bits/stat.h #define _MKNOD_VER_LINUX 0
255
+ rc = xmknod(0, path, mode, FFI::MemoryPointer.new(dev))
256
+ else
257
+ rc = mknod(path, mode, dev)
258
+ end
259
+ rc
260
+ end
261
+
262
+ def copy_metadata(source, destination)
263
+ source_stat = File::lstat(source)
264
+ dest_stat = File::lstat(destination)
265
+
266
+ # If this is a hard-link, there's no metadata to copy.
267
+ # If this is a symlink, what it points to hasn't been copied yet.
268
+ return if source_stat.ino == dest_stat.ino || dest_stat.symlink?
269
+
270
+ File.utime(source_stat.atime, source_stat.mtime, destination)
271
+ mode = source_stat.mode
272
+ begin
273
+ File.lchown(source_stat.uid, source_stat.gid, destination)
274
+ rescue Errno::EPERM
275
+ # clear setuid/setgid
276
+ mode &= 01777
277
+ end
278
+
279
+ unless source_stat.symlink?
280
+ File.chmod(mode, destination)
281
+ end
282
+ end # def copy_metadata
283
+
284
+
285
+ def copy_entry(src, dst, preserve=false, remove_destination=false)
286
+ case File.ftype(src)
287
+ when 'fifo', 'characterSpecial', 'blockSpecial', 'socket'
288
+ st = File.stat(src)
289
+ rc = mknod_w(dst, st.mode, st.dev)
290
+ raise SystemCallError.new("mknod error", FFI.errno) if rc == -1
291
+ when 'directory'
292
+ FileUtils.mkdir(dst) unless File.exists? dst
293
+ else
294
+ # if the file with the same dev and inode has been copied already -
295
+ # hard link it's copy to `dst`, otherwise make an actual copy
296
+ st = File.lstat(src)
297
+ known_entry = copied_entries[[st.dev, st.ino]]
298
+ if known_entry
299
+ FileUtils.ln(known_entry, dst)
300
+ else
301
+ FileUtils.copy_entry(src, dst, preserve=preserve,
302
+ remove_destination=remove_destination)
303
+ copied_entries[[st.dev, st.ino]] = dst
304
+ end
305
+ end # else...
306
+ end # def copy_entry
307
+
308
+ def copied_entries
309
+ # TODO(sissel): I wonder that this entry-copy knowledge needs to be put
310
+ # into a separate class/module. As is, calling copy_entry the same way
311
+ # in slightly different contexts will result in weird or bad behavior.
312
+ # What I mean is if we do:
313
+ # pkg = FPM::Package::Dir...
314
+ # pkg.output()...
315
+ # pkg.output()...
316
+ # The 2nd output call will fail or behave weirdly because @copied_entries
317
+ # is already populated. even though this is anew round of copying.
318
+ return @copied_entries ||= {}
319
+ end # def copied_entries
320
+
321
+ def expand_pessimistic_constraints(constraint)
322
+ name, op, version = constraint.split(/\s+/)
323
+
324
+ if op == '~>'
325
+
326
+ new_lower_constraint = "#{name} >= #{version}"
327
+
328
+ version_components = version.split('.').collect { |v| v.to_i }
329
+
330
+ version_prefix = version_components[0..-3].join('.')
331
+ portion_to_work_with = version_components.last(2)
332
+
333
+ prefix = ''
334
+ unless version_prefix.empty?
335
+ prefix = version_prefix + '.'
336
+ end
337
+
338
+ one_to_increment = portion_to_work_with[0].to_i
339
+ incremented = one_to_increment + 1
340
+
341
+ new_version = ''+ incremented.to_s + '.0'
342
+
343
+ upper_version = prefix + new_version
344
+
345
+ new_upper_constraint = "#{name} < #{upper_version}"
346
+
347
+ return [new_lower_constraint,new_upper_constraint]
348
+ else
349
+ return [constraint]
350
+ end
351
+ end #def expand_pesimistic_constraints
352
+
353
+ def logger
354
+ @logger ||= Cabin::Channel.get
355
+ end # def logger
356
+ end # module FPM::Util
357
+
358
+ require 'fpm/util/tar_writer'
@@ -0,0 +1,80 @@
1
+ require 'rubygems/package'
2
+
3
+ module FPM
4
+ module Issues
5
+ module TarWriter
6
+ # See https://github.com/rubygems/rubygems/issues/1608
7
+ def self.has_issue_1608?
8
+ name, prefix = nil,nil
9
+ io = StringIO.new
10
+ ::Gem::Package::TarWriter.new(io) do |tw|
11
+ name, prefix = tw.split_name('/123456789'*9 + '/1234567890') # abs name 101 chars long
12
+ end
13
+ return prefix.empty?
14
+ end
15
+
16
+ def self.has_issues_with_split_name?
17
+ return false unless ::Gem::Package::TarWriter.method_defined?(:split_name)
18
+ return has_issue_1608?
19
+ end
20
+
21
+ def self.has_issues_with_add_symlink?
22
+ return !::Gem::Package::TarWriter.public_instance_methods.include?(:add_symlink)
23
+ end
24
+ end # module TarWriter
25
+ end # module Issues
26
+ end # module FPM
27
+
28
+ module FPM; module Util; end; end
29
+
30
+ # Like the ::Gem::Package::TarWriter but contains some backports and bug fixes
31
+ class FPM::Util::TarWriter < ::Gem::Package::TarWriter
32
+ if FPM::Issues::TarWriter.has_issues_with_split_name?
33
+ def split_name(name)
34
+ if name.bytesize > 256 then
35
+ raise ::Gem::Package::TooLongFileName.new("File \"#{name}\" has a too long path (should be 256 or less)")
36
+ end
37
+
38
+ prefix = ''
39
+ if name.bytesize > 100 then
40
+ parts = name.split('/', -1) # parts are never empty here
41
+ name = parts.pop # initially empty for names with a trailing slash ("foo/.../bar/")
42
+ prefix = parts.join('/') # if empty, then it's impossible to split (parts is empty too)
43
+ while !parts.empty? && (prefix.bytesize > 155 || name.empty?)
44
+ name = parts.pop + '/' + name
45
+ prefix = parts.join('/')
46
+ end
47
+
48
+ if name.bytesize > 100 or prefix.empty? then
49
+ raise ::Gem::Package::TooLongFileName.new("File \"#{prefix}/#{name}\" has a too long name (should be 100 or less)")
50
+ end
51
+
52
+ if prefix.bytesize > 155 then
53
+ raise ::Gem::Package::TooLongFileName.new("File \"#{prefix}/#{name}\" has a too long base path (should be 155 or less)")
54
+ end
55
+ end
56
+
57
+ return name, prefix
58
+ end
59
+ end # if FPM::Issues::TarWriter.spit_name_has_issues?
60
+
61
+ if FPM::Issues::TarWriter.has_issues_with_add_symlink?
62
+ # Backport Symlink Support to TarWriter
63
+ # https://github.com/rubygems/rubygems/blob/4a778c9c2489745e37bcc2d0a8f12c601a9c517f/lib/rubygems/package/tar_writer.rb#L239-L253
64
+ def add_symlink(name, target, mode)
65
+ check_closed
66
+
67
+ name, prefix = split_name name
68
+
69
+ header = ::Gem::Package::TarHeader.new(:name => name, :mode => mode,
70
+ :size => 0, :typeflag => "2",
71
+ :linkname => target,
72
+ :prefix => prefix,
73
+ :mtime => Time.now).to_s
74
+
75
+ @io.write header
76
+
77
+ self
78
+ end # def add_symlink
79
+ end
80
+ end