fpm 1.16.0 → 1.18.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f6f1fc4a26f95d77ce3626231e5b7674b8908fe7afc7e2c524f573852291ed2b
4
- data.tar.gz: e31fa7d3a39de85c3a75b847fc9c336e5096ca32ecd08720554f3057e73e28ef
3
+ metadata.gz: 0a0ebf62e58835b9331f5f56ec9689e95e725666d7fc2563afd617f2fc882bb4
4
+ data.tar.gz: 7d0043765bdef7b5db3f493978cc94951fbb6cb5ad15bc823a9875e67ad2afd5
5
5
  SHA512:
6
- metadata.gz: f6988868dc8fae9930af5163495bb76d4afa631b58d8608f903349aefd253321ab35d88ad7b5445793b6d6d378ca941bb65d3b3353244511d32d5c13bd22c8fa
7
- data.tar.gz: fd0660808bb60d2796bc80fe25c5fd3e63f942ce24f79c4591e2d7c1917ef42af6529db07d116677ba439283843a99a80d8d5615c787815cd3a44a3540a5e474
6
+ metadata.gz: e887592f69377440a23e193c3a2ce049c0fdc286b6699931bed7c4589785da9532998ef3ccd09946e61974a3f441719d28d9b19332d4a5aa3ccf3c63bfb5c210
7
+ data.tar.gz: 1d21ed70407432b95a8c7095e226073e357bbf7754579df271e216ffc28f4121df949ecb01b4a8718d47033ebd6687175fb133edcca4793269f8bc3f9ffb31db
data/CHANGELOG.rst CHANGED
@@ -1,6 +1,29 @@
1
1
  Release Notes and Change Log
2
2
  ============================
3
3
 
4
+ 1.18.0 (August 26, 2026)
5
+ ^^^^^^^^^^^^^^^^^^^^^^^^
6
+
7
+ * deb/macOS: Fix a bug on macOS caused by a behavior change to the ``ar`` utility provided by Xcode (`#2149`_, `#2148`_; Dan Pashin)
8
+ * Ruby 2.x support: Restored compatibility with older versions of Ruby such as Ruby 2.5.0. (`#2123`_; Jordan Sissel)
9
+ * cpan: Add flag --cpan-metacpan-api-url for custom MetaCPAN urls (`#2146`_; Nicholas B. Hubbard)
10
+ * cpan: Add flag ``--cpan-disable-dependency <name>`` to remove a dependency from the package (`#2128`_, `#2125`_; Nicholas B. Hubbard)
11
+ * cpan: MetaCPAN upgraded their Elasticsearch version which means fpm also needs to use the newer query syntax. (`#2144`_, `#2143`_; Jordan Sissel)
12
+ * cpan: Improvements to cpan search to better support cpan distributions (`#2125`_; Nicholas B. Hubbard)
13
+ * tests: Fixed a bunch of tests that were failing under Github Actions (`#2122`_; Nicholas B. Hubbard)
14
+
15
+ 1.17.0 (October 2, 2025)
16
+ ^^^^^^^^^^^^^^^^^^^^^^^^
17
+
18
+ * python: Support modern Python project features: pyproject.toml, wheels, etc. Now, any project that can be built or installed with ``pip`` can be packaged by fpm. Previously, fpm relied on a long-deprecated features in setup.py to see a python project's metadata such as name, version, and dependencies. Fpm now uses python's package tools to identify the project's name, version, dependencies, and other information. (`#2104`_, `#2105`_, `#2040`_, `#1982`_, `#2029`_; Jordan Sissel, cwegener, amdei, gmabey)
19
+ * dir: When copying files, only use hardlinks if the original files were also hardlinks. (`#2103`_, `#2102`_; Michael Telatynski, Matthew Rathbone, Jordan Sissel). :w
20
+ Related: https://github.com/electron-userland/electron-builder/issues/5721
21
+ * deb: bug fix: when a file given with ``--config-files <path>`` copied into the package, fpm was forgetting to mark the file as being a config file in the package, aka Debian "conffiles" (`#2027`_, `#1823`_; Alexandr Zarubkin, Kientz Arnaud)
22
+ * pacman: Now can build packages with aarch64 and arm7hf architecture (`#2017`_; Markson Hon)
23
+ * rpm: Paths with '{' and '}' characters can now be included in rpms (`#2088`_ `#2087`_; Jordan Sissel, Manish2481983)
24
+ * docs: Updated urls which pointed at rpm documentation (`#2092`_, `#2011`_, `#2054`_; André Kelpe, Natanael Arndt)
25
+ * Ruby 3.4.0 no longer gives warnings related to `ostruct` (`#2106`_, also `#2104`_ and `#2103`_; Jordan Sissel)
26
+
4
27
  1.16.0 (December 8, 2024)
