fpm-itchio 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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