fpm 0.3.11 → 0.4.0pre1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. data/CHANGELIST +15 -0
  2. data/bin/fpm +2 -15
  3. data/lib/fpm.rb +5 -12
  4. data/lib/fpm/command.rb +323 -0
  5. data/lib/fpm/errors.rb +1 -0
  6. data/lib/fpm/namespace.rb +2 -11
  7. data/lib/fpm/package.rb +255 -100
  8. data/lib/fpm/package/deb.rb +367 -0
  9. data/lib/fpm/package/dir.rb +86 -0
  10. data/lib/fpm/package/gem.rb +162 -0
  11. data/lib/fpm/{source → package}/npm.rb +3 -3
  12. data/lib/fpm/package/pear.rb +41 -0
  13. data/lib/fpm/{target → package}/puppet.rb +1 -3
  14. data/lib/fpm/{source → package}/pyfpm/__init__.py +0 -0
  15. data/lib/fpm/package/pyfpm/__init__.pyc +0 -0
  16. data/lib/fpm/{source → package}/pyfpm/get_metadata.py +15 -3
  17. data/lib/fpm/package/pyfpm/get_metadata.pyc +0 -0
  18. data/lib/fpm/package/python.rb +125 -0
  19. data/lib/fpm/package/rpm.rb +132 -0
  20. data/lib/fpm/{target → package}/solaris.rb +3 -2
  21. data/lib/fpm/package/tar.rb +62 -0
  22. data/lib/fpm/util.rb +56 -7
  23. data/templates/deb.erb +12 -12
  24. data/templates/rpm.erb +32 -38
  25. data/templates/solaris.erb +1 -1
  26. metadata +119 -78
  27. data/lib/fpm/builder.rb +0 -220
  28. data/lib/fpm/flags.rb +0 -20
  29. data/lib/fpm/program.rb +0 -273
  30. data/lib/fpm/rubyfixes.rb +0 -11
  31. data/lib/fpm/source.rb +0 -155
  32. data/lib/fpm/source/dir.rb +0 -59
  33. data/lib/fpm/source/gem.rb +0 -162
  34. data/lib/fpm/source/python.rb +0 -137
  35. data/lib/fpm/source/rpm.rb +0 -28
  36. data/lib/fpm/source/tar.rb +0 -50
  37. data/lib/fpm/target/deb.rb +0 -184
  38. data/lib/fpm/target/rpm.rb +0 -68
  39. data/lib/rpm/header.rb +0 -89
  40. data/lib/rpm/lead.rb +0 -48
  41. data/lib/rpm/namespace.rb +0 -1
  42. data/lib/rpm/rpmfile.rb +0 -81
  43. data/lib/rpm/tag.rb +0 -304
data/CHANGELIST CHANGED
@@ -1,3 +1,18 @@
1
+ 0.4.0 (???)
2
+ - Complete rewrite of pretty much everything.
3
+ * Otherwise, the 'fpm' command functionality should be the same
4
+ * Please let me know if something broke!
5
+ - Now has an API (see examples/api directory)
6
+ - Also has a proper test suite
7
+ - Add --license and --vendor settings (via Pieter Loubser)
8
+ - python support: try to name python packages sanely. Some pypi packages
9
+ are literally called 'python-foo' so make sure we generate packages named
10
+ 'python-foo' and not 'python-python-foo' (via Thomas Meson)
11
+ - rpm support: Add --rpm-rpmbuild-define for passing a --define flag to rpmbuild
12
+ (via Naresh V)
13
+ TODO(sissel): PEAR
14
+ - PHP pear source support (fpm -s pear ...) (via Andrew Gaffney)
15
+
1
16
  0.3.10 (Oct 10, 2011)
2
17
  - Allow taking a list of files/inputs on stdin with '-' or with the --inputs
3
18
  flag. (Matt Patterson)
data/bin/fpm CHANGED
@@ -1,21 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
- #
3
2
 
4
3
  require "rubygems"
5
4
  $: << File.join(File.dirname(__FILE__), "..", "lib")
6
5
  require "fpm"
7
- require "fpm/program"
6
+ require "fpm/command"
8
7
 
9
- if $DEBUG
10
- module Kernel
11
- alias :orig_system :system
12
- def system(*args)
13
- p :system => args
14
- orig_system(*args)
15
- end
16
- end
17
- end
18
-
19
- program = FPM::Program.new
20
- ret = program.run(ARGV)
21
- exit(ret.nil? ? 0 : ret)
8
+ exit(FPM::Command.run || 0)
data/lib/fpm.rb CHANGED
@@ -1,15 +1,8 @@
1
1
  require "fpm/namespace"
