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

architecture[RW]

What architecture is this package for?

attributes[RW]

Any other attributes specific to this package. This is where you'd put rpm, deb, or other specific attributes.

attrs[RW]
category[RW]

The category of this package. RedHat calls this 'Group' Debian calls this 'Section' FreeBSD would put this in /usr/ports/<category>/…

config_files[RW]

Array of configuration files

conflicts[RW]

Array of things this package conflicts with. (Not all packages support this)

dependencies[RW]

Array of dependencies.

description[RW]

a summary or description of the package

directories[RW]
epoch[RW]

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.

iteration[RW]

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.

license[RW]

A identifier representing the license. Any string is fine.

maintainer[RW]

Who maintains this package? This could be the upstream author or the package maintainer. You pick.

name[RW]

The name of this package

provides[RW]

Array of things this package provides. (Not all packages support this)

replaces[RW]

Array of things this package replaces. (Not all packages support this)

scripts[RW]

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[RW]

URL for this package. Could be the homepage. Could be the download url. You pick.

vendor[RW]

A identifier representing the vendor. Any string is fine. This is usually who produced the software.

version[RW]

The version of this package (the upstream version)

Public Class Methods

new() click to toggle source
# File lib/fpm/package.rb, line 120
def initialize
  # Attributes for this specific package
  @attributes = {
    # Default work location
    :workdir => ::Dir.tmpdir
  }

  # 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 = {}

  build_path
  # Dont' initialize staging_path just yet, do it lazily so subclass can get a word in.
end

Private Class Methods

apply_options(clampcommand) click to toggle source

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 453
def apply_options(clampcommand)
  @options ||= []
  @options.each do |args|
    flag, param, help, options, block = args
    clampcommand.option(flag, param, help, options, &block)
  end
end
default_attributes() { |to_sym, options| ... } click to toggle source
# File lib/fpm/package.rb, line 461
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
inherited(klass) click to toggle source

This method is invoked when subclass occurs.

Lets us track all known FPM::Package subclasses

# File lib/fpm/package.rb, line 414
def inherited(klass)
  @subclasses ||= {}
  @subclasses[klass.name.gsub(/.*:/, "").downcase] = klass
end
option(flag, param, help, options={}, &block) click to toggle source

This allows packages to define flags for the fpm command line

# File lib/fpm/package.rb, line 425
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
type() click to toggle source

Get the type of this package class.

For “Foo::Bar::BAZ” this will return “baz”

# File lib/fpm/package.rb, line 473
def type
  self.name.split(':').last.downcase
end
types() click to toggle source

Get a list of all known package subclasses

# File lib/fpm/package.rb, line 420
def types
  return @subclasses
end

Public Instance Methods

build_path(path=nil) click to toggle source
# File lib/fpm/package.rb, line 266
def build_path(path=nil)
  @build_path ||= Stud::Temporary.directory("package-#{type}-build")

  if path.nil?
    return @build_path
  else
    return File.join(@build_path, path)
  end
end
cleanup() click to toggle source

Clean up any temporary storage used by this class.

# File lib/fpm/package.rb, line 277
def cleanup
  cleanup_staging
  cleanup_build
end
cleanup_build() click to toggle source
# File lib/fpm/package.rb, line 289
def cleanup_build
  if File.directory?(build_path)
    logger.debug("Cleaning up build path", :path => build_path)
    FileUtils.rm_r(build_path)
  end
end
cleanup_staging() click to toggle source
# File lib/fpm/package.rb, line 282
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(klass) click to toggle source

Convert this package to a new package type

# File lib/fpm/package.rb, line 194
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
converted_from(origin) click to toggle source

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 227
def converted_from(origin)
  # nothing to do by default. Subclasses may implement this.
  # See the RPM package class for an example.
end
edit_file(path) click to toggle source
# File lib/fpm/package.rb, line 366
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
files() click to toggle source

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 304
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
input(thing_to_input) click to toggle source

Add a new source to this package. The exact behavior depends on the kind of package being managed.

