class FPM::Package
This class is the parent of all packages. If you want to implement an FPM
package type, you'll inherit from this.
Attributes
What architecture is this package for?
Any other attributes specific to this package. This is where you'd put rpm, deb, or other specific attributes.
The category of this package. RedHat calls this 'Group' Debian calls this 'Section' FreeBSD would put this in /usr/ports/<category>/…
Array of configuration files
Array of things this package conflicts with. (Not all packages support this)
Array of dependencies.
a summary or description of the package
The epoch version of this package This is used most when an upstream package changes it's versioning style so standard comparisions wouldn't work.
The iteration of this package.
Debian calls this 'release' and is the last '-NUMBER' in the version RedHat has this as 'Release' in the .spec file FreeBSD calls this 'PORTREVISION'
Iteration can be nil. If nil, the fpm package implementation is expected to handle any default value that should be instead.
A identifier representing the license. Any string is fine.
Who maintains this package? This could be the upstream author or the package maintainer. You pick.
The name of this package
Array of things this package provides. (Not all packages support this)
Array of things this package replaces. (Not all packages support this)
hash of scripts for maintainer/package scripts (postinstall, etc)
The keys are :before_install, etc The values are the text to use in the script.
URL for this package. Could be the homepage. Could be the download url. You pick.
A identifier representing the vendor. Any string is fine. This is usually who produced the software.
The version of this package (the upstream version)
Public Class Methods
# File lib/fpm/package.rb, line 118 def initialize # Attributes for this specific package @attributes = {} # Reference # http://www.debian.org/doc/manuals/maint-guide/first.en.html # http://wiki.debian.org/DeveloperConfiguration # https://github.com/jordansissel/fpm/issues/37 if ENV.include?("DEBEMAIL") and ENV.include?("DEBFULLNAME") # Use DEBEMAIL and DEBFULLNAME as the default maintainer if available. @maintainer = "#{ENV["DEBFULLNAME"]} <#{ENV["DEBEMAIL"]}>" else # TODO(sissel): Maybe support using 'git config' for a default as well? # git config --get user.name, etc can be useful. # # Otherwise default to user@currenthost @maintainer = "<#{ENV["USER"]}@#{Socket.gethostname}>" end # Set attribute defaults based on flags # This allows you to define command line options with default values # that also are obeyed if fpm is used programmatically. self.class.default_attributes do |attribute, value| attributes[attribute] = value end @name = nil @architecture = "native" @description = "no description given" @version = nil @epoch = nil @iteration = nil @url = nil @category = "default" @license = "unknown" @vendor = "none" # Iterate over all the options and set defaults if self.class.respond_to?(:declared_options) self.class.declared_options.each do |option| option.attribute_name.tap do |attr| # clamp makes option attributes available as accessor methods # do --foo-bar is available as 'foo_bar' # make these available as package attributes. attr = "#{attr}?" if !respond_to?(attr) input.attributes[attr.to_sym] = send(attr) if respond_to?(attr) end end end @provides = [] @conflicts = [] @replaces = [] @dependencies = [] @scripts = {} @config_files = [] @directories = [] @attrs = {} staging_path build_path end
Private Class Methods
Apply the options for this package on the clamp command
Package
flags become attributes '{type}-flag'
So if you have:
class Foo < FPM::Package option "--bar-baz" ... end
The attribute value for –foo-bar-baz will be :foo_bar_baz“
# File lib/fpm/package.rb, line 434 def apply_options(clampcommand) @options ||= [] @options.each do |args| flag, param, help, options, block = args clampcommand.option(flag, param, help, options, &block) end end
# File lib/fpm/package.rb, line 442 def default_attributes(&block) return if @options.nil? @options.each do |flag, param, help, options, _block| attr = flag.first.gsub(/^-+/, "").gsub(/-/, "_").gsub("[no_]", "") attr += "?" if param == :flag yield attr.to_sym, options[:default] end end
This method is invoked when subclass occurs.
Lets us track all known FPM::Package
subclasses
# File lib/fpm/package.rb, line 395 def inherited(klass) @subclasses ||= {} @subclasses[klass.name.gsub(/.*:/, "").downcase] = klass end
This allows packages to define flags for the fpm command line
# File lib/fpm/package.rb, line 406 def option(flag, param, help, options={}, &block) @options ||= [] if !flag.is_a?(Array) flag = [flag] end if param == :flag # Automatically make 'flag' (boolean) options tunable with '--[no-]...' flag = flag.collect { |f| "--[no-]#{type}-#{f.gsub(/^--/, "")}" } else flag = flag.collect { |f| "--#{type}-#{f.gsub(/^--/, "")}" } end help = "(#{type} only) #{help}" @options << [flag, param, help, options, block] end
Get the type of this package class.
For “Foo::Bar::BAZ” this will return “baz”
# File lib/fpm/package.rb, line 454 def type self.name.split(':').last.downcase end
Get a list of all known package subclasses
# File lib/fpm/package.rb, line 401 def types return @subclasses end
Public Instance Methods
# File lib/fpm/package.rb, line 261 def build_path(path=nil) @build_path ||= ::Dir.mktmpdir("package-#{type}-build") #, ::Dir.pwd) if path.nil? return @build_path else return File.join(@build_path, path) end end
Clean up any temporary storage used by this class.
# File lib/fpm/package.rb, line 272 def cleanup cleanup_staging cleanup_build end
# File lib/fpm/package.rb, line 284 def cleanup_build if File.directory?(build_path) logger.debug("Cleaning up build path", :path => build_path) FileUtils.rm_r(build_path) end end
# File lib/fpm/package.rb, line 277 def cleanup_staging if File.directory?(staging_path) logger.debug("Cleaning up staging path", :path => staging_path) FileUtils.rm_r(staging_path) end end
Convert this package to a new package type
# File lib/fpm/package.rb, line 189 def convert(klass) logger.info("Converting #{self.type} to #{klass.type}") exclude pkg = klass.new pkg.cleanup_staging # purge any directories that may have been created by klass.new # copy other bits ivars = [ :@architecture, :@category, :@config_files, :@conflicts, :@dependencies, :@description, :@epoch, :@iteration, :@license, :@maintainer, :@name, :@provides, :@replaces, :@scripts, :@url, :@vendor, :@version, :@directories, :@staging_path, :@attrs ] ivars.each do |ivar| #logger.debug("Copying ivar", :ivar => ivar, :value => instance_variable_get(ivar), #:from => self.type, :to => pkg.type) pkg.instance_variable_set(ivar, instance_variable_get(ivar)) end # Attributes are special! We do not want to remove the default values of # the destination package type unless their value is specified on the # source package object. pkg.attributes.merge!(self.attributes) pkg.converted_from(self.class) return pkg end
This method is invoked on a package when it has been converted to a new package format. The purpose of this method is to do any extra conversion steps, like translating dependency conditions, etc.
# File lib/fpm/package.rb, line 222 def converted_from(origin) # nothing to do by default. Subclasses may implement this. # See the RPM package class for an example. end
# File lib/fpm/package.rb, line 347 def edit_file(path) editor = ENV['FPM_EDITOR'] || ENV['EDITOR'] || 'vi' logger.info("Launching editor", :file => path) command = "#{editor} #{Shellwords.escape(path)}" system("#{editor} #{Shellwords.escape(path)}") if !$?.success? raise ProcessFailed.new("'#{editor}' failed (exit code " \ "#{$?.exitstatus}) Full command was: " \ "#{command}"); end if File.size(path) == 0 raise "Empty file after editing: #{path.inspect}" end end
List all files in the staging_path
The paths will all be relative to staging_path
and will not include that path.
This method will emit 'leaf' paths. Files, symlinks, and other file-like things are emitted. Intermediate directories are ignored, but empty directories are emitted.
# File lib/fpm/package.rb, line 299 def files is_leaf = lambda do |path| # True if this is a file/symlink/etc, but not a plain directory return true if !(File.directory?(path) and !File.symlink?(path)) # Empty directories are leafs as well. return true if ::Dir.entries(path).sort == [".", ".."] # False otherwise (non-empty directory, etc) return false end # is_leaf # Find all leaf-like paths (files, symlink, empty directories, etc) # Also trim the leading path such that '#{staging_path}/' is removed from # the path before returning. # # Wrapping Find.find in an Enumerator is required for sane operation in ruby 1.8.7, # but requires the 'backports' gem (which is used in other places in fpm) return Enumerator.new { |y| Find.find(staging_path) { |path| y << path } } \ .select { |path| path != staging_path } \ .select { |path| is_leaf.call(path) } \ .collect { |path| path[staging_path.length + 1.. -1] } end
Add a new source to this package. The exact behavior depends on the kind of package being managed.
For instance:
-
for
FPM::Package::Dir
, << expects a path to a directory or files. -
for
FPM::Package::RPM
, << expects a path to an rpm.
The idea is that you can keep pumping in new things to a package for later conversion or output.
Implementations are expected to put files relevant to the 'input' in the staging_path
# File lib/fpm/package.rb, line 240 def input(thing_to_input) raise NotImplementedError.new("#{self.class.name} does not yet support " \ "reading #{self.type} packages") end
Output this package to the given path.
# File lib/fpm/package.rb, line 246 def output(path) raise NotImplementedError.new("#{self.class.name} does not yet support " \ "creating #{self.type} packages") end
# File lib/fpm/package.rb, line 509 def provides=(value) if !value.is_a?(Array) @provides = [value] else @provides = value end end
Get the contents of the script by a given name.
If template_scripts? is set in attributes (often by the –template-scripts flag), then apply it as an ERB template.
# File lib/fpm/package.rb, line 484 def script(script_name) if attributes[:template_scripts?] erb = ERB.new(scripts[script_name], nil, "-") # TODO(sissel): find the original file name for the file. erb.filename = "script(#{script_name})" return erb.result(binding) else return scripts[script_name] end end
# File lib/fpm/package.rb, line 251 def staging_path(path=nil) @staging_path ||= ::Dir.mktmpdir("package-#{type}-staging") #, ::Dir.pwd) if path.nil? return @staging_path else return File.join(@staging_path, path) end end
# File lib/fpm/package.rb, line 334 def to_s(fmt="NAME.TYPE") fmt = "NAME.TYPE" if fmt.nil? fullversion = version.to_s fullversion += "-#{iteration}" if iteration return fmt.gsub("ARCH", architecture.to_s) \ .gsub("NAME", name.to_s) \ .gsub("FULLVERSION", fullversion) \ .gsub("VERSION", version.to_s) \ .gsub("ITERATION", iteration.to_s) \ .gsub("EPOCH", epoch.to_s) \ .gsub("TYPE", type.to_s) end
Get the 'type' for this instance.
For FPM::Package::ABC, this returns 'abc'
# File lib/fpm/package.rb, line 184 def type self.class.type end
Private Instance Methods
This method removes excluded files from the staging_path. Subclasses can remove the files during the input phase rather than deleting them here
# File lib/fpm/package.rb, line 365 def exclude return if attributes[:excludes].nil? if @attributes.include?(:prefix) installdir = staging_path(@attributes[:prefix]) else installdir = staging_path end Find.find(installdir) do |path| match_path = path.sub("#{installdir.chomp('/')}/", '') attributes[:excludes].each do |wildcard| logger.debug("Checking path against wildcard", :path => match_path, :wildcard => wildcard) if File.fnmatch(wildcard, match_path) logger.info("Removing excluded path", :path => match_path, :matches => wildcard) FileUtils.rm_r(path) Find.prune break end end end end
# File lib/fpm/package.rb, line 495 def output_check(output_path) if !File.directory?(File.dirname(output_path)) raise ParentDirectoryMissing.new(output_path) end if File.file?(output_path) if attributes[:force?] logger.warn("Force flag given. Overwriting package at #{output_path}") File.delete(output_path) else raise FileAlreadyExists.new(output_path) end end end
Does this package have the given script?
# File lib/fpm/package.rb, line 476 def script?(name) return scripts.include?(name) end
# File lib/fpm/package.rb, line 325 def template(path) template_path = File.join(template_dir, path) template_code = File.read(template_path) logger.info("Reading template", :path => template_path) erb = ERB.new(template_code, nil, "-") erb.filename = template_path return erb end
# File lib/fpm/package.rb, line 321 def template_dir File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "templates")) end