fpm-aeppert 1.6.2

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.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELIST +661 -0
  3. data/CONTRIBUTORS +26 -0
  4. data/LICENSE +21 -0
  5. data/bin/fpm +8 -0
  6. data/lib/fpm.rb +20 -0
  7. data/lib/fpm/command.rb +648 -0
  8. data/lib/fpm/errors.rb +4 -0
  9. data/lib/fpm/namespace.rb +4 -0
  10. data/lib/fpm/package.rb +539 -0
  11. data/lib/fpm/package/apk.rb +510 -0
  12. data/lib/fpm/package/cpan.rb +405 -0
  13. data/lib/fpm/package/deb.rb +935 -0
  14. data/lib/fpm/package/dir.rb +221 -0
  15. data/lib/fpm/package/empty.rb +13 -0
  16. data/lib/fpm/package/freebsd.rb +147 -0
  17. data/lib/fpm/package/gem.rb +243 -0
  18. data/lib/fpm/package/npm.rb +120 -0
  19. data/lib/fpm/package/osxpkg.rb +165 -0
  20. data/lib/fpm/package/p5p.rb +124 -0
  21. data/lib/fpm/package/pacman.rb +403 -0
  22. data/lib/fpm/package/pear.rb +117 -0
  23. data/lib/fpm/package/pkgin.rb +35 -0
  24. data/lib/fpm/package/pleaserun.rb +63 -0
  25. data/lib/fpm/package/puppet.rb +120 -0
  26. data/lib/fpm/package/pyfpm/__init__.py +1 -0
  27. data/lib/fpm/package/pyfpm/get_metadata.py +104 -0
  28. data/lib/fpm/package/python.rb +318 -0
  29. data/lib/fpm/package/rpm.rb +593 -0
  30. data/lib/fpm/package/sh.rb +69 -0
  31. data/lib/fpm/package/solaris.rb +95 -0
  32. data/lib/fpm/package/tar.rb +86 -0
  33. data/lib/fpm/package/virtualenv.rb +164 -0
  34. data/lib/fpm/package/zip.rb +63 -0
  35. data/lib/fpm/rake_task.rb +60 -0
  36. data/lib/fpm/util.rb +358 -0
  37. data/lib/fpm/util/tar_writer.rb +80 -0
  38. data/lib/fpm/version.rb +3 -0
  39. data/templates/deb.erb +52 -0
  40. data/templates/deb/changelog.erb +5 -0
  41. data/templates/deb/ldconfig.sh.erb +13 -0
  42. data/templates/deb/postinst_upgrade.sh.erb +62 -0
  43. data/templates/deb/postrm_upgrade.sh.erb +46 -0
  44. data/templates/deb/preinst_upgrade.sh.erb +41 -0
  45. data/templates/deb/prerm_upgrade.sh.erb +39 -0
  46. data/templates/osxpkg.erb +11 -0
  47. data/templates/p5p_metadata.erb +12 -0
  48. data/templates/pacman.erb +47 -0
  49. data/templates/pacman/INSTALL.erb +41 -0
  50. data/templates/pleaserun/generate-cleanup.sh +17 -0
  51. data/templates/pleaserun/install-path.sh +17 -0
  52. data/templates/pleaserun/install.sh +117 -0
  53. data/templates/pleaserun/scripts/after-install.sh +4 -0
  54. data/templates/pleaserun/scripts/before-remove.sh +12 -0
  55. data/templates/puppet/package.pp.erb +34 -0
  56. data/templates/puppet/package/remove.pp.erb +13 -0
  57. data/templates/rpm.erb +260 -0
  58. data/templates/rpm/filesystem_list +14514 -0
  59. data/templates/sh.erb +369 -0
  60. data/templates/solaris.erb +15 -0
  61. metadata +322 -0
