fpm 1.10.2 → 1.15.0

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.
@@ -1,7 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  require "fpm/package"
3
3
  require "fpm/util"
4
- require "backports"
4
+ require "backports/latest"
5
5
  require "fileutils"
6
6
  require "find"
7
7
 
@@ -18,10 +18,10 @@ class FPM::Package::Pacman < FPM::Package
18
18
  option "--group", "GROUP", "The group owner of files in this package", :default => 'root'
19
19
 
20
20
  # The list of supported compression types. Default is xz (LZMA2)
21
- COMPRESSION_TYPES = [ "gz", "bzip2", "xz", "none" ]
21
+ COMPRESSION_TYPES = [ "gz", "bzip2", "xz", "zstd", "none" ]
22
22
 
23
23
  option "--compression", "COMPRESSION", "The compression type to use, must " \
24
- "be one of #{COMPRESSION_TYPES.join(", ")}.", :default => "xz" do |value|
24
+ "be one of #{COMPRESSION_TYPES.join(", ")}.", :default => "zstd" do |value|
25
25
  if !COMPRESSION_TYPES.include?(value)
26
26
  raise ArgumentError, "Pacman compression value of '#{value}' is invalid. " \
27
27
  "Must be one of #{COMPRESSION_TYPES.join(", ")}"
@@ -209,31 +209,39 @@ class FPM::Package::Pacman < FPM::Package
209
209
 
210
210
  def compression_option
211
211
  case self.attributes[:pacman_compression]
212
- when nil, "xz"
213
- return "--xz"
212
+ when nil, "zstd"
213
+ return "--zstd"
214
214
  when "none"
215
215
  return ""
216
216
  when "gz"
217
217
  return "-z"
218
+ when "xz"
219
+ return "--xz"
218
220
  when "bzip2"
219
221
  return "-j"
222
+ when "zstd"
223
+ return "--zstd"
220
224
  else
221
- return "--xz"
225
+ return "--zstd"
222
226
  end
223
227
  end
224
228
 
225
229
  def compression_ending
226
230
  case self.attributes[:pacman_compression]
227
- when nil, "xz"
228
- return ".xz"
231
+ when nil, "zstd"
232
+ return ".zst"
229
233
  when "none"
230
234
  return ""
231
235
  when "gz"
232
236
  return ".gz"
237
+ when "xz"
238
+ return ".xz"
233
239
  when "bzip2"
234
240
  return ".bz2"
241
+ when "zstd"
242
+ return ".zst"
235
243
  else
236
- return ".xz"
244
+ return ".zst"
237
245
  end
238
246
  end
239
247
 
@@ -245,7 +253,11 @@ class FPM::Package::Pacman < FPM::Package
245
253
  Find.find(staging_path) do |path|