2
- require "fpm/builder"
3
2
 
4
3
  require "fpm/package"
5
- require "fpm/target/deb"
6
- require "fpm/target/rpm"
7
- require "fpm/target/solaris"
8
- require "fpm/target/puppet"
9
-
10
- require "fpm/source"
11
- require "fpm/source/dir"
12
- require "fpm/source/gem"
13
- require "fpm/source/python"
14
- require "fpm/source/rpm"
15
- require "fpm/source/tar"
4
+ require "fpm/package/dir"
5
+ require "fpm/package/gem"
6
+ require "fpm/package/deb"
7
+ require "fpm/package/rpm"
8
+ require "fpm/package/python"
@@ -0,0 +1,323 @@
1
+ require "rubygems"
2
+ require "fpm/namespace"
3
+ require "fpm/util"
4
+ require "clamp"
5
+ require "ostruct"
6
+ require "fpm"
7
+
8
+ if $DEBUG
9
+ Cabin::Channel.get(Kernel).subscribe($stdout)
10
+ Cabin::Channel.get(Kernel).level = :debug
11
+ end
12
+
13
+ Dir[File.join(File.dirname(__FILE__), "package", "*.rb")].each do |plugin|
14
+ Cabin::Channel.get(Kernel).info("Loading plugin", :path => plugin)
15
+ require plugin
16
+ end
17
+
18
+
19
+ # The main fpm command entry point.
20
+ class FPM::Command < Clamp::Command
21
+ include FPM::Util
22
+
23
+ option "-t", "OUTPUT_TYPE",
24
+ "the type of package you want to create (deb, rpm, solaris, etc)",
25
+ :attribute_name => :output_type
26
+ option "-s", "INPUT_TYPE",
27
+ "the package type to use as input (gem, rpm, python, etc)",
28
+ :attribute_name => :input_type
29
+ option "-C", "CHDIR",
30
+ "Change directory to here before searching for files",
31
+ :attribute_name => :chdir
32
+ option "--prefix", "PREFIX",
33
+ "A path to prefix files with when building the target package. This may " \
34
+ "be necessary for all input packages. For example, the 'gem' type will" \
35
+ "prefix with your gem directory automatically."
36
+ option ["-p", "--package"], "OUTPUT",
37
+ "The package file path to output.", :default => "NAME-FULLVERSION.ARCH.TYPE"
38
+ option ["-n", "--name"], "NAME", "The name to give to the package"
39
+ option "--verbose", :flag, "Enable verbose output"
40
+ option "--debug", :flag, "Enable debug output"
41
+ option ["-v", "--version"], "VERSION", "The version to give to the package"
42
+ option "--iteration", "ITERATION",
43
+ "The iteration to give to the package. RPM calls this the 'release'. " \
44
+ "FreeBSD calls it 'PORTREVISION'. Debian calls this 'debian_revision'",
45
+ :default => "1"
46
+ option "--epoch", "EPOCH",
47
+ "The epoch value for this package. RPM and Debian calls this 'epoch'. " \
48
+ "FreeBSD calls this 'PORTEPOCH'", :default => "1"
49
+ option "--license", "LICENSE",
50
+ "(optional) license name for this package"
51
+ option "--vendor", "VENDOR",
52
+ "(optional) vendor name for this package"
53
+ option "--category", "CATEGORY",
54
+ "(optional) category this package belongs to", :default => "none"
55
+ option ["-d", "--depends"], "DEPENDENCY",
56
+ "A dependency. This flag can be specified multiple times. Value is " \
57
+ "usually in the form of: -d 'name' or -d 'name > version'",
58
+ :default => [], :attribute_name => :dependencies do |val|
59
+ # Clamp doesn't support multivalue flags (ie; specifying -d multiple times)
60
+ # so we can hack around it with this trickery.
61
+ @dependencies ||= []
62
+ @dependencies << val
63
+ end # -d / --depends
64
+ option "--provides", "PROVIDES",
65
+ "What this package provides (usually a name)" do |val|
66
+ @provides ||= []
67
+ @provides << val
68
+ end # --provides
69
+ option "--conflicts", "CONFLICTS",
70
+ "Other packages/versions this package conflicts with" do |val|
71
+ @conflicts ||= []
72
+ @conflicts << val
73
+ end # --conflicts
74
+ option "--replaces", "REPLACES",
75
+ "Other packages/versions this package replaces" do |val|
76
+ @replaces ||= []
77
+ @replaces << val
78
+ end # --replaces
79
+ option "--config-files", "CONFIG_FILES",
80
+ "Mark a file in the package as being a config file. This uses 'conffiles'" \
81
+ " in debs and %config in rpm." do |val|
82
+ @config_files ||= []
83
+ @config_files << val
84
+ end # --config-files
85
+ option ["-a", "--architecture"], "ARCHITECTURE",
86
+ "The architecture name. Usually matches 'uname -m'. For automatic values," \
87
+ " you can use '-a all' or '-a native'. These two strings will be " \
88
+ "translated into the correct value for your platform and target package type."
89
+ option ["-m", "--maintainer"], "MAINTAINER",
90
+ "The maintainer of this package.",
91
+ :default => "<#{ENV["USER"]}@#{Socket.gethostname}>"
92
+ option ["-S", "--package-name-suffix"], "PACKAGE_NAME_SUFFIX",
93
+ "a name suffix to append to package and dependencies."
94
+ option ["-e", "--edit"], :flag,
95
+ "Edit the package spec before building."
96
+ option ["-x", "--exclude"], "EXCLUDE_PATTERN",
97
+ "Exclude paths matching pattern (shell wildcard globs valid here)" do |val|
98
+ @exclude_pattern ||= []
99
+ @exclude_pattern << val
100
+ end # -x / --exclude
101
+ option "--post-install", "FILE",
102
+ "a script to be run after package installation",
103
+ :attribute_name => :after_install do |val|
104
+ File.expand_path(val) # Get the full path to the script
105
+ end # --post-install
106
+ option "--pre-install", "FILE",
107
+ "a script to be run before package installation",
108
+ :attribute_name => :before_install do |val|
109
+ File.expand_path(val) # Get the full path to the script
110
+ end # --pre-install
111
+ # TODO(sissel): Name the flag --post-remove for clarity
112
+ option "--post-uninstall", "FILE",
113
+ "a script to be run after package removal",
114
+ :attribute_name => :after_remove do |val|
115
+ File.expand_path(val) # Get the full path to the script
116
+ end # --post-uninstall
117
+ # TODO(sissel): Name the flag --pre-remove for clarity
118
+ option "--pre-uninstall", "FILE",
119
+ "a script to be run before package removal",
120
+ :attribute_name => :before_remove do |val|
121
+ File.expand_path(val) # Get the full path to the script
122
+ end # --pre-uninstall
123
+ option "--description", "DESCRIPTION", "Add a description for this package.",
124
+ :default => "no description"
125
+ option "--url", "URI", "Add a url for this package.",
126
+ :default => "http://example.com/no-uri-given"
127
+ option "--inputs", "INPUTS_PATH",
128
+ "The path to a file containing a newline-separated list of " \
129
+ "files and dirs to use as input."
130
+ parameter "[ARGS] ...",
131
+ "Inputs to the source package type. For the 'dir' type, this is the files" \
132
+ " and directories you want to include in the package. For others, like " \
133
+ "'gem', it specifies the packages to download and use as the gem input",
134
+ :attribute_name => :args
135
+
136
+ # package-level settings
137
+ def settings
138
+ @settings ||= {}
139
+ end
140
+
141
+ FPM::Package.types.each do |name, klass|
142
+ klass.apply_options(self)
143
+ end
144
+
145
+ # TODO(sissel): expose 'option' and 'parameter' junk to FPM::Package and subclasses.
146
+ # Apply those things to this command.
147
+ #
148
+ # Add extra flags from plugins
149
+ #FPM::Package::Gem.flags(FPM::Flags.new(opts, "gem", "gem only"), @settings)
150
+ #FPM::Package::Python.flags(FPM::Flags.new(opts, "python", "python only"),
151
+ #@settings)
152
+ #FPM::Package::Deb.flags(FPM::Flags.new(opts, "deb", "deb only"), @settings)
153
+ #FPM::Package::Rpm.flags(FPM::Flags.new(opts, "rpm", "rpm only"), @settings)
154
+
155
+ # A new FPM::Command
156
+ def initialize(*args)
157
+ super(*args)
158
+ @conflicts = []
159
+ @replaces = []
160
+ @provides = []
161
+ @dependencies = []
162
+ @config_files = []
163
+ end # def initialize
164
+
165
+ # Execute this command. See Clamp::Command#execute and Clamp's documentation
166
+ def execute
167
+ @logger = Cabin::Channel.get
168
+ @logger.subscribe(STDOUT)
169
+ @logger.level = :warn
170
+ validator = Validator.new(self)
171
+ if !validator.ok?
172
+ validator.messages.each do |message|
173
+ @logger.warn(message)
174
+ end
175
+
176
+ @logger.fatal("Fix the above problems, and you'll be rolling packages in no time!")
177
+ return 1
178
+ end
179
+
180
+ input_class = FPM::Package.types[input_type]
181
+ output_class = FPM::Package.types[output_type]
182
+
183
+ @logger.level = :info if verbose? # --verbose
184
+ @logger.level = :debug if debug? # --debug
185
+
186
+ input = input_class.new
187
+
188
+ # Merge in package settings.
189
+ # The 'settings' stuff comes in from #apply_options, which goes through
190
+ # all the options defined in known packages and puts them into our command.
191
+ # Flags in packages defined as "--foo-bar" become named "--<packagetype>-foo-bar"
192
+ # They are stored in 'settings' as :gem_foo_bar.
193
+ input.attributes ||= {}
194
+
195
+ # Iterate over all the options
196
+ self.class.declared_options.each do |option|
197
+ with(option.attribute_name) do |attr|
198
+ # clamp makes option attributes available as accessor methods
199
+ # do --foo-bar is available as 'foo_bar'
200
+ # make these available as package attributes.
201
+ input.attributes[attr.to_sym] = send(attr) if respond_to?(attr)
202
+ end
203
+ end
204
+
205
+ args.each do |arg|
206
+ input.input(arg)
207
+ end
208
+
209
+ # Override package settings if they are not the default flag values
210
+ # the below proc essentially does:
211
+ #
212
+ # if someflag != default_someflag
213
+ # input.someflag = someflag
214
+ # end
215
+ set = proc do |object, attribute|
216
+ # if the package's attribute is currently nil *or* the flag setting for this
217
+ # attribute is non-default, use the value.
218
+ if object.send(attribute).nil? || send(attribute) != send("default_#{attribute}")
219
+ @logger.info("Setting from flags: #{attribute}=#{send(attribute)}")
220
+ object.send("#{attribute}=", send(attribute))
221
+ end
222
+ end
223
+ set.call(input, :architecture)
224
+ set.call(input, :category)
225
+ set.call(input, :description)
226
+ set.call(input, :epoch)
227
+ set.call(input, :iteration)
228
+ set.call(input, :license)
229
+ set.call(input, :maintainer)
230
+ set.call(input, :name)
231
+ set.call(input, :url)
232
+ set.call(input, :vendor)
233
+ set.call(input, :version)
234
+ set.call(input, :architecture)
235
+
236
+ input.conflicts += conflicts
237
+ input.dependencies += dependencies
238
+ input.provides += provides
239
+ input.replaces += replaces
240
+
241
+ input.scripts[:before_install] = before_install # --pre-install
242
+ input.scripts[:after_install] = after_install # --post-install
243
+ input.scripts[:before_remove] = before_remove # --pre-uninstall
244
+ input.scripts[:after_remove] = after_remove # --post-uninstall
245
+
246
+ # Convert to the output type
247
+ output = input.convert(output_class)
248
+
249
+ # Write the output somewhere
250
+ output.output(output.to_s(package))
251
+ return 0
252
+ ensure
253
+ input.cleanup unless input.nil?
254
+ output.cleanup unless output.nil?
255
+ end # def execute
256
+
257
+ # A simple flag validator
258
+ #
259
+ # The goal of this class is to ensure the flags and arguments given
260
+ # are a valid configuration.
261
+ class Validator
262
+ include FPM::Util
263
+ private
264
+
265
+ def initialize(command)
266
+ @command = command
267
+ @valid = true
268
+ @messages = []
269
+
270
+ validate
271
+ end # def initialize
272
+
273
+ def ok?
274
+ return @valid
275
+ end # def ok?
276
+
277
+ def validate
278
+ # Make sure the user has passed '-s' and '-t' flags
279
+ mandatory(@command.input_type,
280
+ "Missing required -s flag. What package source did you want?")
281
+ mandatory(@command.output_type,
282
+ "Missing required -t flag. What package output did you want?")
283
+
284
+ # Verify the types requested are valid
285
+ types = FPM::Package.types.keys.sort
286
+ with(@command.input_type) do |val|
287
+ next if val.nil?
288
+ mandatory(FPM::Package.types.include?(val),
289
+ "Invalid input package -s flag) type #{val.inspect}. " \
290
+ "Expected one of: #{types.join(", ")}")
291
+ end
292
+
293
+ with(@command.output_type) do |val|
294
+ next if val.nil?
295
+ mandatory(FPM::Package.types.include?(val),
296
+ "Invalid output package (-t flag) type #{val.inspect}. " \
297
+ "Expected one of: #{types.join(", ")}")
298
+ end
299
+
300
+ mandatory(@command.args.any?,
301
+ "No parameters given. You need to pass additional command " \
302
+ "arguments so that I know what you want to build packages " \
303
+ "from. For example, for '-s dir' you would pass a list of " \
304
+ "files and directories. For '-s gem' you would pass a one" \
305
+ " or more gems to package from. As a full example, this " \
306
+ "will make an rpm of the 'json' rubygem: " \
307
+ "`fpm -s gem -t rpm json`")
308
+ end # def validate
309
+
310
+ def mandatory(value, message)
311
+ if value.nil? or !value
312
+ @messages << message
313
+ @valid = false
314
+ end
315
+ end # def mandatory
316
+
317
+ def messages
318
+ return @messages
319
+ end # def messages
320
+
321
+ public(:initialize, :ok?, :messages)
322
+ end # class Validator
323
+ end # class FPM::Program
data/lib/fpm/errors.rb CHANGED
@@ -1,3 +1,4 @@
1
1
  require "fpm/namespace"
