fpm 1.10.2 → 1.15.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,9 +3,14 @@ require "fpm/namespace"
3
3
  require "fpm/package"
4
4
  require "fpm/errors"
5
5
  require "fpm/util"
6
- require "backports"
6
+ require "backports/latest"
7
7
  require "fileutils"
8
8
  require "digest"
9
+ require "zlib"
10
+
11
+ # For handling conversion
12
+ require "fpm/package/cpan"
13
+ require "fpm/package/gem"
9
14
 
10
15
  # Support for debian packages (.deb files)
11
16
  #
@@ -22,7 +27,23 @@ class FPM::Package::Deb < FPM::Package
22
27
  } unless defined?(SCRIPT_MAP)
23
28
 
24
29
  # The list of supported compression types. Default is gz (gzip)
25
- COMPRESSION_TYPES = [ "gz", "bzip2", "xz" ]
30
+ COMPRESSION_TYPES = [ "gz", "bzip2", "xz", "none" ]
31
+
32
+ # https://www.debian.org/doc/debian-policy/ch-relationships.html#syntax-of-relationship-fields
33
+ # Example value with version relationship: libc6 (>= 2.2.1)
34
+ # Example value: libc6
35
+
36
+ # Package name docs here: https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-source
37
+ # Package names (both source and binary, see Package) must consist only of lower case letters (a-z),
38
+ # digits (0-9), plus (+) and minus (-) signs, and periods (.).
39
+ # They must be at least two characters long and must start with an alphanumeric character.
40
+
41
+ # Version string docs here: https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-version
42
+ # The format is: [epoch:]upstream_version[-debian_revision].
43
+ # epoch - This is a single (generally small) unsigned integer
44
+ # upstream_version - must contain only alphanumerics 6 and the characters . + - ~
45
+ # debian_revision - only alphanumerics and the characters + . ~
46
+ RELATIONSHIP_FIELD_PATTERN = /^(?<name>[A-z0-9][A-z0-9_.-]+)(?: *\((?<relation>[<>=]+) *(?<version>(?:[0-9]+:)?[0-9A-Za-z+~.-]+(?:-[0-9A-Za-z+~.]+)?)\))?$/
26
47
 
27
48
  option "--ignore-iteration-in-dependencies", :flag,
28
49
  "For '=' (equal) dependencies, allow iterations on the specified " \
@@ -77,7 +98,7 @@ class FPM::Package::Deb < FPM::Package
77
98
  end
78
99
 
79
100
  option "--priority", "PRIORITY",
80
- "The debian package 'priority' value.", :default => "extra"
101
+ "The debian package 'priority' value.", :default => "optional"
81
102
 
82
103
  option "--use-file-permissions", :flag,
83
104
  "Use existing file permissions when defining ownership and modes"
@@ -177,10 +198,14 @@ class FPM::Package::Deb < FPM::Package
177
198
  end
178
199
 
179
200
  option "--systemd", "FILEPATH", "Add FILEPATH as a systemd script",
180
- :multivalued => true do |file|
201
+ :multivalued => true do |file|
181
202
  next File.expand_path(file)
182
203
  end
183
204
 
205
+ option "--systemd-enable", :flag , "Enable service on install or upgrade", :default => false
206
+
207
+ option "--systemd-auto-start", :flag , "Start service after install or upgrade", :default => false
208
+
184
209
  option "--systemd-restart-after-upgrade", :flag , "Restart service after upgrade", :default => true
185
210
 
186
211
  option "--after-purge", "FILE",
@@ -189,9 +214,14 @@ class FPM::Package::Deb < FPM::Package
189
214
  File.expand_path(val) # Get the full path to the script
190
215
  end # --after-purge
191
216
 
217
+ option "--maintainerscripts-force-errorchecks", :flag ,
218
+ "Activate errexit shell option according to lintian. " \
219
+ "https://lintian.debian.org/tags/maintainer-script-ignores-errors.html",
220
+ :default => false
221
+
192
222
  def initialize(*args)
