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.
@@ -156,7 +156,7 @@ class FPM::Package::RPM < FPM::Package
156
156
 
157
157
  option "--macro-expansion", :flag,
158
158
  "install-time macro expansion in %pre %post %preun %postun scripts " \
159
- "(see: https://rpm.org/user_doc/scriptlet_expansion.html)", :default => false
159
+ "(see: https://rpm-software-management.github.io/rpm/manual/scriptlet_expansion.html)", :default => false
160
160
 
161
161
  option "--verifyscript", "FILE",
162
162
  "a script to be run on verification" do |val|
@@ -175,7 +175,7 @@ class FPM::Package::RPM < FPM::Package
175
175
  rpm_trigger = []
176
176
  option "--trigger-#{trigger_type}", "'[OPT]PACKAGE: FILEPATH'", "Adds a rpm trigger script located in FILEPATH, " \
177
177
  "having 'OPT' options and linking to 'PACKAGE'. PACKAGE can be a comma seperated list of packages. " \
178
- "See: http://rpm.org/api/4.4.2.2/triggers.html" do |trigger|
178
+ "See: https://rpm-software-management.github.io/rpm/manual/triggers.html" do |trigger|
179
179
  match = trigger.match(/^(\[.*\]|)(.*): (.*)$/)
180
180
  @logger.fatal("Trigger '#{trigger_type}' definition can't be parsed ('#{trigger}')") unless match
181
181
  opt, pkg, file = match.captures
@@ -199,8 +199,8 @@ class FPM::Package::RPM < FPM::Package
199
199
  # If and only if any of the above are done, then also replace ' with \', " with \", and \ with \\\\
200
200
  # to accommodate escape and quote processing that rpm will perform in that case (but not otherwise)
201
201
  def rpm_fix_name(name)
