fpm 1.8.1 → 1.9.1

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.
@@ -44,9 +44,27 @@ class FPM::Package::Gem < FPM::Package
44
44
  option "--disable-dependency", "gem_name",
45
45
  "The gem name to remove from dependency list",
46
46
  :multivalued => true, :attribute_name => :gem_disable_dependencies
47
+ option "--embed-dependencies", :flag, "Should the gem dependencies " \
48
+ "be installed?", :default => false
47
49
 
48
50
  option "--version-bins", :flag, "Append the version to the bins", :default => false
49
51
 
52
+ option "--stagingdir", "STAGINGDIR",
53
+ "The directory where fpm installs the gem temporarily before conversion. " \
54
+ "Normally a random subdirectory of workdir."
55
+
56
+ # Override parent method
57
+ def staging_path(path=nil)
58
+ @gem_staging_path ||= attributes[:gem_stagingdir] || Stud::Temporary.directory("package-#{type}-staging")
59
+ @staging_path = @gem_staging_path
60
+
61
+ if path.nil?
62
+ return @staging_path
63
+ else
64
+ return File.join(@staging_path, path)
65
+ end
66
+ end # def staging_path
67
+
50
68
  def input(gem)
51
69
  # 'arg' is the name of the rubygem we should unpack.
52
70
  path_to_gem = download_if_necessary(gem, version)
@@ -144,7 +162,7 @@ class FPM::Package::Gem < FPM::Package
144
162
  # composing multiple packages, it's best to explicitly include it in the provides list.
145
163
  self.provides << "#{self.name} = #{self.version}"
146
164
 
147
- if !attributes[:no_auto_depends?]
165
+ if !attributes[:no_auto_depends?] && !attributes[:gem_embed_dependencies?]
148
166
  spec.runtime_dependencies.map do |dep|
149
167
  # rubygems 1.3.5 doesn't have 'Gem::Dependency#requirement'
150
168
  if dep.respond_to?(:requirement)
@@ -181,7 +199,12 @@ class FPM::Package::Gem < FPM::Package
181
199
  ::FileUtils.mkdir_p(installdir)
182
200
  # TODO(sissel): Allow setting gem tool path
183
201
  args = [attributes[:gem_gem], "install", "--quiet", "--no-ri", "--no-rdoc",
184
- "--no-user-install", "--install-dir", installdir, "--ignore-dependencies"]
202
+ "--no-user-install", "--install-dir", installdir]
203
+
204
+ if !attributes[:gem_embed_dependencies?]
205
+ args += ["--ignore-dependencies"]
206
+ end
207
+
185
208
  if attributes[:gem_env_shebang?]
186
209
  args += ["-E"]
187
210
  end
@@ -231,6 +254,21 @@ class FPM::Package::Gem < FPM::Package
231
254
  FileUtils.mv("#{bin_path}/#{bin}", "#{bin_path}/#{bin}-#{self.version}")
232
255
  end
233
256
  end
257
+
258
+ if attributes[:source_date_epoch_from_changelog?]
259
+ detect_source_date_from_changelog(installdir)
260
+ end
261
+
262
+ # Remove generated Makefile and gem_make.out files, if any; they
263
+ # are not needed, and may contain generated paths that cause
264
+ # different output on successive runs.
265
+ Find.find(installdir) do |path|
266
+ if path =~ /.*(gem_make.out|Makefile|mkmf.log)$/
267
+ logger.info("Removing no longer needed file %s to reduce nondeterminism" % path)
268
+ File.unlink(path)
269
+ end
270
+ end
271
+
234
272
  end # def install_to_staging
235
273
 
236
274
  # Sanitize package name.
@@ -239,5 +277,75 @@ class FPM::Package::Gem < FPM::Package
239
277
  def fix_name(name)
240
278
  return [attributes[:gem_package_name_prefix], name].join("-")
241
279
  end # def fix_name