246
254
  src = path.gsub(/^#{staging_path}/, '')
247
255
  dst = build_path(src)
248
- copy_entry(path, dst, preserve=true, remove_destination=true)
256
+ begin
257
+ copy_entry(path, dst, preserve=true, remove_destination=true)
258
+ rescue
259
+ copy_entry(path, dst, preserve=false, remove_destination=true)
260
+ end
249
261
  copy_metadata(path, dst)
250
262
  end
251
263
 
@@ -18,6 +18,7 @@ class FPM::Package::PleaseRun < FPM::Package
18
18
 
19
19
  option "--name", "SERVICE_NAME", "The name of the service you are creating"
20
20
  option "--chdir", "CHDIR", "The working directory used by the service"
21
+ option "--user", "USER", "The user to use for executing this program."
21
22
 
22
23
  private
23
24
  def input(command)
@@ -90,6 +90,11 @@ class get_metadata(Command):
90
90
  for dep in pkg_resources.parse_requirements(
91
91
  self.distribution.install_requires):
92
92
  final_deps.extend(self.process_dep(dep))
93
+ if getattr(self.distribution, 'extras_require', None):
94
+ for dep in pkg_resources.parse_requirements(
95
+ v for k, v in self.distribution.extras_require.items()
96
+ if k.startswith(':') and pkg_resources.evaluate_marker(k[1:])):
97
+ final_deps.extend(self.process_dep(dep))
93
98
 
94
99
  data["dependencies"] = final_deps
95
100
 
@@ -29,6 +29,9 @@ class FPM::Package::Python < FPM::Package
29
29
  option "--pypi", "PYPI_URL",
30
30
  "PyPi Server uri for retrieving packages.",
31
31
  :default => "https://pypi.python.org/simple"
32
+ option "--trusted-host", "PYPI_TRUSTED",
33
+ "Mark this host or host:port pair as trusted for pip",
34
+ :default => nil
32
35
  option "--package-prefix", "NAMEPREFIX",
33
36
  "(DEPRECATED, use --package-name-prefix) Name to prefix the package " \
34
37
  "name with." do |value|
@@ -76,7 +79,11 @@ class FPM::Package::Python < FPM::Package
76
79
  option "--setup-py-arguments", "setup_py_argument",
77
80
  "Arbitrary argument(s) to be passed to setup.py",
78
81
  :multivalued => true, :attribute_name => :python_setup_py_arguments,
79
- :default => []
82
+ :default => []
83
+ option "--internal-pip", :flag,
84
+ "Use the pip module within python to install modules - aka 'python -m pip'. This is the recommended usage since Python 3.4 (2014) instead of invoking the 'pip' script",
85
+ :attribute_name => :python_internal_pip,
86
+ :default => true
80
87
 
81
88
  private
82
89
 
@@ -127,16 +134,56 @@ class FPM::Package::Python < FPM::Package
127
134
  target = build_path(package)
128
135
  FileUtils.mkdir(target) unless File.directory?(target)
129
136
 
130
- if attributes[:python_pip].nil?
137
+ if attributes[:python_internal_pip?]
138
+ # XXX: Should we detect if internal pip is available?
139
+ attributes[:python_pip] = [ attributes[:python_bin], "-m", "pip"]
140
+ end
141
+
142
+ # attributes[:python_pip] -- expected to be a path
143
+ if attributes[:python_pip]
144
+ logger.debug("using pip", :pip => attributes[:python_pip])
145
+ # TODO: Support older versions of pip
146
+
147
+ pip = [attributes[:python_pip]] if pip.is_a?(String)
148
+ setup_cmd = [
149
+ *attributes[:python_pip],
150
+ "download",
151
+ "--no-clean",
152
+ "--no-deps",
153
+ "--no-binary", ":all:",
154
+ "-d", build_path,
155
+ "-i", attributes[:python_pypi],
156
+ ]
157
+
158
+ if attributes[:python_trusted_host]
159
+ setup_cmd += [
160
+ "--trusted-host",
161
+ attributes[:python_trusted_host],
162
+ ]
163
+ end
164
+
165
+ setup_cmd << want_pkg
166
+
167
+ safesystem(*setup_cmd)
168
+
169
+ # Pip removed the --build flag sometime in 2021, it seems: https://github.com/pypa/pip/issues/8333
170
+ # A workaround for pip removing the `--build` flag. Previously, `pip download --build ...` would leave
171
+ # behind a directory with the Python package extracted and ready to be used.
172
+ # For example, `pip download ... Django` puts `Django-4.0.4.tar.tz` into the build_path directory.
173
+ # If we expect `pip` to leave an unknown-named file in the `build_path` directory, let's check for
174
+ # a single file and unpack it. I don't know if it will /always/ be a .tar.gz though.
175
+ files = ::Dir.glob(File.join(build_path, "*.tar.gz"))
176
+ if files.length != 1
177
+ raise "Unexpected directory layout after `pip download ...`. This might be an fpm bug? The directory is #{build_path}"
178
+ end
179
+
180
+ safesystem("tar", "-zxf", files[0], "-C", target)
181
+ else
131
182
  # no pip, use easy_install
132
183
  logger.debug("no pip, defaulting to easy_install", :easy_install => attributes[:python_easyinstall])
133
184
  safesystem(attributes[:python_easyinstall], "-i",
134
185
  attributes[:python_pypi], "--editable", "-U",
135
186
  "--build-directory", target, want_pkg)
136
- else
137
- logger.debug("using pip", :pip => attributes[:python_pip])
138
- # TODO: Support older versions of pip
139
- safesystem(attributes[:python_pip], "download", "--no-clean", "--no-deps", "--no-binary", ":all:", "-i", attributes[:python_pypi], "--build", target, want_pkg)
140
187
  end
141
188
 
142
189
  # easy_install will put stuff in @tmpdir/packagename/, so find that:
@@ -183,7 +230,7 @@ class FPM::Package::Python < FPM::Package
183
230
 
184
231
  output = ::Dir.chdir(setup_dir) do
185
232
  tmp = build_path("metadata.json")
186
- setup_cmd = "env PYTHONPATH=#{pylib} #{attributes[:python_bin]} " \
233
+ setup_cmd = "env PYTHONPATH=#{pylib}:$PYTHONPATH #{attributes[:python_bin]} " \
187
234
  "setup.py --command-packages=pyfpm get_metadata --output=#{tmp}"
188
235
 
189
236
  if attributes[:python_obey_requirements_txt?]
@@ -1,9 +1,12 @@
1
1
  require "fpm/package"
2
- require "backports"
2
+ require "backports/latest"
3
3
  require "fileutils"
4
4
  require "find"
5
5
  require "arr-pm/file" # gem 'arr-pm'
6
6
 
7
+ # For conversion handling
8
+ require "fpm/package/gem"
9
+
7
10
  # RPM Package type.
8
11
  #
9
12
  # Build RPMs without having to waste hours reading Maximum-RPM.
@@ -24,10 +27,10 @@ class FPM::Package::RPM < FPM::Package
24
27
 
25
28
  COMPRESSION_MAP = {
26
29
  "none" => "w0.gzdio",
27
- "xz" => "w9.xzdio",
28
- "xzmt" => "w9T.xzdio",
29
- "gzip" => "w9.gzdio",
30
- "bzip2" => "w9.bzdio"
30
+ "xz" => ".xzdio",
31
+ "xzmt" => "T.xzdio",
32
+ "gzip" => ".gzdio",
33
+ "bzip2" => ".bzdio"
31
34
  } unless defined?(COMPRESSION_MAP)
32
35
 
33
36
  option "--use-file-permissions", :flag,
@@ -68,6 +71,15 @@ class FPM::Package::RPM < FPM::Package
68
71
  value.downcase
69
72
  end
70
73
 
74
+ option "--compression-level", "[0-9]", "Select a compression level. 0 is store-only. 9 is max compression.",
75
+ :default => "9" do |value|
76
+ valint = value.to_i
77
+ unless value =~ /^\d$/ && valint >= 0 && valint <= 9
78
+ raise "Invalid compression level '#{value}'. Valid values are integers between 0 and 9 inclusive."
79
+ end
80
+ valint
81
+ end
82
+
71
83
  option "--compression", COMPRESSION_MAP.keys.join("|"),
72
84
  "Select a compression method. gzip works on the most platforms.",
73
85
  :default => "gzip" do |value|
@@ -142,6 +154,10 @@ class FPM::Package::RPM < FPM::Package
142
154
  "names in rpm requires instead of the redhat style " \
143
155
  "rubygem(foo).", :default => false
144
156
 
157
+ option "--macro-expansion", :flag,
158
+ "install-time macro expansion in %pre %post %preun %postun scripts " \
159
+ "(see: https://rpm.org/user_doc/scriptlet_expansion.html)", :default => false
160
+
145
161
  option "--verifyscript", "FILE",
146
162
  "a script to be run on verification" do |val|
147
163
  File.expand_path(val) # Get the full path to the script
@@ -176,16 +192,26 @@ class FPM::Package::RPM < FPM::Package
176
192
  # Replace * with [*] to make rpm not use globs
177
193
  # Replace ? with [?] to make rpm not use globs
178
194
  # Replace % with [%] to make rpm not expand macros
195
+ # Replace whitespace with ? to make rpm not split the filename
196
+ # If and only if any of the above are done, then also replace ' with \', " with \", and \ with \\\\
197
+ # to accommodate escape and quote processing that rpm will perform in that case (but not otherwise)
179
198
  def rpm_fix_name(name)
180
- name = name.gsub(/(\ |\[|\]|\*|\?|\%|\$)/, {
181
- ' ' => '?',
182
- '%' => '[%]',
183
- '$' => '[$]',
184
- '?' => '[?]',
185
- '*' => '[*]',
186
- '[' => '[\[]',
187
- ']' => '[\]]'
188
- })
199
+ if name.match?(/[ \t*?%$\[\]]/)
200
+ name = name.gsub(/(\ |\t|\[|\]|\*|\?|\%|\$|'|"|\\)/, {
201
+ ' ' => '?',
202
+ "\t" => '?',
203
+ '%' => '[%]',
204
+ '$' => '[$]',
205
+ '?' => '[?]',
206
+ '*' => '[*]',
207
+ '[' => '[\[]',
208
+ ']' => '[\]]',
209
+ '"' => '\\"',
210
+ "'" => "\\'",
211
+ '\\' => '\\\\\\\\',
212
+ })
213
+ end
214
+ name
189
215
  end
190
216
 
191
217
  def rpm_file_entry(file)
@@ -226,6 +252,8 @@ class FPM::Package::RPM < FPM::Package
226
252
  return %x{uname -m}.chomp # default to current arch
227
253
  when "amd64" # debian and redhat disagree on architecture names
228
254
  return "x86_64"
255
+ when "arm64" # debian and redhat disagree on architecture names
256
+ return "aarch64"
229
257
  when "native"
230
258
  return %x{uname -m}.chomp # 'native' is current arch
231
259
  when "all"
@@ -238,6 +266,12 @@ class FPM::Package::RPM < FPM::Package
238
266
 
239
267
  # This method ensures a default value for iteration if none is provided.
240
268
  def iteration
269
+ if @iteration.kind_of?(String) and @iteration.include?("-")
270
+ logger.warn("Package iteration '#{@iteration}' includes dashes, converting" \
271
+ " to underscores. rpmbuild does not allow the dashes in the package iteration (called 'Release' in rpm)")
272
+ @iteration = @iteration.gsub(/-/, "_")
273
+ end
274
+
241
275
  return @iteration ? @iteration : 1
242
276
  end # def iteration
243
277
 
@@ -516,10 +550,11 @@ class FPM::Package::RPM < FPM::Package
516
550
  end
517
551
 
518
552
  # copy all files from staging to BUILD dir
553
+ # [#1538] Be sure to preserve the original timestamps.
519
554
  Find.find(staging_path) do |path|
520
555
  src = path.gsub(/^#{staging_path}/, '')
521
556
  dst = File.join(build_path, build_sub_dir, src)
522
- copy_entry(path, dst)
557
+ copy_entry(path, dst, preserve=true)
523
558
  end
524
559
 
525
560
  rpmspec = template("rpm.erb").result(binding)
@@ -597,7 +632,12 @@ class FPM::Package::RPM < FPM::Package
597
632
  end # def to_s
598
633
 
599
634
  def payload_compression
600
- return COMPRESSION_MAP[attributes[:rpm_compression]]
635
+ if attributes[:rpm_compression] == 'none'
636
+ # when 'none' ignore any compression level and return w0.gzdio
637
+ return COMPRESSION_MAP[attributes[:rpm_compression]]
638
+ else
639
+ return "w#{attributes[:rpm_compression_level]}" + COMPRESSION_MAP[attributes[:rpm_compression]]
640
+ end
601
641
  end # def payload_compression
602
642
 
603
643
  def digest_algorithm
@@ -3,7 +3,7 @@ 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
9
 
@@ -0,0 +1,130 @@
1
+ require "yaml"
2
+
3
+ require "fpm/package"
4
+ require "fpm/util"
5
+ require "fileutils"
6
+ require "fpm/package/dir"
7
+
8
+ # Support for snaps (.snap files).
9
+ #
10
+ # This supports the input and output of snaps.
11
+ class FPM::Package::Snap < FPM::Package
12
+
13
+ option "--yaml", "FILEPATH",
14
+ "Custom version of the snap.yaml file." do | snap_yaml |
15
+ File.expand_path(snap_yaml)
16
+ end
17
+
18
+ option "--confinement", "CONFINEMENT",
19
+ "Type of confinement to use for this snap.",
20
+ default: "devmode" do | confinement |
21
+ if ['strict', 'devmode', 'classic'].include? confinement
22
+ confinement
23
+ else
24
+ raise "Unsupported confinement type '#{confinement}'"
25
+ end
26
+ end
27
+
28
+ option "--grade", "GRADE", "Grade of this snap.",
29
+ default: "devel" do | grade |
30
+ if ['stable', 'devel'].include? grade
31
+ grade
32
+ else
33
+ raise "Unsupported grade type '#{grade}'"
34
+ end
35
+ end
36
+
37
+ # Input a snap
38
+ def input(input_snap)
39
+ extract_snap_to_staging input_snap
40
+ extract_snap_metadata_from_staging
41
+ end # def input
42
+
43
+ # Output a snap.
44
+ def output(output_snap)
45
+ output_check(output_snap)
46
+
47
+ write_snap_yaml
48
+
49
+ # Create the snap from the staging path
50
+ safesystem("mksquashfs", staging_path, output_snap, "-noappend", "-comp",
51
+ "xz", "-no-xattrs", "-no-fragments", "-all-root")
52
+ end # def output
53
+
54
+ def to_s(format=nil)
55
+ # Default format if nil
56
+ # name_version_arch.snap
57
+ return super(format.nil? ? "NAME_FULLVERSION_ARCH.EXTENSION" : format)
58
+ end # def to_s
59
+
60
+ private
61
+
62
+ def extract_snap_to_staging(snap_path)
63
+ safesystem("unsquashfs", "-f", "-d", staging_path, snap_path)
64
+ end
65
+
66
+ def extract_snap_metadata_from_staging
67
+ metadata = YAML.safe_load(File.read(
68
+ staging_path(File.join("meta", "snap.yaml"))))
69
+
70
+ self.name = metadata["name"]
71
+ self.version = metadata["version"]
72
+ self.description = metadata["summary"] + "\n" + metadata["description"]
73
+ self.architecture = metadata["architectures"][0]
74
+ self.attributes[:snap_confinement] = metadata["confinement"]
75
+ self.attributes[:snap_grade] = metadata["grade"]
76
+
77
+ if metadata["apps"].nil?
78
+ attributes[:snap_apps] = []
79
+ else
80
+ attributes[:snap_apps] = metadata["apps"]
81
+ end
82
+
83
+ if metadata["hooks"].nil?
84
+ attributes[:snap_hooks] = []
85
+ else
86
+ attributes[:snap_hooks] = metadata["hooks"]
87
+ end
88
+ end
89
+
90
+ def write_snap_yaml
91
+ # Write the snap.yaml
92
+ if attributes[:snap_yaml]
93
+ logger.debug("Using '#{attributes[:snap_yaml]}' as the snap.yaml")
94
+ yaml_data = File.read(attributes[:snap_yaml])
95
+ else
96
+ summary, *remainder = (self.description or "no summary given").split("\n")
97
+ description = "no description given"
98
+ if remainder.any?
99
+ description = remainder.join("\n")
100
+ end
101
+
102
+ yaml_data = {
103
+ "name" => self.name,
104
+ "version" => self.version,
105
+ "summary" => summary,
106
+ "description" => description,
107
+ "architectures" => [self.architecture],
108
+ "confinement" => self.attributes[:snap_confinement],
109
+ "grade" => self.attributes[:snap_grade],
110
+ }
111
+
112
+ unless attributes[:snap_apps].nil? or attributes[:snap_apps].empty?
113
+ yaml_data["apps"] = attributes[:snap_apps]
114
+ end
115
+
116
+ unless attributes[:snap_hooks].nil? or attributes[:snap_hooks].empty?
117
+ yaml_data["hooks"] = attributes[:snap_hooks]
118
+ end
119
+
120
+ yaml_data = yaml_data.to_yaml
121
+ end
122
+
123
+ FileUtils.mkdir_p(staging_path("meta"))
124
+ snap_yaml_path = staging_path(File.join("meta", "snap.yaml"))
125
+ logger.debug("Writing snap.yaml", :path => snap_yaml_path)
126
+ File.write(snap_yaml_path, yaml_data)
127
+ File.chmod(0644, snap_yaml_path)
128
+ edit_file(snap_yaml_path) if attributes[:edit?]
129
+ end # def write_snap_yaml
130
+ end # class FPM::Package::Snap
@@ -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 "fileutils"
@@ -2,6 +2,8 @@ require "fpm/namespace"
2
2
  require "fpm/package"
3
3
  require "fpm/util"
4
4
 
5
+ require "fpm/package/dir"
6
+
5
7
  # Support for python virtualenv packages.
6
8
  #
7
9
  # This supports input, but not output.
@@ -111,8 +113,7 @@ class FPM::Package::Virtualenv < FPM::Package
111
113
  # Why is this hack here? It looks important, so I'll keep it in.
112
114
  safesystem(python_exe, pip_exe, "install", "-U", "-i",
113
115
  attributes[:virtualenv_pypi],
114
- "pip", "distribute")
115
- safesystem(python_exe, pip_exe, "uninstall", "-y", "distribute")
116
+ "pip")
116
117
 
117
118
  extra_index_url_args = []
118
119
  if attributes[:virtualenv_pypi_extra_index_urls]
@@ -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 "fileutils"
data/lib/fpm/package.rb CHANGED
@@ -4,7 +4,7 @@ require "pathname" # stdlib
4
4
  require "find"
5
5
  require "tmpdir" # stdlib
6
6
  require "ostruct"
7
- require "backports/2.0.0/stdlib/ostruct"
7
+ require "backports/latest"
8
8
  require "socket" # stdlib, for Socket.gethostname
9
9
  require "shellwords" # stdlib, for Shellwords.escape
10
10
  require "erb" # stdlib, for template processing
@@ -316,7 +316,7 @@ class FPM::Package
316
316
  # the path before returning.
317
317
  #
318
318
  # Wrapping Find.find in an Enumerator is required for sane operation in ruby 1.8.7,
319
- # but requires the 'backports' gem (which is used in other places in fpm)
319
+ # but requires the 'backports/latest' gem (which is used in other places in fpm)
320
320
  return Enumerator.new { |y| Find.find(staging_path) { |path| y << path } } \
321
321
  .select { |path| path != staging_path } \
322
322
  .select { |path| is_leaf.call(path) } \
@@ -331,11 +331,12 @@ class FPM::Package
331
331
  template_path = File.join(template_dir, path)
332
332
  template_code = File.read(template_path)
333
333
  logger.info("Reading template", :path => template_path)
334
- erb = ERB.new(template_code, nil, "-")
334
+ erb = erbnew(template_code)
335
335
  erb.filename = template_path
336
336
  return erb
337
337
  end # def template
338
338
 
339
+
339
340
  #######################################
340
341
  # The following methods are provided to
341
342
  # easily override particular substitut-
@@ -518,7 +519,7 @@ class FPM::Package
518
519
  # flag), then apply it as an ERB template.
519
520
  def script(script_name)
520
521
  if attributes[:template_scripts?]
521
- erb = ERB.new(scripts[script_name], nil, "-")
522
+ erb = erbnew(scripts[script_name])
522
523
  # TODO(sissel): find the original file name for the file.
523
524
  erb.filename = "script(#{script_name})"
524
525
  return erb.result(binding)
@@ -29,7 +29,7 @@ end # module FPM
29
29
 
30
30
  module FPM; module Util; end; end
31
31
 
32
- # Like the ::Gem::Package::TarWriter but contains some backports and bug fixes
32
+ # Like the ::Gem::Package::TarWriter but contains some backports/latest and bug fixes
33
33
  class FPM::Util::TarWriter < ::Gem::Package::TarWriter
34
34
  if FPM::Issues::TarWriter.has_issues_with_split_name?
35
35
  def split_name(name)