fpm 1.3.3 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -90,7 +90,15 @@ class get_metadata(Command):
90
90
 
91
91
  output = open(self.output, "w")
92
92
  if hasattr(json, 'dumps'):
93
- output.write(json.dumps(data, indent=2))
93
+ def default_to_str(obj):
94
+ """ Fall back to using __str__ if possible """
95
+ # This checks if the class of obj defines __str__ itself,
96
+ # so we don't fall back to an inherited __str__ method.
97
+ if "__str__" in type(obj).__dict__:
98
+ return str(obj)
99
+ return json.JSONEncoder.default(self, obj)
100
+
101
+ output.write(json.dumps(data, indent=2, default=default_to_str))
94
102
  else:
95
103
  # For Python 2.5 and Debian's python-json
96
104
  output.write(json.write(data))
@@ -7,7 +7,7 @@ require "fileutils"
7
7
  require "tmpdir"
8
8
  require "json"
9
9
 
10
- # Support for python packages.
10
+ # Support for python packages.
11
11
  #
12
12
  # This supports input, but not output.
13
13
  #
@@ -28,7 +28,7 @@ class FPM::Package::Python < FPM::Package
28
28
  "is used instead", :default => nil
29
29
  option "--pypi", "PYPI_URL",
30
30
  "PyPi Server uri for retrieving packages.",
31
- :default => "http://pypi.python.org/simple"
31
+ :default => "https://pypi.python.org/simple"
32
32
  option "--package-prefix", "NAMEPREFIX",
33
33
  "(DEPRECATED, use --package-name-prefix) Name to prefix the package " \
34
34
  "name with." do |value|
@@ -69,6 +69,10 @@ class FPM::Package::Python < FPM::Package
69
69
  "current python interpreter (sys.executable). This option is equivalent " \
70
70
  "to appending 'build_scripts --executable PYTHON_EXECUTABLE' arguments " \
71
71
  "to 'setup.py install' command."
72
+ option "--disable-dependency", "python_package_name",
73
+ "The python package name to remove from dependency list",
74
+ :multivalued => true, :attribute_name => :python_disable_dependency,
75
+ :default => []
72
76
 
73
77
  private
74
78
 
@@ -88,7 +92,7 @@ class FPM::Package::Python < FPM::Package
88
92
  setup_py = path_to_package
89
93
  end
90
94
 
91
- if !File.exists?(setup_py)
95
+ if !File.exist?(setup_py)
92
96
  logger.error("Could not find 'setup.py'", :path => setup_py)
93
97
  raise "Unable to find python package; tried #{setup_py}"
94
98
  end
@@ -104,7 +108,7 @@ class FPM::Package::Python < FPM::Package
104
108
  # part should go elsewhere.
105
109
  path = package
106
110
  # If it's a path, assume local build.
107
- if File.directory?(path) or (File.exists?(path) and File.basename(path) == "setup.py")
111
+ if File.directory?(path) or (File.exist?(path) and File.basename(path) == "setup.py")
108
112
  return path
109
113
  end
110
114
 
@@ -127,7 +131,7 @@ class FPM::Package::Python < FPM::Package
127
131
  "--build-directory", target, want_pkg)
128
132
  else
129
133
  logger.debug("using pip", :pip => attributes[:python_pip])
130
- safesystem(attributes[:python_pip], "install", "--no-deps", "--no-install", "-i", attributes[:python_pypi], "-U", "--build", target, want_pkg)
134
+ safesystem(attributes[:python_pip], "install", "--no-deps", "--no-install", "--no-use-wheel", "-i", attributes[:python_pypi], "-U", "--build", target, want_pkg)
131
135
  end
132
136
 
133
137
  # easy_install will put stuff in @tmpdir/packagename/, so find that:
@@ -145,7 +149,7 @@ class FPM::Package::Python < FPM::Package
145
149
  attributes[:python_package_name_prefix] = attributes[:python_package_prefix]
146
150
  end
147
151
 