193
223
  super(*args)
194
- attributes[:deb_priority] = "extra"
224
+ attributes[:deb_priority] = "optional"
195
225
  end # def initialize
196
226
 
197
227
  private
@@ -218,6 +248,9 @@ class FPM::Package::Deb < FPM::Package
218
248
  when "x86_64"
219
249
  # Debian calls x86_64 "amd64"
220
250
  @architecture = "amd64"
251
+ when "aarch64"
252
+ # Debian calls aarch64 "arm64"
253
+ @architecture = "arm64"
221
254
  when "noarch"
222
255
  # Debian calls noarch "all"
223
256
  @architecture = "all"
@@ -265,10 +298,32 @@ class FPM::Package::Deb < FPM::Package
265
298
  end # def input
266
299
 
267
300
  def extract_info(package)
301
+ compression = `#{ar_cmd[0]} t #{package}`.split("\n").grep(/control.tar/).first.split(".").last
302
+ case compression
303
+ when "gz"
304
+ controltar = "control.tar.gz"
305
+ compression = "-z"
306
+ when "bzip2","bz2"
307
+ controltar = "control.tar.bz2"
308
+ compression = "-j"
309
+ when "xz"
310
+ controltar = "control.tar.xz"
311
+ compression = "-J"
312
+ when 'tar'
313
+ controltar = "control.tar"
314
+ compression = ""
315
+ when nil
316
+ raise FPM::InvalidPackageConfiguration, "Missing control.tar in deb source package #{package}"
317
+ else
318
+ raise FPM::InvalidPackageConfiguration,
319
+ "Unknown compression type '#{compression}' for control.tar in deb source package #{package}"
320
+ end
321
+
268
322
  build_path("control").tap do |path|
269
323
  FileUtils.mkdir(path) if !File.directory?(path)
324
+ # unpack the control.tar.{,gz,bz2,xz} from the deb package into staging_path
270
325
  # Unpack the control tarball
271
- safesystem(ar_cmd[0] + " p #{package} control.tar.gz | tar -zxf - -C #{path}")
326
+ safesystem(ar_cmd[0] + " p #{package} #{controltar} | tar #{compression} -xf - -C #{path}")
272
327
 
273
328
  control = File.read(File.join(path, "control"))
274
329
 
@@ -376,19 +431,28 @@ class FPM::Package::Deb < FPM::Package
376
431
  when "xz"
377
432
  datatar = "data.tar.xz"
378
433
  compression = "-J"
434
+ when 'tar'
435
+ datatar = "data.tar"
436
+ compression = ""
437
+ when nil
438
+ raise FPM::InvalidPackageConfiguration, "Missing data.tar in deb source package #{package}"
379
439
  else
380
440
  raise FPM::InvalidPackageConfiguration,
381
- "Unknown compression type '#{self.attributes[:deb_compression]}' "
382
- "in deb source package #{package}"
441
+ "Unknown compression type '#{compression}' for data.tar in deb source package #{package}"
383
442
  end
384
443
 
385
444
  # unpack the data.tar.{gz,bz2,xz} from the deb package into staging_path
386
- safesystem(ar_cmd[0] + " p #{package} #{datatar} " \
387
- "| tar #{compression} -xf - -C #{staging_path}")
445
+ safesystem(ar_cmd[0] + " p #{package} #{datatar} | tar #{compression} -xf - -C #{staging_path}")
388
446
  end # def extract_files
389
447
 
390
448
  def output(output_path)
391
449
  self.provides = self.provides.collect { |p| fix_provides(p) }
450
+
451
+ self.provides.each do |provide|
452
+ if !valid_provides_field?(provide)
453
+ raise FPM::InvalidPackageConfiguration, "Found invalid Provides field values (#{provide.inspect}). This is not valid in a Debian package."
454
+ end
455
+ end
392
456
  output_check(output_path)
393
457
  # Abort if the target path already exists.
394
458
 
