/
rpm.rb
380 lines (322 loc) · 11.7 KB
/
rpm.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
require "fpm/package"
require "backports"
require "fileutils"
require "find"
require "arr-pm/file" # gem 'arr-pm'
# RPM Package type.
#
# Build RPMs without having to waste hours reading Maximum-RPM.
# Well, in case you want to read it, here: http://www.rpm.org/max-rpm/
#
# The following attributes are supported:
#
# * :rpm_rpmbuild_define - an array of definitions to give to rpmbuild.
# These are used, verbatim, each as: --define ITEM
class FPM::Package::RPM < FPM::Package
DIGEST_ALGORITHM_MAP = {
"md5" => 1,
"sha1" => 2,
"sha256" => 8,
"sha384" => 9,
"sha512" => 10
} unless defined?(DIGEST_ALGORITHM_MAP)
COMPRESSION_MAP = {
"none" => "w0.gzdio",
"xz" => "w2.xzdio",
"gzip" => "w9.gzdio",
"bzip2" => "w9.bzdio"
} unless defined?(COMPRESSION_MAP)
option "--use-file-permissions", :flag,
"Use existing file permissions when defining ownership and modes"
option "--user", "USER",
"Set the user to USER in the %files section.",
:default => 'root' do |value|
value
end
option "--group", "GROUP",
"Set the group to GROUP in the %files section.",
:default => 'root' do |value|
value
end
rpmbuild_define = []
option "--rpmbuild-define", "DEFINITION",
"Pass a --define argument to rpmbuild." do |define|
rpmbuild_define << define
next rpmbuild_define
end
option "--digest", DIGEST_ALGORITHM_MAP.keys.join("|"),
"Select a digest algorithm. md5 works on the most platforms.",
:default => "md5" do |value|
if !DIGEST_ALGORITHM_MAP.include?(value.downcase)
raise "Unknown digest algorithm '#{value}'. Valid options " \
"include: #{DIGEST_ALGORITHM_MAP.keys.join(", ")}"
end
value.downcase
end
option "--compression", COMPRESSION_MAP.keys.join("|"),
"Select a compression method. gzip works on the most platforms.",
:default => "gzip" do |value|
if !COMPRESSION_MAP.include?(value.downcase)
raise "Unknown compression type '#{value}'. Valid options " \
"include: #{COMPRESSION_MAP.keys.join(", ")}"
end
value.downcase
end
# TODO(sissel): Try to be smart about the default OS.
# issue #309
option "--os", "OS", "The operating system to target this rpm for. " \
"You want to set this to 'linux' if you are using fpm on OS X, for example"
option "--changelog", "FILEPATH", "Add changelog from FILEPATH contents" do |file|
File.read(File.expand_path(file))
end
option "--sign", :flag, "Pass --sign to rpmbuild"
option "--auto-add-directories", :flag, "Auto add directories not part of filesystem"
private
# Fix path name
# Replace [ with [\[] to make rpm not use globs
# Replace * with [*] to make rpm not use globs
# Replace ? with [?] to make rpm not use globs
# Replace % with [%] to make rpm not expand macros
def rpm_fix_name(name)
name = "\"#{name}\"" if name[/\s/]
name = name.gsub("[", "[\\[]")
name = name.gsub("*", "[*]")
name = name.gsub("?", "[?]")
name = name.gsub("%", "[%]")
end
def rpm_file_entry(file)
file = rpm_fix_name(file)
return file unless attributes[:rpm_use_file_permissions?]
stat = File.stat( file.gsub(/\"/, '') )
user = Etc.getpwuid(stat.uid).name
group = Etc.getgrgid(stat.gid).name
mode = stat.mode
return sprintf("%%attr(%o, %s, %s) %s\n", mode & 4095 , user, group, file)
end
# Handle any architecture naming conversions.
# For example, debian calls amd64 what redhat calls x86_64, this
# method fixes those types of things.
def architecture
case @architecture
when nil
return %x{uname -m}.chomp # default to current arch
when "amd64" # debian and redhat disagree on architecture names
return "x86_64"
when "native"
return %x{uname -m}.chomp # 'native' is current arch
when "all"
# Translate fpm "all" arch to what it means in RPM.
return "noarch"
else
return @architecture
end
end # def architecture
# This method ensures a default value for iteration if none is provided.
def iteration
return @iteration ? @iteration : 1
end # def iteration
# See FPM::Package#converted_from
def converted_from(origin)
if origin == FPM::Package::Gem
# Gem dependency operator "~>" is not compatible with rpm. Translate any found.
fixed_deps = []
self.dependencies.collect do |dep|
name, op, version = dep.split(/\s+/)
if op == "~>"
# ~> x.y means: > x.y and < (x+1).0
fixed_deps << "#{name} >= #{version}"
fixed_deps << "#{name} < #{version.to_i + 1}.0.0"
else
fixed_deps << dep
end
end
self.dependencies = fixed_deps
# Convert 'rubygem-foo' provides values to 'rubygem(foo)'
# since that's what most rpm packagers seem to do.
self.provides = self.provides.collect do |provides|
first, remainder = provides.split("-", 2)
if first == "rubygem"
name, remainder = remainder.split(" ", 2)
# yield rubygem(name)...
"rubygem(#{name})#{remainder ? " #{remainder}" : ""}"
else
provides
end
end
self.dependencies = self.dependencies.collect do |dependency|
first, remainder = dependency.split("-", 2)
if first == "rubygem"
name, remainder = remainder.split(" ", 2)
"rubygem(#{name})#{remainder ? " #{remainder}" : ""}"
else
dependency
end
end
#self.provides << "rubygem(#{self.name})"
end
# Convert != dependency as Conflict =, as rpm doesn't understand !=
self.dependencies = self.dependencies.select do |dep|
name, op, version = dep.split(/\s+/)
dep_ok = true
if op == '!='
self.conflicts << "#{name} = #{version}"
dep_ok = false
end
dep_ok
end
# Convert any dashes in version strings to underscores.
self.dependencies = self.dependencies.collect do |dep|
name, op, version = dep.split(/\s+/)
if !version.nil? and version.include?("-")
@logger.warn("Dependency package '#{name}' version '#{version}' " \
"includes dashes, converting to underscores")
version = version.gsub(/-/, "_")
"#{name} #{op} #{version}"
else
dep
end
end
end # def converted
def input(path)
rpm = ::RPM::File.new(path)
tags = {}
rpm.header.tags.each do |tag|
tags[tag.tag] = tag.value
end
self.architecture = tags[:arch]
self.category = tags[:group]
self.description = tags[:description]
self.epoch = (tags[:epoch] || [nil]).first # for some reason epoch is an array
self.iteration = tags[:release]
self.license = tags[:license]
self.maintainer = maintainer
self.name = tags[:name]
self.url = tags[:url]
self.vendor = tags[:vendor]
self.version = tags[:version]
self.scripts[:before_install] = tags[:prein]
self.scripts[:after_install] = tags[:postin]
self.scripts[:before_remove] = tags[:preun]
self.scripts[:after_remove] = tags[:postun]
# TODO(sissel): prefix these scripts above with a shebang line if there isn't one?
# Also taking into account the value of tags[preinprog] etc, something like:
# #!#{tags[:preinprog]}
# #{tags[prein]}
# TODO(sissel): put 'trigger scripts' stuff into attributes
if !attributes[:no_auto_depends?]
self.dependencies += rpm.requires.collect do |name, operator, version|
[name, operator, version].join(" ")
end
end
self.conflicts += rpm.conflicts.collect do |name, operator, version|
[name, operator, version].join(" ")
end
self.provides += rpm.provides.collect do |name, operator, version|
[name, operator, version].join(" ")
end
#input.replaces += replaces
self.config_files += rpm.config_files
# rpms support '%dir' things for specifying empty directories to package,
# but the rpm header itself doesn't actually record this information.
# so there's no 'directories' to copy, so don't try to merge in the
# 'directories' feature.
# TODO(sissel): If you want this feature, we'll have to find scan
# the extracted rpm for empty directories. I'll wait until someone asks for
# this feature
#self.directories += rpm.directories
# Extract to the staging directory
rpm.extract(staging_path)
end # def input
def output(output_path)
output_check(output_path)
%w(BUILD RPMS SRPMS SOURCES SPECS).each { |d| FileUtils.mkdir_p(build_path(d)) }
args = ["rpmbuild", "-bb"]
# issue #309
if !attributes[:rpm_os].nil?
rpm_target = "#{architecture}-unknown-#{attributes[:rpm_os]}"
args += ["--target", rpm_target]
end
args += [
"--define", "buildroot #{build_path}/BUILD",
"--define", "_topdir #{build_path}",
"--define", "_sourcedir #{build_path}",
"--define", "_rpmdir #{build_path}/RPMS",
]
args += ["--sign"] if attributes[:rpm_sign?]
if attributes[:rpm_auto_add_directories?]
fs_dirs_list = File.join(template_dir, "rpm", "filesystem_list")
fs_dirs = File.readlines(fs_dirs_list).reject { |x| x =~ /^\s*#/}.map { |x| x.chomp }
Find.find(staging_path) do |path|
next if path == staging_path
if File.directory? path
add_path = path.gsub(/^#{staging_path}/,'')
self.directories << add_path if not fs_dirs.include? add_path
end
end
else
self.directories = self.directories.map { |x| File.join(self.prefix, x) }
alldirs = []
self.directories.each do |path|
Find.find(File.join(staging_path, path)) do |subpath|
if File.directory? subpath
alldirs << subpath.gsub(/^#{staging_path}/, '')
end
end
end
self.directories = alldirs
end
self.config_files = self.config_files.map { |x| File.join(self.prefix, x) }
(attributes[:rpm_rpmbuild_define] or []).each do |define|
args += ["--define", define]
end
rpmspec = template("rpm.erb").result(binding)
specfile = File.join(build_path("SPECS"), "#{name}.spec")
File.write(specfile, rpmspec)
edit_file(specfile) if attributes[:edit?]
args << specfile
@logger.info("Running rpmbuild", :args => args)
safesystem(*args)
::Dir["#{build_path}/RPMS/**/*.rpm"].each do |rpmpath|
# This should only output one rpm, should we verify this?
FileUtils.cp(rpmpath, output_path)
end
@logger.log("Created rpm", :path => output_path)
end # def output
def prefix
return (attributes[:prefix] or "/")
end # def prefix
def build_sub_dir
return "BUILD"
#return File.join("BUILD", prefix)
end # def prefix
def version
if @version.kind_of?(String) and @version.include?("-")
@logger.warn("Package version '#{@version}' includes dashes, converting" \
" to underscores")
@version = @version.gsub(/-/, "_")
end
return @version
end
# The default epoch value must be nil, see #381
def epoch
return @epoch if @epoch.is_a?(Numeric)
if @epoch.nil? or @epoch.empty?
@logger.warn("no value for epoch is set, defaulting to nil")
return nil
end
return @epoch
end # def epoch
def to_s(format=nil)
return super("NAME-VERSION-ITERATION.ARCH.TYPE") if format.nil?
return super(format)
end # def to_s
def payload_compression
return COMPRESSION_MAP[attributes[:rpm_compression]]
end # def payload_compression
def digest_algorithm
return DIGEST_ALGORITHM_MAP[attributes[:rpm_digest]]
end # def digest_algorithm
public(:input, :output, :converted_from, :architecture, :to_s, :iteration,
:payload_compression, :digest_algorithm, :prefix, :build_sub_dir,
:epoch, :version)
end # class FPM::Package::RPM