5
28
  ^^^^^^^^^^^^^^^^^^^^^^^^^
6
29
 
data/lib/fpm/command.rb CHANGED
@@ -3,7 +3,6 @@ require "fpm/namespace"
3
3
  require "fpm/version"
4
4
  require "fpm/util"
5
5
  require "clamp"
6
- require "ostruct"
7
6
  require "fpm"
8
7
  require "tmpdir" # for Dir.tmpdir
9
8
 
@@ -260,7 +259,11 @@ class FPM::Command < Clamp::Command
260
259
  "'gem', it specifies the packages to download and use as the gem input",
261
260
  :attribute_name => :args
262
261
 
262
+ # Keep a copy of the original flags (ones declared above, not by package types)
263
+ # This helps when generating the documentation
264
+ GENERAL_OPTIONS = @declared_options.clone
263
265
  FPM::Package.types.each do |name, klass|
266
+ # This adds each package's flags to the main command
264
267
  klass.apply_options(self)
265
268
  end
266
269
 
@@ -670,7 +673,7 @@ class FPM::Command < Clamp::Command
670
673
  option.of(self).take(value)
671
674
  end
672
675
  end
673
- end
676
+ end # def load_options
674
677
 
675
678
  # A simple flag validator
676
679
  #
@@ -754,4 +757,4 @@ class FPM::Command < Clamp::Command
754
757
 
755
758
  public(:initialize, :ok?, :messages)
756
759
  end # class Validator
757
- end # class FPM::Program
760
+ end # class FPM::Command
@@ -22,6 +22,13 @@ class FPM::Package::CPAN < FPM::Package
22
22
  option "--package-name-prefix", "NAME_PREFIX",
23
23
  "Name to prefix the package name with.", :default => "perl"
24
24
 
25
+ option "--disable-dependency", "MODULE",
26
+ "Filter modules matching MODULE from those generated by " \
27
+ "the auto-depends for CPAN modules. This flag can be specified " \
28
+ "multiple times. Use the perl module name, IE: URI::Escape.",
29
+ :multivalued => true, :attribute_name => :rejects,
30
+ :default => ['vars','warnings','strict','Config']
31
+
25
32
  option "--test", :flag,
26
33
  "Run the tests before packaging?", :default => true
27
34
 
@@ -37,6 +44,10 @@ class FPM::Package::CPAN < FPM::Package
37
44
  option "--cpanm-force", :flag,
38
45
  "Pass the --force parameter to cpanm", :default => false
39
46
 
47
+ option "--metacpan-api-url", "URL",
48
+ "URL for the MetaCPAN api",
49
+ :default => "https://fastapi.metacpan.org"
50
+
40
51
  private
41
52
  def input(package)
42
53
  #if RUBY_VERSION =~ /^1\.8/
@@ -46,7 +57,6 @@ class FPM::Package::CPAN < FPM::Package
46
57
  #"me know by filing an issue: " \
47
58
  #"https://github.com/jordansissel/fpm/issues"
48
59
  #end
49
- #require "ftw" # for http access
50
60
  require "net/http"
51
61
  require "json"
52
62
 
@@ -54,7 +64,7 @@ class FPM::Package::CPAN < FPM::Package
54
64
  moduledir = package
55
65
  result = {}
56
66
  else
57
- result = search(package)
67
+ result = search_module(package, version)
58
68
  tarball = download(result, version)
59
69
  moduledir = unpack(tarball)
60
70
  end