For instance:

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 245
def input(thing_to_input)
  raise NotImplementedError.new("#{self.class.name} does not yet support " \
                                "reading #{self.type} packages")
end
output(path) click to toggle source

Output this package to the given path.

# File lib/fpm/package.rb, line 251
def output(path)
  raise NotImplementedError.new("#{self.class.name} does not yet support " \
                                "creating #{self.type} packages")
end
provides=(value) click to toggle source
# File lib/fpm/package.rb, line 544
def provides=(value)
  if !value.is_a?(Array)
    @provides = [value]
  else
    @provides = value
  end
end
script(script_name) click to toggle source

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 519
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
staging_path(path=nil) click to toggle source
# File lib/fpm/package.rb, line 256
def staging_path(path=nil)
  @staging_path ||= Stud::Temporary.directory("package-#{type}-staging")

  if path.nil?
    return @staging_path
  else
    return File.join(@staging_path, path)
  end
end
to_s(fmt=nil) click to toggle source
# File lib/fpm/package.rb, line 354
def to_s(fmt=nil)
  fmt = "NAME.EXTENSION" if fmt.nil?
  return fmt.gsub("ARCH", to_s_arch) \
    .gsub("NAME",         to_s_name) \
    .gsub("FULLVERSION",  to_s_fullversion) \
    .gsub("VERSION",      to_s_version) \
    .gsub("ITERATION",    to_s_iteration) \
    .gsub("EPOCH",        to_s_epoch) \
    .gsub("TYPE",         to_s_type) \
    .gsub("EXTENSION",    to_s_extension)
end
type() click to toggle source

Get the 'type' for this instance.

For FPM::Package::ABC, this returns 'abc'

# File lib/fpm/package.rb, line 189
def type
  self.class.type
end

Private Instance Methods

exclude() click to toggle source

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 384
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
output_check(output_path) click to toggle source
# File lib/fpm/package.rb, line 530
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
script?(name) click to toggle source

Does this package have the given script?

# File lib/fpm/package.rb, line 495
def script?(name)
  return scripts.include?(name)
end
template(path) click to toggle source
# File lib/fpm/package.rb, line 330
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
template_dir() click to toggle source
# File lib/fpm/package.rb, line 326
def template_dir
  File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "templates"))
end
to_s_arch() click to toggle source

The following methods are provided to easily override particular substitut- ions performed by to_s for subclasses

# File lib/fpm/package.rb, line 344
def to_s_arch;        architecture.to_s;  end
to_s_epoch() click to toggle source
# File lib/fpm/package.rb, line 349
def to_s_epoch;       epoch.to_s;         end
to_s_extension() click to toggle source
# File lib/fpm/package.rb, line 351
def to_s_extension;   type.to_s;          end
to_s_fullversion() click to toggle source
# File lib/fpm/package.rb, line 346
def to_s_fullversion; iteration ? "#{version}-#{iteration}" : "#{version}"; end
to_s_iteration() click to toggle source
# File lib/fpm/package.rb, line 348
def to_s_iteration;   iteration.to_s;     end
to_s_name() click to toggle source
# File lib/fpm/package.rb, line 345
def to_s_name;        name.to_s;          end
to_s_type() click to toggle source
# File lib/fpm/package.rb, line 350
def to_s_type;        type.to_s;          end
to_s_version() click to toggle source
# File lib/fpm/package.rb, line 347
def to_s_version;     version.to_s;       end
write_scripts() click to toggle source

write all scripts to .scripts (tar and dir)

# File lib/fpm/package.rb, line 500
def write_scripts
  scripts_path = File.join(staging_path, ".scripts")
  target_scripts = [:before_install, :after_install, :before_remove, :after_remove]
  if target_scripts.any? {|name| script?(name)}
    ::Dir.mkdir(scripts_path)
    target_scripts.each do |name|
      next unless script?(name)
      out = File.join(scripts_path, name.to_s)
      logger.debug('Writing script', :source => name, :target => out)
      File.write(out, script(name))
      File.chmod(0755, out)
    end
  end
end