class FPM::Package::RPM

RPM Package type.

Build RPMs without having to waste hours reading Maximum-RPM. Well, in case you want to read it, here: www.rpm.org/max-rpm/

The following attributes are supported:

Constants

COMPRESSION_MAP
DIGEST_ALGORITHM_MAP

Public Instance Methods

architecture() click to toggle source

Handle any architecture naming conversions. For example, debian calls amd64 what redhat calls x86_64, this method fixes those types of things.

# File lib/fpm/package/rpm.rb, line 249
def architecture
  case @architecture
    when nil
      return %x{uname -m}.chomp   # default to current arch
    when "amd64" # debian and redhat disagree on architecture names
      return "x86_64"
    when "arm64" # debian and redhat disagree on architecture names
      return "aarch64"
    when "native"
      return %x{uname -m}.chomp   # 'native' is current arch
    when "all"
      # Translate fpm "all" arch to what it means in RPM.
      return "noarch"
    else
      return @architecture
  end
end
build_sub_dir() click to toggle source
# File lib/fpm/package/rpm.rb, line 585
def build_sub_dir
  return "BUILD"
  #return File.join("BUILD", prefix)
end
converted_from(origin) click to toggle source

See FPM::Package#converted_from

# File lib/fpm/package/rpm.rb, line 279
def converted_from(origin)
  if origin == FPM::Package::Gem
    fixed_deps = []
    self.dependencies.collect do |dep|
      # Gem dependency operator "~>" is not compatible with rpm. Translate any found.
      fixed_deps = fixed_deps + expand_pessimistic_constraints(dep)
    end
    self.dependencies = fixed_deps

    # Convert 'rubygem-foo' provides values to 'rubygem(foo)'
    # since that's what most rpm packagers seem to do.
    self.provides = self.provides.collect do |provides|
      # Tries to match rubygem_prefix [1], gem_name [2] and version [3] if present
      # and return it in rubygem_prefix(gem_name) form
      if name=/^(#{attributes[:gem_package_name_prefix]})-([^\s]+)\s*(.*)$/.match(provides)
        "#{name[1]}(#{name[2]})#{name[3] ? " #{name[3]}" : ""}"
      else
        provides
      end
    end
    if !self.attributes[:rpm_verbatim_gem_dependencies?]
      self.dependencies = self.dependencies.collect do |dependency|
        # Tries to match rubygem_prefix [1], gem_name [2] and version [3] if present
        # and return it in rubygem_prefix(gem_name) form
        if name=/^(#{attributes[:gem_package_name_prefix]})-([^\s]+)\s*(.*)$/.match(dependency)
          "#{name[1]}(#{name[2]})#{name[3] ? " #{name[3]}" : ""}"
        else
          dependency
        end
      end
    end
  end

  # Convert != dependency as Conflict =, as rpm doesn't understand !=
  self.dependencies = self.dependencies.select do |dep|
    name, op, version = dep.split(/\s+/)
    dep_ok = true
    if op == '!='
      self.conflicts << "#{name} = #{version}"
      dep_ok = false
    end
    dep_ok
  end

  # if --ignore-iteration-in-dependencies is true convert foo = X, to
  # foo >= X , foo < X+1
  if self.attributes[:rpm_ignore_iteration_in_dependencies?]
    self.dependencies = self.dependencies.collect do |dep|
      name, op, version = dep.split(/\s+/)
      if op == '='
        nextversion = version.split('.').collect { |v| v.to_i }
        nextversion[-1] += 1
        nextversion = nextversion.join(".")
        logger.warn("Converting dependency #{dep} to #{name} >= #{version}, #{name} < #{nextversion}")
        ["#{name} >= #{version}", "#{name} < #{nextversion}"]
      else
        dep
      end
    end.flatten
  end

setscript = proc do |scriptname|
    script_path = self.attributes[scriptname]
    # Skip scripts not set
    next if script_path.nil?
    if !File.exist?(script_path)
      logger.error("No such file (for #{scriptname.to_s}): #{script_path.inspect}")
      script_errors    << script_path
    end
    # Load the script into memory.
    scripts[scriptname] = File.read(script_path)
  end

setscript.call(:rpm_verifyscript)
setscript.call(:rpm_posttrans)
setscript.call(:rpm_pretrans)
end
digest_algorithm() click to toggle source
# File lib/fpm/package/rpm.rb, line 643
def digest_algorithm
  return DIGEST_ALGORITHM_MAP[attributes[:rpm_digest]]
end
epoch() click to toggle source

The default epoch value must be nil, see #381

# File lib/fpm/package/rpm.rb, line 609
def epoch
  return @epoch if @epoch.is_a?(Numeric)

  if @epoch.nil? or @epoch.empty?
    return nil
  end

  return @epoch
end
input(path) click to toggle source
# File lib/fpm/package/rpm.rb, line 375
def input(path)
  rpm = ::RPM::File.new(path)

  tags = {}
  rpm.header.tags.each do |tag|
    tags[tag.tag] = tag.value
  end

  self.architecture = tags[:arch]
  self.category = tags[:group]
  self.description = tags[:description]
  self.epoch = (tags[:epoch] || [nil]).first # for some reason epoch is an array
  self.iteration = tags[:release]
  self.license = tags[:license]
  self.maintainer = maintainer
  self.name = tags[:name]
  self.url = tags[:url]
  self.vendor = tags[:vendor]
  self.version = tags[:version]

  self.scripts[:before_install] = tags[:prein]
  self.scripts[:after_install] = tags[:postin]
  self.scripts[:before_remove] = tags[:preun]
  self.scripts[:after_remove] = tags[:postun]
  self.scripts[:rpm_verifyscript] = tags[:verifyscript]
  self.scripts[:rpm_posttrans] = tags[:posttrans]
  self.scripts[:rpm_pretrans] = tags[:pretrans]
  # TODO(sissel): prefix these scripts above with a shebang line if there isn't one?
  # Also taking into account the value of tags[preinprog] etc, something like:
  #    #!#{tags[:preinprog]}
  #    #{tags[prein]}

  if !tags[:triggerindex].nil?
    val = tags[:triggerindex].zip(tags[:triggername],tags[:triggerflags],tags[:triggerversion]).group_by{ |x| x[0]}
    val = val.collect do |order,data|
      new_data = data.collect { |x| [ x[1], rpm.operator(x[2]), x[3]].join(" ").strip}.join(", ")
      [order, rpm_get_trigger_type(data[0][2]), new_data]
    end
    val.each do |order, attr,data|
      self.attributes[attr] = [] if self.attributes[attr].nil?
      scriptprog = (tags[:triggerscriptprog][order] == '/bin/sh') ? "" : "-p #{tags[:triggerscriptprog][order]}"
      self.attributes[attr] << [data,tags[:triggerscripts][order],scriptprog]
    end
  end

  if !attributes[:no_auto_depends?]
    self.dependencies += rpm.requires.collect do |name, operator, version|
      [name, operator, version].join(" ")
    end
  end

  self.conflicts += rpm.conflicts.collect do |name, operator, version|
    [name, operator, version].join(" ")
  end
  self.provides += rpm.provides.collect do |name, operator, version|
    [name, operator, version].join(" ")
  end
  #input.replaces += replaces

  self.config_files += rpm.config_files

  # rpms support '%dir' things for specifying empty directories to package,
  # but the rpm header itself doesn't actually record this information.
  # so there's no 'directories' to copy, so don't try to merge in the
  # 'directories' feature.
  # TODO(sissel): If you want this feature, we'll have to find scan
  # the extracted rpm for empty directories. I'll wait until someone asks for
  # this feature
  #self.directories += rpm.directories

  # Extract to the staging directory
  rpm.extract(staging_path)
end
iteration() click to toggle source

This method ensures a default value for iteration if none is provided.

# File lib/fpm/package/rpm.rb, line 268
def iteration
  if @iteration.kind_of?(String) and @iteration.include?("-")
    logger.warn("Package iteration '#{@iteration}' includes dashes, converting" \
                 " to underscores. rpmbuild does not allow the dashes in the package iteration (called 'Release' in rpm)")
    @iteration = @iteration.gsub(/-/, "_")
  end

  return @iteration ? @iteration : 1
end
output(output_path) click to toggle source
# File lib/fpm/package/rpm.rb, line 453
def output(output_path)
  output_check(output_path)
  %w(BUILD RPMS SRPMS SOURCES SPECS).each { |d| FileUtils.mkdir_p(build_path(d)) }
  args = ["rpmbuild", "-bb"]

  if %x{uname -m}.chomp != self.architecture
    rpm_target = self.architecture
  end

  # issue #309
  if !attributes[:rpm_os].nil?
    rpm_target = "#{architecture}-unknown-#{attributes[:rpm_os]}"
  end

  # issue #707
  if !rpm_target.nil?
    args += ["--target", rpm_target]
  end

  # set the rpm dist tag
  args += ["--define", "dist .#{attributes[:rpm_dist]}"] if attributes[:rpm_dist]

  args += [
    "--define", "buildroot #{build_path}/BUILD",
    "--define", "_topdir #{build_path}",
    "--define", "_sourcedir #{build_path}",
    "--define", "_rpmdir #{build_path}/RPMS",
    "--define", "_tmppath #{attributes[:workdir]}"
  ]

  args += ["--sign"] if attributes[:rpm_sign?]

  if attributes[:rpm_auto_add_directories?]
    fs_dirs_list = File.join(template_dir, "rpm", "filesystem_list")
    fs_dirs = File.readlines(fs_dirs_list).reject { |x| x =~ /^\s*#/}.map { |x| x.chomp }
    fs_dirs.concat((attributes[:auto_add_exclude_directories] or []))

    Find.find(staging_path) do |path|
      next if path == staging_path
      if File.directory? path and !File.symlink? path
        add_path = path.gsub(/^#{staging_path}/,'')
        self.directories << add_path if not fs_dirs.include? add_path
      end
    end
  else
    self.directories = self.directories.map { |x| self.prefixed_path(x) }
    alldirs = []
    self.directories.each do |path|
      Find.find(File.join(staging_path, path)) do |subpath|
        if File.directory? subpath and !File.symlink? subpath
          alldirs << subpath.gsub(/^#{staging_path}/, '')
        end
      end
    end
    self.directories = alldirs
  end

  # include external config files
  (attributes[:config_files] or []).each do |conf|
    dest_conf = File.join(staging_path, conf)

    if File.exist?(dest_conf)
      logger.debug("Using --config-file from staging area", :path => conf)
    elsif File.exist?(conf)
      logger.info("Copying --config-file from local path", :path => conf)
      FileUtils.mkdir_p(File.dirname(dest_conf))
      FileUtils.cp_r conf, dest_conf
    else
      logger.error("Failed to find given --config-file", :path => conf)
      raise "Could not find config file '#{conf}' in staging area or on host. This can happen if you specify `--config-file '#{conf}'` but this file does not exist in the source package and also does not exist in filesystem."
    end
  end

  # scan all conf file paths for files and add them
  allconfigs = []
  self.config_files.each do |path|
    cfg_path = File.join(staging_path, path)
    raise "Config file path #{cfg_path} does not exist" unless File.exist?(cfg_path)
    Find.find(cfg_path) do |p|
      allconfigs << p.gsub("#{staging_path}/", '') if File.file? p
    end
  end
  allconfigs.sort!.uniq!

  self.config_files = allconfigs.map { |x| File.join("/", x) }

  # add init script if present
  (attributes[:rpm_init_list] or []).each do |init|
    name = File.basename(init, ".init")
    dest_init = File.join(staging_path, "etc/init.d/#{name}")
    FileUtils.mkdir_p(File.dirname(dest_init))
    FileUtils.cp init, dest_init
    File.chmod(0755, dest_init)
  end

  (attributes[:rpm_rpmbuild_define] or []).each do |define|
    args += ["--define", define]
  end

  # copy all files from staging to BUILD dir
  # [#1538] Be sure to preserve the original timestamps.
  Find.find(staging_path) do |path|
    src = path.gsub(/^#{staging_path}/, '')
    dst = File.join(build_path, build_sub_dir, src)
    copy_entry(path, dst, preserve=true)
  end

  rpmspec = template("rpm.erb").result(binding)
  specfile = File.join(build_path("SPECS"), "#{name}.spec")
  File.write(specfile, rpmspec)

  edit_file(specfile) if attributes[:edit?]

  args << specfile

  logger.info("Running rpmbuild", :args => args)
  safesystem(*args)

  ::Dir["#{build_path}/RPMS/**/*.rpm"].each do |rpmpath|
    # This should only output one rpm, should we verify this?
    FileUtils.cp(rpmpath, output_path)
  end
end
payload_compression() click to toggle source
# File lib/fpm/package/rpm.rb, line 634
def payload_compression
  if attributes[:rpm_compression] == 'none'
    # when 'none' ignore any compression level and return w0.gzdio
    return COMPRESSION_MAP[attributes[:rpm_compression]]
  else
    return "w#{attributes[:rpm_compression_level]}" + COMPRESSION_MAP[attributes[:rpm_compression]]
  end
end
prefix() click to toggle source
# File lib/fpm/package/rpm.rb, line 577
def prefix
  if attributes[:prefix] and attributes[:prefix] != '/'
    return attributes[:prefix].chomp('/')
  else
    return "/"
  end
end
prefixed_path(path) click to toggle source
# File lib/fpm/package/rpm.rb, line 449
def prefixed_path(path)
  Pathname.new(path).absolute?() ? path : File.join(self.prefix, path)
end
summary() click to toggle source
# File lib/fpm/package/rpm.rb, line 590
def summary
  if !attributes[:rpm_summary]
    return @description.split("\n").find { |line| !line.strip.empty? } || "_"
  end

  return attributes[:rpm_summary]
end
to_s(format=nil) click to toggle source
Calls superclass method FPM::Package#to_s
# File lib/fpm/package/rpm.rb, line 623
def to_s(format=nil)
  if format.nil?
    format = if attributes[:rpm_dist]
      "NAME-VERSION-ITERATION.DIST.ARCH.EXTENSION"
    else
      "NAME-VERSION-ITERATION.ARCH.EXTENSION"
    end
  end
  return super(format.gsub("DIST", to_s_dist))
end
version() click to toggle source
# File lib/fpm/package/rpm.rb, line 598
def version
  if @version.kind_of?(String) and @version.include?("-")
    logger.warn("Package version '#{@version}' includes dashes, converting" \
                 " to underscores")
    @version = @version.gsub(/-/, "_")
  end

  return @version
end

Private Instance Methods

rpm_file_entry(file) click to toggle source
# File lib/fpm/package/rpm.rb, line 217
def rpm_file_entry(file)
  original_file = file
  file = rpm_fix_name(file)

  if !attributes[:rpm_use_file_permissions?]

    if attrs[file].nil?
      return file
    else
      return sprintf("%%attr(%s) %s\n", attrs[file], file)
    end
  end

  return sprintf("%%attr(%s) %s\n", attrs[file], file) unless attrs[file].nil?

  # Stat the original filename in the relative staging path
  ::Dir.chdir(staging_path) do
    stat = File.lstat(original_file.gsub(/\"/, '').sub(/^\//,''))

    # rpm_user and rpm_group attribute should override file ownership
    # otherwise use the current file user/group by name.
    user = attributes[:rpm_user] || Etc.getpwuid(stat.uid).name
    group = attributes[:rpm_group] || Etc.getgrgid(stat.gid).name
    mode = stat.mode
    return sprintf("%%attr(%o, %s, %s) %s\n", mode & 4095 , user, group, file)
  end
end
rpm_fix_name(name) click to toggle source

Fix path name Replace [ with [[] to make rpm not use globs Replace * with [*] to make rpm not use globs Replace ? with [?] to make rpm not use globs Replace % with [%] to make rpm not expand macros Replace whitespace with ? to make rpm not split the filename If and only if any of the above are done, then also replace ‘ with ', “ with ", and \ with \\

to accommodate escape and quote processing that rpm will perform in that case (but not otherwise)
# File lib/fpm/package/rpm.rb, line 198
def rpm_fix_name(name)
  if name.match?(/[ \t*?%$\[\]]/)
    name = name.gsub(/(\ |\t|\[|\]|\*|\?|\%|\$|'|"|\\)/, {
      ' '  => '?',
      "\t" => '?',
      '%'  => '[%]',
      '$'  => '[$]',
      '?'  => '[?]',
      '*'  => '[*]',
      '['  => '[\[]',
      ']'  => '[\]]',
      '"'  => '\\"',
      "'"  => "\\'",
      '\\' => '\\\\\\\\',
    })
  end
  name
end
rpm_get_trigger_type(flag) click to toggle source
# File lib/fpm/package/rpm.rb, line 357
def rpm_get_trigger_type(flag)
  if (flag & (1 << 25)) == (1 << 25)
     # RPMSENSE_TRIGGERPREIN = (1 << 25),  /*!< %triggerprein dependency. */
     :rpm_trigger_before_install
  elsif (flag & (1 << 16)) == (1 << 16)
     # RPMSENSE_TRIGGERIN  = (1 << 16),    /*!< %triggerin dependency. */
     :rpm_trigger_after_install
  elsif (flag & (1 << 17)) == (1 << 17)
     # RPMSENSE_TRIGGERUN  = (1 << 17),    /*!< %triggerun dependency. */
     :rpm_trigger_before_uninstall
  elsif (flag & (1 << 18)) == (1 << 18)
     # RPMSENSE_TRIGGERPOSTUN = (1 << 18), /*!< %triggerpostun dependency. */
     :rpm_trigger_after_target_uninstall
  else
     @logger.fatal("I don't know about this triggerflag ('#{flag}')")
  end
end
to_s_dist() click to toggle source
# File lib/fpm/package/rpm.rb, line 619
def to_s_dist;
  attributes[:rpm_dist] ? "#{attributes[:rpm_dist]}" : "DIST";
end