202
- if name.match?(/[ \t*?%$\[\]]/)
203
- name = name.gsub(/(\ |\t|\[|\]|\*|\?|\%|\$|'|"|\\)/, {
202
+ if name.match?(/[ \t*?%${}\[\]]/)
203
+ name = name.gsub(/(\ |\t|\[|\]|\*|\?|\%|\$|'|"|\{|\}|\\)/, {
204
204
  ' ' => '?',
205
205
  "\t" => '?',
206
206
  '%' => '[%]',
@@ -209,6 +209,10 @@ class FPM::Package::RPM < FPM::Package
209
209
  '*' => '[*]',
210
210
  '[' => '[\[]',
211
211
  ']' => '[\]]',
212
+ #'{' => '[\{]',
213
+ #'}' => '[\}]',
214
+ '{' => '?',
215
+ '}' => '?',
212
216
  '"' => '\\"',
213
217
  "'" => "\\'",
214
218
  '\\' => '\\\\\\\\',
@@ -36,7 +36,7 @@ class FPM::Package::Virtualenv < FPM::Package
36
36
  :default => nil
37
37
 
38
38
  option "--setup-install", :flag, "After building virtualenv run setup.py install "\
39
- "useful when building a virtualenv for packages and including their requirements from "
39
+ "useful when building a virtualenv for packages and including their requirements from "\
40
40
  "requirements.txt"
41
41
 
42
42
  option "--system-site-packages", :flag, "Give the virtual environment access to the "\
@@ -158,6 +158,31 @@ class FPM::Package::Virtualenv < FPM::Package
158
158
  end
159
159
  end
160
160
 
161
+ # [2025-09-30] virtualenv-tools seems broken?
162
+ # The --update-path will look for a VIRTUAL_ENV= line in bin/activate,
163
+ # however, the version I tested looks for it with quotations, like VIRTUAL_ENV='
164
+ # And at time of writing, my `virtualenv` tool doesn't use quotations on this variable
165
+ #
166
+ # Maybe best case we can patch it here instead. The path update tool
167
+ # looks for the original virtualenv path and I think updates any bin
168
+ # files which point to it.
169
+ patched = []
170
+ activate_bin = File.join(virtualenv_build_folder, "bin/activate")
171
+ fd = File.open(activate_bin)
172
+ fd.each_line do |line|
173
+ re = /^VIRTUAL_ENV=([^'"].*)$/
174
+ match = line.match(re)
175
+ if match
176
+ # Quote the VIRTUAL_ENV var assignment to help virtualenv-tools work?
177
+ patched << "VIRTUAL_ENV='#{match}'\n"
178
+ else
179
+ patched << line
180
+ end
181
+ end
182
+ fd.close
183
+ File.write(activate_bin, patched.join)
184
+
185
+ # Rewrite the base path inside the virtualenv to prepare it to be packaged.
161
186
  ::Dir.chdir(virtualenv_build_folder) do
162
187
  safesystem("virtualenv-tools", "--update-path", virtualenv_folder)
163
188
  end
@@ -191,7 +216,6 @@ class FPM::Package::Virtualenv < FPM::Package
191
216
  dir.input(".")
192
217
  @staging_path = dir.staging_path
193
218
  dir.cleanup_build
194
-
195
219
  end # def input
196
220
 
197
221
  # Delete python precompiled files found in a given folder.
data/lib/fpm/package.rb CHANGED
@@ -3,7 +3,6 @@ require "fpm/util" # local
3
3
  require "pathname" # stdlib
4
4
  require "find"
5
5
  require "tmpdir" # stdlib
6
- require "ostruct"
7
6
  require "backports/latest"
8
7
  require "socket" # stdlib, for Socket.gethostname
9
8
  require "shellwords" # stdlib, for Shellwords.escape
@@ -227,7 +226,7 @@ class FPM::Package
227
226
  def converted_from(origin)
228
227
  # nothing to do by default. Subclasses may implement this.
229
228
  # See the RPM package class for an example.
230
- end # def converted
229
+ end # def converted_from
231
230
 
232
231
  # Add a new source to this package.
233
232
  # The exact behavior depends on the kind of package being managed.
@@ -325,7 +324,7 @@ class FPM::Package
325
324
 
326
325
  def template_dir
327
326
  File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "templates"))
328
- end
327
+ end # def template_dir
329
328
 
330
329
  def template(path)
331
330
  template_path = File.join(template_dir, path)
@@ -438,7 +437,7 @@ class FPM::Package
438
437
 
439
438
  help = "(#{type} only) #{help}"
440
439
  @options << [flag, param, help, options, block]
441
- end # def options
440
+ end # def option
442
441
 
443
442
  # Apply the options for this package on the clamp command
444
443
  #
@@ -511,7 +510,7 @@ class FPM::Package
511
510
  File.chmod(0755, out)
512
511
  end
513
512
  end
514
- end
513
+ end # def write_scripts
515
514
 
516
515
  # Get the contents of the script by a given name.
517
516
  #
@@ -548,7 +547,7 @@ class FPM::Package
548
547
  else
549
548
  @provides = value
550
549
  end
551
- end
550
+ end # def provides=
552
551
 
553
552
  # General public API
554
553
  public(:type, :initialize, :convert, :input, :output, :to_s, :cleanup, :files,
data/lib/fpm/rake_task.rb CHANGED
@@ -1,13 +1,39 @@
1
1
  require "fpm/namespace"
2
- require "ostruct"
3
2
  require "rake"
4
3
  require "rake/tasklib"
5
4
 
6
5
  class FPM::RakeTask < Rake::TaskLib
6
+ class Options
7
+ attr_accessor :args
8
+
9
+ def initialize(defaults=nil)
10
+ if defaults.nil?
11
+ @h = Hash.new
12
+ else
13
+ @h = defaults
14
+ end
15
+ end
16
+
17
+ def method_missing(m, *args)
18
+ if m.end_with?("=")
19
+ raise ArgumentError, "#{self.class.name}##{m} ... Expected 1 arg, got #{args.length}" if args.length != 1
20
+ @h[m[0...-1]] = args[0]
21
+ else
22
+ raise ArgumentError, "Expected 0 arg, got #{args.length}" if args.length != 0
23
+ return @h[m]
24
+ end
25
+ end
26
+
27
+ def to_h
28
+ return @h
29
+ end
30
+ end # Options
31
+
7
32
  attr_reader :options
8
33
 
9
34
  def initialize(package_name, opts = {}, &block)
10
- @options = OpenStruct.new(:name => package_name.to_s)
35
+ #@options = OpenStruct.new(:name => package_name.to_s)
36
+ @options = Options.new(:name => package_name.to_s)
11
37
  @source, @target = opts.values_at(:source, :target).map(&:to_s)
12
38
  @directory = File.expand_path(opts[:directory].to_s)
13
39
 
@@ -18,8 +44,8 @@ class FPM::RakeTask < Rake::TaskLib
18
44
 
19
45
  task(options.name) do |_, task_args|
20
46
  block.call(*[options, task_args].first(block.arity)) if block_given?
21
- abort("Must specify args") unless options.respond_to?(:args)
22
- @args = options.delete_field(:args)
47
+ abort("Must specify args") if options.args.nil?
48
+ @args = options.args
23
49
  run_cli
24
50
  end
25
51
  end
data/lib/fpm/util.rb CHANGED
@@ -38,7 +38,7 @@ module FPM::Util
38
38
  shell = ENV["SHELL"]
39
39
  return "/bin/sh" if shell.nil? || shell.empty?
40
40
  return shell
41
- end
41
+ end # def default_shell
42
42
 
43
43
  ############################################################################
44
44
  # execmd([env,] cmd [,opts])
@@ -136,7 +136,7 @@ module FPM::Util
136
136
  raise ExecutableNotFound.new(program)
137
137
  end
138
138
 
139
- logger.debug("Running command", :args => args2)
139
+ logger.info("Running command", :args => args2)
140
140
 
141
141
  stdout_r, stdout_w = IO.pipe
142
142
  stderr_r, stderr_w = IO.pipe
@@ -250,7 +250,7 @@ module FPM::Util
250
250
  end
251
251
  end
252
252
  # If no combination of ar and options omits timestamps, fall back to default.
253
- @@ar_cmd = ["ar", "-qc"]
253
+ @@ar_cmd = ["ar", "-qcS"]
254
254
  FileUtils.rm_f([testarchive, emptyfile])
255
255
  return @@ar_cmd
256
256
  end # def ar_cmd
@@ -259,7 +259,7 @@ module FPM::Util
259
259
  def ar_cmd_deterministic?
260
260
  ar_cmd if not defined? @@ar_cmd_deterministic
261
261
  return @@ar_cmd_deterministic
262
- end
262
+ end # def ar_cmd_deterministic?
263
263
 
264
264
  # Get the recommended 'tar' command for this platform.
265
265
  def tar_cmd
@@ -306,7 +306,7 @@ module FPM::Util
306
306
  def tar_cmd_supports_sort_names_and_set_mtime?
307
307
  tar_cmd if not defined? @@tar_cmd_deterministic
308
308
  return @@tar_cmd_deterministic
309
- end
309
+ end # def tar_cmd_supports_sort_names_and_set_mtime?
310
310
 
311
311
  def copy_metadata(source, destination)
312
312
  source_stat = File::lstat(source)
@@ -332,7 +332,15 @@ module FPM::Util
332
332
 
333
333
 
334
334
  def copy_entry(src, dst, preserve=false, remove_destination=false)
335
- case File.ftype(src)
335
+ st = File.lstat(src)
336
+
337
+ filetype = if st.ftype == "file" && st.nlink > 1
338
+ "hardlink"
339
+ else
340
+ st.ftype
341
+ end
342
+
343
+ case filetype
336
344
  when 'fifo'
337
345
  if File.respond_to?(:mkfifo)
338
346
  File.mkfifo(dst)
@@ -350,18 +358,23 @@ module FPM::Util
350
358
  raise UnsupportedSpecialFile.new("File is device which fpm doesn't know how to copy (#{File.ftype(src)}): #{src}")
351
359
  when 'directory'
352
360
  FileUtils.mkdir(dst) unless File.exist? dst
353
- else
354
- # if the file with the same dev and inode has been copied already -
361
+ when 'hardlink'
362
+ # Handle hardlinks
363
+ # if the file with the same dev and inode has been copied already.
355
364
  # hard link it's copy to `dst`, otherwise make an actual copy
356
- st = File.lstat(src)
357
365
  known_entry = copied_entries[[st.dev, st.ino]]
358
366
  if known_entry
359
367
  FileUtils.ln(known_entry, dst)
368
+ logger.debug("Copying hardlink", :src => src, :dst => dst, :link => known_entry)
360
369
  else
361
370
  FileUtils.copy_entry(src, dst, preserve, false,
362
371
  remove_destination)
363
372
  copied_entries[[st.dev, st.ino]] = dst
364
373
  end
374
+ else
375
+ # Normal file, just copy it.
376
+ FileUtils.copy_entry(src, dst, preserve, false,
377
+ remove_destination)
365
378
  end # else...
366
379
  end # def copy_entry
367
380
 
@@ -439,7 +452,7 @@ module FPM::Util
439
452
  # Ruby 3.1.0 and newer
440
453
  return ERB.new(template_code, trim_mode: "-")
441
454
  end
442
- end
455
+ end # def erbnew
443
456
  end # module FPM::Util
444
457
 
445
458
  require 'fpm/util/tar_writer'
data/lib/fpm/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module FPM
2
- VERSION = "1.16.0"
2
+ VERSION = "1.18.0"
3
3
  end
data/templates/sh.erb CHANGED
@@ -137,7 +137,7 @@ function save_environment(){
137
137
 
138
138
  # just piping env to a file doesn't quote the variables. This does
139
139
  # filter out multiline junk, _, and functions. _ is a readonly variable.
140
- env | grep -v "^_=" | grep -v "^[^=(]*()=" | egrep "^[^ ]+=" | while read ENVVAR ; do
140
+ env | grep -v "^_=" | grep -v "^[^=(]*()=" | grep -v "^BASH_FUNC_.*%%=" |egrep "^[^ ]+=" | while read ENVVAR ; do
141
141
  local NAME=${ENVVAR%%=*}
142
142
  # sed is to preserve variable values with dollars (for escaped variables or $() style command replacement),
143
143
  # and command replacement backticks
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fpm
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.16.0
4
+ version: 1.18.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jordan Sissel
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-12-09 00:00:00.000000000 Z
11
+ date: 2026-08-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cabin
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 0.6.0
19
+ version: 0.9.1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 0.6.0
26
+ version: 0.9.1
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: backports
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -114,14 +114,14 @@ dependencies:
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: 3.0.0
117
+ version: 3.13.0
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: 3.0.0
124
+ version: 3.13.0
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: insist
127
127
  requirement: !ruby/object:Gem::Requirement
@@ -198,7 +198,7 @@ files:
198
198
  - lib/fpm/package/pleaserun.rb
199
199
  - lib/fpm/package/puppet.rb
200
200
  - lib/fpm/package/pyfpm/__init__.py
201
- - lib/fpm/package/pyfpm/get_metadata.py
201
+ - lib/fpm/package/pyfpm/parse_requires.py
202
202
  - lib/fpm/package/python.rb
203
203
  - lib/fpm/package/rpm.rb
204
204
  - lib/fpm/package/sh.rb
@@ -238,7 +238,7 @@ homepage: https://github.com/jordansissel/fpm
238
238
  licenses:
239
239
  - MIT-like
240
240
  metadata: {}
241
- post_install_message:
241
+ post_install_message:
242
242
  rdoc_options: []
243
243
  require_paths:
244
244
  - lib
@@ -254,8 +254,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
254
254
  - !ruby/object:Gem::Version
255
255
  version: '0'
256
256
  requirements: []
257
- rubygems_version: 3.2.22
258
- signing_key:
257
+ rubygems_version: 3.0.3.1
258
+ signing_key:
259
259
  specification_version: 4
260
260
  summary: fpm - package building and mangling
261
261
  test_files: []
@@ -1,115 +0,0 @@
1
- from distutils.core import Command
2
- import os
3
- import sys
4
- import pkg_resources
5
- try:
6
- import json
7
- except ImportError:
8
- import simplejson as json
9
-
10
- PY3 = sys.version_info[0] == 3
11
-
12
- if PY3:
13
- def u(s):
14
- return s
15
- else:
16
- def u(s):
17
- if isinstance(u, unicode):
18
- return u
19
- return s.decode('utf-8')
20
-
21
-
22
- # Note, the last time I coded python daily was at Google, so it's entirely
23
- # possible some of my techniques below are outdated or bad.
24
- # If you have fixes, let me know.
25
-
26
-
27
- class get_metadata(Command):
28
- description = "get package metadata"
29
- user_options = [
30
- ('load-requirements-txt', 'l',
31
- "load dependencies from requirements.txt"),
32
- ("output=", "o", "output destination for metadata json")
33
- ]
34
- boolean_options = ['load-requirements-txt']
35
-
36
- def initialize_options(self):
37
- self.load_requirements_txt = False
38
- self.cwd = None
39
- self.output = None
40
-
41
- def finalize_options(self):
42
- self.cwd = os.getcwd()
43
- self.requirements_txt = os.path.join(self.cwd, "requirements.txt")
44
- # make sure we have a requirements.txt
45
- if self.load_requirements_txt:
46
- self.load_requirements_txt = os.path.exists(self.requirements_txt)
47
-
48
- def process_dep(self, dep):
49
- deps = []
50
- if hasattr(dep, 'marker') and dep.marker:
51
- # PEP0508 marker present
52
- if not dep.marker.evaluate():
53
- return deps
54
-
55
- if dep.specs:
56
- for operator, version in dep.specs:
57
- deps.append("%s %s %s" % (dep.project_name,
58
- operator, version))
59
- else:
60
- deps.append(dep.project_name)
61
-
62
- return deps
63
-
64
- def run(self):
65
- data = {
66
- "name": self.distribution.get_name(),
67
- "version": self.distribution.get_version(),
68
- "author": u("%s <%s>") % (
69
- u(self.distribution.get_author()),
70
- u(self.distribution.get_author_email()),
71
- ),
72
- "description": self.distribution.get_description(),
73
- "license": self.distribution.get_license(),
74
- "url": self.distribution.get_url(),
75
- }
76
-
77
- if self.distribution.has_ext_modules():
78
- data["architecture"] = "native"
79
- else:
80
- data["architecture"] = "all"
81
-
82
- final_deps = []
83
-
84
- if self.load_requirements_txt:
85
- requirement = open(self.requirements_txt).readlines()
86
- for dep in pkg_resources.parse_requirements(requirement):
87
- final_deps.extend(self.process_dep(dep))
88
- else:
89
- if getattr(self.distribution, 'install_requires', None):
90
- for dep in pkg_resources.parse_requirements(
91
- self.distribution.install_requires):
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))
98
-
99
- data["dependencies"] = final_deps
100
-
101
- output = open(self.output, "w")
102
- if hasattr(json, 'dumps'):
103
- def default_to_str(obj):
104
- """ Fall back to using __str__ if possible """
105
- # This checks if the class of obj defines __str__ itself,
106
- # so we don't fall back to an inherited __str__ method.
107
- if "__str__" in type(obj).__dict__:
108
- return str(obj)
109
- return json.JSONEncoder.default(self, obj)
110
-
111
- output.write(json.dumps(data, indent=2, default=default_to_str))
112
- else:
113
- # For Python 2.5 and Debian's python-json
114
- output.write(json.write(data))
115
- output.close()