@@ -92,20 +102,17 @@ class FPM::Package::CPAN < FPM::Package
92
102
  else; metadata["license"]
93
103
  end
94
104
 
95
- unless metadata["distribution"].nil?
105
+ unless metadata["release"].nil?
106
+ dist_name, _, dist_version = metadata["release"].rpartition('-')
96
107
  logger.info("Setting package name from 'distribution'",
97
- :distribution => metadata["distribution"])
98
- self.name = fix_name(metadata["distribution"])
108
+ :distribution => dist_name)
109
+ self.name = fix_name(dist_name)
110
+ self.provides = search_provided_modules(dist_name, dist_version)
99
111
  else
100
112
  logger.info("Setting package name from 'name'",
101
113
  :name => metadata["name"])
102
114
  self.name = fix_name(metadata["name"])
103
- end
104
-
105
- unless metadata["module"].nil?
106
- metadata["module"].each do |m|
107
- self.provides << cap_name(m["name"]) + " = #{self.version}"
108
- end
115
+ self.provides << cap_name(metadata["name"]) + " = #{self.version}"
109
116
  end
110
117
 
111
118
  # author is not always set or it may be a string instead of an array
@@ -168,7 +175,6 @@ class FPM::Package::CPAN < FPM::Package
168
175
  self.dependencies << "#{dep_name} >= #{version}"
169
176
  next
170
177
  end
171
- dep = search(dep_name)
172
178
 
173
179
  name = cap_name(dep_name)
174
180
 
@@ -299,7 +305,7 @@ class FPM::Package::CPAN < FPM::Package
299
305
  self.architecture = "native"
300
306
  end
301
307
  end
302
- end
308
+ end # def input
303
309
 
304
310
  def unpack(tarball)
305
311
  directory = build_path("module")
@@ -308,7 +314,7 @@ class FPM::Package::CPAN < FPM::Package
308
314
  %q{--transform=s,[./]*[^/]*/,,} ]
309
315
  safesystem("tar", *args)
310
316
  return directory
311
- end
317
+ end # def unpack
312
318
 
313
319
  def download(metadata, cpan_version=nil)
314
320
  distribution = metadata["distribution"]
@@ -326,8 +332,8 @@ class FPM::Package::CPAN < FPM::Package
326
332
  end
327
333
 
328
334
  # Search metacpan to get download URL for this version of the module
329
- metacpan_search_url = "https://fastapi.metacpan.org/v1/release/_search"
330
- metacpan_search_query = '{"fields":["download_url"],"filter":{"term":{"name":"' + "#{distribution}-#{self.version}" + '"}}}'
335
+ metacpan_search_url = "#{attributes[:cpan_metacpan_api_url]}/v1/release/_search?_source=download_url"
336
+ metacpan_search_query = {"query":{"term":{"name": "#{distribution}-#{self.version}" } } }.to_json
331
337
  begin
332
338
  search_response = httppost(metacpan_search_url,metacpan_search_query)
333
339
  rescue Net::HTTPServerException => e
@@ -339,7 +345,7 @@ class FPM::Package::CPAN < FPM::Package
339
345
  data = search_response.body
340
346
  release_metadata = JSON.parse(data)
341
347
 
342
- download_url = release_metadata['hits']['hits'][0]['fields']['download_url']
348
+ download_url = release_metadata['hits']['hits'][0]['_source']['download_url']
343
349
  download_path = URI.parse(download_url).path
344
350
  tarball = File.basename(download_path)
345
351
 
@@ -365,25 +371,81 @@ class FPM::Package::CPAN < FPM::Package
365
371
  return build_path(tarball)
366
372
  end # def download
367
373
 