@@ -428,6 +492,7 @@ class FPM::Package::Deb < FPM::Package
428
492
  raise "#{name}: tar is insufficient to support source_date_epoch."
429
493
  end
430
494
 
495
+ attributes[:deb_systemd] = []
431
496
  attributes.fetch(:deb_systemd_list, []).each do |systemd|
432
497
  name = File.basename(systemd, ".service")
433
498
  dest_systemd = staging_path("lib/systemd/system/#{name}.service")
@@ -435,19 +500,19 @@ class FPM::Package::Deb < FPM::Package
435
500
  FileUtils.cp(systemd, dest_systemd)
436
501
  File.chmod(0644, dest_systemd)
437
502
 
438
- # set the attribute with the systemd service name
439
- attributes[:deb_systemd] = name
503
+ # add systemd service name to attribute
504
+ attributes[:deb_systemd] << name
440
505
  end
441
506
 
442
- if script?(:before_upgrade) or script?(:after_upgrade) or attributes[:deb_systemd]
507
+ if script?(:before_upgrade) or script?(:after_upgrade) or attributes[:deb_systemd].any?
443
508
  puts "Adding action files"
444
509
  if script?(:before_install) or script?(:before_upgrade)
445
510
  scripts[:before_install] = template("deb/preinst_upgrade.sh.erb").result(binding)
446
511
  end
447
- if script?(:before_remove) or attributes[:deb_systemd]
512
+ if script?(:before_remove) or not attributes[:deb_systemd].empty?
448
513
  scripts[:before_remove] = template("deb/prerm_upgrade.sh.erb").result(binding)
449
514
  end
450
- if script?(:after_install) or script?(:after_upgrade) or attributes[:deb_systemd]
515
+ if script?(:after_install) or script?(:after_upgrade) or attributes[:deb_systemd].any?
451
516
  scripts[:after_install] = template("deb/postinst_upgrade.sh.erb").result(binding)
452
517
  end
453
518
  if script?(:after_remove)
@@ -552,19 +617,25 @@ class FPM::Package::Deb < FPM::Package
552
617
  case self.attributes[:deb_compression]
553
618
  when "gz", nil
554
619
  datatar = build_path("data.tar.gz")
555
- compression = "-z"
620
+ controltar = build_path("control.tar.gz")
621
+ compression_flags = ["-z"]
556
622
  when "bzip2"
557
623
  datatar = build_path("data.tar.bz2")
558
- compression = "-j"
624
+ controltar = build_path("control.tar.gz")
625
+ compression_flags = ["-j"]
559
626
  when "xz"
560
627
  datatar = build_path("data.tar.xz")
561
- compression = "-J"
628
+ controltar = build_path("control.tar.xz")
629
+ compression_flags = ["-J"]
630
+ when "none"
631
+ datatar = build_path("data.tar")
632
+ controltar = build_path("control.tar")
633
+ compression_flags = []
562
634
  else
563
635
  raise FPM::InvalidPackageConfiguration,
564
636
  "Unknown compression type '#{self.attributes[:deb_compression]}'"
565
637
  end
566
-
567
- args = [ tar_cmd, "-C", staging_path, compression ] + data_tar_flags + [ "-cf", datatar, "." ]
638
+ args = [ tar_cmd, "-C", staging_path ] + compression_flags + data_tar_flags + [ "-cf", datatar, "." ]
568
639
  if tar_cmd_supports_sort_names_and_set_mtime? and not attributes[:source_date_epoch].nil?
569
640
  # Use gnu tar options to force deterministic file order and timestamp
570
641
  args += ["--sort=name", ("--mtime=@%s" % attributes[:source_date_epoch])]
@@ -577,7 +648,7 @@ class FPM::Package::Deb < FPM::Package
577
648
  # the 'debian-binary' file has to be first
578
649
  File.expand_path(output_path).tap do |output_path|
579
650
  ::Dir.chdir(build_path) do
580
- safesystem(*ar_cmd, output_path, "debian-binary", "control.tar.gz", datatar)
651
+ safesystem(*ar_cmd, output_path, "debian-binary", controltar, datatar)
581
652
  end
