fpm 0.3.11 → 0.4.0pre1

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