2
2
 
3
+ # Raised if a package is configured in an unsupported way
3
4
  class FPM::InvalidPackageConfiguration < StandardError; end
data/lib/fpm/namespace.rb CHANGED
@@ -1,13 +1,4 @@
1
+ # The FPM namespace
1
2
  module FPM
2
- module Target; end # TODO(sissel): Make this the 'package' ?
3
- DIRS = {
4
- :templates => File.expand_path(
5
- File.join(
6
- File.dirname(__FILE__),
7
- '..',
8
- '..',
9
- 'templates'
10
- )
11
- )
12
- }
3
+ class Package; end
13
4
  end
data/lib/fpm/package.rb CHANGED
@@ -1,9 +1,20 @@
1
1
  require "fpm/namespace"
2
+ require "fpm/util"
2
3
  require "socket" # for Socket.gethostname
3
- require "logger"
4
- require "find" # for Find.find (directory walking)
4
+ require "cabin"
5
+ require "tmpdir"
5
6
 
7
+ # This class is the parent of all packages.
8
+ # If you want to implement an FPM package type, you'll inherit from this.
9
+ #
10
+ # There are
6
11
  class FPM::Package
12
+ include FPM::Util
13
+ include Cabin::Inspectable
14
+
15
+ # This class is raised if there's something wrong with a setting in the package.
16
+ class InvalidArgument < StandardError; end
17
+
7
18
  # The name of this package