582
653
  end
583
654
 
@@ -614,6 +685,43 @@ class FPM::Package::Deb < FPM::Package
614
685
  fix_provides(provides)
615
686
  end.flatten
616
687
 
688
+ if origin == FPM::Package::CPAN
689
+ # The fpm cpan code presents dependencies and provides fields as perl(ModuleName)
690
+ # so we'll need to convert them to something debian supports.
691
+
692
+ # Replace perl(ModuleName) > 1.0 with Debian-style perl-ModuleName (> 1.0)
693
+ perldepfix = lambda do |dep|
694
+ m = dep.match(/perl\((?<name>[A-Za-z0-9_:]+)\)\s*(?<op>.*$)/)
695
+ if m.nil?
696
+ # 'dep' syntax didn't look like 'perl(Name) > 1.0'
697
+ dep
698
+ else
699
+ # Also replace '::' in the perl module name with '-'
700
+ modulename = m["name"].gsub("::", "-")
701
+
702
+ # Fix any upper-casing or other naming concerns Debian has about packages
703
+ name = "#{attributes[:cpan_package_name_prefix]}-#{modulename}"
704
+
705
+ if m["op"].empty?
706
+ name
707
+ else
708
+ # 'dep' syntax was like this (version constraint): perl(Module) > 1.0
709
+ "#{name} (#{m["op"]})"
710
+ end
711
+ end
712
+ end
713
+
714
+ rejects = [ "perl(vars)", "perl(warnings)", "perl(strict)", "perl(Config)" ]
715
+ self.dependencies = self.dependencies.reject do |dep|
716
+ # Reject non-module Perl dependencies like 'vars' and 'warnings'
717
+ rejects.include?(dep)
718
+ end.collect(&perldepfix).collect(&method(:fix_dependency))
719
+
720
+ # Also fix the Provides field 'perl(ModuleName) = version' to be 'perl-modulename (= version)'
721
+ self.provides = self.provides.collect(&perldepfix).collect(&method(:fix_provides))
722
+
723
+ end # if origin == FPM::Packagin::CPAN
724
+
617
725
  if origin == FPM::Package::Deb
618
726
  changelog_path = staging_path("usr/share/doc/#{name}/changelog.Debian.gz")
619
727
  if File.exists?(changelog_path)
@@ -641,6 +749,19 @@ class FPM::Package::Deb < FPM::Package
641
749
  File.unlink(changelog_path)
642
750
  end
643
751
  end
