cure-fpm 1.3.3b → 1.6.0b
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELIST +73 -0
- data/CONTRIBUTORS +1 -1
- data/LICENSE +1 -1
- data/lib/fpm.rb +2 -0
- data/lib/fpm/command.rb +100 -50
- data/lib/fpm/package.rb +42 -28
- data/lib/fpm/package/apk.rb +510 -0
- data/lib/fpm/package/cpan.rb +50 -25
- data/lib/fpm/package/deb.rb +211 -47
- data/lib/fpm/package/dir.rb +29 -28
- data/lib/fpm/package/empty.rb +6 -0
- data/lib/fpm/package/freebsd.rb +144 -0
- data/lib/fpm/package/gem.rb +5 -5
- data/lib/fpm/package/npm.rb +2 -2
- data/lib/fpm/package/osxpkg.rb +8 -7
- data/lib/fpm/package/p5p.rb +124 -0
- data/lib/fpm/package/pacman.rb +399 -0
- data/lib/fpm/package/pleaserun.rb +63 -0
- data/lib/fpm/package/pyfpm/get_metadata.py +9 -1
- data/lib/fpm/package/python.rb +19 -7
- data/lib/fpm/package/rpm.rb +58 -18
- data/lib/fpm/package/sh.rb +1 -7
- data/lib/fpm/package/solaris.rb +1 -1
- data/lib/fpm/package/tar.rb +14 -2
- data/lib/fpm/package/virtualenv.rb +145 -0
- data/lib/fpm/package/zip.rb +1 -1
- data/lib/fpm/rake_task.rb +60 -0
- data/lib/fpm/util.rb +176 -48
- data/lib/fpm/util/tar_writer.rb +80 -0
- data/lib/fpm/version.rb +1 -1
- data/templates/deb/postinst_upgrade.sh.erb +33 -2
- data/templates/deb/postrm_upgrade.sh.erb +10 -1
- data/templates/deb/preinst_upgrade.sh.erb +11 -2
- data/templates/deb/prerm_upgrade.sh.erb +14 -2
- data/templates/p5p_metadata.erb +12 -0
- data/templates/pacman.erb +47 -0
- data/templates/pacman/INSTALL.erb +41 -0
- data/templates/pleaserun/generate-cleanup.sh +17 -0
- data/templates/pleaserun/install-path.sh +17 -0
- data/templates/pleaserun/install.sh +117 -0
- data/templates/pleaserun/scripts/after-install.sh +4 -0
- data/templates/pleaserun/scripts/before-remove.sh +12 -0
- data/templates/rpm.erb +38 -6
- data/templates/sh.erb +38 -3
- metadata +81 -9
@@ -0,0 +1,399 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require "fpm/package"
|
3
|
+
require "fpm/util"
|
4
|
+
require "backports"
|
5
|
+
require "fileutils"
|
6
|
+
require "find"
|
7
|
+
|
8
|
+
class FPM::Package::Pacman < FPM::Package
|
9
|
+
|
10
|
+
option "--optional-depends", "PACKAGE",
|
11
|
+
"Add an optional dependency to the pacman package.", :multivalued => true
|
12
|
+
|
13
|
+
option "--use-file-permissions", :flag,
|
14
|
+
"Use existing file permissions when defining ownership and modes"
|
15
|
+
|
16
|
+
option "--user", "USER", "The owner of files in this package", :default => 'root'
|
17
|
+
|
18
|
+
option "--group", "GROUP", "The group owner of files in this package", :default => 'root'
|
19
|
+
|
20
|
+
# The list of supported compression types. Default is xz (LZMA2)
|
21
|
+
COMPRESSION_TYPES = [ "gz", "bzip2", "xz", "none" ]
|
22
|
+
|
23
|
+
option "--compression", "COMPRESSION", "The compression type to use, must " \
|
24
|
+
"be one of #{COMPRESSION_TYPES.join(", ")}.", :default => "xz" do |value|
|
25
|
+
if !COMPRESSION_TYPES.include?(value)
|
26
|
+
raise ArgumentError, "Pacman compression value of '#{value}' is invalid. " \
|
27
|
+
"Must be one of #{COMPRESSION_TYPES.join(", ")}"
|
28
|
+
end
|
29
|
+
value
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(*args)
|
33
|
+
super(*args)
|
34
|
+
attributes[:pacman_optional_depends] = []
|
35
|
+
end # def initialize
|
36
|
+
|
37
|
+
def architecture
|
38
|
+
case @architecture
|
39
|
+
when nil
|
40
|
+
return %x{uname -m}.chomp # default to current arch
|
41
|
+
when "amd64" # debian and pacman disagree on architecture names
|
42
|
+
return "x86_64"
|
43
|
+
when "native"
|
44
|
+
return %x{uname -m}.chomp # 'native' is current arch
|
45
|
+
when "all", "any", "noarch"
|
46
|
+
return "any"
|
47
|
+
else
|
48
|
+
return @architecture
|
49
|
+
end
|
50
|
+
end # def architecture
|
51
|
+
|
52
|
+
def iteration
|
53
|
+
return @iteration || 1
|
54
|
+
end # def iteration
|
55
|
+
|
56
|
+
def config_files
|
57
|
+
return @config_files || []
|
58
|
+
end # def config_files
|
59
|
+
|
60
|
+
def dependencies
|
61
|
+
bogus_regex = /[^\sA-Za-z0-9><=-]/
|
62
|
+
# Actually modifies depencies if they are not right
|
63
|
+
bogus_dependencies = @dependencies.grep bogus_regex
|
64
|
+
if bogus_dependencies.any?
|
65
|
+
@dependencies.reject! { |a| a =~ bogus_regex }
|
66
|
+
logger.warn("Some of the dependencies looked like they weren't package " \
|
67
|
+
"names. Such dependency entries only serve to confuse arch. " \
|
68
|
+
"I am removing them.",
|
69
|
+
:removed_dependencies => bogus_dependencies,
|
70
|
+
:fixed_dependencies => @dependencies)
|
71
|
+
end
|
72
|
+
return @dependencies
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
# This method is invoked on a package when it has been converted to a new
|
77
|
+
# package format. The purpose of this method is to do any extra conversion
|
78
|
+
# steps, like translating dependency conditions, etc.
|
79
|
+
#def converted_from(origin)
|
80
|
+
# nothing to do by default. Subclasses may implement this.
|
81
|
+
# See the RPM package class for an example.
|
82
|
+
#end # def converted_from
|
83
|
+
|
84
|
+
# Add a new source to this package.
|
85
|
+
# The exact behavior depends on the kind of package being managed.
|
86
|
+
#
|
87
|
+
# For instance:
|
88
|
+
#
|
89
|
+
# * for FPM::Package::Dir, << expects a path to a directory or files.
|
90
|
+
# * for FPM::Package::RPM, << expects a path to an rpm.
|
91
|
+
#
|
92
|
+
# The idea is that you can keep pumping in new things to a package
|
93
|
+
# for later conversion or output.
|
94
|
+
#
|
95
|
+
# Implementations are expected to put files relevant to the 'input' in the
|
96
|
+
# staging_path
|
97
|
+
def input(pacman_pkg_path)
|
98
|
+
control = {}
|
99
|
+
# Unpack the control tarball
|
100
|
+
safesystem(tar_cmd, "-C", staging_path, "-xf", pacman_pkg_path)
|
101
|
+
pkginfo = staging_path(".PKGINFO")
|
102
|
+
mtree = staging_path(".MTREE")
|
103
|
+
install = staging_path(".INSTALL")
|
104
|
+
|
105
|
+
control_contents = File.read(pkginfo)
|
106
|
+
FileUtils.rm(pkginfo)
|
107
|
+
FileUtils.rm(mtree)
|
108
|
+
|
109
|
+
|
110
|
+
control_lines = control_contents.split("\n")
|
111
|
+
control_lines.each do |line|
|
112
|
+
key, val = line.split(/ += +/, 2)
|
113
|
+
if control.has_key? key
|
114
|
+
control[key].push(val)
|
115
|
+
else
|
116
|
+
control[key] = [val]
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
self.name = control["pkgname"][0]
|
121
|
+
|
122
|
+
# Parse 'epoch:version-iteration' in the version string
|
123
|
+
version_re = /^(?:([0-9]+):)?(.+?)(?:-(.*))?$/
|
124
|
+
m = version_re.match(control["pkgver"][0])
|
125
|
+
if !m
|
126
|
+
raise "Unsupported version string '#{control["pkgver"][0]}'"
|
127
|
+
end
|
128
|
+
self.epoch, self.version, self.iteration = m.captures
|
129
|
+
|
130
|
+
self.maintainer = control["packager"][0]
|
131
|
+
|
132
|
+
# Arch has no notion of vendor, so...
|
133
|
+
#self.vendor =
|
134
|
+
|
135
|
+
self.url = control["url"][0]
|
136
|
+
# Groups could include more than one.
|
137
|
+
# Speaking of just taking the first entry of the field:
|
138
|
+
# A crude thing to do, but I suppose it's better than nothing.
|
139
|
+
# -- Daniel Haskin, 3/24/2015
|
140
|
+
self.category = control["group"][0] || self.category
|
141
|
+
|
142
|
+
# Licenses could include more than one.
|
143
|
+
# Speaking of just taking the first entry of the field:
|
144
|
+
# A crude thing to do, but I suppose it's better than nothing.
|
145
|
+
# -- Daniel Haskin, 3/24/2015
|
146
|
+
self.license = control["license"][0] || self.license
|
147
|
+
|
148
|
+
self.architecture = control["arch"][0]
|
149
|
+
|
150
|
+
self.dependencies = control["depend"] || self.dependencies
|
151
|
+
|
152
|
+
self.provides = control["provides"] || self.provides
|
153
|
+
|
154
|
+
self.conflicts = control["conflict"] || self.conflicts
|
155
|
+
|
156
|
+
self.replaces = control["replaces"] || self.replaces
|
157
|
+
|
158
|
+
self.description = control["pkgdesc"][0]
|
159
|
+
|
160
|
+
if control.include? "backup"
|
161
|
+
self.config_files = control["backup"].map{|file| "/" + file}
|
162
|
+
else
|
163
|
+
self.config_files = []
|
164
|
+
end
|
165
|
+
|
166
|
+
self.dependencies = control["depend"] || self.dependencies
|
167
|
+
|
168
|
+
self.attributes[:pacman_optional_depends] = control["optdepend"] || []
|
169
|
+
# There are other available attributes, but I didn't include them because:
|
170
|
+
# - makedepend: deps needed to make the arch package. But it's already
|
171
|
+
# made. It just needs to be converted at this point
|
172
|
+
# - checkdepend: See above
|
173
|
+
# - makepkgopt: See above
|
174
|
+
# - size: can be dynamically generated
|
175
|
+
# - builddate: Should be changed to time of package conversion in the new
|
176
|
+
# package, so this value should be thrown away.
|
177
|
+
|
178
|
+
if File.exist?(install)
|
179
|
+
functions = parse_install_script(install)
|
180
|
+
if functions.include?("pre_install")
|
181
|
+
self.scripts[:before_install] = functions["pre_install"].join("\n")
|
182
|
+
end
|
183
|
+
if functions.include?("post_install")
|
184
|
+
self.scripts[:after_install] = functions["post_install"].join("\n")
|
185
|
+
end
|
186
|
+
if functions.include?("pre_upgrade")
|
187
|
+
self.scripts[:before_upgrade] = functions["pre_upgrade"].join("\n")
|
188
|
+
end
|
189
|
+
if functions.include?("post_upgrade")
|
190
|
+
self.scripts[:after_upgrade] = functions["post_upgrade"].join("\n")
|
191
|
+
end
|
192
|
+
if functions.include?("pre_remove")
|
193
|
+
self.scripts[:before_remove] = functions["pre_remove"].join("\n")
|
194
|
+
end
|
195
|
+
if functions.include?("post_remove")
|
196
|
+
self.scripts[:after_remove] = functions["post_remove"].join("\n")
|
197
|
+
end
|
198
|
+
FileUtils.rm(install)
|
199
|
+
end
|
200
|
+
|
201
|
+
# Note: didn't use `self.directories`.
|
202
|
+
# Pacman doesn't really record that information, to my knowledge.
|
203
|
+
|
204
|
+
end # def input
|
205
|
+
|
206
|
+
def compression_option
|
207
|
+
case self.attributes[:pacman_compression]
|
208
|
+
when nil, "xz"
|
209
|
+
return "--xz"
|
210
|
+
when "none"
|
211
|
+
return ""
|
212
|
+
when "gz"
|
213
|
+
return "-z"
|
214
|
+
when "bzip2"
|
215
|
+
return "-j"
|
216
|
+
else
|
217
|
+
return "--xz"
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def compression_ending
|
222
|
+
case self.attributes[:pacman_compression]
|
223
|
+
when nil, "xz"
|
224
|
+
return ".xz"
|
225
|
+
when "none"
|
226
|
+
return ""
|
227
|
+
when "gz"
|
228
|
+
return ".gz"
|
229
|
+
when "bzip2"
|
230
|
+
return ".bz2"
|
231
|
+
else
|
232
|
+
return ".xz"
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
# Output this package to the given path.
|
237
|
+
def output(output_path)
|
238
|
+
output_check(output_path)
|
239
|
+
|
240
|
+
# Copy all files from staging to BUILD dir
|
241
|
+
Find.find(staging_path) do |path|
|
242
|
+
src = path.gsub(/^#{staging_path}/, '')
|
243
|
+
dst = build_path(src)
|
244
|
+
copy_entry(path, dst, preserve=true, remove_destination=true)
|
245
|
+
copy_metadata(path, dst)
|
246
|
+
end
|
247
|
+
|
248
|
+
# This value is used later in the template for PKGINFO
|
249
|
+
size = safesystemout("du", "-sk", build_path).split(/\s+/)[0].to_i * 1024
|
250
|
+
builddate = Time.new.to_i
|
251
|
+
|
252
|
+
pkginfo = template("pacman.erb").result(binding)
|
253
|
+
pkginfo_file = build_path(".PKGINFO")
|
254
|
+
File.write(pkginfo_file, pkginfo)
|
255
|
+
|
256
|
+
if script?(:before_install) or script?(:after_install) or \
|
257
|
+
script?(:before_upgrade) or script?(:after_upgrade) or \
|
258
|
+
script?(:before_remove) or script?(:after_remove)
|
259
|
+
install_script = template("pacman/INSTALL.erb").result(binding)
|
260
|
+
install_script_file = build_path(".INSTALL")
|
261
|
+
File.write(install_script_file, install_script)
|
262
|
+
end
|
263
|
+
|
264
|
+
generate_mtree
|
265
|
+
|
266
|
+
File.expand_path(output_path).tap do |path|
|
267
|
+
::Dir.chdir(build_path) do
|
268
|
+
safesystem(*([tar_cmd,
|
269
|
+
compression_option,
|
270
|
+
"-cf",
|
271
|
+
path] + data_tar_flags + \
|
272
|
+
::Dir.entries(".").reject{|entry| entry =~ /^\.{1,2}$/ }))
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end # def output
|
276
|
+
|
277
|
+
def data_tar_flags
|
278
|
+
data_tar_flags = []
|
279
|
+
if attributes[:pacman_use_file_permissions?].nil?
|
280
|
+
if !attributes[:pacman_user].nil?
|
281
|
+
if attributes[:pacman_user] == 'root'
|
282
|
+
data_tar_flags += [ "--numeric-owner", "--owner", "0" ]
|
283
|
+
else
|
284
|
+
data_tar_flags += [ "--owner", attributes[:deb_user] ]
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
if !attributes[:pacman_group].nil?
|
289
|
+
if attributes[:pacman_group] == 'root'
|
290
|
+
data_tar_flags += [ "--numeric-owner", "--group", "0" ]
|
291
|
+
else
|
292
|
+
data_tar_flags += [ "--group", attributes[:deb_group] ]
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
return data_tar_flags
|
297
|
+
end # def data_tar_flags
|
298
|
+
|
299
|
+
def default_output
|
300
|
+
v = version
|
301
|
+
v = "#{epoch}:#{v}" if epoch
|
302
|
+
if iteration
|
303
|
+
"#{name}_#{v}-#{iteration}_#{architecture}.#{type}"
|
304
|
+
else
|
305
|
+
"#{name}_#{v}_#{architecture}.#{type}"
|
306
|
+
end
|
307
|
+
end # def default_output
|
308
|
+
|
309
|
+
def to_s_extension; "pkg.tar#{compression_ending}"; end
|
310
|
+
|
311
|
+
def to_s(format=nil)
|
312
|
+
# Default format if nil
|
313
|
+
# git_1.7.9.3-1-amd64.pkg.tar.xz
|
314
|
+
return super(format.nil? ? "NAME-FULLVERSION-ARCH.EXTENSION" : format)
|
315
|
+
end # def to_s
|
316
|
+
|
317
|
+
private
|
318
|
+
|
319
|
+
def generate_mtree
|
320
|
+
::Dir.chdir(build_path) do
|
321
|
+
cmd = "LANG=C bsdtar "
|
322
|
+
cmd += "-czf .MTREE "
|
323
|
+
cmd += "--format=mtree "
|
324
|
+
cmd += "--options='!all,use-set,type,uid,gid,mode,time,size,md5,sha256,link' "
|
325
|
+
cmd += ::Dir.entries(".").reject{|entry| entry =~ /^\.{1,2}$/ }.join(" ")
|
326
|
+
safesystem(cmd)
|
327
|
+
end
|
328
|
+
end # def generate_mtree
|
329
|
+
|
330
|
+
# KNOWN ISSUE:
|
331
|
+
# If an un-matched bracket is used in valid bash, as in
|
332
|
+
# `echo "{"`, this function will choke.
|
333
|
+
# However, to cover this case basically
|
334
|
+
# requires writing almost half a bash parser,
|
335
|
+
# and it is a very small corner case.
|
336
|
+
# Otherwise, this approach is very robust.
|
337
|
+
def gobble_function(cons,prod)
|
338
|
+
level = 1
|
339
|
+
while level > 0
|
340
|
+
line = cons.next
|
341
|
+
# Not the best, but pretty good
|
342
|
+
# short of writing an *actual* sh
|
343
|
+
# parser
|
344
|
+
level += line.count "{"
|
345
|
+
level -= line.count "}"
|
346
|
+
if level > 0
|
347
|
+
prod.push(line.rstrip())
|
348
|
+
else
|
349
|
+
fine = line.sub(/\s*[}]\s*$/, "")
|
350
|
+
if !(fine =~ /^\s*$/)
|
351
|
+
prod.push(fine.rstrip())
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end # def gobble_function
|
356
|
+
|
357
|
+
FIND_SCRIPT_FUNCTION_LINE =
|
358
|
+
/^\s*(\w+)\s*\(\s*\)\s*\{\s*([^}]+?)?\s*(\})?\s*$/
|
359
|
+
|
360
|
+
def parse_install_script(path)
|
361
|
+
global_lines = []
|
362
|
+
look_for = Set.new(["pre_install", "post_install",
|
363
|
+
"pre_upgrade", "post_upgrade",
|
364
|
+
"pre_remove", "post_remove"])
|
365
|
+
functions = {}
|
366
|
+
look_for.each do |fname|
|
367
|
+
functions[fname] = []
|
368
|
+
end
|
369
|
+
|
370
|
+
open(path, "r") do |iscript|
|
371
|
+
lines = iscript.each
|
372
|
+
begin
|
373
|
+
while true
|
374
|
+
line = lines.next
|
375
|
+
# This regex picks up beginning names of posix shell
|
376
|
+
# functions
|
377
|
+
# Examples:
|
378
|
+
# fname() {
|
379
|
+
# fname() { echo hi }
|
380
|
+
m = FIND_SCRIPT_FUNCTION_LINE.match(line)
|
381
|
+
if not m.nil? and look_for.include? m[1]
|
382
|
+
if not m[2].nil?
|
383
|
+
functions[m[1]].push(m[2].rstrip())
|
384
|
+
end
|
385
|
+
gobble_function(lines, functions[m[1]])
|
386
|
+
else
|
387
|
+
global_lines.push(line.rstrip())
|
388
|
+
end
|
389
|
+
end
|
390
|
+
rescue StopIteration
|
391
|
+
end
|
392
|
+
end
|
393
|
+
look_for.each do |name|
|
394
|
+
# Add global lines to each function to preserve global variables, etc.
|
395
|
+
functions[name] = global_lines + functions[name]
|
396
|
+
end
|
397
|
+
return functions
|
398
|
+
end # def parse_install_script
|
399
|
+
end # class FPM::Package::Pacman
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require "fpm/namespace"
|
2
|
+
require "fpm/package"
|
3
|
+
require "fpm/util"
|
4
|
+
require "fileutils"
|
5
|
+
|
6
|
+
require "pleaserun/cli"
|
7
|
+
|
8
|
+
# A pleaserun package.
|
9
|
+
#
|
10
|
+
# This does not currently support 'output'
|
11
|
+
class FPM::Package::PleaseRun < FPM::Package
|
12
|
+
# TODO(sissel): Implement flags.
|
13
|
+
|
14
|
+
require "pleaserun/platform/systemd"
|
15
|
+
require "pleaserun/platform/upstart"
|
16
|
+
require "pleaserun/platform/launchd"
|
17
|
+
require "pleaserun/platform/sysv"
|
18
|
+
|
19
|
+
option "--name", "SERVICE_NAME", "The name of the service you are creating"
|
20
|
+
|
21
|
+
private
|
22
|
+
def input(command)
|
23
|
+
platforms = [
|
24
|
+
::PleaseRun::Platform::Systemd.new("default"), # RHEL 7, Fedora 19+, Debian 8, Ubuntu 16.04
|
25
|
+
::PleaseRun::Platform::Upstart.new("1.5"), # Recent Ubuntus
|
26
|
+
::PleaseRun::Platform::Upstart.new("0.6.5"), # CentOS 6
|
27
|
+
::PleaseRun::Platform::Launchd.new("10.9"), # OS X
|
28
|
+
::PleaseRun::Platform::SYSV.new("lsb-3.1") # Ancient stuff
|
29
|
+
]
|
30
|
+
|
31
|
+
attributes[:pleaserun_name] ||= File.basename(command.first)
|
32
|
+
attributes[:prefix] ||= "/usr/share/pleaserun/#{attributes[:pleaserun_name]}"
|
33
|
+
|
34
|
+
platforms.each do |platform|
|
35
|
+
logger.info("Generating service manifest.", :platform => platform.class.name)
|
36
|
+
platform.program = command.first
|
37
|
+
platform.name = attributes[:pleaserun_name]
|
38
|
+
platform.args = command[1..-1]
|
39
|
+
platform.description = if attributes[:description_given?]
|
40
|
+
attributes[:description]
|
41
|
+
else
|
42
|
+
platform.name
|
43
|
+
end
|
44
|
+
base = staging_path(File.join(attributes[:prefix], "#{platform.platform}/#{platform.target_version || "default"}"))
|
45
|
+
target = File.join(base, "files")
|
46
|
+
actions_script = File.join(base, "install_actions.sh")
|
47
|
+
::PleaseRun::Installer.install_files(platform, target, false)
|
48
|
+
::PleaseRun::Installer.write_actions(platform, actions_script)
|
49
|
+
end
|
50
|
+
|
51
|
+
libs = [ "install.sh", "install-path.sh", "generate-cleanup.sh" ]
|
52
|
+
libs.each do |file|
|
53
|
+
base = staging_path(File.join(attributes[:prefix]))
|
54
|
+
File.write(File.join(base, file), template(File.join("pleaserun", file)).result(binding))
|
55
|
+
File.chmod(0755, File.join(base, file))
|
56
|
+
end
|
57
|
+
|
58
|
+
scripts[:after_install] = template(File.join("pleaserun", "scripts", "after-install.sh")).result(binding)
|
59
|
+
scripts[:before_remove] = template(File.join("pleaserun", "scripts", "before-remove.sh")).result(binding)
|
60
|
+
end # def input
|
61
|
+
|
62
|
+
public(:input)
|
63
|
+
end # class FPM::Package::PleaseRun
|