280
+
281
+ # Regular expression to accept a gem changelog line, and store date & version, if any, in named capture groups.
282
+ # Supports formats suggested by http://keepachangelog.com and https://github.com/tech-angels/vandamme
283
+ # as well as other similar formats that actually occur in the wild.
284
+ # Build it in pieces for readability, and allow version and date in either order.
285
+ # Whenever you change this, add a row to the test case in spec/fpm/package/gem_spec.rb.
286
+ # Don't even try to handle dates that lack four-digit years.
287
+ # Building blocks:
288
+ P_RE_LEADIN = '^[#=]{0,3}\s?'
289
+ P_RE_VERSION_ = '[\w\.-]+\.[\w\.-]+[a-zA-Z0-9]'
290
+ P_RE_SEPARATOR = '\s[-=/(]?\s?'
291
+ P_RE_DATE1 = '\d{4}-\d{2}-\d{2}'
292
+ P_RE_DATE2 = '\w+ \d{1,2}(?:st|nd|rd|th)?,\s\d{4}'
293
+ P_RE_DATE3 = '\w+\s+\w+\s+\d{1,2},\s\d{4}'
294
+ P_RE_DATE = "(?<date>#{P_RE_DATE1}|#{P_RE_DATE2}|#{P_RE_DATE3})"
295
+ P_RE_URL = '\(https?:[-\w/.%]*\)' # In parens, per markdown
296
+ P_RE_GTMAGIC = '\[\]' # github magic version diff, per chandler
297
+ P_RE_VERSION = "\\[?(?:Version |v)?(?<version>#{P_RE_VERSION_})\\]?(?:#{P_RE_URL}|#{P_RE_GTMAGIC})?"
298
+ # The final RE's:
299
+ P_RE_VERSION_DATE = "#{P_RE_LEADIN}#{P_RE_VERSION}#{P_RE_SEPARATOR}#{P_RE_DATE}"
300
+ P_RE_DATE_VERSION = "#{P_RE_LEADIN}#{P_RE_DATE}#{P_RE_SEPARATOR}#{P_RE_VERSION}"
301
+
302
+ # Detect release date, if found, store in attributes[:source_date_epoch]
303
+ def detect_source_date_from_changelog(installdir)
304
+ name = self.name.sub("rubygem-", "") + "-" + self.version
305
+ changelog = nil
306
+ datestr = nil
307
+ r1 = Regexp.new(P_RE_VERSION_DATE)
308
+ r2 = Regexp.new(P_RE_DATE_VERSION)
309
+
310
+ # Changelog doesn't have a standard name, so check all common variations
311
+ # Sort this list using LANG=C, i.e. caps first
312
+ [
313
+ "CHANGELIST",
314
+ "CHANGELOG", "CHANGELOG.asciidoc", "CHANGELOG.md", "CHANGELOG.rdoc", "CHANGELOG.rst", "CHANGELOG.txt",
315
+ "CHANGES", "CHANGES.md", "CHANGES.txt",
316
+ "ChangeLog", "ChangeLog.md", "ChangeLog.txt",
317
+ "Changelog", "Changelog.md", "Changelog.txt",
318
+ "changelog", "changelog.md", "changelog.txt",
319
+ ].each do |changelogname|
320
+ path = File.join(installdir, "gems", name, changelogname)
321
+ if File.exist?(path)
322
+ changelog = path
323
+ File.open path do |file|
324
+ file.each_line do |line|
325
+ if line =~ /#{self.version}/
326
+ [r1, r2].each do |r|
327
+ if r.match(line)
328
+ datestr = $~[:date]
329
+ break
330
+ end
331
+ end
332
+ end
333
+ end
334
+ end
335
+ end
336
+ end
337
+ if datestr
338
+ date = Date.parse(datestr)
339
+ sec = date.strftime("%s")
340
+ attributes[:source_date_epoch] = sec
341
+ logger.debug("Gem %s has changelog date %s, setting source_date_epoch to %s" % [name, datestr, sec])
342
+ elsif changelog
343
+ logger.debug("Gem %s changelog %s did not have recognizable date for release %s" % [name, changelog, self.version])
344
+ else
345
+ logger.debug("Gem %s did not have changelog with recognized name" % [name])
346
+ # FIXME: check rubygems.org?
347
+ end
348
+ end # detect_source_date_from_changelog
349
+
242
350
  public(:input, :output)