368
- def search(package)
369
- logger.info("Asking metacpan about a module", :module => package)
370
- metacpan_url = "https://fastapi.metacpan.org/v1/module/" + package
374
+ def search_module(module_name, version=nil)
375
+ logger.info("Asking metacpan about a module", :module => module_name, :version => version)
376
+ metacpan_api_url = "#{attributes[:cpan_metacpan_api_url]}/v1/module/_search"
377
+ metacpan_api_query = <<-EOS
378
+ {
379
+ "query": {
380
+ "bool": {
381
+ "must": [
382
+ { "term": { "module.name": "#{module_name}" } },
383
+ { "term": { "maturity": "released" } },
384
+ { "term": { #{version.nil? ? '"status": "latest"' : '"module.version": "' + version + '"'} } }
385
+ ]
386
+ }
387
+ },
388
+ "size": 1
389
+ }
390
+ EOS
371
391
  begin
372
- response = httpfetch(metacpan_url)
392
+ response = httppost(metacpan_api_url, metacpan_api_query)
373
393
  rescue Net::HTTPServerException => e
374
394
  #logger.error("metacpan query failed.", :error => response.status_line,
375
395
  #:module => package, :url => metacpan_url)
376
396
  logger.error("metacpan query failed.", :error => e.message,
377
- :module => package, :url => metacpan_url)
397
+ :module => module_name, :version => version, :url => metacpan_api_url)
378
398
  raise FPM::InvalidPackageConfiguration, "metacpan query failed"
379
399
  end
380
400
 
381
401
  #data = ""
382
402
  #response.read_body { |c| p c; data << c }
383
403
  data = response.body
384
- metadata = JSON.parse(data)
404
+ metadata = JSON.parse(data)['hits']['hits'][0]['_source']
385
405
  return metadata
386
- end # def metadata
406
+ end # def search
407
+
408
+ def search_provided_modules(distribution, version)
409
+ logger.info("Asking metacpan about a releases provided modules",
410
+ :distribution => distribution,
411
+ :version => version)
412
+ metacpan_api_url = "#{attributes[:cpan_metacpan_api_url]}/v1/module/_search?_source=module.name,module.version"
413
+ metacpan_api_query = <<-EOS
414
+ {
415
+ "query": {
416
+ "bool": {
417
+ "filter": [
418
+ { "exists": { "field": "module" } },
419
+ { "term": { "release": "#{distribution}-#{version}" } }
420
+ ]
421
+ }
422
+ },
423
+ "sort": [ { "module.name": { "order": "asc" } } ],
424
+ "size": 5000
425
+ }
426
+ EOS
427
+
428
+ begin
429
+ response = httppost(metacpan_api_url, metacpan_api_query)
430
+ rescue Net::HTTPServerException => e
431
+ logger.error("metacpan release query failed.", :error => e.message,
432
+ :url => metacpan_api_url)
433
+ raise FPM::InvalidPackageConfiguration, "metacpan release query failed"
434
+ end
435
+ query_hits = JSON.parse(response.body)['hits']['hits']
436
+
437
+ provided_modules = []
438
+ query_hits.each do |h|
439
+ h["_source"]["module"].each do |m|
440
+ module_name = m["name"]
441
+ module_version = m["version"]
442
+ Array(module_name).zip(Array(module_version)).each do |name, version|
443
+ provided_modules << cap_name(name) + (version ? " = #{version}" : "")
444
+ end
445
+ end
446
+ end
447
+ return provided_modules
448
+ end # def search_provided_modules
387
449
 
388
450
  def cap_name(name)
389
451
  return "perl(" + name.gsub("-", "::") + ")"
@@ -411,7 +473,7 @@ class FPM::Package::CPAN < FPM::Package
411
473
  when Net::HTTPRedirection; return httpfetch(response["location"])
412
474
  else; response.error!
413
475
  end
414
- end
476
+ end # def httpfetch
415
477
 
416
478
  def httppost(url, body)
417
479
  uri = URI.parse(url)
@@ -428,7 +490,7 @@ class FPM::Package::CPAN < FPM::Package
428
490
  when Net::HTTPRedirection; return httppost(response["location"])
429
491
  else; response.error!
430
492
  end
431
- end
493
+ end # def httppost
432
494
 
433
495
  public(:input)
434
- end # class FPM::Package::NPM
496
+ end # class FPM::Package::CPAN
@@ -542,6 +542,20 @@ class FPM::Package::Deb < FPM::Package
542
542
  raise FPM::InvalidPackageConfiguration, "#{name}: tar is insufficient to support source_date_epoch."
543
543
  end
544
544
 
545
+ systemd_file_extensions = [
546
+ ".service",
547
+ ".socket",
548
+ ".device",
549
+ ".mount",
550
+ ".automount",
551
+ ".swap",
552
+ ".target",
553
+ ".path",
554
+ ".timer",
555
+ ".slice",
556
+ ".scope",
557
+ ]
558
+
545
559
  attributes[:deb_systemd] = []
546
560
  attributes.fetch(:deb_systemd_list, []).each do |systemd|
547
561
  name = File.basename(systemd)
@@ -549,10 +563,16 @@ class FPM::Package::Deb < FPM::Package
549
563
 
550
564
  name_with_extension = if extname.empty?
551
565
  "#{name}.service"
552
- elsif [".service", ".timer"].include?(extname)
566
+ elsif systemd_file_extensions.include?(extname)
553
567
  name
554
568
  else
555
- raise FPM::InvalidPackageConfiguration, "Invalid systemd unit file extension: #{extname}. Expected .service or .timer, or no extension."
569
+ # Mutating the array is fine as we raise directly after, and the array will be re-initialised next time
570
+ # this method is called. If this branch is changed in the future so as not to diverge, care should be
571
+ # taken to ensure that this mutated version of the array is only used for generating the error message.
572
+ systemd_file_extensions[-1] = systemd_file_extensions[-1].prepend("or ")
573
+ possible_extensions_str = systemd_file_extensions.join(", ")
574
+ raise FPM::InvalidPackageConfiguration,
575
+ "Invalid systemd unit file extension: #{extname}. Expected one of: #{possible_extensions_str}"
556
576
  end
557
577
 
558
578
  dest_systemd = staging_path(File.join(attributes[:deb_systemd_path], "#{name_with_extension}"))
@@ -767,10 +787,19 @@ class FPM::Package::Deb < FPM::Package
767
787
  self.provides = self.provides.reject { |p| p.empty? }
768
788
 
769
789
  if origin == FPM::Package::CPAN
790
+
791
+ # By default, we'd prefer to name Debian-targeted Perl packages using the
792
+ # same naming scheme that Debian itself uses, which is usually something
793
+ # like "lib<module-name-hyphenated>-perl", such as libregexp-common-perl
794
+ #
795
+ logger.info("Changing package name to match Debian's typical libmodule-name-perl style")
796
+ self.name = "lib#{self.name.sub(/^perl-/, "")}-perl"
797
+
770
798
  # The fpm cpan code presents dependencies and provides fields as perl(ModuleName)
771
799
  # so we'll need to convert them to something debian supports.
772
800
 
773
- # Replace perl(ModuleName) > 1.0 with Debian-style perl-ModuleName (> 1.0)
801
+ # Replace perl(Module::Name) > 1.0 with Debian-style libmodule-name-perl (> 1.0)
802
+ # per: https://www.debian.org/doc/packaging-manuals/perl-policy/ch-module_packages.html
774
803
  perldepfix = lambda do |dep|
775
804
  m = dep.match(/perl\((?<name>[A-Za-z0-9_:]+)\)\s*(?<op>.*$)/)
776
805
  if m.nil?
@@ -781,7 +810,7 @@ class FPM::Package::Deb < FPM::Package
781
810
  modulename = m["name"].gsub("::", "-")
782
811
 
783
812
  # Fix any upper-casing or other naming concerns Debian has about packages
784
- name = "#{attributes[:cpan_package_name_prefix]}-#{modulename}"
813
+ name = "lib#{modulename}-perl"
785
814
 
786
815
  if m["op"].empty?
787
816
  name
@@ -792,9 +821,12 @@ class FPM::Package::Deb < FPM::Package
792
821
  end
793
822
  end
794
823
 
795
- rejects = [ "perl(vars)", "perl(warnings)", "perl(strict)", "perl(Config)" ]
824
+ rejects = attributes[:rejects].map do |skip|
825
+ "perl(#{skip})"
826
+ end
796
827
  self.dependencies = self.dependencies.reject do |dep|
797
828
  # Reject non-module Perl dependencies like 'vars' and 'warnings'
829
+ dep, _ = dep.split(' ') # strip potential version component
798
830
  rejects.include?(dep)
799
831
  end.collect(&perldepfix).collect(&method(:fix_dependency))
800
832
 
@@ -1151,6 +1183,7 @@ class FPM::Package::Deb < FPM::Package
1151
1183
  logger.debug("Adding config file #{path} to Staging area #{staging_path}")
1152
1184
  FileUtils.mkdir_p(File.dirname(dcl))
1153
1185
  FileUtils.cp_r path, dcl
1186
+ add_path(path, allconfigs)
1154
1187
  else
1155
1188
  logger.debug("Config file aready exists in staging area.")
1156
1189
  end
@@ -1173,7 +1206,7 @@ class FPM::Package::Deb < FPM::Package
1173
1206
  end
1174
1207
  upstarts.each do |upstart|
1175
1208
  name = File.basename(upstart, ".upstart")
1176
- upstartscript = "etc/init/#{name}.conf"
1209
+ upstartscript = "/etc/init/#{name}.conf"
1177
1210
  logger.debug("Add conf file declaration for upstart script", :script => upstartscript)
1178
1211
  allconfigs << upstartscript[1..-1]
1179
1212
  end
@@ -197,10 +197,6 @@ class FPM::Package::Dir < FPM::Package
197
197
  else
198
198
  # Otherwise try copying the file.
199
199
  begin
200
- logger.debug("Linking", :source => source, :destination => destination)
201
- File.link(source, destination)
202
- rescue Errno::ENOENT, Errno::EXDEV, Errno::EPERM
203
- # Hardlink attempt failed, copy it instead
204
200
  logger.debug("Copying", :source => source, :destination => destination)
205
201
  copy_entry(source, destination)
206
202
  rescue Errno::EEXIST
@@ -381,7 +381,7 @@ class FPM::Package::Gem < FPM::Package
381
381
  logger.debug("Gem %s did not have changelog with recognized name" % [name])
382
382
  # FIXME: check rubygems.org?
383
383
  end
384
- end # detect_source_date_from_changelog
384
+ end # def detect_source_date_from_changelog
385
385
 
386
386
  public(:input, :output)
387
387
  end # class FPM::Package::Gem
@@ -37,11 +37,15 @@ class FPM::Package::Pacman < FPM::Package
37
37
  def architecture
38
38
  case @architecture
39
39
  when nil
40
- return %x{uname -m}.chomp # default to current arch
41
- when "amd64" # debian and pacman disagree on architecture names
42
- return "x86_64"
40
+ return %x{uname -m}.chomp # default to current arch
41
+ when "amd64" # Debian uses amd64
42
+ return "x86_64" # Arch Linux uses x86_64
43
+ when "arm64" # Debian uses arm64
44
+ return "aarch64" # Arch Linux ARM uses aarch64
45
+ when "armhf" # Debian uses armhf
46
+ return "arm7hf" # Arch Linux ARM uses arm7hf
43
47
  when "native"
44
- return %x{uname -m}.chomp # 'native' is current arch
48
+ return %x{uname -m}.chomp # 'native' is the current arch
45
49
  when "all", "any", "noarch"
46
50
  return "any"
47
51
  else
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env python3
2
+
3
+ #import pkg_resources
4
+ import packaging.requirements
5
+ import packaging.markers
6
+ import json
7
+ import sys
8
+
9
+ # Expect requirements lines via stdin.
10
+ #requirements = pkg_resources.parse_requirements(sys.stdin)
11
+
12
+ # Process environment markers, if any, and produce a list of requirements for the current environment.
13
+ def evaluate_requirements(fd):
14
+ all_requirements = [packaging.requirements.Requirement(line) for line in sys.stdin]
15
+ default_env = packaging.markers.default_environment()
16
+ for req in all_requirements:
17
+ if req.marker is None or req.marker.evaluate(environment=default_env):
18
+ if len(req.specifier) > 0:
19
+ for spec in req.specifier:
20
+ yield "%s%s" % (req.name, spec)
21
+ else:
22
+ yield str(req.name)
23
+
24
+ print(json.dumps(list(evaluate_requirements(sys.stdin))))