752
+
753
+ if origin == FPM::Package::Gem
754
+ # fpm's gem input will have provides as "rubygem-name = version"
755
+ # and we need to convert this to Debian-style "rubygem-name (= version)"
756
+ self.provides = self.provides.collect do |provides|
757
+ m = /^(#{attributes[:gem_package_name_prefix]})-([^\s]+)\s*=\s*(.*)$/.match(provides)
758
+ if m
759
+ "#{m[1]}-#{m[2]} (= #{m[3]})"
760
+ else
761
+ provides
762
+ end
763
+ end
764
+ end
644
765
  end # def converted_from
645
766
 
646
767
  def debianize_op(op)
@@ -682,8 +803,13 @@ class FPM::Package::Deb < FPM::Package
682
803
  name, version = dep.gsub(/[()~>]/, "").split(/ +/)[0..1]
683
804
  nextversion = version.split(".").collect { |v| v.to_i }
684
805
  l = nextversion.length
685
- nextversion[l-2] += 1
686
- nextversion[l-1] = 0
806
+ if l > 1
807
+ nextversion[l-2] += 1
808
+ nextversion[l-1] = 0
809
+ else
810
+ # Single component versions ~> 1
811
+ nextversion[l-1] += 1
812
+ end
687
813
  nextversion = nextversion.join(".")
688
814
  return ["#{name} (>= #{version})", "#{name} (<< #{nextversion})"]
689
815
  elsif (m = dep.match(/(\S+)\s+\(!= (.+)\)/))
@@ -710,6 +836,32 @@ class FPM::Package::Deb < FPM::Package
710
836
  end
711
837
  end # def fix_dependency
712
838
 
839
+ def valid_provides_field?(text)
840
+ m = RELATIONSHIP_FIELD_PATTERN.match(text)
841
+ if m.nil?
842
+ logger.error("Invalid relationship field for debian package: #{text}")
843
+ return false
844
+ end
845
+
846
+ # Per Debian Policy manual, https://www.debian.org/doc/debian-policy/ch-relationships.html#syntax-of-relationship-fields
847
+ # >> The relations allowed are <<, <=, =, >= and >> for strictly earlier, earlier or equal,
848
+ # >> exactly equal, later or equal and strictly later, respectively. The exception is the
849
+ # >> Provides field, for which only = is allowed
850
+ if m["relation"] == "=" || m["relation"] == nil
851
+ return true
852
+ end
853
+ return false
854
+ end
855
+
856
+ def valid_relationship_field?(text)
857
+ m = RELATIONSHIP_FIELD_PATTERN.match(text)
858
+ if m.nil?
859
+ logger.error("Invalid relationship field for debian package: #{text}")
860
+ return false
861
+ end
862
+ return true
863
+ end
864
+
713
865
  def fix_provides(provides)
714
866
  name_re = /^[^ \(]+/
715
867
  name = provides[name_re]
@@ -724,6 +876,11 @@ class FPM::Package::Deb < FPM::Package
724
876
  "debs don't like underscores")
725
877
  provides = provides.gsub("_", "-")
726
878
  end
879
+
880
+ if m = provides.match(/^([A-Za-z0-9_-]+)\s*=\s*(\d+.*$)/)
881
+ logger.warn("Replacing 'provides' entry #{provides} with syntax 'name (= version)'")
882
+ provides = "#{m[1]} (= #{m[2]})"
883
+ end
727
884
  return provides.rstrip
728
885
  end
729
886
 
@@ -749,11 +906,27 @@ class FPM::Package::Deb < FPM::Package
749
906
  write_triggers # write trigger config to 'triggers' file
750
907
  write_md5sums # write the md5sums file
751
908
 
909
+ # Tar up the staging_path into control.tar.{compression type}
910
+ case self.attributes[:deb_compression]
911
+ when "gz", "bzip2", nil
912
+ controltar = "control.tar.gz"
913
+ compression_flags = ["-z"]
914
+ when "xz"
915
+ controltar = "control.tar.xz"
916
+ compression_flags = ["-J"]
917
+ when "none"
918
+ controltar = "control.tar"
919
+ compression_flags = []
920
+ else
921
+ raise FPM::InvalidPackageConfiguration,
922
+ "Unknown compression type '#{self.attributes[:deb_compression]}'"
923
+ end
924
+
752
925
  # Make the control.tar.gz
753
- build_path("control.tar.gz").tap do |controltar|
926
+ build_path(controltar).tap do |controltar|
754
927
  logger.info("Creating", :path => controltar, :from => control_path)
755
928
 
756
- args = [ tar_cmd, "-C", control_path, "-zcf", controltar,
929
+ args = [ tar_cmd, "-C", control_path ] + compression_flags + [ "-cf", controltar,
757
930
  "--owner=0", "--group=0", "--numeric-owner", "." ]
758
931
  if tar_cmd_supports_sort_names_and_set_mtime? and not attributes[:source_date_epoch].nil?
759
932
  # Force deterministic file order and timestamp
@@ -842,7 +1015,7 @@ class FPM::Package::Deb < FPM::Package
842
1015
  etcfiles = []
843
1016
  # Add everything in /etc
844
1017
  begin
845
- if !attributes[:deb_no_default_config_files?]
1018
+ if !attributes[:deb_no_default_config_files?] && File.exists?(staging_path("/etc"))
846
1019
  logger.warn("Debian packaging tools generally labels all files in /etc as config files, " \
847
1020
  "as mandated by policy, so fpm defaults to this behavior for deb packages. " \
848
1021
  "You can disable this default behavior with --deb-no-default-config-files flag")
@@ -931,7 +1104,7 @@ class FPM::Package::Deb < FPM::Package
931
1104
 
932
1105
  if attributes[:deb_templates]
933
1106
  FileUtils.cp(attributes[:deb_templates], control_path("templates"))
934
- File.chmod(0755, control_path("templates"))
1107
+ File.chmod(0644, control_path("templates"))
935
1108
  end
936
1109
  end # def write_debconf
937
1110
 
@@ -1,6 +1,6 @@
1
1
  require "fpm/package"
2
2
  require "fpm/util"
3
- require "backports"
3
+ require "backports/latest"
4
4
  require "fileutils"
5
5
  require "find"
6
6
  require "socket"
@@ -83,8 +83,8 @@ class FPM::Package::Dir < FPM::Package
83
83
  # can include license data from themselves (rpms, gems, etc),
84
84
  # but to make sure a simple dir -> rpm works without having
85
85
  # to specify a license.
86
- self.license = "unknown"
87
- self.vendor = [ENV["USER"], Socket.gethostname].join("@")
86
+ self.license ||= "unknown"
87
+ self.vendor ||= [ENV["USER"], Socket.gethostname].join("@")
88
88
  ensure
89
89
  # Clean up any logger context we added.
90
90
  logger.remove("method")
@@ -1,9 +1,21 @@
1
1
  require "fpm/package"
2
- require "backports"
2
+ require "backports/latest"
3
3
 
4
4
  # Empty Package type. For strict/meta/virtual package creation
5
5
 
6
6
  class FPM::Package::Empty < FPM::Package
7
+ def initialize(*args)
8
+ super(*args)
9
+
10
+ # Override FPM::Package's default "native" architecture value
11
+ # This feels like the right default because an empty package has no
12
+ # architecture-specific files, and in most cases an empty package should be
13
+ # installable anywhere.
14
+ #
15
+ # https://github.com/jordansissel/fpm/issues/1846
16
+ @architecture = "all"
17
+ end
18
+
7
19
  def output(output_path)
8
20
  logger.warn("Your package has gone into the void.")
9
21
  end
@@ -1,4 +1,4 @@
1
- require "backports" # gem backports
1
+ require "backports/latest" # gem backports/latest
2
2
  require "fpm/package"
3
3
  require "fpm/util"
4
4
  require "digest"
@@ -17,10 +17,6 @@ class FPM::Package::FreeBSD < FPM::Package
17
17
  :default => "fpm/<name>"
18
18
 
19
19
  def output(output_path)
20
- # See https://github.com/jordansissel/fpm/issues/1090
21
- # require xz later, because this triggers a load of liblzma.so.5 that is
22
- # unavailable on older CentOS/RH distros.
23
- require "xz"
24
20
  output_check(output_path)
25
21
 
26
22
  # Build the packaging metadata files.
@@ -80,22 +76,17 @@ class FPM::Package::FreeBSD < FPM::Package
80
76
  file.write(pkgdata.to_json + "\n")
81
77
  end
82
78
 
83
- # Create the .txz package archive from the files in staging_path.
84
- File.open(output_path, "wb") do |file|
85
- XZ::StreamWriter.new(file) do |xz|
86
- FPM::Util::TarWriter.new(xz) do |tar|
87
- # The manifests must come first for pkg.
88
- add_path(tar, "+COMPACT_MANIFEST",
89
- File.join(staging_path, "+COMPACT_MANIFEST"))
90
- add_path(tar, "+MANIFEST",
91
- File.join(staging_path, "+MANIFEST"))
92
-
93
- checksums.keys.each do |path|
94
- add_path(tar, "/" + path, File.join(staging_path, path))
95
- end
96
- end
97
- end
79
+ file_list = File.new(build_path("file_list"), "w")
80
+ files.each do |i|
81
+ file_list.puts(i)
98
82
  end
83
+ file_list.close
84
+
85
+ # Create the .txz package archive from the files in staging_path.
86
+ # We use --files-from here to keep the tar entries from having `./` as the prefix.
87
+ # This is done as a best effor to mimic what FreeBSD packages do, having everything at the top-level as
88
+ # file names, like "+MANIFEST" instead of "./+MANIFEST"
89
+ safesystem("tar", "-Jcf", output_path, "-C", staging_path, "--files-from", build_path("file_list"), "--transform", 's|^\([^+]\)|/\1|')
99
90
  end # def output
100
91
 
101
92
  # Handle architecture naming conversion:
@@ -110,6 +101,8 @@ class FPM::Package::FreeBSD < FPM::Package
110
101
  wordsize = case @architecture
111
102
  when nil, 'native'
112
103
  %x{getconf LONG_BIT}.chomp # 'native' is current arch
104
+ when 'arm64'
105
+ '64'
113
106
  when 'amd64'
114
107
  '64'
115
108
  when 'i386'
@@ -53,6 +53,15 @@ class FPM::Package::Gem < FPM::Package
53
53
  "The directory where fpm installs the gem temporarily before conversion. " \
54
54
  "Normally a random subdirectory of workdir."
55
55
 
56
+ option "--git-repo", "GIT_REPO",
57
+ "Use this git repo address as the source of the gem instead of " \
58
+ "rubygems.org.", :default => nil
59
+
60
+ option "--git-branch", "GIT_BRANCH",
61
+ "When using a git repo as the source of the gem instead of " \
62
+ "rubygems.org, use this git branch.",
63
+ :default => nil
64
+
56
65
  # Override parent method
57
66
  def staging_path(path=nil)
58
67
  @gem_staging_path ||= attributes[:gem_stagingdir] || Stud::Temporary.directory("package-#{type}-staging")
@@ -91,20 +100,32 @@ class FPM::Package::Gem < FPM::Package
91
100
 
92
101
  logger.info("Trying to download", :gem => gem_name, :version => gem_version)
93
102
 
94
- gem_fetch = [ "#{attributes[:gem_gem]}", "fetch", gem_name]
95
-
96
- gem_fetch += ["--prerelease"] if attributes[:gem_prerelease?]
97
- gem_fetch += ["--version", gem_version] if gem_version
98
-
99
103
  download_dir = build_path(gem_name)
100
104
  FileUtils.mkdir(download_dir) unless File.directory?(download_dir)
101
105
 
102
- ::Dir.chdir(download_dir) do |dir|
103
- logger.debug("Downloading in directory #{dir}")
104
- safesystem(*gem_fetch)
105
- end
106
+ if attributes[:gem_git_repo]
107
+ logger.debug("Git cloning in directory #{download_dir}")
108
+ safesystem("git", "-C", download_dir, "clone", attributes[:gem_git_repo], ".")
109
+ if attributes[:gem_git_branch]
110
+ safesystem("git", "-C", download_dir, "checkout", attributes[:gem_git_branch])
111
+ end
106
112
 
107
- gem_files = ::Dir.glob(File.join(download_dir, "*.gem"))
113
+ gem_build = [ "#{attributes[:gem_gem]}", "build", "#{download_dir}/#{gem_name}.gemspec"]
114
+ ::Dir.chdir(download_dir) do |dir|
115
+ logger.debug("Building in directory #{dir}")
116
+ safesystem(*gem_build)
117
+ end
118
+ gem_files = ::Dir.glob(File.join(download_dir, "*.gem"))
119
+ else
120
+ gem_fetch = [ "#{attributes[:gem_gem]}", "fetch", gem_name]
121
+ gem_fetch += ["--prerelease"] if attributes[:gem_prerelease?]
122
+ gem_fetch += ["--version", gem_version] if gem_version
123
+ ::Dir.chdir(download_dir) do |dir|
124
+ logger.debug("Downloading in directory #{dir}")
125
+ safesystem(*gem_fetch)
126
+ end
127
+ gem_files = ::Dir.glob(File.join(download_dir, "*.gem"))
128
+ end
108
129
 
109
130
  if gem_files.length != 1
110
131
  raise "Unexpected number of gem files in #{download_dir}, #{gem_files.length} should be 1"
@@ -113,9 +134,19 @@ class FPM::Package::Gem < FPM::Package
113
134
  return gem_files.first
114
135
  end # def download
115
136
 
137
+ GEMSPEC_YAML_CLASSES = [ ::Gem::Specification, ::Gem::Version, Time, ::Gem::Dependency, ::Gem::Requirement, Symbol ]
116
138
  def load_package_info(gem_path)
117
-
118
- spec = YAML.load(%x{#{attributes[:gem_gem]} specification #{gem_path} --yaml})
139
+ # TODO(sissel): Maybe we should check if `safe_load` method exists instead of this version check?
140
+ if ::Gem::Version.new(RUBY_VERSION) >= ::Gem::Version.new("3.1.0")
141
+ # Ruby 3.1.0 switched to a Psych/YAML version that defaults to "safe" loading
142
+ # and unfortunately `gem specification --yaml` emits YAML that requires
143
+ # class loaders to process correctly
144
+ spec = YAML.load(%x{#{attributes[:gem_gem]} specification #{gem_path} --yaml},
145
+ :permitted_classes => GEMSPEC_YAML_CLASSES)
146
+ else
147
+ # Older versions of ruby call this method YAML.safe_load
148
+ spec = YAML.safe_load(%x{#{attributes[:gem_gem]} specification #{gem_path} --yaml}, GEMSPEC_YAML_CLASSES)
149
+ end
119
150
 
120
151
  if !attributes[:gem_package_prefix].nil?
121
152
  attributes[:gem_package_name_prefix] = attributes[:gem_package_prefix]
@@ -198,8 +229,13 @@ class FPM::Package::Gem < FPM::Package
198
229
 
199
230
  ::FileUtils.mkdir_p(installdir)
200
231
  # TODO(sissel): Allow setting gem tool path
201
- args = [attributes[:gem_gem], "install", "--quiet", "--no-ri", "--no-rdoc",
202
- "--no-user-install", "--install-dir", installdir]
232
+ args = [attributes[:gem_gem], "install", "--quiet", "--no-user-install", "--install-dir", installdir]
233
+ if ::Gem::VERSION =~ /^[012]\./
234
+ args += [ "--no-ri", "--no-rdoc" ]
235
+ else
236
+ # Rubygems 3.0.0 changed --no-ri to --no-document
237
+ args += [ "--no-document" ]
238
+ end
203
239
 
204
240
  if !attributes[:gem_embed_dependencies?]
205
241
  args += ["--ignore-dependencies"]
@@ -4,7 +4,6 @@ require "fileutils"
4
4
  require "fpm/package/dir"
5
5
  require 'tempfile' # stdlib
6
6
  require 'pathname' # stdlib
7
- require 'rexml/document' # stdlib
8
7
 
9
8
  # Use an OS X pkg built with pkgbuild.
10
9
  #
@@ -103,6 +102,7 @@ class FPM::Package::OSXpkg < FPM::Package
103
102
 
104
103
  # Extract name and version from PackageInfo XML
105
104
  def extract_info(package)
105
+ require 'rexml/document'
106
106
  build_path("expand").tap do |path|
107
107
  doc = REXML::Document.new File.open(File.join(path, "PackageInfo"))
108
108
  pkginfo_elem = doc.elements["pkg-info"]
@@ -148,6 +148,11 @@ class FPM::Package::OSXpkg < FPM::Package
148
148
  write_scripts
149
149
  args += ["--scripts", scripts_path]
150
150
  end
151
+
152
+ if attributes[:prefix]
153
+ args += ["--install-location", attributes[:prefix]]
154
+ end
155
+
151
156
  args << output_path
152
157
 
153
158
  safesystem("pkgbuild", *args)