243
351
  end # class FPM::Package::Gem
@@ -10,7 +10,7 @@ require "pleaserun/cli"
10
10
  # This does not currently support 'output'
11
11
  class FPM::Package::PleaseRun < FPM::Package
12
12
  # TODO(sissel): Implement flags.
13
-
13
+
14
14
  require "pleaserun/platform/systemd"
15
15
  require "pleaserun/platform/upstart"
16
16
  require "pleaserun/platform/launchd"
@@ -28,6 +28,10 @@ class FPM::Package::PleaseRun < FPM::Package
28
28
  ::PleaseRun::Platform::Launchd.new("10.9"), # OS X
29
29
  ::PleaseRun::Platform::SYSV.new("lsb-3.1") # Ancient stuff
30
30
  ]
31
+ pleaserun_attributes = [ "chdir", "user", "group", "umask", "chroot", "nice", "limit_coredump",
32
+ "limit_cputime", "limit_data", "limit_file_size", "limit_locked_memory",
33
+ "limit_open_files", "limit_user_processes", "limit_physical_memory", "limit_stack_size",
34
+ "log_directory", "log_file_stderr", "log_file_stdout"]
31
35
 
32
36
  attributes[:pleaserun_name] ||= File.basename(command.first)
33
37
  attributes[:prefix] ||= "/usr/share/pleaserun/#{attributes[:pleaserun_name]}"
@@ -37,12 +41,18 @@ class FPM::Package::PleaseRun < FPM::Package
37
41
  platform.program = command.first
38
42
  platform.name = attributes[:pleaserun_name]
39
43
  platform.args = command[1..-1]
40
- platform.chdir = attributes[:pleaserun_chdir] if attributes[:pleaserun_chdir]
41
44
  platform.description = if attributes[:description_given?]
42
45
  attributes[:description]
43
46
  else
44
47
  platform.name
45
48
  end
49
+ pleaserun_attributes.each do |attribute_name|
50
+ attribute = "pleaserun_#{attribute_name}".to_sym
51
+ if attributes.has_key?(attribute) and not attributes[attribute].nil?
52
+ platform.send("#{attribute_name}=", attributes[attribute])
53
+ end
54
+ end
55
+
46
56
  base = staging_path(File.join(attributes[:prefix], "#{platform.platform}/#{platform.target_version || "default"}"))
47
57
  target = File.join(base, "files")
48
58
  actions_script = File.join(base, "install_actions.sh")
@@ -73,6 +73,10 @@ class FPM::Package::Python < FPM::Package
73
73
  "The python package name to remove from dependency list",
74
74
  :multivalued => true, :attribute_name => :python_disable_dependency,
75
75
  :default => []
76
+ option "--setup-py-arguments", "setup_py_argument",
77
+ "Arbitrary argument(s) to be passed to setup.py",
78
+ :multivalued => true, :attribute_name => :python_setup_py_arguments,
79
+ :default => []
76
80
 
77
81
  private
78
82
 
@@ -310,6 +314,13 @@ class FPM::Package::Python < FPM::Package
310
314
  flags += [ "build_scripts", "--executable", attributes[:python_scripts_executable] ]
311
315
  end
312
316
 
317
+ if !attributes[:python_setup_py_arguments].nil? and !attributes[:python_setup_py_arguments].empty?
318
+ # Add optional setup.py arguments
319
+ attributes[:python_setup_py_arguments].each do |a|
320
+ flags += [ a ]
321
+ end
322
+ end
323
+
313
324
  safesystem(attributes[:python_bin], "setup.py", "install", *flags)
314
325
  end
315
326
  end # def install_to_staging
@@ -472,6 +472,15 @@ class FPM::Package::RPM < FPM::Package
472
472
  self.directories = alldirs
473
473
  end
474
474
 
