fpm-itchio 1.4.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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELIST +629 -0
  3. data/CONTRIBUTORS +26 -0
  4. data/LICENSE +21 -0
  5. data/bin/fpm +8 -0
  6. data/lib/fpm.rb +18 -0
  7. data/lib/fpm/command.rb +642 -0
  8. data/lib/fpm/errors.rb +4 -0
  9. data/lib/fpm/namespace.rb +4 -0
  10. data/lib/fpm/package.rb +524 -0
  11. data/lib/fpm/package/cpan.rb +378 -0
  12. data/lib/fpm/package/deb.rb +887 -0
  13. data/lib/fpm/package/dir.rb +207 -0
  14. data/lib/fpm/package/empty.rb +13 -0
  15. data/lib/fpm/package/gem.rb +224 -0
  16. data/lib/fpm/package/npm.rb +120 -0
  17. data/lib/fpm/package/osxpkg.rb +164 -0
  18. data/lib/fpm/package/p5p.rb +124 -0
  19. data/lib/fpm/package/pacman.rb +397 -0
  20. data/lib/fpm/package/pear.rb +117 -0
  21. data/lib/fpm/package/pkgin.rb +35 -0
  22. data/lib/fpm/package/puppet.rb +120 -0
  23. data/lib/fpm/package/pyfpm/__init__.py +1 -0
  24. data/lib/fpm/package/pyfpm/get_metadata.py +104 -0
  25. data/lib/fpm/package/python.rb +317 -0
  26. data/lib/fpm/package/rpm.rb +583 -0
  27. data/lib/fpm/package/sh.rb +69 -0
  28. data/lib/fpm/package/solaris.rb +95 -0
  29. data/lib/fpm/package/tar.rb +74 -0
  30. data/lib/fpm/package/virtualenv.rb +145 -0
  31. data/lib/fpm/package/zip.rb +63 -0
  32. data/lib/fpm/rake_task.rb +59 -0
  33. data/lib/fpm/util.rb +253 -0
  34. data/lib/fpm/version.rb +3 -0
  35. data/templates/deb.erb +52 -0
  36. data/templates/deb/changelog.erb +5 -0
  37. data/templates/deb/ldconfig.sh.erb +13 -0
  38. data/templates/deb/postinst_upgrade.sh.erb +62 -0
  39. data/templates/deb/postrm_upgrade.sh.erb +46 -0
  40. data/templates/deb/preinst_upgrade.sh.erb +41 -0
  41. data/templates/deb/prerm_upgrade.sh.erb +39 -0
  42. data/templates/osxpkg.erb +11 -0
  43. data/templates/p5p_metadata.erb +12 -0
  44. data/templates/pacman.erb +47 -0
  45. data/templates/pacman/INSTALL.erb +41 -0
  46. data/templates/puppet/package.pp.erb +34 -0
  47. data/templates/puppet/package/remove.pp.erb +13 -0
  48. data/templates/rpm.erb +261 -0
  49. data/templates/rpm/filesystem_list +14514 -0
  50. data/templates/sh.erb +367 -0
  51. data/templates/solaris.erb +15 -0
  52. metadata +265 -0
