cure-fpm 1.3.3b → 1.6.0b

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELIST +73 -0
  3. data/CONTRIBUTORS +1 -1
  4. data/LICENSE +1 -1
  5. data/lib/fpm.rb +2 -0
  6. data/lib/fpm/command.rb +100 -50
  7. data/lib/fpm/package.rb +42 -28
  8. data/lib/fpm/package/apk.rb +510 -0
  9. data/lib/fpm/package/cpan.rb +50 -25
  10. data/lib/fpm/package/deb.rb +211 -47
  11. data/lib/fpm/package/dir.rb +29 -28
  12. data/lib/fpm/package/empty.rb +6 -0
  13. data/lib/fpm/package/freebsd.rb +144 -0
  14. data/lib/fpm/package/gem.rb +5 -5
  15. data/lib/fpm/package/npm.rb +2 -2
  16. data/lib/fpm/package/osxpkg.rb +8 -7
  17. data/lib/fpm/package/p5p.rb +124 -0
  18. data/lib/fpm/package/pacman.rb +399 -0
  19. data/lib/fpm/package/pleaserun.rb +63 -0
  20. data/lib/fpm/package/pyfpm/get_metadata.py +9 -1
  21. data/lib/fpm/package/python.rb +19 -7
  22. data/lib/fpm/package/rpm.rb +58 -18
  23. data/lib/fpm/package/sh.rb +1 -7
  24. data/lib/fpm/package/solaris.rb +1 -1
  25. data/lib/fpm/package/tar.rb +14 -2
  26. data/lib/fpm/package/virtualenv.rb +145 -0
  27. data/lib/fpm/package/zip.rb +1 -1
  28. data/lib/fpm/rake_task.rb +60 -0
  29. data/lib/fpm/util.rb +176 -48
  30. data/lib/fpm/util/tar_writer.rb +80 -0
  31. data/lib/fpm/version.rb +1 -1
  32. data/templates/deb/postinst_upgrade.sh.erb +33 -2
  33. data/templates/deb/postrm_upgrade.sh.erb +10 -1
  34. data/templates/deb/preinst_upgrade.sh.erb +11 -2
  35. data/templates/deb/prerm_upgrade.sh.erb +14 -2
  36. data/templates/p5p_metadata.erb +12 -0
  37. data/templates/pacman.erb +47 -0
  38. data/templates/pacman/INSTALL.erb +41 -0
  39. data/templates/pleaserun/generate-cleanup.sh +17 -0
  40. data/templates/pleaserun/install-path.sh +17 -0
  41. data/templates/pleaserun/install.sh +117 -0
  42. data/templates/pleaserun/scripts/after-install.sh +4 -0
  43. data/templates/pleaserun/scripts/before-remove.sh +12 -0
  44. data/templates/rpm.erb +38 -6
  45. data/templates/sh.erb +38 -3
  46. metadata +81 -9
@@ -42,7 +42,7 @@ class FPM::Package::Zip < FPM::Package
42
42
  # the compression type.
43
43
  def output(output_path)
44
44
  output_check(output_path)
45
-
45
+
46
46
  files = Find.find(staging_path).to_a
47
47
  safesystem("zip", output_path, *files)
48
48
  end # def output
@@ -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_comment
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
@@ -24,6 +24,8 @@ module FPM::Util
24
24
 
25
25
  # Is the given program in the system's PATH?
26
26
  def program_in_path?(program)
27
+ # return false if path is not set
28
+ return false unless ENV['PATH']
27
29
  # Scan path to find the executable
28
30
  # Do this to help the user get a better error message.
29
31
  envpath = ENV["PATH"].split(":")
@@ -33,52 +35,168 @@ module FPM::Util
33
35
  def program_exists?(program)
34
36
  # Scan path to find the executable
35
37
  # Do this to help the user get a better error message.
36
- return program_in_path?(program) if !program.include?("/")
38
+ return program_in_path?(program) if !program.include?("/")
37
39
  return File.executable?(program)
38
40
  end # def program_exists?
39
41
 
40
42
  def default_shell
41
- shell = ENV["SHELL"]
43
+ shell = ENV["SHELL"]
42
44
  return "/bin/sh" if shell.nil? || shell.empty?
43
45
  return shell
44
46
  end
45
47
 
46
- # Run a command safely in a way that gets reports useful errors.
47
- def safesystem(*args)
48
- # ChildProcess isn't smart enough to run a $SHELL if there's
49
- # spaces in the first arg and there's only 1 arg.
50
- if args.size == 1
51
- args = [ default_shell, "-c", args[0] ]
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
52
112
  end