475
+ # include external config files
476
+ (attributes[:config_files] or []).each do |conf|
477
+ path = conf
478
+ dest_conf = File.join(staging_path, path)
479
+ FileUtils.mkdir_p(File.dirname(dest_conf))
480
+ FileUtils.cp_r conf, dest_conf
481
+ File.chmod(0755, dest_conf)
482
+ end
483
+
475
484
  # scan all conf file paths for files and add them
476
485
  allconfigs = []
477
486
  self.config_files.each do |path|
@@ -50,15 +50,7 @@ class FPM::Package::Tar < FPM::Package
50
50
  output_check(output_path)
51
51
 
52
52
  # Write the scripts, too.
53
- scripts_path = File.join(staging_path, ".scripts")
54
- ::Dir.mkdir(scripts_path)
55
- [:before_install, :after_install, :before_remove, :after_remove].each do |name|
56
- next unless script?(name)
57
- out = File.join(scripts_path, name.to_s)
58
- logger.debug("Writing script", :source => name, :target => out)
59
- File.write(out, script(name))
60
- File.chmod(0755, out)
61
- end
53
+ write_scripts
62
54
 
63
55
  # Unpack the tarball to the staging path
64
56
  args = ["-cf", output_path, "-C", staging_path]
@@ -15,8 +15,11 @@ class FPM::Package::Virtualenv < FPM::Package
15
15
  option "--package-name-prefix", "PREFIX", "Name to prefix the package " \
16
16
  "name with.", :default => "virtualenv"
17
17
 
18
- option "--install-location", "DIRECTORY", "Location to which to " \
19
- "install the virtualenv by default.", :default => "/usr/share/python" do |path|
18
+ option "--install-location", "DIRECTORY", "DEPRECATED: Use --prefix instead." \
19
+ " Location to which to install the virtualenv by default.",
20
+ :default => "/usr/share/python" do |path|
21
+ logger.warn("Using deprecated flag: --install-location. Please use " \
22
+ "--prefix instead.")
20
23
  File.expand_path(path)
21
24
  end
22
25
 
@@ -37,6 +40,12 @@ class FPM::Package::Virtualenv < FPM::Package
37
40
  option "--system-site-packages", :flag, "Give the virtual environment access to the "\
38
41
  "global site-packages"
39
42
 
43
+ option "--find-links", "PIP_FIND_LINKS", "If a url or path to an html file, then parse for "\
44
+ "links to archives. If a local path or file:// url that's a directory, then look "\
45
+ "for archives in the directory listing.",
46
+ :multivalued => true, :attribute_name => :virtualenv_find_links_urls,
47
+ :default => nil
48
+
40
49
  private
41
50
 
42
51
  # Input a package.
@@ -76,9 +85,14 @@ class FPM::Package::Virtualenv < FPM::Package
76
85
  self.name].join("-")
77
86
  end
78
87
 
88
+ # prefix wins over previous virtual_install_location behaviour
79
89
  virtualenv_folder =
80
- File.join(installdir,
81
- virtualenv_name)
90
+ if self.attributes[:prefix]
91
+ self.attributes[:prefix]
92
+ else
93
+ File.join(installdir,
94
+ virtualenv_name)
95
+ end
82
96
 
83
97
  virtualenv_build_folder = build_path(virtualenv_folder)
84
98
 
@@ -107,6 +121,13 @@ class FPM::Package::Virtualenv < FPM::Package
107
121
  end
108
122
  end
109
123
 
124
+ find_links_url_args = []
125
+ if attributes[:virtualenv_find_links_urls]
126
+ attributes[:virtualenv_find_links_urls].each do |links_url|
127
+ find_links_url_args << "--find-links" << links_url
128
+ end
129
+ end
130
+
110
131
  target_args = []
111
132
  if is_requirements_file
112
133
  target_args << "-r" << package
@@ -114,7 +135,7 @@ class FPM::Package::Virtualenv < FPM::Package
114
135
  target_args << package
115
136
  end
116
137
 
117
- pip_args = [python_exe, pip_exe, "install", "-i", attributes[:virtualenv_pypi]] << extra_index_url_args << target_args
138
+ pip_args = [python_exe, pip_exe, "install", "-i", attributes[:virtualenv_pypi]] << extra_index_url_args << find_links_url_args << target_args
118
139
  safesystem(*pip_args.flatten)