148
- begin
152
+ begin
149
153
  json_test_code = [
150
154
  "try:",
151
155
  " import json",
@@ -219,7 +223,7 @@ class FPM::Package::Python < FPM::Package
219
223
  self.name = self.name.downcase if attributes[:python_downcase_name?]
220
224
 
221
225
  if !attributes[:no_auto_depends?] and attributes[:python_dependencies?]
222
- self.dependencies = metadata["dependencies"].collect do |dep|
226
+ metadata["dependencies"].each do |dep|
223
227
  dep_re = /^([^<>!= ]+)\s*(?:([<>!=]{1,2})\s*(.*))?$/
224
228
  match = dep_re.match(dep)
225
229
  if match.nil?
@@ -228,6 +232,8 @@ class FPM::Package::Python < FPM::Package
228
232
  end
229
233
  name, cmp, version = match.captures
230
234
 
235
+ next if attributes[:python_disable_dependency].include?(name)
236
+
231
237
  # convert == to =
232
238
  if cmp == "=="
233
239
  logger.info("Converting == dependency requirement to =", :dependency => dep )
@@ -241,7 +247,8 @@ class FPM::Package::Python < FPM::Package
241
247
 
242
248
  # convert dependencies from python-Foo to python-foo
243
249
  name = name.downcase if attributes[:python_downcase_dependencies?]
244
- "#{name} #{cmp} #{version}"
250
+
251
+ self.dependencies << "#{name} #{cmp} #{version}"
245
252
  end
246
253
  end # if attributes[:python_dependencies?]
247
254
  end # def load_package_info
@@ -267,7 +274,7 @@ class FPM::Package::Python < FPM::Package
267
274
 
268
275
  prefix = "/"
269
276
  prefix = attributes[:prefix] unless attributes[:prefix].nil?
270
-
277
+
271
278
  # Some setup.py's assume $PWD == current directory of setup.py, so let's
272
279
  # chdir first.
273
280
  ::Dir.chdir(project_dir) do
@@ -29,7 +29,7 @@ class FPM::Package::RPM < FPM::Package
29
29
  "bzip2" => "w9.bzdio"
30
30
  } unless defined?(COMPRESSION_MAP)
31
31
 
32
- option "--use-file-permissions", :flag,
32
+ option "--use-file-permissions", :flag,
33
33
  "Use existing file permissions when defining ownership and modes."
34
34
 
35
35
  option "--user", "USER", "Set the user to USER in the %files section. Overrides the user when used with use-file-permissions setting."
@@ -55,6 +55,8 @@ class FPM::Package::RPM < FPM::Package
55
55
  next rpmbuild_define
56
56
  end
57
57
 
58
+ option "--dist", "DIST-TAG", "Set the rpm distribution."
59
+
58
60
  option "--digest", DIGEST_ALGORITHM_MAP.keys.join("|"),
59
61
  "Select a digest algorithm. md5 works on the most platforms.",
60
62
  :default => "md5" do |value|
@@ -98,7 +100,7 @@ class FPM::Package::RPM < FPM::Package
98
100
  option "--attr", "ATTRFILE",
99
101
  "Set the attribute for a file (%attr).",
100
102
  :multivalued => true, :attribute_name => :attrs
101
-
103
+
102
104
  option "--init", "FILEPATH", "Add FILEPATH as an init script",
103
105
  :multivalued => true do |file|
104
106
  next File.expand_path(file)
@@ -149,14 +151,14 @@ class FPM::Package::RPM < FPM::Package
149
151
  match = trigger.match(/^(\[.*\]|)(.*): (.*)$/)
150
152
  @logger.fatal("Trigger '#{trigger_type}' definition can't be parsed ('#{trigger}')") unless match
151
153
  opt, pkg, file = match.captures
152
- @logger.fatal("File given for --trigger-#{trigger_type} does not exist (#{file})") unless File.exists?(file)
154
+ @logger.fatal("File given for --trigger-#{trigger_type} does not exist (#{file})") unless File.exist?(file)
153
155
  rpm_trigger << [pkg, File.read(file), opt.tr('[]','')]
154
156
  next rpm_trigger
155
157
  end
156
158
  end
157
-
159
+
158
160
  private
159
-
161
+
160
162
  # Fix path name
161
163
  # Replace [ with [\[] to make rpm not use globs
162
164
  # Replace * with [*] to make rpm not use globs
@@ -289,7 +291,7 @@ class FPM::Package::RPM < FPM::Package
289
291
  script_path = self.attributes[scriptname]
290
292
  # Skip scripts not set
291
293
  next if script_path.nil?
