fpm-itchio 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELIST +629 -0
- data/CONTRIBUTORS +26 -0
- data/LICENSE +21 -0
- data/bin/fpm +8 -0
- data/lib/fpm.rb +18 -0
- data/lib/fpm/command.rb +642 -0
- data/lib/fpm/errors.rb +4 -0
- data/lib/fpm/namespace.rb +4 -0
- data/lib/fpm/package.rb +524 -0
- data/lib/fpm/package/cpan.rb +378 -0
- data/lib/fpm/package/deb.rb +887 -0
- data/lib/fpm/package/dir.rb +207 -0
- data/lib/fpm/package/empty.rb +13 -0
- data/lib/fpm/package/gem.rb +224 -0
- data/lib/fpm/package/npm.rb +120 -0
- data/lib/fpm/package/osxpkg.rb +164 -0
- data/lib/fpm/package/p5p.rb +124 -0
- data/lib/fpm/package/pacman.rb +397 -0
- data/lib/fpm/package/pear.rb +117 -0
- data/lib/fpm/package/pkgin.rb +35 -0
- data/lib/fpm/package/puppet.rb +120 -0
- data/lib/fpm/package/pyfpm/__init__.py +1 -0
- data/lib/fpm/package/pyfpm/get_metadata.py +104 -0
- data/lib/fpm/package/python.rb +317 -0
- data/lib/fpm/package/rpm.rb +583 -0
- data/lib/fpm/package/sh.rb +69 -0
- data/lib/fpm/package/solaris.rb +95 -0
- data/lib/fpm/package/tar.rb +74 -0
- data/lib/fpm/package/virtualenv.rb +145 -0
- data/lib/fpm/package/zip.rb +63 -0
- data/lib/fpm/rake_task.rb +59 -0
- data/lib/fpm/util.rb +253 -0
- data/lib/fpm/version.rb +3 -0
- data/templates/deb.erb +52 -0
- data/templates/deb/changelog.erb +5 -0
- data/templates/deb/ldconfig.sh.erb +13 -0
- data/templates/deb/postinst_upgrade.sh.erb +62 -0
- data/templates/deb/postrm_upgrade.sh.erb +46 -0
- data/templates/deb/preinst_upgrade.sh.erb +41 -0
- data/templates/deb/prerm_upgrade.sh.erb +39 -0
- data/templates/osxpkg.erb +11 -0
- data/templates/p5p_metadata.erb +12 -0
- data/templates/pacman.erb +47 -0
- data/templates/pacman/INSTALL.erb +41 -0
- data/templates/puppet/package.pp.erb +34 -0
- data/templates/puppet/package/remove.pp.erb +13 -0
- data/templates/rpm.erb +261 -0
- data/templates/rpm/filesystem_list +14514 -0
- data/templates/sh.erb +367 -0
- data/templates/solaris.erb +15 -0
- 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
|