8
19
  attr_accessor :name
9
20
 
@@ -19,13 +30,19 @@ class FPM::Package
19
30
  # Debian calls this 'release' and is the last '-NUMBER' in the version
20
31
  # RedHat has this as 'Release' in the .spec file
21
32
  # FreeBSD calls this 'PORTREVISION'
22
- # If left unpicked, it defaults to 1.
33
+ #
34
+ # Iteration can be nil. If nil, the fpm package implementation is expected
35
+ # to handle any default value that should be instead.
23
36
  attr_accessor :iteration
24
37
 
25
38
  # Who maintains this package? This could be the upstream author
26
39
  # or the package maintainer. You pick.
27
40
  attr_accessor :maintainer
28
41
 
42
+ # A identifier representing the vendor. Any string is fine.
43
+ # This is usually who produced the software.
44
+ attr_accessor :vendor
45
+
29
46
  # URL for this package.
30
47
  # Could be the homepage. Could be the download url. You pick.
31
48
  attr_accessor :url
@@ -66,116 +83,254 @@ class FPM::Package
66
83
  # Array of configuration files
67
84
  attr_accessor :config_files
68
85
 
69
- # target-specific settings
70
- attr_accessor :settings
71
-
72
- def initialize(source, params={})
73
- @source = source
74
- @logger = Logger.new(STDERR)
75
- @logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN
76
-
77
- @name = source[:name] # || fail
78
-
79
- # Default version is 1.0 in case nobody told us a specific version.
80
- @version = source[:version] || "1.0"
81
- @epoch = source[:epoch]
82
-
83
- @dependencies = source[:dependencies] || []
84
- # Iteration can be nil. If nil, the fpm package implementation is expected
85
- # to handle any default value that should be instead.
86
- @iteration = source[:iteration]
87
- @url = source[:url] || "http://nourlgiven.example.com/no/url/given"
88
- @category = source[:category] || "default"
89
- @license = source[:license] || "unknown"
90
- #@maintainer = source[:maintainer] || "<#{ENV["USER"]}@#{Socket.gethostname}>"
91
- @maintainer = source[:maintainer]
92
-
93
- # Default maintainer if none given.
94
- if @maintainer.nil? or @maintainer.empty?
95
- # Reference
96
- # http://www.debian.org/doc/manuals/maint-guide/first.en.html
97
- # http://wiki.debian.org/DeveloperConfiguration
98
- # https://github.com/jordansissel/fpm/issues/37
99
- if ENV.include?("DEBEMAIL") and ENV.include?("DEBFULLNAME")
100
- # Use DEBEMAIL and DEBFULLNAME as the default maintainer if available.
101
- @maintainer = "#{ENV["DEBFULLNAME"]} <#{ENV["DEBEMAIL"]}>"
102
- else
103
- # TODO(sissel): Maybe support using 'git config' for a default as well?
104
- # git config --get user.name, etc can be useful.
105
- #
106
- # Otherwise default to user@currenthost
107
- @maintainer = "<#{ENV["USER"]}@#{Socket.gethostname}>"
108
- end
86
+ # Any other attributes specific to this package.
87
+ # This is where you'd put rpm, deb, or other specific attributes.
88
+ attr_accessor :attributes
89
+
90
+ private
91
+
92
+ def initialize
93
+ @logger = Cabin::Channel.get
94
+
95
+ # Attributes for this specific package
96
+ @attributes = {}
97
+
98
+ # Reference
99
+ # http://www.debian.org/doc/manuals/maint-guide/first.en.html
100
+ # http://wiki.debian.org/DeveloperConfiguration
101
+ # https://github.com/jordansissel/fpm/issues/37
102
+ if ENV.include?("DEBEMAIL") and ENV.include?("DEBFULLNAME")
103
+ # Use DEBEMAIL and DEBFULLNAME as the default maintainer if available.
104
+ @maintainer = "#{ENV["DEBFULLNAME"]} <#{ENV["DEBEMAIL"]}>"
105
+ else
106
+ # TODO(sissel): Maybe support using 'git config' for a default as well?
107
+ # git config --get user.name, etc can be useful.
108
+ #
109
+ # Otherwise default to user@currenthost
110
+ @maintainer = "<#{ENV["USER"]}@#{Socket.gethostname}>"
109
111
  end