53
- program = args[0]
54
113
 
55
- if !program_exists?(program)
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)
56
141
  raise ExecutableNotFound.new(program)
57
142
  end
58
143
 
59
- logger.debug("Running command", :args => args)
144
+ logger.debug("Running command", :args => args2)
60
145
 
61
- # Create a pair of pipes to connect the
62
- # invoked process to the cabin logger
63
146
  stdout_r, stdout_w = IO.pipe
64
147
  stderr_r, stderr_w = IO.pipe
65
148
 
66
- process = ChildProcess.build(*args)
149
+ process = ChildProcess.build(*args2)
150
+ process.environment.merge!(env)
151
+
67
152
  process.io.stdout = stdout_w
68
153
  process.io.stderr = stderr_w
69
154
 
155
+ if block_given? and opts[:stdin]
156
+ process.duplex = true
157
+ end
158
+
70
159
  process.start
160
+
71
161
  stdout_w.close; stderr_w.close
72
- logger.debug('Process is running', :pid => process.pid)
73
- # Log both stdout and stderr as 'info' because nobody uses stderr for
74
- # actually reporting errors and as a result 'stderr' is a misnomer.
75
- logger.pipe(stdout_r => :info, stderr_r => :info)
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
76
180
 
77
- process.wait
78
- success = (process.exit_code == 0)
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)
79
197
 
80
198
  if !success
81
- raise ProcessFailed.new("#{program} failed (exit code #{process.exit_code})" \
199
+ raise ProcessFailed.new("#{program} failed (exit code #{exit_code})" \
82
200
  ". Full command was:#{args.inspect}")
83
201
  end
84
202
  return success
@@ -95,29 +213,16 @@ module FPM::Util
95
213
  raise ExecutableNotFound.new(program)
96
214
  end
97
215
 
98
- logger.debug("Running command", :args => args)
99
-
100
- stdout_r, stdout_w = IO.pipe
101
- stderr_r, stderr_w = IO.pipe
102
-
103
- process = ChildProcess.build(*args)
104
- process.io.stdout = stdout_w
105
- process.io.stderr = stderr_w
106
-
107
- process.start
108
- stdout_w.close; stderr_w.close
109
- stdout_r_str = stdout_r.read
110
- stdout_r.close; stderr_r.close
111
- logger.debug("Process is running", :pid => process.pid)
112
-
113
- process.wait
114
- success = (process.exit_code == 0)
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)
115
221
 
116
222
  if !success
117
- raise ProcessFailed.new("#{program} failed (exit code #{process.exit_code})" \
223
+ raise ProcessFailed.new("#{program} failed (exit code #{exit_code})" \
118
224
  ". Full command was:#{args.inspect}")
119
225
  end
120
-
121
226
  return stdout_r_str
122
227
  end # def safesystemout
123
228
 
@@ -133,17 +238,14 @@ module FPM::Util
133
238
  system("#{tar} > /dev/null 2> /dev/null")
134
239
  return tar unless $?.exitstatus == 127
135
240
  end
241
+ when "FreeBSD"
242
+ # use gnutar instead
243
+ return "gtar"
136
244
  else
137
245
  return "tar"
138
246
  end
139
247
  end # def tar_cmd
140
248
 
141
- # Run a block with a value.
142
- # Useful in lieu of assigning variables
143
- def with(value, &block)
144
- block.call(value)
145
- end # def with
146
-
147
249
  # wrapper around mknod ffi calls
148
250
  def mknod_w(path, mode, dev)
149
251
  rc = -1
@@ -157,7 +259,30 @@ module FPM::Util
157
259
  rc
158
260
  end
159
261
 
160
- def copy_entry(src, dst)
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)
161
286
  case File.ftype(src)
162
287
  when 'fifo', 'characterSpecial', 'blockSpecial', 'socket'
163
288
  st = File.stat(src)
@@ -173,7 +298,8 @@ module FPM::Util
173
298
  if known_entry
174
299
  FileUtils.ln(known_entry, dst)
175
300
  else
176
- FileUtils.copy_entry(src, dst)
301
+ FileUtils.copy_entry(src, dst, preserve=preserve,
302
+ remove_destination=remove_destination)
177
303
  copied_entries[[st.dev, st.ino]] = dst
178
304
  end
179
305
  end # else...
@@ -228,3 +354,5 @@ module FPM::Util
228
354
  @logger ||= Cabin::Channel.get
229
355
  end # def logger
230
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