292
- if !File.exists?(script_path)
294
+ if !File.exist?(script_path)
293
295
  logger.error("No such file (for #{scriptname.to_s}): #{script_path.inspect}")
294
296
  script_errors << script_path
295
297
  end
@@ -303,7 +305,6 @@ class FPM::Package::RPM < FPM::Package
303
305
  end # def converted
304
306
 
305
307
  def rpm_get_trigger_type(flag)
306
- puts "#{flag.to_s(2)}"
307
308
  if (flag & (1 << 25)) == (1 << 25)
308
309
  :rpm_trigger_before_install
309
310
  elsif (flag & (1 << 16)) == (1 << 16)
@@ -375,13 +376,13 @@ class FPM::Package::RPM < FPM::Package
375
376
  [name, operator, version].join(" ")
376
377
  end
377
378
  #input.replaces += replaces
378
-
379
+
379
380
  self.config_files += rpm.config_files
380
381
 
381
382
  # rpms support '%dir' things for specifying empty directories to package,
382
383
  # but the rpm header itself doesn't actually record this information.
383
384
  # so there's no 'directories' to copy, so don't try to merge in the
384
- # 'directories' feature.
385
+ # 'directories' feature.
385
386
  # TODO(sissel): If you want this feature, we'll have to find scan
386
387
  # the extracted rpm for empty directories. I'll wait until someone asks for
387
388
  # this feature
@@ -414,6 +415,9 @@ class FPM::Package::RPM < FPM::Package
414
415
  args += ["--target", rpm_target]
415
416
  end
416
417
 