110
112
 
111
- # If @architecture is nil, the target package should provide a default.
112
- # Special 'architecture' values include "all" (aka rpm's noarch, debian's all)
113
- # Another special includes "native" which will be the current platform's arch.
114
- @architecture = source[:architecture]
115
- @description = source[:description] || "no description given"
116
- @provides = source[:provides] || []
117
- @replaces = source[:replaces] || []
118
- @conflicts = source[:conflicts] || []
119
- @scripts = source[:scripts]
120
- @config_files = source[:config_files] || []
121
-
122
- # Target-specific settings, mirrors :settings metadata in FPM::Source
123
- @settings = params[:settings] || {}
124
- end # def initialize
113
+ @name = nil
114
+ @architecture = "all"
115
+ @description = "no description given"
116
+ @version = nil
117
+ @epoch = nil
118
+ @iteration = nil
119
+ @url = nil
120
+ @category = "default"
121
+ @license = "unknown"
122
+ @vendor = "none"
125
123
 
126
- # nobody needs md5sums by default.
127
- def needs_md5sums
128
- false
129
- end # def needs_md5sums
124
+ @provides = []
125
+ @conflicts = []
126
+ @replaces = []
127
+ @dependencies = []
128
+ @scripts = {}
129
+ @config_files = []
130
130
 
