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