119
140
 
120
141
  if attributes[:virtualenv_setup_install?]
@@ -155,7 +176,8 @@ class FPM::Package::Virtualenv < FPM::Package
155
176
  # use dir to set stuff up properly, mainly so I don't have to reimplement
156
177
  # the chdir/prefix stuff special for tar.
157
178
  dir = convert(FPM::Package::Dir)
158
-
179
+ # don't double prefix the files
180
+ dir.attributes[:prefix] = nil
159
181
  if attributes[:chdir]
160
182
  dir.attributes[:chdir] = File.join(build_path, attributes[:chdir])
161
183
  else
@@ -36,28 +36,12 @@ class FPM::Package::Zip < FPM::Package
36
36
  dir.cleanup_build
37
37
  end # def input
38
38
 
39
- # Output a tarball.
40
- #
41
- # If the output path ends predictably (like in .tar.gz) it will try to obey
42
- # the compression type.
39
+ # Output a zipfile.
43
40
  def output(output_path)
44
41
  output_check(output_path)
45
-
46
- files = Find.find(staging_path).to_a
47
- safesystem("zip", output_path, *files)
48
- end # def output
49
-
50
- # Generate the proper tar flags based on the path name.
51
- def tar_compression_flag(path)
52
- case path
53
- when /\.tar\.bz2$/
54
- return "-j"
55
- when /\.tar\.gz$|\.tgz$/
56
- return "-z"
57
- when /\.tar\.xz$/
58
- return "-J"
59
- else
60
- return nil
42
+ realpath = Pathname.new(output_path).realdirpath.to_s
43
+ ::Dir.chdir(staging_path) do
44
+ safesystem("zip", "-9r", realpath, ".")
61
45
  end
62
- end # def tar_compression_flag
46
+ end # def output
63
47
  end # class FPM::Package::Tar
@@ -1,6 +1,7 @@
1
1
  require "fpm/namespace"
2
2
  require "childprocess"
3
3
  require "ffi"
4
+ require "fileutils"
4
5
 
5
6
  # Some utility functions
6
7
  module FPM::Util
@@ -190,9 +191,14 @@ module FPM::Util
190
191
  if args.size == 1
191
192
  args = [ default_shell, "-c", args[0] ]
192
193
  end
193
- program = args[0]
194
194
 
195
- exit_code = execmd(args)
195
+ if args[0].kind_of?(Hash)
196
+ env = args.shift()
197
+ exit_code = execmd(env, args)
198
+ else
199
+ exit_code = execmd(args)
200
+ end
201
+ program = args[0]
196
202
  success = (exit_code == 0)
197
203
 
198
204
  if !success
@@ -226,26 +232,90 @@ module FPM::Util
226
232
  return stdout_r_str
227
233
  end # def safesystemout
228
234
 
235
+ # Get an array containing the recommended 'ar' command for this platform
236
+ # and the recommended options to quickly create/append to an archive
237
+ # without timestamps or uids (if possible).
238
+ def ar_cmd
239
+ return @@ar_cmd if defined? @@ar_cmd
240
+
241
+ @@ar_cmd_deterministic = false
242
+
243
+ # FIXME: don't assume current directory writeable
244
+ FileUtils.touch(["fpm-dummy.tmp"])
245
+ ["ar", "gar"].each do |ar|
246
+ ["-qc", "-qcD"].each do |ar_create_opts|
247
+ FileUtils.rm_f(["fpm-dummy.ar.tmp"])
248
+ # Return this combination if it creates archives without uids or timestamps.
249
+ # Exitstatus will be nonzero if the archive can't be created,
250
+ # or its table of contents doesn't match the regular expression.
251
+ # Be extra-careful about locale and timezone when matching output.
252
+ system("#{ar} #{ar_create_opts} fpm-dummy.ar.tmp fpm-dummy.tmp 2>/dev/null && env TZ=UTC LANG=C LC_TIME=C #{ar} -tv fpm-dummy.ar.tmp | grep '0/0.*1970' > /dev/null 2>&1")
253
+ if $?.exitstatus == 0
254
+ @@ar_cmd = [ar, ar_create_opts]
255
+ @@ar_cmd_deterministic = true
256
+ return @@ar_cmd
257
+ end
258
+ end
259
+ end
260
+ # If no combination of ar and options omits timestamps, fall back to default.
261
+ @@ar_cmd = ["ar", "-qc"]
262
+ return @@ar_cmd
263
+ ensure
264
+ # Clean up
265
+ FileUtils.rm_f(["fpm-dummy.ar.tmp", "fpm-dummy.tmp"])
266
+ end # def ar_cmd
267
+
268
+ # Return whether the command returned by ar_cmd can create deterministic archives
269
+ def ar_cmd_deterministic?
270
+ ar_cmd if not defined? @@ar_cmd_deterministic
271
+ return @@ar_cmd_deterministic
272
+ end
273
+
229
274
  # Get the recommended 'tar' command for this platform.