418
+ # set the rpm dist tag
419
+ args += ["--define", "dist .#{attributes[:rpm_dist]}"] if attributes[:rpm_dist]
420
+
417
421
  args += [
418
422
  "--define", "buildroot #{build_path}/BUILD",
419
423
  "--define", "_topdir #{build_path}",
@@ -531,7 +535,10 @@ class FPM::Package::RPM < FPM::Package
531
535
  end # def epoch
532
536
 
533
537
  def to_s(format=nil)
534
- return super("NAME-VERSION-ITERATION.ARCH.TYPE") if format.nil?
538
+ if format.nil?
539
+ return super("NAME-VERSION-ITERATION.DIST.ARCH.TYPE").gsub('DIST', attributes[:rpm_dist]) if attributes[:rpm_dist]
540
+ return super("NAME-VERSION-ITERATION.ARCH.TYPE")
541
+ end
535
542
  return super(format)
536
543
  end # def to_s
537
544
 
@@ -11,7 +11,7 @@ require "digest"
11
11
  #
12
12
  # This class only supports output of packages.
13
13
  #
14
- # The sh package is a single sh file with a bzipped tar payload concatenated to the end.
14
+ # The sh package is a single sh file with a tar payload concatenated to the end.
15
15
  # The script can unpack the tarball to install it and call optional post install scripts.
16
16
  class FPM::Package::Sh < FPM::Package
17
17
 
@@ -24,12 +24,6 @@ class FPM::Package::Sh < FPM::Package
24
24
  end
25
25
 
26
26
  def create_scripts
27
- if script?(:before_install)
28
- # the scripts are kept in the payload so what would before install be if we've already
29
- # unpacked the payload?
30
- raise "sh package does not support before install scripts."
31
- end
32
-
33
27
  if script?(:after_install)
34
28
  File.write(File.join(fpm_meta_path, "after_install"), script(:after_install))
35
29
  end
@@ -75,7 +75,7 @@ class FPM::Package::Solaris < FPM::Package
75
75
  # Should create a package directory named by the package name.
76
76
  safesystem("pkgmk", "-o", "-f", "#{build_path}/Prototype", "-d", build_path)
77
77
  end
78
-
78
+
79
79
 
80
80
  # Convert the 'package directory' built above to a real solaris package.
81
81
  safesystem("pkgtrans", "-s", build_path, output_path, name)
@@ -0,0 +1,136 @@
1
+ require "fpm/namespace"
2
+ require "fpm/package"
3
+ require "fpm/util"
4
+
5
+ # Support for python virtualenv packages.
6
+ #
7
+ # This supports input, but not output.
8
+ #
9
+ class FPM::Package::Virtualenv < FPM::Package
10
+ # Flags '--foo' will be accessable as attributes[:virtualenv_foo]
11
+
12
+ option "--pypi", "PYPI_URL",
13
+ "PyPi Server uri for retrieving packages.",
14
+ :default => "https://pypi.python.org/simple"
15
+ option "--package-name-prefix", "PREFIX", "Name to prefix the package " \
16
+ "name with.", :default => "virtualenv"
17
+
18
+ option "--install-location", "DIRECTORY", "Location to which to " \
19
+ "install the virtualenv by default.", :default => "/usr/share/python" do |path|
20
+ File.expand_path(path)
21
+ end
22
+
23
+ option "--fix-name", :flag, "Should the target package name be prefixed?",
24
+ :default => true
25
+ option "--other-files-dir", "DIRECTORY", "Optionally, the contents of the " \
26
+ "specified directory may be added to the package. This is useful if the " \
27
+ "virtualenv needs configuration files, etc.", :default => nil
28
+
29
+ private
30
+
31
+ # Input a package.
32
+ #
33
+ # `package` can look like `psutil==2.2.1` or `psutil`.
34
+ def input(package)
35
+ installdir = attributes[:virtualenv_install_location]
36
+ m = /^([^=]+)==([^=]+)$/.match(package)
37
+ package_version = nil
38
+
39
+ if m
40
+ package_name = m[1]
41
+ package_version = m[2]
42
+ self.version ||= package_version
43
+ else
44
+ package_name = package
45
+ package_version = nil
46
+ end
47
+
48
+ virtualenv_name = package_name
49
+
50
+ self.name ||= package_name
51
+
52
+ if self.attributes[:virtualenv_fix_name?]
53
+ self.name = [self.attributes[:virtualenv_package_name_prefix],
54
+ self.name].join("-")
55
+ end
56
+
57
+ virtualenv_folder =
58
+ File.join(installdir,
59
+ virtualenv_name)
60
+
61
+ virtualenv_build_folder = build_path(virtualenv_folder)
62
+
63
+ ::FileUtils.mkdir_p(virtualenv_build_folder)
64
+
65
+ safesystem("virtualenv", virtualenv_build_folder)
66
+ pip_exe = File.join(virtualenv_build_folder, "bin", "pip")
67
+ python_exe = File.join(virtualenv_build_folder, "bin", "python")
68
+
69
+ # Why is this hack here? It looks important, so I'll keep it in.
70
+ safesystem(pip_exe, "install", "-U", "-i",
71
+ attributes[:virtualenv_pypi],
72
+ "pip", "distribute")
73
+ safesystem(pip_exe, "uninstall", "-y", "distribute")
74
+
75
+ safesystem(pip_exe, "install", "-i",
76
+ attributes[:virtualenv_pypi],
77
+ package)
78
+
79
+ if package_version.nil?
80
+ frozen = safesystemout(pip_exe, "freeze")
81
+ package_version = frozen[/#{package}==[^=]+$/].split("==")[1].chomp!
82
+ self.version ||= package_version
83
+ end
84
+
85
+ ::Dir[build_path + "/**/*"].each do |f|
86
+ if ! File.world_readable? f
87
+ File.lchmod(File.stat(f).mode | 444)
88
+ end
89
+ end
90
+
91
+ ::Dir.chdir(virtualenv_build_folder) do
92
+ safesystem("virtualenv-tools", "--update-path", virtualenv_folder)
93
+ end
94
+
95
+ if !attributes[:virtualenv_other_files_dir].nil?
96
+ # Copy all files from other dir to build_path
97
+ Find.find(attributes[:virtualenv_other_files_dir]) do |path|
98
+ src = path.gsub(/^#{attributes[:virtualenv_other_files_dir]}/, '')
99
+ dst = File.join(build_path, src)
100
+ copy_entry(path, dst, preserve=true, remove_destination=true)
101
+ copy_metadata(path, dst)
102
+ end
103
+ end
104
+
105
+ remove_python_compiled_files virtualenv_build_folder
106
+
107
+ # use dir to set stuff up properly, mainly so I don't have to reimplement
108
+ # the chdir/prefix stuff special for tar.
109
+ dir = convert(FPM::Package::Dir)
110
+
111
+ if attributes[:chdir]
112
+ dir.attributes[:chdir] = File.join(build_path, attributes[:chdir])
113
+ else
114
+ dir.attributes[:chdir] = build_path
115
+ end
116
+
117
+ cleanup_staging
118
+ # Tell 'dir' to input "." and chdir/prefix will help it figure out the
119
+ # rest.
120
+ dir.input(".")
121
+ @staging_path = dir.staging_path
122
+ dir.cleanup_build
123
+
124
+ end # def input
125
+
126
+ # Delete python precompiled files found in a given folder.
127
+ def remove_python_compiled_files path
128
+ logger.debug("Now removing python object and compiled files from the virtualenv")
129
+ Find.find(path) do |path|
130
+ if path.end_with? '.pyc' or path.end_with? '.pyo'
131
+ FileUtils.rm path
132
+ end
133
+ end
134
+ end
135
+ public(:input)
136
+ end # class FPM::Package::Virtualenv
@@ -42,7 +42,7 @@ class FPM::Package::Zip < FPM::Package
42
42
  # the compression type.
43
43
  def output(output_path)
44
44
  output_check(output_path)
45
-
45
+
46
46
  files = Find.find(staging_path).to_a
47
47
  safesystem("zip", output_path, *files)
48
48
  end # def output
@@ -24,6 +24,8 @@ module FPM::Util
24
24
 
25
25
  # Is the given program in the system's PATH?
26
26
  def program_in_path?(program)
27
+ # return false if path is not set
28
+ return false unless ENV['PATH']
27
29
  # Scan path to find the executable
28
30
  # Do this to help the user get a better error message.
29
31
  envpath = ENV["PATH"].split(":")
@@ -33,12 +35,12 @@ module FPM::Util
33
35
  def program_exists?(program)
34
36
  # Scan path to find the executable
35
37
  # Do this to help the user get a better error message.
36
- return program_in_path?(program) if !program.include?("/")
38
+ return program_in_path?(program) if !program.include?("/")
37
39
  return File.executable?(program)
38
40
  end # def program_exists?
39
41
 
40
42
  def default_shell
41
- shell = ENV["SHELL"]
43
+ shell = ENV["SHELL"]
42
44
  return "/bin/sh" if shell.nil? || shell.empty?
43
45
  return shell
44
46
  end
@@ -139,7 +141,7 @@ module FPM::Util
139
141
  end # def tar_cmd
140
142
 
141
143
  # Run a block with a value.
142
- # Useful in lieu of assigning variables
144
+ # Useful in lieu of assigning variables
143
145
  def with(value, &block)
144
146
  block.call(value)
145
147
  end # def with
@@ -157,7 +159,30 @@ module FPM::Util
157
159
  rc
158
160
  end
159
161
 
160
- def copy_entry(src, dst)
162
+ def copy_metadata(source, destination)
163
+ source_stat = File::lstat(source)
164
+ dest_stat = File::lstat(destination)
165
+
166
+ # If this is a hard-link, there's no metadata to copy.
167
+ # If this is a symlink, what it points to hasn't been copied yet.
168
+ return if source_stat.ino == dest_stat.ino || dest_stat.symlink?
169
+
170
+ File.utime(source_stat.atime, source_stat.mtime, destination)
171
+ mode = source_stat.mode
172
+ begin
173
+ File.lchown(source_stat.uid, source_stat.gid, destination)
174
+ rescue Errno::EPERM
175
+ # clear setuid/setgid
176
+ mode &= 01777
177
+ end
178
+
179
+ unless source_stat.symlink?
180
+ File.chmod(mode, destination)
181
+ end
182
+ end # def copy_metadata
183
+
184
+
185
+ def copy_entry(src, dst, preserve=false, remove_destination=false)
161
186
  case File.ftype(src)
162
187
  when 'fifo', 'characterSpecial', 'blockSpecial', 'socket'
163
188
  st = File.stat(src)
@@ -173,7 +198,8 @@ module FPM::Util
173
198
  if known_entry
174
199
  FileUtils.ln(known_entry, dst)
175
200
  else
176
- FileUtils.copy_entry(src, dst)
201
+ FileUtils.copy_entry(src, dst, preserve=preserve,
202
+ remove_destination=remove_destination)
177
203
  copied_entries[[st.dev, st.ino]] = dst
178
204
  end
179
205
  end # else...