fpm 1.5.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ce725d9c506143e502cfc1e27ff5c6346240f305
4
- data.tar.gz: 09b9b1840e5525eb9930e7dad71070abc589275a
3
+ metadata.gz: 173d3185b39df15876683e91714e7a93970490cc
4
+ data.tar.gz: 15ffd8f3ff5b2658cd35aeb8b6188d90a00d3f7f
5
5
  SHA512:
6
- metadata.gz: 4a33d2dc5a2ca57fcceef601c5ed80da8021bbd8ae6365ec62b5a2b91bc7c2bd59f62ced044dafe592dd48b6f211b68c6200cd761dbd105f0d684c56105d2922
7
- data.tar.gz: 526287eceec55b3280544fbfcf851f091bc614a14137efbaf796a5ac549a7743ee04438099901f7896a823f627d731c019a44f6ac2f623f8b38bf0fc0edf30d7
6
+ metadata.gz: d25c03ef3397d2731d75d97c976a69174caa7b8eed89d8318d561e4516ecf5b4fb47d1f0fe5e96edb26094870b9c27e30d9366e89c3384bc527dea6cfd85c7f6
7
+ data.tar.gz: e6ee35e8c70e12f88d8350bfb7d5f4e06939d33257fcea64e3f16918f60acb84865c66bd7ac2ee6e6c0dea0ade119e7982256f0e9644ad860c6a8b40936427ca
data/CHANGELIST CHANGED
@@ -1,3 +1,13 @@
1
+ 1.6.0 (May 25, 2016)
2
+ - New source: pleaserun. This lets you create packages that will install a
3
+ system service. An after-install script is used in the package to determine
4
+ which service platform to target (systemd, upstart, etc). (#1119, #1112)
5
+ - New target: Alpine Linux "apk" packages. (#1054, George Lester)
6
+ - deb: don't append `.conf` to an upstart file if the file name already ends
7
+ in `.conf`. (#1115, josegonzalez)
8
+ - freebsd: fix bug where --package flag was ignored. (#1093, Paweł Tomulik)
9
+ - Improvements to the fpm rake tasks (#1101, Evan Gilman)
10
+
1
11
  1.5.0 (April 12, 2016)
2
12
  - Arch package support is now available via -s pacman and -t pacman.
3
13
  (#916; wonderful community effort making this happen!)
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  (This is an MIT-style license)
2
2
 
3
- Copyright (c) 2011,2012,2013 Jordan Sissel and contributors.
3
+ Copyright (c) 2011-2016 Jordan Sissel and contributors.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -111,8 +111,8 @@ class FPM::Command < Clamp::Command
111
111
  "specified multiple times.", :multivalued => true,
112
112
  :attribute_name => :conflicts
113
113
  option "--replaces", "REPLACES",
114
- "Other packages/versions this package replaces. This flag can be " \
115
- "specified multiple times.", :multivalued => true,
114
+ "Other packages/versions this package replaces. Equivalent of rpm's 'Obsoletes'. " \
115
+ "This flag can be specified multiple times.", :multivalued => true,
116
116
  :attribute_name => :replaces
117
117
 
118
118
  option "--config-files", "CONFIG_FILES",
@@ -332,12 +332,18 @@ class FPM::Command < Clamp::Command
332
332
  end
333
333
  end
334
334
 
335
- # Each remaining command line parameter is used as an 'input' argument.
336
- # For directories, this means paths. For things like gem and python, this
337
- # means package name or paths to the packages (rails, foo-1.0.gem, django,
338
- # bar/setup.py, etc)
339
- args.each do |arg|
340
- input.input(arg)
335
+ if input_type == "pleaserun"
336
+ # Special case for pleaserun that all parameters are considered the 'command'
337
+ # to run through pleaserun.
338
+ input.input(args)
339
+ else
340
+ # Each remaining command line parameter is used as an 'input' argument.
341
+ # For directories, this means paths. For things like gem and python, this
342
+ # means package name or paths to the packages (rails, foo-1.0.gem, django,
343
+ # bar/setup.py, etc)
344
+ args.each do |arg|
345
+ input.input(arg)
346
+ end
341
347
  end
342
348
 
343
349
  # If --inputs was specified, read it as a file.
@@ -331,17 +331,31 @@ class FPM::Package
331
331
  return erb
332
332
  end # def template
333
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)
334
+ #######################################
335
+ # The following methods are provided to
336
+ # easily override particular substitut-
337
+ # ions performed by to_s for subclasses
338
+ #######################################
339
+ def to_s_arch; architecture.to_s; end
340
+ def to_s_name; name.to_s; end
341
+ def to_s_fullversion; iteration ? "#{version}-#{iteration}" : "#{version}"; end
342
+ def to_s_version; version.to_s; end
343
+ def to_s_iteration; iteration.to_s; end
344
+ def to_s_epoch; epoch.to_s; end
345
+ def to_s_type; type.to_s; end
346
+ def to_s_extension; type.to_s; end
347
+ #######################################
348
+
349
+ def to_s(fmt=nil)
350
+ fmt = "NAME.EXTENSION" if fmt.nil?
351
+ return fmt.gsub("ARCH", to_s_arch) \
352
+ .gsub("NAME", to_s_name) \
353
+ .gsub("FULLVERSION", to_s_fullversion) \
354
+ .gsub("VERSION", to_s_version) \
355
+ .gsub("ITERATION", to_s_iteration) \
356
+ .gsub("EPOCH", to_s_epoch) \
357
+ .gsub("TYPE", to_s_type) \
358
+ .gsub("EXTENSION", to_s_extension)
345
359
  end # def to_s
346
360
 
347
361
  def edit_file(path)
@@ -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