@@ -0,0 +1,318 @@
1
+ require "fpm/namespace"
2
+ require "fpm/package"
3
+ require "fpm/util"
4
+ require "rubygems/package"
5
+ require "rubygems"
6
+ require "fileutils"
7
+ require "tmpdir"
8
+ require "json"
9
+
10
+ # Support for python packages.
11
+ #
12
+ # This supports input, but not output.
13
+ #
14
+ # Example:
15
+ #
16
+ # # Download the django python package:
17
+ # pkg = FPM::Package::Python.new
18
+ # pkg.input("Django")
19
+ #
20
+ class FPM::Package::Python < FPM::Package
21
+ # Flags '--foo' will be accessable as attributes[:python_foo]
22
+ option "--bin", "PYTHON_EXECUTABLE",
23
+ "The path to the python executable you wish to run.", :default => "python"
24
+ option "--easyinstall", "EASYINSTALL_EXECUTABLE",
25
+ "The path to the easy_install executable tool", :default => "easy_install"
26
+ option "--pip", "PIP_EXECUTABLE",
27
+ "The path to the pip executable tool. If not specified, easy_install " \
28
+ "is used instead", :default => nil
29
+ option "--pypi", "PYPI_URL",
30
+ "PyPi Server uri for retrieving packages.",
31
+ :default => "https://pypi.python.org/simple"
32
+ option "--package-prefix", "NAMEPREFIX",
33
+ "(DEPRECATED, use --package-name-prefix) Name to prefix the package " \
34
+ "name with." do |value|
35
+ logger.warn("Using deprecated flag: --package-prefix. Please use " \
36
+ "--package-name-prefix")
37
+ value
38
+ end
39
+ option "--package-name-prefix", "PREFIX", "Name to prefix the package " \
40
+ "name with.", :default => "python"
41
+ option "--fix-name", :flag, "Should the target package name be prefixed?",
42
+ :default => true
43
+ option "--fix-dependencies", :flag, "Should the package dependencies be " \
44
+ "prefixed?", :default => true
45
+
46
+ option "--downcase-name", :flag, "Should the target package name be in " \
47
+ "lowercase?", :default => true
48
+ option "--downcase-dependencies", :flag, "Should the package dependencies " \
49
+ "be in lowercase?", :default => true
50
+
51
+ option "--install-bin", "BIN_PATH", "The path to where python scripts " \
52
+ "should be installed to."
53
+ option "--install-lib", "LIB_PATH", "The path to where python libs " \
54
+ "should be installed to (default depends on your python installation). " \
55
+ "Want to find out what your target platform is using? Run this: " \
56
+ "python -c 'from distutils.sysconfig import get_python_lib; " \
57
+ "print get_python_lib()'"
58
+ option "--install-data", "DATA_PATH", "The path to where data should be " \
59
+ "installed to. This is equivalent to 'python setup.py --install-data " \
60
+ "DATA_PATH"
61
+ option "--dependencies", :flag, "Include requirements defined in setup.py" \
62
+ " as dependencies.", :default => true
63
+ option "--obey-requirements-txt", :flag, "Use a requirements.txt file " \
64
+ "in the top-level directory of the python package for dependency " \
65
+ "detection.", :default => false
66
+ option "--scripts-executable", "PYTHON_EXECUTABLE", "Set custom python " \
67
+ "interpreter in installing scripts. By default distutils will replace " \
68
+ "python interpreter in installing scripts (specified by shebang) with " \
69
+ "current python interpreter (sys.executable). This option is equivalent " \
70
+ "to appending 'build_scripts --executable PYTHON_EXECUTABLE' arguments " \
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 => []
76
+
77
+ private
78
+
79
+ # Input a package.
80
+ #
81
+ # The 'package' can be any of:
82
+ #
83
+ # * A name of a package on pypi (ie; easy_install some-package)
84
+ # * The path to a directory containing setup.py
85
+ # * The path to a setup.py
86
+ def input(package)
87
+ path_to_package = download_if_necessary(package, version)
88
+
89
+ if File.directory?(path_to_package)
90
+ setup_py = File.join(path_to_package, "setup.py")
91
+ else
92
+ setup_py = path_to_package
93
+ end
94
+
95
+ if !File.exist?(setup_py)
96
+ logger.error("Could not find 'setup.py'", :path => setup_py)
97
+ raise "Unable to find python package; tried #{setup_py}"
98
+ end
99
+
100
+ load_package_info(setup_py)
101
+ install_to_staging(setup_py)
102
+ end # def input
103
+
104
+ # Download the given package if necessary. If version is given, that version
105
+ # will be downloaded, otherwise the latest is fetched.
106
+ def download_if_necessary(package, version=nil)
107
+ # TODO(sissel): this should just be a 'download' method, the 'if_necessary'
108
+ # part should go elsewhere.
109
+ path = package
110
+ # If it's a path, assume local build.
111
+ if File.directory?(path) or (File.exist?(path) and File.basename(path) == "setup.py")
112
+ return path
113
+ end
114
+
115
+ logger.info("Trying to download", :package => package)
116
+
117
+ if version.nil?
118
+ want_pkg = "#{package}"
119
+ else
120
+ want_pkg = "#{package}==#{version}"
121
+ end
122
+
123
+ target = build_path(package)
124
+ FileUtils.mkdir(target) unless File.directory?(target)
125
+
126
+ if attributes[:python_pip].nil?
127
+ # no pip, use easy_install
128
+ logger.debug("no pip, defaulting to easy_install", :easy_install => attributes[:python_easyinstall])
129
+ safesystem(attributes[:python_easyinstall], "-i",
130
+ attributes[:python_pypi], "--editable", "-U",
131
+ "--build-directory", target, want_pkg)
132
+ else
133
+ logger.debug("using pip", :pip => attributes[:python_pip])
134
+ # TODO: Support older versions of pip
135
+ safesystem(attributes[:python_pip], "download", "--no-clean", "--no-deps", "--no-binary", ":all:", "-i", attributes[:python_pypi], "--build", target, want_pkg)
136
+ end
137
+
138
+ # easy_install will put stuff in @tmpdir/packagename/, so find that:
139
+ # @tmpdir/somepackage/setup.py
140
+ dirs = ::Dir.glob(File.join(target, "*"))
141
+ if dirs.length != 1
142
+ raise "Unexpected directory layout after easy_install. Maybe file a bug? The directory is #{build_path}"
143
+ end
144
+ return dirs.first
145
+ end # def download
146
+
147
+ # Load the package information like name, version, dependencies.
148
+ def load_package_info(setup_py)
149
+ if !attributes[:python_package_prefix].nil?
150
+ attributes[:python_package_name_prefix] = attributes[:python_package_prefix]
151
+ end
152
+
153
+ begin
154
+ json_test_code = [
155
+ "try:",
156
+ " import json",
157
+ "except ImportError:",
158
+ " import simplejson as json"
159
+ ].join("\n")
160
+ safesystem("#{attributes[:python_bin]} -c '#{json_test_code}'")
161
+ rescue FPM::Util::ProcessFailed => e
162
+ logger.error("Your python environment is missing json support (either json or simplejson python module). I cannot continue without this.", :python => attributes[:python_bin], :error => e)
163
+ raise FPM::Util::ProcessFailed, "Python (#{attributes[:python_bin]}) is missing simplejson or json modules."
164
+ end
165
+
166
+ begin
167
+ safesystem("#{attributes[:python_bin]} -c 'import pkg_resources'")
168
+ rescue FPM::Util::ProcessFailed => e
169
+ logger.error("Your python environment is missing a working setuptools module. I tried to find the 'pkg_resources' module but failed.", :python => attributes[:python_bin], :error => e)
170
+ raise FPM::Util::ProcessFailed, "Python (#{attributes[:python_bin]}) is missing pkg_resources module."
171
+ end
172
+
173
+ # Add ./pyfpm/ to the python library path
174
+ pylib = File.expand_path(File.dirname(__FILE__))
175
+
176
+ # chdir to the directory holding setup.py because some python setup.py's assume that you are
177
+ # in the same directory.
178
+ setup_dir = File.dirname(setup_py)
179
+
180
+ output = ::Dir.chdir(setup_dir) do
181
+ tmp = build_path("metadata.json")
182
+ setup_cmd = "env PYTHONPATH=#{pylib} #{attributes[:python_bin]} " \
183
+ "setup.py --command-packages=pyfpm get_metadata --output=#{tmp}"
184
+
185
+ if attributes[:python_obey_requirements_txt?]
186
+ setup_cmd += " --load-requirements-txt"
187
+ end
188
+
189
+ # Capture the output, which will be JSON metadata describing this python
190
+ # package. See fpm/lib/fpm/package/pyfpm/get_metadata.py for more
191
+ # details.
192
+ logger.info("fetching package metadata", :setup_cmd => setup_cmd)
193
+
194
+ success = safesystem(setup_cmd)
195
+ #%x{#{setup_cmd}}
196
+ if !success
197
+ logger.error("setup.py get_metadata failed", :command => setup_cmd,
198
+ :exitcode => $?.exitstatus)
199
+ raise "An unexpected error occurred while processing the setup.py file"
200
+ end
201
+ File.read(tmp)
202
+ end
203
+ logger.debug("result from `setup.py get_metadata`", :data => output)
204
+ metadata = JSON.parse(output)
205
+ logger.info("object output of get_metadata", :json => metadata)
206
+
207
+ self.architecture = metadata["architecture"]
208
+ self.description = metadata["description"]
209
+ # Sometimes the license field is multiple lines; do best-effort and just
210
+ # use the first line.
211
+ self.license = metadata["license"].split(/[\r\n]+/).first
212
+ self.version = metadata["version"]
213
+ self.url = metadata["url"]
214
+
215
+ # name prefixing is optional, if enabled, a name 'foo' will become
216
+ # 'python-foo' (depending on what the python_package_name_prefix is)
217
+ if attributes[:python_fix_name?]
218
+ self.name = fix_name(metadata["name"])
219
+ else
220
+ self.name = metadata["name"]
221
+ end
222
+
223
+ # convert python-Foo to python-foo if flag is set
224
+ self.name = self.name.downcase if attributes[:python_downcase_name?]
225
+
226
+ if !attributes[:no_auto_depends?] and attributes[:python_dependencies?]
227
+ metadata["dependencies"].each do |dep|
228
+ dep_re = /^([^<>!= ]+)\s*(?:([<>!=]{1,2})\s*(.*))?$/
229
+ match = dep_re.match(dep)
230
+ if match.nil?
231
+ logger.error("Unable to parse dependency", :dependency => dep)
232
+ raise FPM::InvalidPackageConfiguration, "Invalid dependency '#{dep}'"
233
+ end
234
+ name, cmp, version = match.captures
235
+
236
+ next if attributes[:python_disable_dependency].include?(name)
237
+
238
+ # convert == to =
239
+ if cmp == "=="
240
+ logger.info("Converting == dependency requirement to =", :dependency => dep )
241
+ cmp = "="
242
+ end
243
+
244
+ # dependency name prefixing is optional, if enabled, a name 'foo' will
245
+ # become 'python-foo' (depending on what the python_package_name_prefix
246
+ # is)
247
+ name = fix_name(name) if attributes[:python_fix_dependencies?]
248
+
249
+ # convert dependencies from python-Foo to python-foo
250
+ name = name.downcase if attributes[:python_downcase_dependencies?]
251
+
252
+ self.dependencies << "#{name} #{cmp} #{version}"
253
+ end
254
+ end # if attributes[:python_dependencies?]
255
+ end # def load_package_info
256
+
257
+ # Sanitize package name.
258
+ # Some PyPI packages can be named 'python-foo', so we don't want to end up
259
+ # with a package named 'python-python-foo'.
260
+ # But we want packages named like 'pythonweb' to be suffixed
261
+ # 'python-pythonweb'.
262
+ def fix_name(name)
263
+ if name.start_with?("python")
264
+ # If the python package is called "python-foo" strip the "python-" part while
265
+ # prepending the package name prefix.
266
+ return [attributes[:python_package_name_prefix], name.gsub(/^python-/, "")].join("-")
267
+ else
268
+ return [attributes[:python_package_name_prefix], name].join("-")
269
+ end
270
+ end # def fix_name
271
+
272
+ # Install this package to the staging directory
273
+ def install_to_staging(setup_py)
274
+ project_dir = File.dirname(setup_py)
275
+
276
+ prefix = "/"
277
+ prefix = attributes[:prefix] unless attributes[:prefix].nil?
278
+
279
+ # Some setup.py's assume $PWD == current directory of setup.py, so let's
280
+ # chdir first.
281
+ ::Dir.chdir(project_dir) do
282
+ flags = [ "--root", staging_path ]
283
+ if !attributes[:python_install_lib].nil?
284
+ flags += [ "--install-lib", File.join(prefix, attributes[:python_install_lib]) ]
285
+ elsif !attributes[:prefix].nil?
286
+ # setup.py install --prefix PREFIX still installs libs to
287
+ # PREFIX/lib64/python2.7/site-packages/
288
+ # but we really want something saner.
289
+ #
290
+ # since prefix is given, but not python_install_lib, assume PREFIX/lib
291
+ flags += [ "--install-lib", File.join(prefix, "lib") ]
292
+ end
293
+
294
+ if !attributes[:python_install_data].nil?
295
+ flags += [ "--install-data", File.join(prefix, attributes[:python_install_data]) ]
296
+ elsif !attributes[:prefix].nil?
297
+ # prefix given, but not python_install_data, assume PREFIX/data
298
+ flags += [ "--install-data", File.join(prefix, "data") ]
299
+ end
300
+
301
+ if !attributes[:python_install_bin].nil?
302
+ flags += [ "--install-scripts", File.join(prefix, attributes[:python_install_bin]) ]
303
+ elsif !attributes[:prefix].nil?
304
+ # prefix given, but not python_install_bin, assume PREFIX/bin
305
+ flags += [ "--install-scripts", File.join(prefix, "bin") ]
306
+ end
307
+
308
+ if !attributes[:python_scripts_executable].nil?
309
+ # Overwrite installed python scripts shebang binary with provided executable
310
+ flags += [ "build_scripts", "--executable", attributes[:python_scripts_executable] ]
311
+ end
312
+
313
+ safesystem(attributes[:python_bin], "setup.py", "install", *flags)
314
+ end
315
+ end # def install_to_staging
316
+
317
+ public(:input)
318
+ end # class FPM::Package::Python
@@ -0,0 +1,593 @@
1
+ require "fpm/package"
2
+ require "backports"
3
+ require "fileutils"
4
+ require "find"
5
+ require "arr-pm/file" # gem 'arr-pm'
6
+
7
+ # RPM Package type.
8
+ #
9
+ # Build RPMs without having to waste hours reading Maximum-RPM.
10
+ # Well, in case you want to read it, here: http://www.rpm.org/max-rpm/
11
+ #
12
+ # The following attributes are supported:
13
+ #
14
+ # * :rpm_rpmbuild_define - an array of definitions to give to rpmbuild.
15
+ # These are used, verbatim, each as: --define ITEM
16
+ class FPM::Package::RPM < FPM::Package
17
+ DIGEST_ALGORITHM_MAP = {
18
+ "md5" => 1,
19
+ "sha1" => 2,
20
+ "sha256" => 8,
21
+ "sha384" => 9,
22
+ "sha512" => 10
23
+ } unless defined?(DIGEST_ALGORITHM_MAP)
24
+
25
+ COMPRESSION_MAP = {
26
+ "none" => "w0.gzdio",
27
+ "xz" => "w9.xzdio",
28
+ "gzip" => "w9.gzdio",
29
+ "bzip2" => "w9.bzdio"
30
+ } unless defined?(COMPRESSION_MAP)
31
+
32
+ option "--use-file-permissions", :flag,
33
+ "Use existing file permissions when defining ownership and modes."
34
+
35
+ option "--user", "USER", "Set the user to USER in the %files section. Overrides the user when used with use-file-permissions setting."
36
+
37
+ option "--group", "GROUP", "Set the group to GROUP in the %files section. Overrides the group when used with use-file-permissions setting."
38
+
39
+ option "--defattrfile", "ATTR",
40
+ "Set the default file mode (%defattr).",
41
+ :default => '-' do |value|
42
+ value
43
+ end
44
+
45
+ option "--defattrdir", "ATTR",
46
+ "Set the default dir mode (%defattr).",
47
+ :default => '-' do |value|
48
+ value
49
+ end
50
+
51
+ rpmbuild_define = []
52
+ option "--rpmbuild-define", "DEFINITION",
53
+ "Pass a --define argument to rpmbuild." do |define|
54
+ rpmbuild_define << define
55
+ next rpmbuild_define
56
+ end
57
+
58
+ option "--dist", "DIST-TAG", "Set the rpm distribution."
59
+
60
+ option "--digest", DIGEST_ALGORITHM_MAP.keys.join("|"),
61
+ "Select a digest algorithm. md5 works on the most platforms.",
62
+ :default => "md5" do |value|
63
+ if !DIGEST_ALGORITHM_MAP.include?(value.downcase)
64
+ raise "Unknown digest algorithm '#{value}'. Valid options " \
65
+ "include: #{DIGEST_ALGORITHM_MAP.keys.join(", ")}"
66
+ end
67
+ value.downcase
68
+ end
69
+
70
+ option "--compression", COMPRESSION_MAP.keys.join("|"),
71
+ "Select a compression method. gzip works on the most platforms.",
72
+ :default => "gzip" do |value|
73
+ if !COMPRESSION_MAP.include?(value.downcase)
74
+ raise "Unknown compression type '#{value}'. Valid options " \
75
+ "include: #{COMPRESSION_MAP.keys.join(", ")}"
76
+ end
77
+ value.downcase
78
+ end
79
+
80
+ # TODO(sissel): Try to be smart about the default OS.
81
+ # issue #309
82
+ option "--os", "OS", "The operating system to target this rpm for. " \
83
+ "You want to set this to 'linux' if you are using fpm on OS X, for example"
84
+
85
+ option "--changelog", "FILEPATH", "Add changelog from FILEPATH contents" do |file|
86
+ File.read(File.expand_path(file))
87
+ end
88
+
89
+ option "--summary", "SUMMARY",
90
+ "Set the RPM summary. Overrides the first line on the description if set"
91
+
92
+ option "--sign", :flag, "Pass --sign to rpmbuild"
93
+
94
+ option "--auto-add-directories", :flag, "Auto add directories not part of filesystem"
95
+ option "--auto-add-exclude-directories", "DIRECTORIES",
96
+ "Additional directories ignored by '--rpm-auto-add-directories' flag",
97
+ :multivalued => true, :attribute_name => :auto_add_exclude_directories
98
+
99
+ option "--autoreqprov", :flag, "Enable RPM's AutoReqProv option"
100
+ option "--autoreq", :flag, "Enable RPM's AutoReq option"
101
+ option "--autoprov", :flag, "Enable RPM's AutoProv option"
102
+
103
+ option "--attr", "ATTRFILE",
104
+ "Set the attribute for a file (%attr), e.g. --rpm-attr 750,user1,group1:/some/file",
105
+ :multivalued => true, :attribute_name => :attrs
106
+
107
+ option "--init", "FILEPATH", "Add FILEPATH as an init script",
108
+ :multivalued => true do |file|
109
+ next File.expand_path(file)
110
+ end
111
+
112
+ rpmbuild_filter_from_provides = []
113
+ option "--filter-from-provides", "REGEX",
114
+ "Set %filter_from_provides to the supplied REGEX." do |filter_from_provides|
115
+ rpmbuild_filter_from_provides << filter_from_provides
116
+ next rpmbuild_filter_from_provides
117
+ end
118
+ rpmbuild_filter_from_requires = []
119
+ option "--filter-from-requires", "REGEX",
120
+ "Set %filter_from_requires to the supplied REGEX." do |filter_from_requires|
121
+ rpmbuild_filter_from_requires << filter_from_requires
122
+ next rpmbuild_filter_from_requires
123
+ end
124
+
125
+ rpm_tags = []
126
+ option "--tag", "TAG",
127
+ "Adds a custom tag in the spec file as is. " \
128
+ "Example: --rpm-tag 'Requires(post): /usr/sbin/alternatives'" do |add_tag|
129
+ rpm_tags << add_tag
130
+ next rpm_tags
131
+ end
132
+
133
+ option "--ignore-iteration-in-dependencies", :flag,
134
+ "For '=' (equal) dependencies, allow iterations on the specified " \
135
+ "version. Default is to be specific. This option allows the same " \
136
+ "version of a package but any iteration is permitted"
137
+
138
+ option "--verbatim-gem-dependencies", :flag,
139
+ "When converting from a gem, leave the old (fpm 0.4.x) style " \
140
+ "dependency names. This flag will use the old 'rubygem-foo' " \
141
+ "names in rpm requires instead of the redhat style " \
142
+ "rubygem(foo).", :default => false
143
+
144
+ option "--verifyscript", "FILE",
145
+ "a script to be run on verification" do |val|
146
+ File.expand_path(val) # Get the full path to the script
147
+ end # --verifyscript
148
+ option "--pretrans", "FILE",
149
+ "pretrans script" do |val|
150
+ File.expand_path(val) # Get the full path to the script
151
+ end # --pretrans
152
+ option "--posttrans", "FILE",
153
+ "posttrans script" do |val|
154
+ File.expand_path(val) # Get the full path to the script
155
+ end # --posttrans
156
+
157
+ ["before-install","after-install","before-uninstall","after-target-uninstall"].each do |trigger_type|
158
+ rpm_trigger = []
159
+ option "--trigger-#{trigger_type}", "'[OPT]PACKAGE: FILEPATH'", "Adds a rpm trigger script located in FILEPATH, " \
160
+ "having 'OPT' options and linking to 'PACKAGE'. PACKAGE can be a comma seperated list of packages. " \
161
+ "See: http://rpm.org/api/4.4.2.2/triggers.html" do |trigger|
162
+ match = trigger.match(/^(\[.*\]|)(.*): (.*)$/)
163
+ @logger.fatal("Trigger '#{trigger_type}' definition can't be parsed ('#{trigger}')") unless match
164
+ opt, pkg, file = match.captures
165
+ @logger.fatal("File given for --trigger-#{trigger_type} does not exist (#{file})") unless File.exist?(file)
166
+ rpm_trigger << [pkg, File.read(file), opt.tr('[]','')]
167
+ next rpm_trigger
168
+ end
169
+ end
170
+
171
+ private
172
+
173
+ # Fix path name
174
+ # Replace [ with [\[] to make rpm not use globs
175
+ # Replace * with [*] to make rpm not use globs
176
+ # Replace ? with [?] to make rpm not use globs
177
+ # Replace % with [%] to make rpm not expand macros
178
+ def rpm_fix_name(name)
179
+ name = name.gsub(/(\ |\[|\]|\*|\?|\%|\$)/, {
180
+ ' ' => '?',
181
+ '%' => '[%]',
182
+ '$' => '[$]',
183
+ '?' => '[?]',
184
+ '*' => '[*]',
185
+ '[' => '[\[]',
186
+ ']' => '[\]]'
187
+ })
188
+ end
189
+
190
+ def rpm_file_entry(file)
191
+ original_file = file
192
+ file = rpm_fix_name(file)
193
+
194
+ if !attributes[:rpm_use_file_permissions?]
195
+
196
+ if attrs[file].nil?
197
+ return file
198
+ else
199
+ return sprintf("%%attr(%s) %s\n", attrs[file], file)
200
+ end
201
+ end
202
+
203
+ return sprintf("%%attr(%s) %s\n", attrs[file], file) unless attrs[file].nil?
204
+
205
+ # Stat the original filename in the relative staging path
206
+ ::Dir.chdir(staging_path) do
207
+ stat = File.lstat(original_file.gsub(/\"/, '').sub(/^\//,''))
208
+
209
+ # rpm_user and rpm_group attribute should override file ownership
210
+ # otherwise use the current file user/group by name.
211
+ user = attributes[:rpm_user] || Etc.getpwuid(stat.uid).name
212
+ group = attributes[:rpm_group] || Etc.getgrgid(stat.gid).name
213
+ mode = stat.mode
214
+ return sprintf("%%attr(%o, %s, %s) %s\n", mode & 4095 , user, group, file)
215
+ end
216
+ end
217
+
218
+
219
+ # Handle any architecture naming conversions.
220
+ # For example, debian calls amd64 what redhat calls x86_64, this
221
+ # method fixes those types of things.
222
+ def architecture
223
+ case @architecture
224
+ when nil
225
+ return %x{uname -m}.chomp # default to current arch
226
+ when "amd64" # debian and redhat disagree on architecture names
227
+ return "x86_64"
228
+ when "native"
229
+ return %x{uname -m}.chomp # 'native' is current arch
230
+ when "all"
231
+ # Translate fpm "all" arch to what it means in RPM.
232
+ return "noarch"
233
+ else
234
+ return @architecture
235
+ end
236
+ end # def architecture
237
+
238
+ # This method ensures a default value for iteration if none is provided.
239
+ def iteration
240
+ return @iteration ? @iteration : 1
241
+ end # def iteration
242
+
243
+ # See FPM::Package#converted_from
244
+ def converted_from(origin)
245
+ if origin == FPM::Package::Gem
246
+ fixed_deps = []
247
+ self.dependencies.collect do |dep|
248
+ # Gem dependency operator "~>" is not compatible with rpm. Translate any found.
249
+ fixed_deps = fixed_deps + expand_pessimistic_constraints(dep)
250
+ end
251
+ self.dependencies = fixed_deps
252
+
253
+ # Convert 'rubygem-foo' provides values to 'rubygem(foo)'
254
+ # since that's what most rpm packagers seem to do.
255
+ self.provides = self.provides.collect do |provides|
256
+ # Tries to match rubygem_prefix [1], gem_name [2] and version [3] if present
257
+ # and return it in rubygem_prefix(gem_name) form
258
+ if name=/^(#{attributes[:gem_package_name_prefix]})-([^\s]+)\s*(.*)$/.match(provides)
259
+ "#{name[1]}(#{name[2]})#{name[3] ? " #{name[3]}" : ""}"
260
+ else
261
+ provides
262
+ end
263
+ end
264
+ if !self.attributes[:rpm_verbatim_gem_dependencies?]
265
+ self.dependencies = self.dependencies.collect do |dependency|
266
+ # Tries to match rubygem_prefix [1], gem_name [2] and version [3] if present
267
+ # and return it in rubygem_prefix(gem_name) form
268
+ if name=/^(#{attributes[:gem_package_name_prefix]})-([^\s]+)\s*(.*)$/.match(dependency)
269
+ "#{name[1]}(#{name[2]})#{name[3] ? " #{name[3]}" : ""}"
270
+ else
271
+ dependency
272
+ end
273
+ end
274
+ end
275
+ end
276
+
277
+ # Convert != dependency as Conflict =, as rpm doesn't understand !=
278
+ self.dependencies = self.dependencies.select do |dep|
279
+ name, op, version = dep.split(/\s+/)
280
+ dep_ok = true
281
+ if op == '!='
282
+ self.conflicts << "#{name} = #{version}"
283
+ dep_ok = false
284
+ end
285
+ dep_ok
286
+ end
287
+
288
+ # if --ignore-iteration-in-dependencies is true convert foo = X, to
289
+ # foo >= X , foo < X+1
290
+ if self.attributes[:rpm_ignore_iteration_in_dependencies?]
291
+ self.dependencies = self.dependencies.collect do |dep|
292
+ name, op, version = dep.split(/\s+/)
293
+ if op == '='
294
+ nextversion = version.split('.').collect { |v| v.to_i }
295
+ nextversion[-1] += 1
296
+ nextversion = nextversion.join(".")
297
+ logger.warn("Converting dependency #{dep} to #{name} >= #{version}, #{name} < #{nextversion}")
298
+ ["#{name} >= #{version}", "#{name} < #{nextversion}"]
299
+ else
300
+ dep
301
+ end
302
+ end.flatten
303
+ end
304
+
305
+ setscript = proc do |scriptname|
306
+ script_path = self.attributes[scriptname]
307
+ # Skip scripts not set
308
+ next if script_path.nil?
309
+ if !File.exist?(script_path)
310
+ logger.error("No such file (for #{scriptname.to_s}): #{script_path.inspect}")
311
+ script_errors << script_path
312
+ end
313
+ # Load the script into memory.
314
+ scripts[scriptname] = File.read(script_path)
315
+ end
316
+
317
+ setscript.call(:rpm_verifyscript)
318
+ setscript.call(:rpm_posttrans)
319
+ setscript.call(:rpm_pretrans)
320
+ end # def converted
321
+
322
+ def rpm_get_trigger_type(flag)
323
+ if (flag & (1 << 25)) == (1 << 25)
324
+ # RPMSENSE_TRIGGERPREIN = (1 << 25), /*!< %triggerprein dependency. */
325
+ :rpm_trigger_before_install
326
+ elsif (flag & (1 << 16)) == (1 << 16)
327
+ # RPMSENSE_TRIGGERIN = (1 << 16), /*!< %triggerin dependency. */
328
+ :rpm_trigger_after_install
329
+ elsif (flag & (1 << 17)) == (1 << 17)
330
+ # RPMSENSE_TRIGGERUN = (1 << 17), /*!< %triggerun dependency. */
331
+ :rpm_trigger_before_uninstall
332
+ elsif (flag & (1 << 18)) == (1 << 18)
333
+ # RPMSENSE_TRIGGERPOSTUN = (1 << 18), /*!< %triggerpostun dependency. */
334
+ :rpm_trigger_after_target_uninstall
335
+ else
336
+ @logger.fatal("I don't know about this triggerflag ('#{flag}')")
337
+ end
338
+ end # def rpm_get_trigger
339
+
340
+ def input(path)
341
+ rpm = ::RPM::File.new(path)
342
+
343
+ tags = {}
344
+ rpm.header.tags.each do |tag|
345
+ tags[tag.tag] = tag.value
346
+ end
347
+
348
+ self.architecture = tags[:arch]
349
+ self.category = tags[:group]
350
+ self.description = tags[:description]
351
+ self.epoch = (tags[:epoch] || [nil]).first # for some reason epoch is an array
352
+ self.iteration = tags[:release]
353
+ self.license = tags[:license]
354
+ self.maintainer = maintainer
355
+ self.name = tags[:name]
356
+ self.url = tags[:url]
357
+ self.vendor = tags[:vendor]
358
+ self.version = tags[:version]
359
+
360
+ self.scripts[:before_install] = tags[:prein]
361
+ self.scripts[:after_install] = tags[:postin]
362
+ self.scripts[:before_remove] = tags[:preun]
363
+ self.scripts[:after_remove] = tags[:postun]
364
+ self.scripts[:rpm_verifyscript] = tags[:verifyscript]
365
+ self.scripts[:rpm_posttrans] = tags[:posttrans]
366
+ self.scripts[:rpm_pretrans] = tags[:pretrans]
367
+ # TODO(sissel): prefix these scripts above with a shebang line if there isn't one?
368
+ # Also taking into account the value of tags[preinprog] etc, something like:
369
+ # #!#{tags[:preinprog]}
370
+ # #{tags[prein]}
371
+
372
+ if !tags[:triggerindex].nil?
373
+ val = tags[:triggerindex].zip(tags[:triggername],tags[:triggerflags],tags[:triggerversion]).group_by{ |x| x[0]}
374
+ val = val.collect do |order,data|
375
+ new_data = data.collect { |x| [ x[1], rpm.operator(x[2]), x[3]].join(" ").strip}.join(", ")
376
+ [order, rpm_get_trigger_type(data[0][2]), new_data]
377
+ end
378
+ val.each do |order, attr,data|
379
+ self.attributes[attr] = [] if self.attributes[attr].nil?
380
+ scriptprog = (tags[:triggerscriptprog][order] == '/bin/sh') ? "" : "-p #{tags[:triggerscriptprog][order]}"
381
+ self.attributes[attr] << [data,tags[:triggerscripts][order],scriptprog]
382
+ end
383
+ end
384
+
385
+ if !attributes[:no_auto_depends?]
386
+ self.dependencies += rpm.requires.collect do |name, operator, version|
387
+ [name, operator, version].join(" ")
388
+ end
389
+ end
390
+
391
+ self.conflicts += rpm.conflicts.collect do |name, operator, version|
392
+ [name, operator, version].join(" ")
393
+ end
394
+ self.provides += rpm.provides.collect do |name, operator, version|
395
+ [name, operator, version].join(" ")
396
+ end
397
+ #input.replaces += replaces
398
+
399
+ self.config_files += rpm.config_files
400
+
401
+ # rpms support '%dir' things for specifying empty directories to package,
402
+ # but the rpm header itself doesn't actually record this information.
403
+ # so there's no 'directories' to copy, so don't try to merge in the
404
+ # 'directories' feature.
405
+ # TODO(sissel): If you want this feature, we'll have to find scan
406
+ # the extracted rpm for empty directories. I'll wait until someone asks for
407
+ # this feature
408
+ #self.directories += rpm.directories
409
+
410
+ # Extract to the staging directory
411
+ rpm.extract(staging_path)
412
+ end # def input
413
+
414
+ def prefixed_path(path)
415
+ Pathname.new(path).absolute?() ? path : File.join(self.prefix, path)
416
+ end # def prefixed_path
417
+
418
+ def output(output_path)
419
+ output_check(output_path)
420
+ %w(BUILD RPMS SRPMS SOURCES SPECS).each { |d| FileUtils.mkdir_p(build_path(d)) }
421
+ args = ["rpmbuild", "-bb"]
422
+
423
+ if %x{uname -m}.chomp != self.architecture
424
+ rpm_target = self.architecture
425
+ end
426
+
427
+ # issue #309
428
+ if !attributes[:rpm_os].nil?
429
+ rpm_target = "#{architecture}-unknown-#{attributes[:rpm_os]}"
430
+ end
431
+
432
+ # issue #707
433
+ if !rpm_target.nil?
434
+ args += ["--target", rpm_target]
435
+ end
436
+
437
+ # set the rpm dist tag
438
+ args += ["--define", "dist .#{attributes[:rpm_dist]}"] if attributes[:rpm_dist]
439
+
440
+ args += [
441
+ "--define", "buildroot #{build_path}/BUILD",
442
+ "--define", "_topdir #{build_path}",
443
+ "--define", "_sourcedir #{build_path}",
444
+ "--define", "_rpmdir #{build_path}/RPMS",
445
+ "--define", "_tmppath #{attributes[:workdir]}"
446
+ ]
447
+
448
+ args += ["--sign"] if attributes[:rpm_sign?]
449
+
450
+ if attributes[:rpm_auto_add_directories?]
451
+ fs_dirs_list = File.join(template_dir, "rpm", "filesystem_list")
452
+ fs_dirs = File.readlines(fs_dirs_list).reject { |x| x =~ /^\s*#/}.map { |x| x.chomp }
453
+ fs_dirs.concat((attributes[:auto_add_exclude_directories] or []))
454
+
455
+ Find.find(staging_path) do |path|
456
+ next if path == staging_path
457
+ if File.directory? path and !File.symlink? path
458
+ add_path = path.gsub(/^#{staging_path}/,'')
459
+ self.directories << add_path if not fs_dirs.include? add_path
460
+ end
461
+ end
462
+ else
463
+ self.directories = self.directories.map { |x| self.prefixed_path(x) }
464
+ alldirs = []
465
+ self.directories.each do |path|
466
+ Find.find(File.join(staging_path, path)) do |subpath|
467
+ if File.directory? subpath and !File.symlink? subpath
468
+ alldirs << subpath.gsub(/^#{staging_path}/, '')
469
+ end
470
+ end
471
+ end
472
+ self.directories = alldirs
473
+ end
474
+
475
+ # scan all conf file paths for files and add them
476
+ allconfigs = []
477
+ self.config_files.each do |path|
478
+ cfg_path = File.join(staging_path, path)
479
+ raise "Config file path #{cfg_path} does not exist" unless File.exist?(cfg_path)
480
+ Find.find(cfg_path) do |p|
481
+ allconfigs << p.gsub("#{staging_path}/", '') if File.file? p
482
+ end
483
+ end
484
+ allconfigs.sort!.uniq!
485
+
486
+ self.config_files = allconfigs.map { |x| File.join("/", x) }
487
+
488
+ # add init script if present
489
+ (attributes[:rpm_init_list] or []).each do |init|
490
+ name = File.basename(init, ".init")
491
+ dest_init = File.join(staging_path, "etc/init.d/#{name}")
492
+ FileUtils.mkdir_p(File.dirname(dest_init))
493
+ FileUtils.cp init, dest_init
494
+ File.chmod(0755, dest_init)
495
+ end
496
+
497
+ (attributes[:rpm_rpmbuild_define] or []).each do |define|
498
+ args += ["--define", define]
499
+ end
500
+
501
+ # copy all files from staging to BUILD dir
502
+ Find.find(staging_path) do |path|
503
+ src = path.gsub(/^#{staging_path}/, '')
504
+ dst = File.join(build_path, build_sub_dir, src)
505
+ copy_entry(path, dst)
506
+ end
507
+
508
+ rpmspec = template("rpm.erb").result(binding)
509
+ specfile = File.join(build_path("SPECS"), "#{name}.spec")
510
+ File.write(specfile, rpmspec)
511
+
512
+ edit_file(specfile) if attributes[:edit?]
513
+
514
+ args << specfile
515
+
516
+ logger.info("Running rpmbuild", :args => args)
517
+ safesystem(*args)
518
+
519
+ ::Dir["#{build_path}/RPMS/**/*.rpm"].each do |rpmpath|
520
+ # This should only output one rpm, should we verify this?
521
+ FileUtils.cp(rpmpath, output_path)
522
+ end
523
+ end # def output
524
+
525
+ def prefix
526
+ if attributes[:prefix] and attributes[:prefix] != '/'
527
+ return attributes[:prefix].chomp('/')
528
+ else
529
+ return "/"
530
+ end
531
+ end # def prefix
532
+
533
+ def build_sub_dir
534
+ return "BUILD"
535
+ #return File.join("BUILD", prefix)
536
+ end # def build_sub_dir
537
+
538
+ def summary
539
+ if !attributes[:rpm_summary]
540
+ return @description.split("\n").first || "_"
541
+ end
542
+
543
+ return attributes[:rpm_summary]
544
+ end # def summary
545
+
546
+ def version
547
+ if @version.kind_of?(String) and @version.include?("-")
548
+ logger.warn("Package version '#{@version}' includes dashes, converting" \
549
+ " to underscores")
550
+ @version = @version.gsub(/-/, "_")
551
+ end
552
+
553
+ return @version
554
+ end
555
+
556
+ # The default epoch value must be nil, see #381
557
+ def epoch
558
+ return @epoch if @epoch.is_a?(Numeric)
559
+
560
+ if @epoch.nil? or @epoch.empty?
561
+ return nil
562
+ end
563
+
564
+ return @epoch
565
+ end # def epoch
566
+
567
+ def to_s_dist;
568
+ attributes[:rpm_dist] ? "#{attributes[:rpm_dist]}" : "DIST";
569
+ end
570
+
571
+ def to_s(format=nil)
572
+ if format.nil?
573
+ format = if attributes[:rpm_dist]
574
+ "NAME-VERSION-ITERATION.DIST.ARCH.EXTENSION"
575
+ else
576
+ "NAME-VERSION-ITERATION.ARCH.EXTENSION"
577
+ end
578
+ end
579
+ return super(format.gsub("DIST", to_s_dist))
580
+ end # def to_s
581
+
582
+ def payload_compression
583
+ return COMPRESSION_MAP[attributes[:rpm_compression]]
584
+ end # def payload_compression
585
+
586
+ def digest_algorithm
587
+ return DIGEST_ALGORITHM_MAP[attributes[:rpm_digest]]
588
+ end # def digest_algorithm
589
+
590
+ public(:input, :output, :converted_from, :architecture, :to_s, :iteration,
591
+ :payload_compression, :digest_algorithm, :prefix, :build_sub_dir,
592
+ :summary, :epoch, :version, :prefixed_path)
593
+ end # class FPM::Package::RPM