@@ -0,0 +1,378 @@
1
+ require "fpm/namespace"
2
+ require "fpm/package"
3
+ require "fpm/util"
4
+ require "fileutils"
5
+ require "find"
6
+
7
+ class FPM::Package::CPAN < FPM::Package
8
+ # Flags '--foo' will be accessable as attributes[:npm_foo]
9
+ option "--perl-bin", "PERL_EXECUTABLE",
10
+ "The path to the perl executable you wish to run.", :default => "perl"
11
+
12
+ option "--cpanm-bin", "CPANM_EXECUTABLE",
13
+ "The path to the cpanm executable you wish to run.", :default => "cpanm"
14
+
15
+ option "--mirror", "CPAN_MIRROR",
16
+ "The CPAN mirror to use instead of the default."
17
+
18
+ option "--mirror-only", :flag,
19
+ "Only use the specified mirror for metadata.", :default => false
20
+
21
+ option "--package-name-prefix", "NAME_PREFIX",
22
+ "Name to prefix the package name with.", :default => "perl"
23
+
24
+ option "--test", :flag,
25
+ "Run the tests before packaging?", :default => true
26
+
27
+ option "--perl-lib-path", "PERL_LIB_PATH",
28
+ "Path of target Perl Libraries"
29
+
30
+ option "--sandbox-non-core", :flag,
31
+ "Sandbox all non-core modules, even if they're already installed", :default => true
32
+
33
+ option "--cpanm-force", :flag,
34
+ "Pass the --force parameter to cpanm", :default => false
35
+
36
+ private
37
+ def input(package)
38
+ #if RUBY_VERSION =~ /^1\.8/
39
+ #raise FPM::Package::InvalidArgument,
40
+ #"Sorry, CPAN support requires ruby 1.9 or higher. You have " \
41
+ #"#{RUBY_VERSION}. If this negatively impacts you, please let " \
42
+ #"me know by filing an issue: " \
43
+ #"https://github.com/jordansissel/fpm/issues"
44
+ #end
45
+ #require "ftw" # for http access
46
+ require "net/http"
47
+ require "json"
48
+
49
+ if File.exist?(package)
50
+ moduledir = package
51
+ else
52
+ result = search(package)
53
+ tarball = download(result, version)
54
+ moduledir = unpack(tarball)
55
+ end
56
+
57
+ # Read package metadata (name, version, etc)
58
+ if File.exist?(File.join(moduledir, "META.json"))
59
+ local_metadata = JSON.parse(File.read(File.join(moduledir, ("META.json"))))
60
+ elsif File.exist?(File.join(moduledir, ("META.yml")))
61
+ require "yaml"
62
+ local_metadata = YAML.load_file(File.join(moduledir, ("META.yml")))
63
+ elsif File.exist?(File.join(moduledir, "MYMETA.json"))
64
+ local_metadata = JSON.parse(File.read(File.join(moduledir, ("MYMETA.json"))))
65
+ elsif File.exist?(File.join(moduledir, ("MYMETA.yml")))
66
+ require "yaml"
67
+ local_metadata = YAML.load_file(File.join(moduledir, ("MYMETA.yml")))
68
+ end
69
+
70
+ # Merge the MetaCPAN query result and the metadata pulled from the local
71
+ # META file(s). The local data overwrites the query data for all keys the
72
+ # two hashes have in common. Merge with an empty hash if there was no
73
+ # local META file.
74
+ metadata = result.merge(local_metadata || {})
75
+
76
+ if metadata.empty?
77
+ raise FPM::InvalidPackageConfiguration,
78
+ "Could not find package metadata. Checked for META.json, META.yml, and MetaCPAN API data"
79
+ end
80
+
81
+ self.version = metadata["version"]
82
+ self.description = metadata["abstract"]
83
+
84
+ self.license = case metadata["license"]
85
+ when Array; metadata["license"].first
86
+ when nil; "unknown"
87
+ else; metadata["license"]
88
+ end
89
+
90
+ unless metadata["distribution"].nil?
91
+ logger.info("Setting package name from 'distribution'",
92
+ :distribution => metadata["distribution"])
93
+ self.name = fix_name(metadata["distribution"])
94
+ else
95
+ logger.info("Setting package name from 'name'",
96
+ :name => metadata["name"])
97
+ self.name = fix_name(metadata["name"])
98
+ end
99
+
100
+ # author is not always set or it may be a string instead of an array
101
+ self.vendor = case metadata["author"]
102
+ when String; metadata["author"]
103
+ when Array; metadata["author"].join(", ")
104
+ else
105
+ raise FPM::InvalidPackageConfiguration, "Unexpected CPAN 'author' field type: #{metadata["author"].class}. This is a bug."
106
+ end if metadata.include?("author")
107
+
108
+ self.url = metadata["resources"]["homepage"] rescue "unknown"
109
+
110
+ # TODO(sissel): figure out if this perl module compiles anything
111
+ # and set the architecture appropriately.
112
+ self.architecture = "all"
113
+
114
+ # Install any build/configure dependencies with cpanm.
115
+ # We'll install to a temporary directory.
116
+ logger.info("Installing any build or configure dependencies")
117
+
118
+ if attributes[:cpan_sandbox_non_core?]
119
+ cpanm_flags = ["-L", build_path("cpan"), moduledir]
120
+ else
121
+ cpanm_flags = ["-l", build_path("cpan"), moduledir]
122
+ end
123
+
124
+ # This flag causes cpanm to ONLY download dependencies, skipping the target
125
+ # module itself. This is fine, because the target module has already been
126
+ # downloaded, and there's no need to download twice, test twice, etc.
127
+ cpanm_flags += ["--installdeps"]
128
+ cpanm_flags += ["-n"] if !attributes[:cpan_test?]
129
+ cpanm_flags += ["--mirror", "#{attributes[:cpan_mirror]}"] if !attributes[:cpan_mirror].nil?
130
+ cpanm_flags += ["--mirror-only"] if attributes[:cpan_mirror_only?] && !attributes[:cpan_mirror].nil?
131
+ cpanm_flags += ["--force"] if attributes[:cpan_cpanm_force?]
132
+
133
+ safesystem(attributes[:cpan_cpanm_bin], *cpanm_flags)
134
+
135
+ if !attributes[:no_auto_depends?]
136
+ unless metadata["requires"].nil?
137
+ metadata["requires"].each do |dep_name, version|
138
+ # Special case for representing perl core as a version.
139
+ if dep_name == "perl"
140
+ self.dependencies << "#{dep_name} >= #{version}"
141
+ next
142
+ end
143
+ dep = search(dep_name)
144
+
145
+ if dep.include?("distribution")
146
+ name = fix_name(dep["distribution"])
147
+ else
148
+ name = fix_name(dep_name)
149
+ end
150
+
151
+ if version.to_s == "0"
152
+ # Assume 'Foo = 0' means any version?
153
+ self.dependencies << "#{name}"
154
+ else
155
+ # The 'version' string can be something complex like:
156
+ # ">= 0, != 1.0, != 1.2"
157
+ if version.is_a?(String)
158
+ version.split(/\s*,\s*/).each do |v|
159
+ if v =~ /\s*[><=]/
160
+ self.dependencies << "#{name} #{v}"
161
+ else
162
+ self.dependencies << "#{name} = #{v}"
163
+ end
164
+ end
165
+ else
166
+ self.dependencies << "#{name} >= #{version}"
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end #no_auto_depends
172
+
173
+ ::Dir.chdir(moduledir) do
174
+ # TODO(sissel): install build and config dependencies to resolve
175
+ # build/configure requirements.
176
+ # META.yml calls it 'configure_requires' and 'build_requires'
177
+ # META.json calls it prereqs/build and prereqs/configure
178
+
179
+ prefix = attributes[:prefix] || "/usr/local"
180
+ # TODO(sissel): Set default INSTALL path?
181
+
182
+ # Try Makefile.PL, Build.PL
183
+ #
184
+ if File.exist?("Build.PL")
185
+ # Module::Build is in use here; different actions required.
186
+ safesystem(attributes[:cpan_perl_bin],
187
+ "-Mlocal::lib=#{build_path("cpan")}",
188
+ "Build.PL")
189
+ safesystem(attributes[:cpan_perl_bin],
190
+ "-Mlocal::lib=#{build_path("cpan")}",
191
+ "./Build")
192
+
193
+ if attributes[:cpan_test?]
194
+ safesystem(attributes[:cpan_perl_bin],
195
+ "-Mlocal::lib=#{build_path("cpan")}",
196
+ "./Build", "test")
197
+ end
198
+ if attributes[:cpan_perl_lib_path]
199
+ perl_lib_path = attributes[:cpan_perl_lib_path]
200
+ safesystem("./Build install --install_path lib=#{perl_lib_path} \
201
+ --destdir #{staging_path} --prefix #{prefix} --destdir #{staging_path}")
202
+ else
203
+ safesystem("./Build", "install",
204
+ "--prefix", prefix, "--destdir", staging_path,
205
+ # Empty install_base to avoid local::lib being used.
206
+ "--install_base", "")
207
+ end
208
+ elsif File.exist?("Makefile.PL")
209
+ if attributes[:cpan_perl_lib_path]
210
+ perl_lib_path = attributes[:cpan_perl_lib_path]
211
+ safesystem(attributes[:cpan_perl_bin],
212
+ "-Mlocal::lib=#{build_path("cpan")}",
213
+ "Makefile.PL", "PREFIX=#{prefix}", "LIB=#{perl_lib_path}",
214
+ # Empty install_base to avoid local::lib being used.
215
+ "INSTALL_BASE=")
216
+ else
217
+ safesystem(attributes[:cpan_perl_bin],
218
+ "-Mlocal::lib=#{build_path("cpan")}",
219
+ "Makefile.PL", "PREFIX=#{prefix}",
220
+ # Empty install_base to avoid local::lib being used.
221
+ "INSTALL_BASE=")
222
+ end
223
+ if attributes[:cpan_test?]
224
+ make = [ "env", "PERL5LIB=#{build_path("cpan/lib/perl5")}", "make" ]
225
+ else
226
+ make = [ "make" ]
227
+ end
228
+ safesystem(*make)
229
+ safesystem(*(make + ["test"])) if attributes[:cpan_test?]
230
+ safesystem(*(make + ["DESTDIR=#{staging_path}", "install"]))
231
+
232
+
233
+ else
234
+ raise FPM::InvalidPackageConfiguration,
235
+ "I don't know how to build #{name}. No Makefile.PL nor " \
236
+ "Build.PL found"
237
+ end
238
+
239
+ # Fix any files likely to cause conflicts that are duplicated
240
+ # across packages.
241
+ # https://github.com/jordansissel/fpm/issues/443
242
+ # https://github.com/jordansissel/fpm/issues/510
243
+ glob_prefix = attributes[:cpan_perl_lib_path] || prefix
244
+ ::Dir.glob(File.join(staging_path, glob_prefix, "**/perllocal.pod")).each do |path|
245
+ logger.debug("Removing useless file.",
246
+ :path => path.gsub(staging_path, ""))
247
+ File.unlink(path)
248
+ end
249
+ end
250
+
251
+
252
+ # TODO(sissel): figure out if this perl module compiles anything
253
+ # and set the architecture appropriately.
254
+ self.architecture = "all"
255
+
256
+ # Find any shared objects in the staging directory to set architecture as
257
+ # native if found; otherwise keep the 'all' default.
258
+ Find.find(staging_path) do |path|
259
+ if path =~ /\.so$/
260
+ logger.info("Found shared library, setting architecture=native",
261
+ :path => path)
262
+ self.architecture = "native"
263
+ end
264
+ end
265
+ end
266
+
267
+ def unpack(tarball)
268
+ directory = build_path("module")
269
+ ::Dir.mkdir(directory)
270
+ args = [ "-C", directory, "-zxf", tarball,
271
+ "--strip-components", "1" ]
272
+ safesystem("tar", *args)
273
+ return directory
274
+ end
275
+
276
+ def download(metadata, cpan_version=nil)
277
+ distribution = metadata["distribution"]
278
+ author = metadata["author"]
279
+
280
+ logger.info("Downloading perl module",
281
+ :distribution => distribution,
282
+ :version => cpan_version)
283
+
284
+ # default to latest versionunless we specify one
285
+ if cpan_version.nil?
286
+ self.version = metadata["version"]
287
+ else
288
+ if metadata["version"] =~ /^v\d/
289
+ self.version = "v#{cpan_version}"
290
+ else
291
+ self.version = cpan_version
292
+ end
293
+ end
294
+
295
+ metacpan_release_url = "http://api.metacpan.org/v0/release/#{author}/#{distribution}-#{self.version}"
296
+ begin
297
+ release_response = httpfetch(metacpan_release_url)
298
+ rescue Net::HTTPServerException => e
299
+ logger.error("metacpan release query failed.", :error => e.message,
300
+ :module => package, :url => metacpan_release_url)
301
+ raise FPM::InvalidPackageConfiguration, "metacpan release query failed"
302
+ end
303
+
304
+ data = release_response.body
305
+ release_metadata = JSON.parse(data)
306
+ archive = release_metadata["archive"]
307
+
308
+ # should probably be basepathed from the url
309
+ tarball = File.basename(archive)
310
+
311
+ url_base = "http://www.cpan.org/"
312
+ url_base = "#{attributes[:cpan_mirror]}" if !attributes[:cpan_mirror].nil?
313
+
314
+ #url = "http://www.cpan.org/CPAN/authors/id/#{author[0,1]}/#{author[0,2]}/#{author}/#{tarball}"
315
+ url = "#{url_base}/authors/id/#{author[0,1]}/#{author[0,2]}/#{author}/#{archive}"
316
+ logger.debug("Fetching perl module", :url => url)
317
+
318
+ begin
319
+ response = httpfetch(url)
320
+ rescue Net::HTTPServerException => e
321
+ #logger.error("Download failed", :error => response.status_line,
322
+ #:url => url)
323
+ logger.error("Download failed", :error => e, :url => url)
324
+ raise FPM::InvalidPackageConfiguration, "metacpan query failed"
325
+ end
326
+
327
+ File.open(build_path(tarball), "w") do |fd|
328
+ #response.read_body { |c| fd.write(c) }
329
+ fd.write(response.body)
330
+ end
331
+ return build_path(tarball)
332
+ end # def download
333
+
334
+ def search(package)
335
+ logger.info("Asking metacpan about a module", :module => package)
336
+ metacpan_url = "http://api.metacpan.org/v0/module/" + package
337
+ begin
338
+ response = httpfetch(metacpan_url)
339
+ rescue Net::HTTPServerException => e
340
+ #logger.error("metacpan query failed.", :error => response.status_line,
341
+ #:module => package, :url => metacpan_url)
342
+ logger.error("metacpan query failed.", :error => e.message,
343
+ :module => package, :url => metacpan_url)
344
+ raise FPM::InvalidPackageConfiguration, "metacpan query failed"
345
+ end
346
+
347
+ #data = ""
348
+ #response.read_body { |c| p c; data << c }
349
+ data = response.body
350
+ metadata = JSON.parse(data)
351
+ return metadata
352
+ end # def metadata
353
+
354
+ def fix_name(name)
355
+ case name
356
+ when "perl"; return "perl"
357
+ else; return [attributes[:cpan_package_name_prefix], name].join("-").gsub("::", "-")
358
+ end
359
+ end # def fix_name
360
+
361
+ def httpfetch(url)
362
+ uri = URI.parse(url)
363
+ if ENV['http_proxy']
364
+ proxy = URI.parse(ENV['http_proxy'])
365
+ http = Net::HTTP.Proxy(proxy.host,proxy.port,proxy.user,proxy.password).new(uri.host, uri.port)
366
+ else
367
+ http = Net::HTTP.new(uri.host, uri.port)
368
+ end
369
+ response = http.request(Net::HTTP::Get.new(uri.request_uri))
370
+ case response
371
+ when Net::HTTPSuccess; return response
372
+ when Net::HTTPRedirection; return httpfetch(response["location"])
373
+ else; response.error!
374
+ end
375
+ end
376
+
377
+ public(:input)
378
+ end # class FPM::Package::NPM
@@ -0,0 +1,887 @@
1
+ require "erb"
2
+ require "fpm/namespace"
3
+ require "fpm/package"
4
+ require "fpm/errors"
5
+ require "fpm/util"
6
+ require "backports"
7
+ require "fileutils"
8
+ require "digest"
9
+
10
+ # Support for debian packages (.deb files)
11
+ #
12
+ # This class supports both input and output of packages.
13
+ class FPM::Package::Deb < FPM::Package
14
+
15
+ # Map of what scripts are named.
16
+ SCRIPT_MAP = {
17
+ :before_install => "preinst",
18
+ :after_install => "postinst",
19
+ :before_remove => "prerm",
20
+ :after_remove => "postrm",
21
+ } unless defined?(SCRIPT_MAP)
22
+
23
+ # The list of supported compression types. Default is gz (gzip)
24
+ COMPRESSION_TYPES = [ "gz", "bzip2", "xz" ]
25
+
26
+ option "--ignore-iteration-in-dependencies", :flag,
27
+ "For '=' (equal) dependencies, allow iterations on the specified " \
28
+ "version. Default is to be specific. This option allows the same " \
29
+ "version of a package but any iteration is permitted"
30
+
31
+ option "--build-depends", "DEPENDENCY",
32
+ "Add DEPENDENCY as a Build-Depends" do |dep|
33
+ @build_depends ||= []
34
+ @build_depends << dep
35
+ end
36
+
37
+ option "--pre-depends", "DEPENDENCY",
38
+ "Add DEPENDENCY as a Pre-Depends" do |dep|
39
+ @pre_depends ||= []
40
+ @pre_depends << dep
41
+ end
42
+
43
+ option "--compression", "COMPRESSION", "The compression type to use, must " \
44
+ "be one of #{COMPRESSION_TYPES.join(", ")}.", :default => "gz" do |value|
45
+ if !COMPRESSION_TYPES.include?(value)
46
+ raise ArgumentError, "deb compression value of '#{value}' is invalid. " \
47
+ "Must be one of #{COMPRESSION_TYPES.join(", ")}"
48
+ end
49
+ value
50
+ end
51
+
52
+ # Take care about the case when we want custom control file but still use fpm ...
53
+ option "--custom-control", "FILEPATH",
54
+ "Custom version of the Debian control file." do |control|
55
+ File.expand_path(control)
56
+ end
57
+
58
+ # Add custom debconf config file
59
+ option "--config", "SCRIPTPATH",
60
+ "Add SCRIPTPATH as debconf config file." do |config|
61
+ File.expand_path(config)
62
+ end
63
+
64
+ # Add custom debconf templates file
65
+ option "--templates", "FILEPATH",
66
+ "Add FILEPATH as debconf templates file." do |templates|
67
+ File.expand_path(templates)
68
+ end
69
+
70
+ option "--installed-size", "KILOBYTES",
71
+ "The installed size, in kilobytes. If omitted, this will be calculated " \
72
+ "automatically" do |value|
73
+ value.to_i
74
+ end
75
+
76
+ option "--priority", "PRIORITY",
77
+ "The debian package 'priority' value.", :default => "extra"
78
+
79
+ option "--use-file-permissions", :flag,
80
+ "Use existing file permissions when defining ownership and modes"
81
+
82
+ option "--user", "USER", "The owner of files in this package", :default => 'root'
83
+
84
+ option "--group", "GROUP", "The group owner of files in this package", :default => 'root'
85
+
86
+ option "--changelog", "FILEPATH", "Add FILEPATH as debian changelog" do |file|
87
+ File.expand_path(file)
88
+ end
89
+
90
+ option "--recommends", "PACKAGE", "Add PACKAGE to Recommends" do |pkg|
91
+ @recommends ||= []
92
+ @recommends << pkg
93
+ next @recommends
94
+ end
95
+
96
+ option "--suggests", "PACKAGE", "Add PACKAGE to Suggests" do |pkg|
97
+ @suggests ||= []
98
+ @suggests << pkg
99
+ next @suggests
100
+ end
101
+
102
+ option "--meta-file", "FILEPATH", "Add FILEPATH to DEBIAN directory" do |file|
103
+ @meta_files ||= []
104
+ @meta_files << File.expand_path(file)
105
+ next @meta_files
106
+ end
107
+
108
+ option "--interest", "EVENT", "Package is interested in EVENT trigger" do |event|
109
+ @interested_triggers ||= []
110
+ @interested_triggers << event
111
+ next @interested_triggers
112
+ end
113
+
114
+ option "--activate", "EVENT", "Package activates EVENT trigger" do |event|
115
+ @activated_triggers ||= []
116
+ @activated_triggers << event
117
+ next @activated_triggers
118
+ end
119
+
120
+ option "--field", "'FIELD: VALUE'", "Add custom field to the control file" do |fv|
121
+ @custom_fields ||= {}
122
+ field, value = fv.split(/: */, 2)
123
+ @custom_fields[field] = value
124
+ next @custom_fields
125
+ end
126
+
127
+ option "--no-default-config-files", :flag,
128
+ "Do not add all files in /etc as configuration files by default for Debian packages.",
129
+ :default => false
130
+
131
+ option "--auto-config-files", :flag,
132
+ "Init script and default configuration files will be labeled as " \
133
+ "configuration files for Debian packages.",
134
+ :default => true
135
+
136
+ option "--shlibs", "SHLIBS", "Include control/shlibs content. This flag " \
137
+ "expects a string that is used as the contents of the shlibs file. " \
138
+ "See the following url for a description of this file and its format: " \
139
+ "http://www.debian.org/doc/debian-policy/ch-sharedlibs.html#s-shlibs"
140
+
141
+ option "--init", "FILEPATH", "Add FILEPATH as an init script",
142
+ :multivalued => true do |file|
143
+ next File.expand_path(file)
144
+ end
145
+
146
+ option "--default", "FILEPATH", "Add FILEPATH as /etc/default configuration",
147
+ :multivalued => true do |file|
148
+ next File.expand_path(file)
149
+ end
150
+
151
+ option "--upstart", "FILEPATH", "Add FILEPATH as an upstart script",
152
+ :multivalued => true do |file|
153
+ next File.expand_path(file)
154
+ end
155
+
156
+ option "--systemd", "FILEPATH", "Add FILEPATH as a systemd script",
157
+ :multivalued => true do |file|
158
+ next File.expand_path(file)
159
+ end
160
+
161
+ option "--systemd-restart-after-upgrade", :flag , "Restart service after upgrade", :default => true
162
+
163
+ def initialize(*args)
164
+ super(*args)
165
+ attributes[:deb_priority] = "extra"
166
+ end # def initialize
167
+
168
+ private
169
+
170
+ # Return the architecture. This will default to native if not yet set.
171
+ # It will also try to use dpkg and 'uname -m' to figure out what the
172
+ # native 'architecture' value should be.
173
+ def architecture
174
+ if @architecture.nil? or @architecture == "native"
175
+ # Default architecture should be 'native' which we'll need to ask the
176
+ # system about.
177
+ if program_in_path?("dpkg")
178
+ @architecture = %x{dpkg --print-architecture 2> /dev/null}.chomp
179
+ if $?.exitstatus != 0 or @architecture.empty?
180
+ # if dpkg fails or emits nothing, revert back to uname -m
181
+ @architecture = %x{uname -m}.chomp
182
+ end
183
+ else
184
+ @architecture = %x{uname -m}.chomp
185
+ end
186
+ end
187
+
188
+ case @architecture
189
+ when "x86_64"
190
+ # Debian calls x86_64 "amd64"
191
+ @architecture = "amd64"
192
+ when "noarch"
193
+ # Debian calls noarch "all"
194
+ @architecture = "all"
195
+ end
196
+ return @architecture
197
+ end # def architecture
198
+
199
+ # Get the name of this package. See also FPM::Package#name
200
+ #
201
+ # This accessor actually modifies the name if it has some invalid or unwise
202
+ # characters.
203
+ def name
204
+ if @name =~ /[A-Z]/
205
+ logger.warn("Debian tools (dpkg/apt) don't do well with packages " \
206
+ "that use capital letters in the name. In some cases it will " \
207
+ "automatically downcase them, in others it will not. It is confusing." \
208
+ " Best to not use any capital letters at all. I have downcased the " \
209
+ "package name for you just to be safe.",
210
+ :oldname => @name, :fixedname => @name.downcase)
211
+ @name = @name.downcase
212
+ end
213
+
214
+ if @name.include?("_")
215
+ logger.info("Debian package names cannot include underscores; " \
216
+ "automatically converting to dashes", :name => @name)
217
+ @name = @name.gsub(/[_]/, "-")
218
+ end
219
+
220
+ if @name.include?(" ")
221
+ logger.info("Debian package names cannot include spaces; " \
222
+ "automatically converting to dashes", :name => @name)
223
+ @name = @name.gsub(/[ ]/, "-")
224
+ end
225
+
226
+ return @name
227
+ end # def name
228
+
229
+ def prefix
230
+ return (attributes[:prefix] or "/")
231
+ end # def prefix
232
+
233
+ def input(input_path)
234
+ extract_info(input_path)
235
+ extract_files(input_path)
236
+ end # def input
237
+
238
+ def extract_info(package)
239
+ build_path("control").tap do |path|
240
+ FileUtils.mkdir(path) if !File.directory?(path)
241
+ # Unpack the control tarball
242
+ safesystem("ar p #{package} control.tar.gz | tar -zxf - -C #{path}")
243
+
244
+ control = File.read(File.join(path, "control"))
245
+
246
+ parse = lambda do |field|
247
+ value = control[/^#{field.capitalize}: .*/]
248
+ if value.nil?
249
+ return nil
250
+ else
251
+ logger.info("deb field", field => value.split(": ", 2).last)
252
+ return value.split(": ",2).last
253
+ end
254
+ end
255
+
256
+ # Parse 'epoch:version-iteration' in the version string
257
+ version_re = /^(?:([0-9]+):)?(.+?)(?:-(.*))?$/
258
+ m = version_re.match(parse.call("Version"))
259
+ if !m
260
+ raise "Unsupported version string '#{parse.call("Version")}'"
261
+ end
262
+ self.epoch, self.version, self.iteration = m.captures
263
+
264
+ self.architecture = parse.call("Architecture")
265
+ self.category = parse.call("Section")
266
+ self.license = parse.call("License") || self.license
267
+ self.maintainer = parse.call("Maintainer")
268
+ self.name = parse.call("Package")
269
+ self.url = parse.call("Homepage")
270
+ self.vendor = parse.call("Vendor") || self.vendor
271
+ parse.call("Provides").tap do |provides_str|
272
+ next if provides_str.nil?
273
+ self.provides = provides_str.split(/\s*,\s*/)
274
+ end
275
+
276
+ # The description field is a special flower, parse it that way.
277
+ # The description is the first line as a normal Description field, but also continues
278
+ # on future lines indented by one space, until the end of the file. Blank
279
+ # lines are marked as ' .'
280
+ description = control[/^Description: .*/m].split(": ", 2).last
281
+ self.description = description.gsub(/^ /, "").gsub(/^\.$/, "")
282
+
283
+ #self.config_files = config_files
284
+
285
+ self.dependencies += parse_depends(parse.call("Depends")) if !attributes[:no_auto_depends?]
286
+
287
+ if File.file?(File.join(path, "preinst"))
288
+ self.scripts[:before_install] = File.read(File.join(path, "preinst"))
289
+ end
290
+ if File.file?(File.join(path, "postinst"))
291
+ self.scripts[:after_install] = File.read(File.join(path, "postinst"))
292
+ end
293
+ if File.file?(File.join(path, "prerm"))
294
+ self.scripts[:before_remove] = File.read(File.join(path, "prerm"))
295
+ end
296
+ if File.file?(File.join(path, "postrm"))
297
+ self.scripts[:after_remove] = File.read(File.join(path, "postrm"))
298
+ end
299
+ if File.file?(File.join(path, "conffiles"))
300
+ self.config_files = File.read(File.join(path, "conffiles")).split("\n")
301
+ end
302
+ end
303
+ end # def extract_info
304
+
305
+ # Parse a 'depends' line from a debian control file.
306
+ #
307
+ # The expected input 'data' should be everything after the 'Depends: ' string
308
+ #
309
+ # Example:
310
+ #
311
+ # parse_depends("foo (>= 3), bar (= 5), baz")
312
+ def parse_depends(data)
313
+ return [] if data.nil? or data.empty?
314
+ # parse dependencies. Debian dependencies come in one of two forms:
315
+ # * name
316
+ # * name (op version)
317
+ # They are all on one line, separated by ", "
318
+
319
+ dep_re = /^([^ ]+)(?: \(([>=<]+) ([^)]+)\))?$/
320
+ return data.split(/, */).collect do |dep|
321
+ m = dep_re.match(dep)
322
+ if m
323
+ name, op, version = m.captures
324
+ # deb uses ">>" and "<<" for greater and less than respectively.
325
+ # fpm wants just ">" and "<"
326
+ op = "<" if op == "<<"
327
+ op = ">" if op == ">>"
328
+ # this is the proper form of dependency
329
+ "#{name} #{op} #{version}"
330
+ else
331
+ # Assume normal form dependency, "name op version".
332
+ dep
333
+ end
334
+ end
335
+ end # def parse_depends
336
+
337
+ def extract_files(package)
338
+ # Find out the compression type
339
+ compression = `ar t #{package}`.split("\n").grep(/data.tar/).first.split(".").last
340
+ case compression
341
+ when "gz"
342
+ datatar = "data.tar.gz"
343
+ compression = "-z"
344
+ when "bzip2"
345
+ datatar = "data.tar.bz2"
346
+ compression = "-j"
347
+ when "xz"
348
+ datatar = "data.tar.xz"
349
+ compression = "-J"
350
+ else
351
+ raise FPM::InvalidPackageConfiguration,
352
+ "Unknown compression type '#{self.attributes[:deb_compression]}' "
353
+ "in deb source package #{package}"
354
+ end
355
+
356
+ # unpack the data.tar.{gz,bz2,xz} from the deb package into staging_path
357
+ safesystem("ar p #{package} #{datatar} " \
358
+ "| tar #{compression} -xf - -C #{staging_path}")
359
+ end # def extract_files
360
+
361
+ def output(output_path)
362
+ self.provides = self.provides.collect { |p| fix_provides(p) }
363
+ output_check(output_path)
364
+ # Abort if the target path already exists.
365
+
366
+ # create 'debian-binary' file, required to make a valid debian package
367
+ File.write(build_path("debian-binary"), "2.0\n")
368
+
369
+ # If we are given --deb-shlibs but no --after-install script, we
370
+ # should implicitly create a before/after scripts that run ldconfig
371
+ if attributes[:deb_shlibs]
372
+ if !script?(:after_install)
373
+ logger.info("You gave --deb-shlibs but no --after-install, so " \
374
+ "I am adding an after-install script that runs " \
375
+ "ldconfig to update the system library cache")
376
+ scripts[:after_install] = template("deb/ldconfig.sh.erb").result(binding)
377
+ end
378
+ if !script?(:after_remove)
379
+ logger.info("You gave --deb-shlibs but no --after-remove, so " \
380
+ "I am adding an after-remove script that runs " \
381
+ "ldconfig to update the system library cache")
382
+ scripts[:after_remove] = template("deb/ldconfig.sh.erb").result(binding)
383
+ end
384
+ end
385
+
386
+ attributes.fetch(:deb_systemd_list, []).each do |systemd|
387
+ name = File.basename(systemd, ".service")
388
+ dest_systemd = staging_path("lib/systemd/system/#{name}.service")
389
+ FileUtils.mkdir_p(File.dirname(dest_systemd))
390
+ FileUtils.cp(systemd, dest_systemd)
391
+ File.chmod(0644, dest_systemd)
392
+
393
+ # set the attribute with the systemd service name
394
+ attributes[:deb_systemd] = name
395
+ end
396
+
397
+ if script?(:before_upgrade) or script?(:after_upgrade) or attributes[:deb_systemd]
398
+ puts "Adding action files"
399
+ if script?(:before_install) or script?(:before_upgrade)
400
+ scripts[:before_install] = template("deb/preinst_upgrade.sh.erb").result(binding)
401
+ end
402
+ if script?(:before_remove) or attributes[:deb_systemd]
403
+ scripts[:before_remove] = template("deb/prerm_upgrade.sh.erb").result(binding)
404
+ end
405
+ if script?(:after_install) or script?(:after_upgrade) or attributes[:deb_systemd]
406
+ scripts[:after_install] = template("deb/postinst_upgrade.sh.erb").result(binding)
407
+ end
408
+ if script?(:after_remove)
409
+ scripts[:after_remove] = template("deb/postrm_upgrade.sh.erb").result(binding)
410
+ end
411
+ end
412
+
413
+ write_control_tarball
414
+
415
+ # Tar up the staging_path into data.tar.{compression type}
416
+ case self.attributes[:deb_compression]
417
+ when "gz", nil
418
+ datatar = build_path("data.tar.gz")
419
+ compression = "-z"
420
+ when "bzip2"
421
+ datatar = build_path("data.tar.bz2")
422
+ compression = "-j"
423
+ when "xz"
424
+ datatar = build_path("data.tar.xz")
425
+ compression = "-J"
426
+ else
427
+ raise FPM::InvalidPackageConfiguration,
428
+ "Unknown compression type '#{self.attributes[:deb_compression]}'"
429
+ end
430
+
431
+ # Write the changelog file
432
+ dest_changelog = File.join(staging_path, "usr/share/doc/#{name}/changelog.Debian.gz")
433
+ FileUtils.mkdir_p(File.dirname(dest_changelog))
434
+ File.new(dest_changelog, "wb", 0644).tap do |changelog|
435
+ Zlib::GzipWriter.new(changelog, Zlib::BEST_COMPRESSION).tap do |changelog_gz|
436
+ if attributes[:deb_changelog]
437
+ logger.info("Writing user-specified changelog", :source => attributes[:deb_changelog])
438
+ File.new(attributes[:deb_changelog]).tap do |fd|
439
+ chunk = nil
440
+ # Ruby 1.8.7 doesn't have IO#copy_stream
441
+ changelog_gz.write(chunk) while chunk = fd.read(16384)
442
+ end.close
443
+ else
444
+ logger.info("Creating boilerplate changelog file")
445
+ changelog_gz.write(template("deb/changelog.erb").result(binding))
446
+ end
447
+ end.close
448
+ end # No need to close, GzipWriter#close will close it.
449
+
450
+ attributes.fetch(:deb_init_list, []).each do |init|
451
+ name = File.basename(init, ".init")
452
+ dest_init = File.join(staging_path, "etc/init.d/#{name}")
453
+ FileUtils.mkdir_p(File.dirname(dest_init))
454
+ FileUtils.cp init, dest_init
455
+ File.chmod(0755, dest_init)
456
+ end
457
+
458
+ attributes.fetch(:deb_default_list, []).each do |default|
459
+ name = File.basename(default, ".default")
460
+ dest_default = File.join(staging_path, "etc/default/#{name}")
461
+ FileUtils.mkdir_p(File.dirname(dest_default))
462
+ FileUtils.cp default, dest_default
463
+ File.chmod(0644, dest_default)
464
+ end
465
+
466
+ attributes.fetch(:deb_upstart_list, []).each do |upstart|
467
+ name = File.basename(upstart, ".upstart")
468
+ dest_upstart = staging_path("etc/init/#{name}.conf")
469
+ FileUtils.mkdir_p(File.dirname(dest_upstart))
470
+ FileUtils.cp(upstart, dest_upstart)
471
+ File.chmod(0644, dest_upstart)
472
+
473
+ # Install an init.d shim that calls upstart
474
+ dest_init = staging_path("etc/init.d/#{name}")
475
+ FileUtils.mkdir_p(File.dirname(dest_init))
476
+ FileUtils.ln_s("/lib/init/upstart-job", dest_init)
477
+ end
478
+
479
+ attributes.fetch(:deb_systemd_list, []).each do |systemd|
480
+ name = File.basename(systemd, ".service")
481
+ dest_systemd = staging_path("lib/systemd/system/#{name}.service")
482
+ FileUtils.mkdir_p(File.dirname(dest_systemd))
483
+ FileUtils.cp(systemd, dest_systemd)
484
+ File.chmod(0644, dest_systemd)
485
+ end
486
+
487
+ write_control_tarball
488
+
489
+ # Tar up the staging_path into data.tar.{compression type}
490
+ case self.attributes[:deb_compression]
491
+ when "gz", nil
492
+ datatar = build_path("data.tar.gz")
493
+ compression = "-z"
494
+ when "bzip2"
495
+ datatar = build_path("data.tar.bz2")
496
+ compression = "-j"
497
+ when "xz"
498
+ datatar = build_path("data.tar.xz")
499
+ compression = "-J"
500
+ else
501
+ raise FPM::InvalidPackageConfiguration,
502
+ "Unknown compression type '#{self.attributes[:deb_compression]}'"
503
+ end
504
+
505
+ args = [ tar_cmd, "-C", staging_path, compression ] + data_tar_flags + [ "-cf", datatar, "." ]
506
+ safesystem(*args)
507
+
508
+ # pack up the .deb, which is just an 'ar' archive with 3 files
509
+ # the 'debian-binary' file has to be first
510
+ File.expand_path(output_path).tap do |output_path|
511
+ ::Dir.chdir(build_path) do
512
+ safesystem("ar", "-qc", output_path, "debian-binary", "control.tar.gz", datatar)
513
+ end
514
+ end
515
+ end # def output
516
+
517
+ def converted_from(origin)
518
+ self.dependencies = self.dependencies.collect do |dep|
519
+ fix_dependency(dep)
520
+ end.flatten
521
+ self.provides = self.provides.collect do |provides|
522
+ fix_provides(provides)
523
+ end.flatten
524
+
525
+ if origin == FPM::Package::Deb
526
+ changelog_path = staging_path("usr/share/doc/#{name}/changelog.Debian.gz")
527
+ if File.exists?(changelog_path)
528
+ logger.debug("Found a deb changelog file, using it.", :path => changelog_path)
529
+ attributes[:deb_changelog] = build_path("deb_changelog")
530
+ File.open(attributes[:deb_changelog], "w") do |deb_changelog|
531
+ Zlib::GzipReader.open(changelog_path) do |gz|
532
+ IO::copy_stream(gz, deb_changelog)
533
+ end
534
+ end
535
+ File.unlink(changelog_path)
536
+ end
537
+ end
538
+ end # def converted_from
539
+
540
+ def debianize_op(op)
541
+ # Operators in debian packaging are <<, <=, =, >= and >>
542
+ # So any operator like < or > must be replaced
543
+ {:< => "<<", :> => ">>"}[op.to_sym] or op
544
+ end
545
+
546
+ def fix_dependency(dep)
547
+ # Deb dependencies are: NAME (OP VERSION), like "zsh (> 3.0)"
548
+ # Convert anything that looks like 'NAME OP VERSION' to this format.
549
+ if dep =~ /[\(,\|]/
550
+ # Don't "fix" ones that could appear well formed already.
551
+ else
552
+ # Convert ones that appear to be 'name op version'
553
+ name, op, version = dep.split(/ +/)
554
+ if !version.nil?
555
+ # Convert strings 'foo >= bar' to 'foo (>= bar)'
556
+ dep = "#{name} (#{debianize_op(op)} #{version})"
557
+ end
558
+ end
559
+
560
+ name_re = /^[^ \(]+/
561
+ name = dep[name_re]
562
+ if name =~ /[A-Z]/
563
+ logger.warn("Downcasing dependency '#{name}' because deb packages " \
564
+ " don't work so good with uppercase names")
565
+ dep = dep.gsub(name_re) { |n| n.downcase }
566
+ end
567
+
568
+ if dep.include?("_")
569
+ logger.warn("Replacing dependency underscores with dashes in '#{dep}' because " \
570
+ "debs don't like underscores")
571
+ dep = dep.gsub("_", "-")
572
+ end
573
+
574
+ # Convert gem ~> X.Y.Z to '>= X.Y.Z' and << X.Y+1.0
575
+ if dep =~ /\(~>/
576
+ name, version = dep.gsub(/[()~>]/, "").split(/ +/)[0..1]
577
+ nextversion = version.split(".").collect { |v| v.to_i }
578
+ l = nextversion.length
579
+ nextversion[l-2] += 1
580
+ nextversion[l-1] = 0
581
+ nextversion = nextversion.join(".")
582
+ return ["#{name} (>= #{version})", "#{name} (<< #{nextversion})"]
583
+ elsif (m = dep.match(/(\S+)\s+\(!= (.+)\)/))
584
+ # Move '!=' dependency specifications into 'Breaks'
585
+ self.attributes[:deb_breaks] ||= []
586
+ self.attributes[:deb_breaks] << dep.gsub(/!=/,"=")
587
+ return []
588
+ elsif (m = dep.match(/(\S+)\s+\(= (.+)\)/)) and
589
+ self.attributes[:deb_ignore_iteration_in_dependencies?]
590
+ # Convert 'foo (= x)' to 'foo (>= x)' and 'foo (<< x+1)'
591
+ # but only when flag --ignore-iteration-in-dependencies is passed.
592
+ name, version = m[1..2]
593
+ nextversion = version.split('.').collect { |v| v.to_i }
594
+ nextversion[-1] += 1
595
+ nextversion = nextversion.join(".")
596
+ return ["#{name} (>= #{version})", "#{name} (<< #{nextversion})"]
597
+ elsif (m = dep.match(/(\S+)\s+\(> (.+)\)/))
598
+ # Convert 'foo (> x) to 'foo (>> x)'
599
+ name, version = m[1..2]
600
+ return ["#{name} (>> #{version})"]
601
+ else
602
+ # otherwise the dep is probably fine
603
+ return dep.rstrip
604
+ end
605
+ end # def fix_dependency
606
+
607
+ def fix_provides(provides)
608
+ name_re = /^[^ \(]+/
609
+ name = provides[name_re]
610
+ if name =~ /[A-Z]/
611
+ logger.warn("Downcasing provides '#{name}' because deb packages " \
612
+ " don't work so good with uppercase names")
613
+ provides = provides.gsub(name_re) { |n| n.downcase }
614
+ end
615
+
616
+ if provides.include?("_")
617
+ logger.warn("Replacing 'provides' underscores with dashes in '#{provides}' because " \
618
+ "debs don't like underscores")
619
+ provides = provides.gsub("_", "-")
620
+ end
621
+ return provides.rstrip
622
+ end
623
+
624
+ def control_path(path=nil)
625
+ @control_path ||= build_path("control")
626
+ FileUtils.mkdir(@control_path) if !File.directory?(@control_path)
627
+
628
+ if path.nil?
629
+ return @control_path
630
+ else
631
+ return File.join(@control_path, path)
632
+ end
633
+ end # def control_path
634
+
635
+ def write_control_tarball
636
+ # Use custom Debian control file when given ...
637
+ write_control # write the control file
638
+ write_shlibs # write optional shlibs file
639
+ write_scripts # write the maintainer scripts
640
+ write_conffiles # write the conffiles
641
+ write_debconf # write the debconf files
642
+ write_meta_files # write additional meta files
643
+ write_triggers # write trigger config to 'triggers' file
644
+ write_md5sums # write the md5sums file
645
+
646
+ # Make the control.tar.gz
647
+ build_path("control.tar.gz").tap do |controltar|
648
+ logger.info("Creating", :path => controltar, :from => control_path)
649
+
650
+ args = [ tar_cmd, "-C", control_path, "-zcf", controltar,
651
+ "--owner=0", "--group=0", "--numeric-owner", "." ]
652
+ safesystem(*args)
653
+ end
654
+
655
+ logger.debug("Removing no longer needed control dir", :path => control_path)
656
+ ensure
657
+ FileUtils.rm_r(control_path)
658
+ end # def write_control_tarball
659
+
660
+ def write_control
661
+ # warn user if epoch is set
662
+ logger.warn("epoch in Version is set", :epoch => self.epoch) if self.epoch
663
+
664
+ # calculate installed-size if necessary:
665
+ if attributes[:deb_installed_size].nil?
666
+ logger.info("No deb_installed_size set, calculating now.")
667
+ total = 0
668
+ Find.find(staging_path) do |path|
669
+ stat = File.lstat(path)
670
+ next if stat.directory?
671
+ total += stat.size
672
+ end
673
+ # Per http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Installed-Size
674
+ # "The disk space is given as the integer value of the estimated
675
+ # installed size in bytes, divided by 1024 and rounded up."
676
+ attributes[:deb_installed_size] = total / 1024
677
+ end
678
+
679
+ # Write the control file
680
+ control_path("control").tap do |control|
681
+ if attributes[:deb_custom_control]
682
+ logger.debug("Using '#{attributes[:deb_custom_control]}' template for the control file")
683
+ control_data = File.read(attributes[:deb_custom_control])
684
+ else
685
+ logger.debug("Using 'deb.erb' template for the control file")
686
+ control_data = template("deb.erb").result(binding)
687
+ end
688
+
689
+ logger.debug("Writing control file", :path => control)
690
+ File.write(control, control_data)
691
+ File.chmod(0644, control)
692
+ edit_file(control) if attributes[:edit?]
693
+ end
694
+ end # def write_control
695
+
696
+ # Write out the maintainer scripts
697
+ #
698
+ # SCRIPT_MAP is a map from the package ':after_install' to debian
699
+ # 'post_install' names
700
+ def write_scripts
701
+ SCRIPT_MAP.each do |scriptname, filename|
702
+ next unless script?(scriptname)
703
+
704
+ control_path(filename).tap do |controlscript|
705
+ logger.debug("Writing control script", :source => filename, :target => controlscript)
706
+ File.write(controlscript, script(scriptname))
707
+ # deb maintainer scripts are required to be executable
708
+ File.chmod(0755, controlscript)
709
+ end
710
+ end
711
+ end # def write_scripts
712
+
713
+ def write_conffiles
714
+ # check for any init scripts or default files
715
+ inits = attributes.fetch(:deb_init_list, [])
716
+ defaults = attributes.fetch(:deb_default_list, [])
717
+ upstarts = attributes.fetch(:deb_upstart_list, [])
718
+ return unless (config_files.any? or inits.any? or defaults.any? or upstarts.any?)
719
+
720
+ allconfigs = []
721
+
722
+ # expand recursively a given path to be put in allconfigs
723
+ def add_path(path, allconfigs)
724
+ # Strip leading /
725
+ path = path[1..-1] if path[0,1] == "/"
726
+ cfg_path = File.expand_path(path, staging_path)
727
+ Find.find(cfg_path) do |p|
728
+ if File.file?(p)
729
+ allconfigs << p.gsub("#{staging_path}/", '')
730
+ end
731
+ end
732
+ end
733
+
734
+ # scan all conf file paths for files and add them
735
+ config_files.each do |path|
736
+ begin
737
+ add_path(path, allconfigs)
738
+ rescue Errno::ENOENT
739
+ raise FPM::InvalidPackageConfiguration,
740
+ "Error trying to use '#{path}' as a config file in the package. Does it exist?"
741
+ end
742
+ end
743
+
744
+ # Also add everything in /etc
745
+ begin
746
+ if !attributes[:deb_no_default_config_files?]
747
+ logger.warn("Debian packaging tools generally labels all files in /etc as config files, " \
748
+ "as mandated by policy, so fpm defaults to this behavior for deb packages. " \
749
+ "You can disable this default behavior with --deb-no-default-config-files flag")
750
+ add_path("/etc", allconfigs)
751
+ end
752
+ rescue Errno::ENOENT
753
+ end
754
+
755
+ if attributes[:deb_auto_config_files?]
756
+ inits.each do |init|
757
+ name = File.basename(init, ".init")
758
+ initscript = "/etc/init.d/#{name}"
759
+ logger.debug("Add conf file declaration for init script", :script => initscript)
760
+ allconfigs << initscript[1..-1]
761
+ end
762
+ defaults.each do |default|
763
+ name = File.basename(default, ".default")
764
+ confdefaults = "/etc/default/#{name}"
765
+ logger.debug("Add conf file declaration for defaults", :default => confdefaults)
766
+ allconfigs << confdefaults[1..-1]
767
+ end
768
+ upstarts.each do |upstart|
769
+ name = File.basename(upstart, ".upstart")
770
+ upstartscript = "etc/init/#{name}.conf"
771
+ logger.debug("Add conf file declaration for upstart script", :script => upstartscript)
772
+ allconfigs << upstartscript[1..-1]
773
+ end
774
+ end
775
+
776
+ allconfigs.sort!.uniq!
777
+ return unless allconfigs.any?
778
+
779
+ control_path("conffiles").tap do |conffiles|
780
+ File.open(conffiles, "w") do |out|
781
+ allconfigs.each do |cf|
782
+ # We need to put the leading / back. Stops lintian relative-conffile error.
783
+ out.puts("/" + cf)
784
+ end
785
+ end
786
+ File.chmod(0644, conffiles)
787
+ end
788
+ end # def write_conffiles
789
+
790
+ def write_shlibs
791
+ return unless attributes[:deb_shlibs]
792
+ logger.info("Adding shlibs", :content => attributes[:deb_shlibs])
793
+ File.open(control_path("shlibs"), "w") do |out|
794
+ out.write(attributes[:deb_shlibs])
795
+ end
796
+ File.chmod(0644, control_path("shlibs"))
797
+ end # def write_shlibs
798
+
799
+ def write_debconf
800
+ if attributes[:deb_config]
801
+ FileUtils.cp(attributes[:deb_config], control_path("config"))
802
+ File.chmod(0755, control_path("config"))
803
+ end
804
+
805
+ if attributes[:deb_templates]
806
+ FileUtils.cp(attributes[:deb_templates], control_path("templates"))
807
+ File.chmod(0755, control_path("templates"))
808
+ end
809
+ end # def write_debconf
810
+
811
+ def write_meta_files
812
+ files = attributes[:deb_meta_file]
813
+ return unless files
814
+ files.each do |fn|
815
+ dest = control_path(File.basename(fn))
816
+ FileUtils.cp(fn, dest)
817
+ File.chmod(0644, dest)
818
+ end
819
+ end
820
+
821
+ def write_triggers
822
+ lines = [['interest', :deb_interest],
823
+ ['activate', :deb_activate]].map { |label, attr|
824
+ (attributes[attr] || []).map { |e| "#{label} #{e}\n" }
825
+ }.flatten.join('')
826
+
827
+ if lines.size > 0
828
+ File.open(control_path("triggers"), 'a') do |f|
829
+ f.chmod 0644
830
+ f.write "\n" if f.size > 0
831
+ f.write lines
832
+ end
833
+ end
834
+ end
835
+
836
+ def write_md5sums
837
+ md5_sums = {}
838
+
839
+ Find.find(staging_path) do |path|
840
+ if File.file?(path) && !File.symlink?(path)
841
+ md5 = Digest::MD5.file(path).hexdigest
842
+ md5_path = path.gsub("#{staging_path}/", "")
843
+ md5_sums[md5_path] = md5
844
+ end
845
+ end
846
+
847
+ if not md5_sums.empty?
848
+ File.open(control_path("md5sums"), "w") do |out|
849
+ md5_sums.each do |path, md5|
850
+ out.puts "#{md5} #{path}"
851
+ end
852
+ end
853
+ File.chmod(0644, control_path("md5sums"))
854
+ end
855
+ end # def write_md5sums
856
+
857
+ def to_s(format=nil)
858
+ # Default format if nil
859
+ # git_1.7.9.3-1_amd64.deb
860
+ return super("NAME_FULLVERSION_ARCH.TYPE") if format.nil?
861
+ return super(format)
862
+ end # def to_s
863
+
864
+ def data_tar_flags
865
+ data_tar_flags = []
866
+ if attributes[:deb_use_file_permissions?].nil?
867
+ if !attributes[:deb_user].nil?
868
+ if attributes[:deb_user] == 'root'
869
+ data_tar_flags += [ "--numeric-owner", "--owner", "0" ]
870
+ else
871
+ data_tar_flags += [ "--owner", attributes[:deb_user] ]
872
+ end
873
+ end
874
+
875
+ if !attributes[:deb_group].nil?
876
+ if attributes[:deb_group] == 'root'
877
+ data_tar_flags += [ "--numeric-owner", "--group", "0" ]
878
+ else
879
+ data_tar_flags += [ "--group", attributes[:deb_group] ]
880
+ end
881
+ end
882
+ end
883
+ return data_tar_flags
884
+ end # def data_tar_flags
885
+
886
+ public(:input, :output, :architecture, :name, :prefix, :converted_from, :to_s, :data_tar_flags)
887
+ end # class FPM::Target::Deb