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
@@ -0,0 +1,510 @@
1
+ require "erb"
2
+ require "fpm/namespace"
3
+ require "fpm/package"
4
+ require "fpm/errors"
5
+ require "fpm/util"
6
+ require "backports"
7
+ require "fileutils"
8
+ require "digest"
9
+ require 'digest/sha1'
10
+
11
+ # Support for debian packages (.deb files)
12
+ #
13
+ # This class supports both input and output of packages.
14
+ class FPM::Package::APK< FPM::Package
15
+
16
+ TAR_CHUNK_SIZE = 512
17
+ TAR_TYPEFLAG_OFFSET = 156
18
+ TAR_NAME_OFFSET_START = 0
19
+ TAR_NAME_OFFSET_END = 99
20
+ TAR_LENGTH_OFFSET_START = 124
21
+ TAR_LENGTH_OFFSET_END = 135
22
+ TAR_CHECKSUM_OFFSET_START = 148
23
+ TAR_CHECKSUM_OFFSET_END = 155
24
+ TAR_MAGIC_START = 257
25
+ TAR_MAGIC_END = 264
26
+ TAR_UID_START = 108
27
+ TAR_UID_END = 115
28
+ TAR_GID_START = 116
29
+ TAR_GID_END = 123
30
+ TAR_UNAME_START = 265
31
+ TAR_UNAME_END = 296
32
+ TAR_GNAME_START = 297
33
+ TAR_GNAME_END = 328
34
+ TAR_MAJOR_START = 329
35
+ TAR_MAJOR_END = 336
36
+ TAR_MINOR_START = 337
37
+ TAR_MINOR_END = 344
38
+
39
+ private
40
+
41
+ # Get the name of this package. See also FPM::Package#name
42
+ #
43
+ # This accessor actually modifies the name if it has some invalid or unwise
44
+ # characters.
45
+ def name
46
+ if @name =~ /[A-Z]/
47
+ logger.warn("apk packages should not have uppercase characters in their names")
48
+ @name = @name.downcase
49
+ end
50
+
51
+ if @name.include?("_")
52
+ logger.warn("apk packages should not include underscores")
53
+ @name = @name.gsub(/[_]/, "-")
54
+ end
55
+
56
+ if @name.include?(" ")
57
+ logger.warn("apk packages should not contain spaces")
58
+ @name = @name.gsub(/[ ]/, "-")
59
+ end
60
+
61
+ return @name
62
+ end
63
+
64
+ def prefix
65
+ return (attributes[:prefix] or "/")
66
+ end
67
+
68
+ def architecture
69
+
70
+ # "native" in apk should be "noarch"
71
+ if @architecture.nil? or @architecture == "native"
72
+ @architecture = "noarch"
73
+ end
74
+ return @architecture
75
+ end
76
+
77
+ def input(input_path)
78
+ logger.error("apk extraction is not yet implemented")
79
+ end
80
+
81
+ def output(output_path)
82
+
83
+ output_check(output_path)
84
+
85
+ control_path = build_path("control")
86
+ controltar_path = build_path("control.tar")
87
+ datatar_path = build_path("data.tar")
88
+
89
+ FileUtils.mkdir(control_path)
90
+
91
+ # data tar.
92
+ tar_path(staging_path(""), datatar_path)
93
+
94
+ # control tar.
95
+ begin
96
+ write_pkginfo(control_path)
97
+ write_control_scripts(control_path)
98
+ tar_path(control_path, controltar_path)
99
+ ensure
100
+ FileUtils.rm_r(control_path)
101
+ end
102
+
103
+ # concatenate the two into a real apk.
104
+ begin
105
+
106
+ # cut end-of-tar record from control tar
107
+ cut_tar_record(controltar_path)
108
+
109
+ # calculate/rewrite sha1 hashes for data tar
110
+ hash_datatar(datatar_path)
111
+
112
+ # concatenate the two into the final apk
113
+ concat_zip_tars(controltar_path, datatar_path, output_path)
114
+ end
115
+
116
+ logger.warn("apk output does not currently sign packages.")
117
+ logger.warn("It's recommended that your package be installed with '--allow-untrusted'")
118
+ end
119
+
120
+ def write_pkginfo(base_path)
121
+
122
+ pkginfo = ""
123
+
124
+ pkginfo << "# Generated by fpm\n"
125
+ pkginfo << "pkgname = #{@name}\n"
126
+ pkginfo << "pkgver = #{to_s("FULLVERSION")}\n"
127
+ pkginfo << "arch = #{architecture()}\n"
128
+ pkginfo << "pkgdesc = #{description()}\n"
129
+ pkginfo << "url = #{url()}\n"
130
+ pkginfo << "size = 102400\n" # totally magic, not sure what it's used for.
131
+
132
+ # write depends lines
133
+ for dependency in dependencies()
134
+ pkginfo << "depend = #{dependency}\n"
135
+ end
136
+
137
+ File.write("#{base_path}/.PKGINFO", pkginfo)
138
+ end
139
+
140
+ # Writes each control script from template into the build path,
141
+ # in the folder given by [base_path]
142
+ def write_control_scripts(base_path)
143
+
144
+ scripts = {}
145
+
146
+ scripts = register_script('post-install', :after_install, scripts)
147
+ scripts = register_script('post-install', :before_install, scripts)
148
+ scripts = register_script('post-install', :before_upgrade, scripts)
149
+ scripts = register_script('post-install', :after_upgrade, scripts)
150
+ scripts = register_script('pre-deinstall', :before_remove, scripts)
151
+ scripts = register_script('post-deinstall', :after_remove, scripts)
152
+
153
+ scripts.each do |key, content|
154
+
155
+ File.write("#{base_path}/.#{key}", content)
156
+ end
157
+ end
158
+
159
+ # Convenience method for 'write_control_scripts' to register control scripts
160
+ # if they exist.
161
+ def register_script(key, value, hash)
162
+
163
+ if(script?(value))
164
+ hash[key] = scripts[value]
165
+ end
166
+ return hash
167
+ end
168
+
169
+ # Removes the end-of-tar records from the given [target_path].
170
+ # End of tar records are two contiguous empty tar records at the end of the file
171
+ # Taken together, they comprise 1k of null data.
172
+ def cut_tar_record(target_path)
173
+
174
+ temporary_target_path = target_path + "~"
175
+
176
+ record_length = 0
177
+ empty_records = 0
178
+
179
+ open(temporary_target_path, "wb") do |target_file|
180
+
181
+ # Scan to find the location of the two contiguous null records
182
+ open(target_path, "rb") do |file|
183
+
184
+ until(empty_records == 2)
185
+
186
+ header = file.read(TAR_CHUNK_SIZE)
187
+
188
+ # clear off ownership info
189
+ header = replace_ownership_headers(header, true)
190
+
191
+ typeflag = header[TAR_TYPEFLAG_OFFSET]
192
+ ascii_length = header[TAR_LENGTH_OFFSET_START..TAR_LENGTH_OFFSET_END]
193
+
194
+ if(file.eof?())
195
+ raise StandardError.new("Invalid tar stream, eof before end-of-tar record")
196
+ end
197
+
198
+ if(typeflag == "\0")
199
+ empty_records += 1
200
+ next
201
+ end
202
+
203
+ record_length = ascii_length.to_i(8)
204
+ record_length = determine_record_length(record_length)
205
+
206
+ target_file.write(header)
207
+ target_file.write(file.read(record_length))
208
+ end
209
+ end
210
+ end
211
+
212
+ FileUtils::mv(temporary_target_path, target_path)
213
+ end
214
+
215
+ # Rewrites the tar file located at the given [target_tar_path]
216
+ # to have its record headers use a simple checksum,
217
+ # and the apk sha1 hash extension.
218
+ def hash_datatar(target_path)
219
+
220
+ header = extension_header = ""
221
+ data = extension_data = ""
222
+ record_length = extension_length = 0
223
+ empty_records = 0
224
+
225
+ temporary_file_name = target_path + "~"
226
+
227
+ target_file = open(temporary_file_name, "wb")
228
+ file = open(target_path, "rb")
229
+ begin
230
+
231
+ until(file.eof?() || empty_records == 2)
232
+
233
+ header = file.read(TAR_CHUNK_SIZE)
234
+ typeflag = header[TAR_TYPEFLAG_OFFSET]
235
+ record_length = header[TAR_LENGTH_OFFSET_START..TAR_LENGTH_OFFSET_END].to_i(8)
236
+
237
+ data = ""
238
+ record_length = determine_record_length(record_length)
239
+
240
+ until(data.length == record_length)
241
+ data += file.read(TAR_CHUNK_SIZE)
242
+ end
243
+
244
+ # Clear ownership fields
245
+ header = replace_ownership_headers(header, false)
246
+
247
+ # If it's not a null record, do extension hash.
248
+ if(typeflag != "\0")
249
+ extension_header = header.dup()
250
+
251
+ extension_header = replace_ownership_headers(extension_header, true)
252
+
253
+ # directories have a magic string inserted into their name
254
+ full_record_path = extension_header[TAR_NAME_OFFSET_START..TAR_NAME_OFFSET_END].delete("\0")
255
+ full_record_path = add_paxstring(full_record_path)
256
+
257
+ # hash data contents with sha1, if there is any content.
258
+ if(typeflag == '5')
259
+
260
+ extension_data = ""
261
+
262
+ # ensure it doesn't end with a slash
263
+ if(full_record_path[full_record_path.length-1] == '/')
264
+ full_record_path = full_record_path.chop()
265
+ end
266
+ else
267
+ extension_data = hash_record(data)
268
+ end
269
+
270
+ full_record_path = pad_string_to(full_record_path, 100)
271
+ extension_header[TAR_NAME_OFFSET_START..TAR_NAME_OFFSET_END] = full_record_path
272
+
273
+ extension_header[TAR_TYPEFLAG_OFFSET] = 'x'
274
+ extension_header[TAR_LENGTH_OFFSET_START..TAR_LENGTH_OFFSET_END] = extension_data.length.to_s(8).rjust(12, '0')
275
+ extension_header = checksum_header(extension_header)
276
+
277
+ # write extension record
278
+ target_file.write(extension_header)
279
+ target_file.write(extension_data)
280
+ else
281
+ empty_records += 1
282
+ end
283
+
284
+ # write header and data to target file.
285
+ target_file.write(header)
286
+ target_file.write(data)
287
+ end
288
+ FileUtils.mv(temporary_file_name, target_path)
289
+ ensure
290
+ file.close()
291
+ target_file.close()
292
+ end
293
+ end
294
+
295
+ # Concatenates each of the given [apath] and [bpath] into the given [target_path]
296
+ def concat_zip_tars(apath, bpath, target_path)
297
+
298
+ temp_apath = apath + "~"
299
+ temp_bpath = bpath + "~"
300
+
301
+ # zip each path separately
302
+ Zlib::GzipWriter.open(temp_apath) do |target_writer|
303
+ open(apath, "rb") do |file|
304
+ until(file.eof?())
305
+ target_writer.write(file.read(4096))
306
+ end
307
+ end
308
+ end
309
+
310
+ Zlib::GzipWriter.open(temp_bpath) do |target_writer|
311
+ open(bpath, "rb") do |file|
312
+ until(file.eof?())
313
+ target_writer.write(file.read(4096))
314
+ end
315
+ end
316
+ end
317
+
318
+ # concat both into one.
319
+ File.open(target_path, "wb") do |target_writer|
320
+ open(temp_apath, "rb") do |file|
321
+ until(file.eof?())
322
+ target_writer.write(file.read(4096))
323
+ end
324
+ end
325
+ open(temp_bpath, "rb") do |file|
326
+ until(file.eof?())
327
+ target_writer.write(file.read(4096))
328
+ end
329
+ end
330
+ end
331
+ end
332
+
333
+ # Rounds the given [record_length] to the nearest highest evenly-divisble number of 512.
334
+ def determine_record_length(record_length)
335
+
336
+ sans_size = TAR_CHUNK_SIZE-1
337
+
338
+ if(record_length % TAR_CHUNK_SIZE != 0)
339
+ record_length = (record_length + sans_size) & ~sans_size;
340
+ end
341
+ return record_length
342
+ end
343
+
344
+ # Checksums the entire contents of the given [header]
345
+ # Writes the resultant checksum into indices 148-155 of the same [header],
346
+ # and returns the modified header.
347
+ # 148-155 is the "size" range in a tar/ustar header.
348
+ def checksum_header(header)
349
+
350
+ # blank out header checksum
351
+ replace_string_range(header, TAR_CHECKSUM_OFFSET_START, TAR_CHECKSUM_OFFSET_END, ' ')
352
+
353
+ # calculate new checksum
354
+ checksum = 0
355
+
356
+ for i in 0..(TAR_CHUNK_SIZE-1)
357
+ checksum += header.getbyte(i)
358
+ end
359
+
360
+ checksum = checksum.to_s(8).rjust(6, '0')
361
+ header[TAR_CHECKSUM_OFFSET_START..TAR_CHECKSUM_OFFSET_END-2] = checksum
362
+ header[TAR_CHECKSUM_OFFSET_END-1] = "\0"
363
+ return header
364
+ end
365
+
366
+ # SHA-1 hashes the given data, then places it in the APK hash string format
367
+ # then returns.
368
+ def hash_record(data)
369
+
370
+ # %u %s=%s\n
371
+ # len name=hash
372
+
373
+ hash = Digest::SHA1.hexdigest(data)
374
+ name = "APK-TOOLS.checksum.SHA1"
375
+
376
+ ret = "#{name}=#{hash}\n"
377
+
378
+ # the length requirement needs to know its own length too, because the length
379
+ # is the entire length of the line, not just the contents.
380
+ length = ret.length
381
+ line_length = length.to_s
382
+ length += line_length.length
383
+ candidate_ret = "#{line_length} #{ret}"
384
+
385
+ if(candidate_ret.length != length)
386
+ length += 1
387
+ candidate_ret = "#{length.to_s} #{ret}"
388
+ end
389
+
390
+ ret = candidate_ret
391
+
392
+ # pad out the result
393
+ ret = pad_string_to(ret, TAR_CHUNK_SIZE)
394
+ return ret
395
+ end
396
+
397
+ # Tars the current contents of the given [path] to the given [target_path].
398
+ def tar_path(path, target_path)
399
+
400
+ # Change directory to the source path, and glob files
401
+ # This is done so that we end up with a "flat" archive, that doesn't
402
+ # have any path artifacts from the packager's absolute path.
403
+ ::Dir::chdir(path) do
404
+ entries = ::Dir::glob("**", File::FNM_DOTMATCH)
405
+
406
+ args =
407
+ [
408
+ tar_cmd,
409
+ "-f",
410
+ target_path,
411
+ "-c"
412
+ ]
413
+
414
+ # Move pkginfo to the front, if it exists.
415
+ for i in (0..entries.length)
416
+ if(entries[i] == ".PKGINFO")
417
+ entries[i] = entries[0]
418
+ entries[0] = ".PKGINFO"
419
+ break
420
+ end
421
+ end
422
+
423
+ # add entries to arguments.
424
+ entries.each do |entry|
425
+ unless(entry == '..' || entry == '.')
426
+ args = args << entry
427
+ end
428
+ end
429
+
430
+ safesystem(*args)
431
+ end
432
+ end
433
+
434
+ # APK adds a "PAX" magic string into most directory names.
435
+ # This takes an unchanged directory name and "paxifies" it.
436
+ def add_paxstring(ret)
437
+
438
+ pax_slash = ret.rindex('/')
439
+ if(pax_slash == nil)
440
+ pax_slash = 0
441
+ else
442
+ pax_slash = ret.rindex('/', pax_slash-1)
443
+ if(pax_slash == nil || pax_slash < 0)
444
+ pax_slash = 0
445
+ end
446
+ end
447
+
448
+ ret = ret.insert(pax_slash, "/PaxHeaders.14670/")
449
+ ret = ret.sub("//", "/")
450
+ return ret
451
+ end
452
+
453
+ # Appends null zeroes to the end of [ret] until it is divisible by [length].
454
+ # Returns the padded result.
455
+ def pad_string_to(ret, length)
456
+
457
+ until(ret.length % length == 0)
458
+ ret << "\0"
459
+ end
460
+ return ret
461
+ end
462
+
463
+ # Replaces every character between [start] and [finish] in the given [str]
464
+ # with [character].
465
+ def replace_string_range(str, start, finish, character)
466
+
467
+ for i in (start..finish)
468
+ str[i] = character
469
+ end
470
+
471
+ return str
472
+ end
473
+
474
+ # Nulls out the ownership bits of the given tar [header].
475
+ def replace_ownership_headers(header, nullify_names)
476
+
477
+ # magic
478
+ header[TAR_MAGIC_START..TAR_MAGIC_END] = "ustar\0" + "00"
479
+
480
+ # ids
481
+ header = replace_string_range(header, TAR_UID_START, TAR_UID_END, "0")
482
+ header = replace_string_range(header, TAR_GID_START, TAR_GID_END, "0")
483
+ header[TAR_GID_END] = "\0"
484
+ header[TAR_UID_END] = "\0"
485
+
486
+ # names
487
+ if(nullify_names)
488
+ header = replace_string_range(header, TAR_UNAME_START, TAR_UNAME_END, "\0")
489
+ header = replace_string_range(header, TAR_GNAME_START, TAR_GNAME_END, "\0")
490
+
491
+ # major/minor
492
+ header[TAR_MAJOR_START..TAR_MAJOR_END] = "0".rjust(8, '0')
493
+ header[TAR_MINOR_START..TAR_MINOR_END] = "0".rjust(8, '0')
494
+ header[TAR_MAJOR_END] = "\0"
495
+ header[TAR_MINOR_END] = "\0"
496
+ else
497
+ header[TAR_UNAME_START..TAR_UNAME_END] = pad_string_to("root", 32)
498
+ header[TAR_GNAME_START..TAR_GNAME_END] = pad_string_to("root", 32)
499
+ end
500
+
501
+ return header
502
+ end
503
+
504
+ def to_s(format=nil)
505
+ return super("NAME_FULLVERSION_ARCH.TYPE") if format.nil?
506
+ return super(format)
507
+ end
508
+
509
+ public(:input, :output, :architecture, :name, :prefix, :converted_from, :to_s)
510
+ end