131
- # TODO [Jay]: make this better...?
131
+ staging_path
132
+ build_path
133
+ end # def initialize
134
+
135
+ # Get the 'type' for this instance.
136
+ #
137
+ # For FPM::Package::ABC, this returns 'abc'
132
138
  def type
133
- self.class.name.split(':').last.downcase
139
+ self.class.type
134
140
  end # def type
135
141
 
136
- def template(path=nil)
137
- path ||= "#{type}.erb"
138
- @logger.info("Reading template: #{path}")
139
- tpl = File.read("#{FPM::DIRS[:templates]}/#{path}")
140
- return ERB.new(tpl, nil, "-")
141
- end # def template
142
+ # Convert this package to a new package type
143
+ def convert(klass)
144
+ @logger.info("Converting #{self.type} to #{klass.type}")
145
+ pkg = klass.new
146
+ pkg.cleanup_staging # purge any directories that may have been created by klass.new
142
147
 
143
- def render_spec
144
- # find all files in paths given.
145
- paths = []
146
- @source.paths.each do |path|
147
- Find.find(path) { |p| paths << p }
148
+ # copy other bits
149
+ ivars = [
150
+ :@architecture, :@attributes, :@category, :@config_files, :@conflicts,
151
+ :@dependencies, :@description, :@epoch, :@iteration, :@license, :@maintainer,
152
+ :@name, :@provides, :@replaces, :@scripts, :@url, :@vendor, :@version,
153
+ :@config_files, :@staging_path
154
+ ]
155
+ ivars.each do |ivar|
156
+ #@logger.debug("Copying ivar", :ivar => ivar, :value => instance_variable_get(ivar),
157
+ #:from => self.type, :to => pkg.type)
158
+ pkg.instance_variable_set(ivar, instance_variable_get(ivar))
148
159
  end