230
275
  def tar_cmd
231
- # Rely on gnu tar for solaris and OSX.
232
- case %x{uname -s}.chomp
233
- when "SunOS"
234
- return "gtar"
235
- when "Darwin"
236
- # Try running gnutar, it was renamed(??) in homebrew to 'gtar' at some point, I guess? I don't know.
237
- ["gnutar", "gtar"].each do |tar|
238
- system("#{tar} > /dev/null 2> /dev/null")
239
- return tar unless $?.exitstatus == 127
276
+ return @@tar_cmd if defined? @@tar_cmd
277
+
278
+ # FIXME: don't assume current directory writeable
279
+ FileUtils.touch(["fpm-dummy.tmp"])
280
+
281
+ # Prefer tar that supports more of the features we want, stop if we find tar of our dreams
282
+ best="tar"
283
+ bestscore=0
284
+ @@tar_cmd_deterministic = false
285
+ # GNU Tar, if not the default, is usually on the path as gtar, but
286
+ # Mac OS X 10.8 and earlier shipped it as /usr/bin/gnutar
287
+ ["tar", "gtar", "gnutar"].each do |tar|
288
+ opts=[]
289
+ score=0
290
+ ["--sort=name", "--mtime=@0"].each do |opt|
291
+ system("#{tar} #{opt} -cf fpm-dummy.tar.tmp fpm-dummy.tmp > /dev/null 2>&1")
292
+ if $?.exitstatus == 0
293
+ opts << opt
294
+ score += 1
295
+ end
296
+ end
297
+ if score > bestscore
298
+ best=tar
299
+ bestscore=score
300
+ if score == 2
301
+ @@tar_cmd_deterministic = true
302
+ break
303
+ end
240
304
  end
241
- when "FreeBSD"
242
- # use gnutar instead
243
- return "gtar"
244
- else
245
- return "tar"
246
305
  end
306
+ @@tar_cmd = best
307
+ return @@tar_cmd
308
+ ensure
309
+ # Clean up
310
+ FileUtils.rm_f(["fpm-dummy.tar.tmp", "fpm-dummy.tmp"])
247
311
  end # def tar_cmd
248
312
 
313
+ # Return whether the command returned by tar_cmd can create deterministic archives
314
+ def tar_cmd_supports_sort_names_and_set_mtime?
315
+ tar_cmd if not defined? @@tar_cmd_deterministic
316
+ return @@tar_cmd_deterministic
317
+ end
318
+
249
319
  # wrapper around mknod ffi calls
250
320
  def mknod_w(path, mode, dev)
251
321
  rc = -1
@@ -298,8 +368,8 @@ module FPM::Util
298
368
  if known_entry
299
369
  FileUtils.ln(known_entry, dst)
300
370
  else
301
- FileUtils.copy_entry(src, dst, preserve=preserve,
302
- remove_destination=remove_destination)
371
+ FileUtils.copy_entry(src, dst, preserve, false,
372
+ remove_destination)
303
373
  copied_entries[[st.dev, st.ino]] = dst
304
374
  end
305
375
  end # else...