149
- #@logger.info(:paths => paths.sort)
150
- template.result(binding)
151
- end # def render_spec
152
-
153
- # Default specfile generator just makes one specfile, whatever that is for
154
- # this package.
155
- def generate_specfile(builddir)
156
- File.open(specfile(builddir), "w") do |f|
157
- f.puts render_spec
160
+
161
+ pkg.converted_from(self.class)
162
+ return pkg
163
+ end # def convert
164
+
165
+ # This method is invoked on a package when it has been covered to a new
166
+ # package format. The purpose of this method is to do any extra conversion
167
+ # steps, like translating dependency conditions, etc.
168
+ def converted_from(origin)
169
+ # nothing to do by default. Subclasses may implement this.
170
+ # See the RPM package class for an example.
171
+ end # def converted
172
+
173
+ # Add a new source to this package.
174
+ # The exact behavior depends on the kind of package being managed.
175
+ #
176
+ # For instance:
177
+ #
178
+ # * for FPM::Package::Dir, << expects a path to a directory or files.
179
+ # * for FPM::Package::RPM, << expects a path to an rpm.
180
+ #
181
+ # The idea is that you can keep pumping in new things to a package
182
+ # for later conversion or output.
183
+ #
184
+ # Implementations are expected to put files relevant to the 'input' in the
185
+ # staging_path
186
+ def input(thing_to_input)
187
+ raise NotImplementedError.new("#{self.class.name} does not yet support " \
188
+ "reading #{self.type} packages")
189
+ end # def input
190
+
191
+ # Output this package to the given path.
192
+ def output(path)
193
+ raise NotImplementedError.new("#{self.class.name} does not yet support " \
194
+ "creating #{self.type} packages")
195
+ end # def output
196
+
197
+ def staging_path(path=nil)
198
+ @staging_path ||= ::Dir.mktmpdir(File.join(::Dir.pwd, "package-#{type}-staging"))
199
+
200
+ if path.nil?
201
+ return @staging_path
202
+ else
203
+ return File.join(@staging_path, path)
158
204
  end
159
- end # def generate_specfile
205
+ end # def staging_path
206
+
207
+ def build_path(path=nil)
208
+ @build_path ||= ::Dir.mktmpdir(File.join(::Dir.pwd, "package-#{type}-build"))
160
209
 
161
- def default_output
162
- if iteration
163
- "#{name}-#{version}-#{iteration}.#{architecture}.#{type}"
210
+ if path.nil?
211
+ return @build_path
164
212
  else
165
- "#{name}-#{version}.#{architecture}.#{type}"
213
+ return File.join(@build_path, path)
214
+ end
215
+ end # def build_path
216
+
217
+ # Clean up any temporary storage used by this class.
218
+ def cleanup
219
+ cleanup_staging
220
+ cleanup_build
221
+ end # def cleanup
222
+
223
+ def cleanup_staging
224
+ if File.directory?(staging_path)
225
+ @logger.debug("Cleaning up staging path", :path => staging_path)
226
+ FileUtils.rm_r(staging_path)
166
227
  end
167
- end # def default_output
228
+ end # def cleanup_staging
168
229
 
169
- def fixpath(path)
170
- if path[0,1] != "/"
171
- path = File.join(@source.root, path)
230
+ def cleanup_build
231
+ if File.directory?(build_path)
232
+ @logger.debug("Cleaning up build path", :path => build_path)
233
+ FileUtils.rm_r(build_path)
172
234
  end
173
- return path if File.symlink?(path)
174
- @logger.info(:fixpath => path)
175
- realpath = Pathname.new(path).realpath.to_s
176
- re = Regexp.new("^#{Regexp.escape(@source.root)}")
177
- realpath.gsub!(re, "")
178
- @logger.info(:fixpath_result => realpath)
179
- return realpath
180
- end # def fixpath
235
+ end # def cleanup_build
236
+
237
+ # List all files in the staging_path
238
+ #
239
+ # The paths will all be relative to staging_path and will not include that
240
+ # path.
241
+ def files
242
+ # Find will print the path you're searching first, so skip it and return
243
+ # the rest. Also trim the leading path such that '#{staging_path}/' is removed
244
+ # from the path before returning.
245
+ return Find.find(staging_path) \
246
+ .select { |path| path != staging_path } \
247
+ .collect { |path| path[staging_path.length + 1.. -1] }
248
+ end # def files
249
+
250
+ def template(path)
251
+ require "erb"
252
+ template_dir = File.join(File.dirname(__FILE__), "..", "..", "templates")
253
+ template_path = File.join(template_dir, path)
254
+ template_code = File.read(template_path)
255
+ @logger.info("Reading template", :path => template_path)
256
+ erb = ERB.new(template_code, nil, "-")
257
+ erb.filename = template_path
258
+ return erb
259
+ end # def template
260
+
261
+ def to_s(fmt="NAME.TYPE")
262
+ fullversion = version.to_s
263
+ fullversion += "-#{iteration}" if iteration
264
+ return fmt.gsub("ARCH", architecture.to_s) \
265
+ .gsub("NAME", name.to_s) \
266
+ .gsub("FULLVERSION", fullversion) \
267
+ .gsub("VERSION", version.to_s) \
268
+ .gsub("EPOCH", epoch.to_s)
269
+ .gsub("TYPE", type.to_s)
270
+ end # def to_s
271
+
272
+ class << self
273
+ # This method is invoked when subclass occurs.
274
+ #
275
+ # Lets us track all known FPM::Package subclasses
276
+ def inherited(klass)
277
+ @subclasses ||= {}
278
+ @subclasses[klass.name.gsub(/.*:/, "").downcase] = klass
279
+ end # def self.inherited
280
+
281
+ # Get a list of all known package subclasses
282
+ def types
283
+ return @subclasses
284
+ end # def self.types
285
+
286
+ # This allows packages to define flags for the fpm command line
287
+ def option(flag, param, help, options={}, &block)
288
+ @options ||= []
289
+ if !flag.is_a?(Array)
290
+ flag = [flag]
291
+ end
292
+
293
+ flag = flag.collect { |f| "--#{type}-#{f.gsub(/^--/, "")}" }
294
+ help = "(#{type} only) #{help}"
295
+ @options << [flag, param, help, options, block]
296
+ end # def options
297
+
298
+ # Apply the options for this package on the clamp command
299
+ #
300
+ # Package flags become attributes '{type}-flag'
301
+ #
302
+ # So if you have:
303
+ #
304
+ # class Foo < FPM::Package
305
+ # option "--bar-baz" ...
306
+ # end
307
+ #
308
+ # The attribute value for --foo-bar-baz will be :foo_bar_baz"
309
+ def apply_options(clampcommand)
310
+ @options ||= []
311
+ @options.each do |args|
312
+ flag, param, help, options, block = args
313
+ clampcommand.option(flag, param, help, options) do |value|
314
+ # This is run in the scope of FPM::Command
315
+ value = block.call(value) unless block.nil?
316
+ # flag is an array, use the first flag as the attribute name
317
+ attr = flag.first[2..-1].gsub(/-+/, "_").to_sym
318
+ settings[attr] = value
319
+ end
320
+ end
321
+ end # def apply_options
322
+
323
+ # Get the type of this package class.
324
+ #
325
+ # For "Foo::Bar::BAZ" this will return "baz"
326
+ def type
327
+ self.name.split(':').last.downcase
328
+ end # def self.type
329
+ end # class << self
330
+
331
+ # General public API
332
+ public(:type, :initialize, :convert, :input, :output, :to_s, :cleanup, :files)
333
+
334
+ # Package internal public api
335
+ public(:cleanup_staging, :cleanup_build, :staging_path, :converted_from)
181
336
  end # class FPM::Package