class FPM::Package::Gem

A rubygems package.

This does not currently support 'output'

The following attributes are supported:

Constants

P_RE_DATE
P_RE_DATE1
P_RE_DATE2
P_RE_DATE3
P_RE_DATE_VERSION
P_RE_GTMAGIC
P_RE_LEADIN

Regular expression to accept a gem changelog line, and store date & version, if any, in named capture groups. Supports formats suggested by keepachangelog.com and github.com/tech-angels/vandamme as well as other similar formats that actually occur in the wild. Build it in pieces for readability, and allow version and date in either order. Whenever you change this, add a row to the test case in spec/fpm/package/gem_spec.rb. Don't even try to handle dates that lack four-digit years. Building blocks:

P_RE_SEPARATOR
P_RE_URL
P_RE_VERSION
P_RE_VERSION_
P_RE_VERSION_DATE

The final RE's:

Public Instance Methods

detect_source_date_from_changelog(installdir) click to toggle source

Detect release date, if found, store in attributes

# File lib/fpm/package/gem.rb, line 330
def detect_source_date_from_changelog(installdir)
  name = self.name.sub("rubygem-", "") + "-" + self.version
  changelog = nil
  datestr = nil
  r1 = Regexp.new(P_RE_VERSION_DATE)
  r2 = Regexp.new(P_RE_DATE_VERSION)

  # Changelog doesn't have a standard name, so check all common variations
  # Sort this list using LANG=C, i.e. caps first
  [
    "CHANGELIST",
    "CHANGELOG", "CHANGELOG.asciidoc", "CHANGELOG.md", "CHANGELOG.rdoc", "CHANGELOG.rst", "CHANGELOG.txt",
    "CHANGES",   "CHANGES.md",   "CHANGES.txt",
    "ChangeLog", "ChangeLog.md", "ChangeLog.txt",
    "Changelog", "Changelog.md", "Changelog.txt",
    "changelog", "changelog.md", "changelog.txt",
  ].each do |changelogname|
    path = File.join(installdir, "gems", name, changelogname)
    if File.exist?(path)
      changelog = path
      File.open path do |file|
        file.each_line do |line|
          if line =~ /#{self.version}/
            [r1, r2].each do |r|
              if r.match(line)
                datestr = $~[:date]
                break
              end
            end
          end
        end
      end
    end
  end
  if datestr
    date = Date.parse(datestr)
    sec = date.strftime("%s")
    attributes[:source_date_epoch] = sec
    logger.debug("Gem %s has changelog date %s, setting source_date_epoch to %s" % [name, datestr, sec])
  elsif changelog
    logger.debug("Gem %s changelog %s did not have recognizable date for release %s" % [name, changelog, self.version])
  else
    logger.debug("Gem %s did not have changelog with recognized name" % [name])
    # FIXME: check rubygems.org?
  end
end
download(gem_name, gem_version=nil) click to toggle source
# File lib/fpm/package/gem.rb, line 100
def download(gem_name, gem_version=nil)

  logger.info("Trying to download", :gem => gem_name, :version => gem_version)

  download_dir = build_path(gem_name)
  FileUtils.mkdir(download_dir) unless File.directory?(download_dir)

  if attributes[:gem_git_repo]
    logger.debug("Git cloning in directory #{download_dir}")
    g = Git.clone(attributes[:gem_git_repo],gem_name,:path => download_dir)
    if attributes[:gem_git_branch]
      g.branch(attributes[:gem_git_branch]).checkout
      g.pull('origin',attributes[:gem_git_branch])
    end
    gem_build = [ "#{attributes[:gem_gem]}", "build", "#{g.dir.to_s}/#{gem_name}.gemspec"]
    ::Dir.chdir(g.dir.to_s) do |dir|
      logger.debug("Building in directory #{dir}")
      safesystem(*gem_build)
    end
    gem_files = ::Dir.glob(File.join(g.dir.to_s, "*.gem"))
  else
    gem_fetch = [ "#{attributes[:gem_gem]}", "fetch", gem_name]
    gem_fetch += ["--prerelease"] if attributes[:gem_prerelease?]
    gem_fetch += ["--version", gem_version] if gem_version
    ::Dir.chdir(download_dir) do |dir|
      logger.debug("Downloading in directory #{dir}")
      safesystem(*gem_fetch)
    end
    gem_files = ::Dir.glob(File.join(download_dir, "*.gem"))
  end

  if gem_files.length != 1
    raise "Unexpected number of gem files in #{download_dir},  #{gem_files.length} should be 1"
  end

  return gem_files.first
end
download_if_necessary(gem, gem_version) click to toggle source
# File lib/fpm/package/gem.rb, line 90
def download_if_necessary(gem, gem_version)
  path = gem
  if !File.exist?(path)
    path = download(gem, gem_version)
  end

  logger.info("Using gem file", :path => path)
  return path
end
fix_name(name) click to toggle source

Sanitize package name. This prefixes the package name with 'rubygem' (but depends on the attribute :gem_package_name_prefix

# File lib/fpm/package/gem.rb, line 304
def fix_name(name)
  return [attributes[:gem_package_name_prefix], name].join("-")
end
input(gem) click to toggle source
# File lib/fpm/package/gem.rb, line 78
def input(gem)
  # 'arg'  is the name of the rubygem we should unpack.
  path_to_gem = download_if_necessary(gem, version)

  # Got a good gem now (downloaded or otherwise)
  #
  # 1. unpack it into staging_path
  # 2. take the metadata from it and update our wonderful package with it.
  load_package_info(path_to_gem)
  install_to_staging(path_to_gem)
end
install_to_staging(gem_path) click to toggle source
# File lib/fpm/package/gem.rb, line 213
def install_to_staging(gem_path)
  if attributes.include?(:prefix) && ! attributes[:prefix].nil?
    installdir = "#{staging_path}/#{attributes[:prefix]}"
  else
    gemdir = safesystemout(*[attributes[:gem_gem], 'env', 'gemdir']).chomp
    installdir = File.join(staging_path, gemdir)
  end

  ::FileUtils.mkdir_p(installdir)
  # TODO(sissel): Allow setting gem tool path
  args = [attributes[:gem_gem], "install", "--quiet", "--no-user-install", "--install-dir", installdir]
  if ::Gem::VERSION =~ /^[012]\./ 
    args += [ "--no-ri", "--no-rdoc" ]
  else
    # Rubygems 3.0.0 changed --no-ri to --no-document
    args += [ "--no-document" ]
  end

  if !attributes[:gem_embed_dependencies?]
    args += ["--ignore-dependencies"]
  end

  if attributes[:gem_env_shebang?]
    args += ["-E"]
  end

  if attributes.include?(:gem_bin_path) && ! attributes[:gem_bin_path].nil?
    bin_path = File.join(staging_path, attributes[:gem_bin_path])
  else
    gem_env  = safesystemout(*[attributes[:gem_gem], 'env']).split("\n")
    gem_bin  = gem_env.select{ |line| line =~ /EXECUTABLE DIRECTORY/ }.first.split(': ').last
    bin_path = File.join(staging_path, gem_bin)
  end

  args += ["--bindir", bin_path]
  ::FileUtils.mkdir_p(bin_path)
  args << gem_path
  safesystem(*args)

  # Replace the shebangs in the executables
  if attributes[:gem_shebang]
    ::Dir.entries(bin_path).each do |file_name|
      # exclude . and ..
      next if ['.', '..'].include?(file_name)
      # exclude everything which is not a file
      file_path = File.join(bin_path, file_name)
      next unless File.ftype(file_path) == 'file'
      # replace shebang in files if there is one
      file = File.read(file_path)
      if file.gsub!(/\A#!.*$/, "#!#{attributes[:gem_shebang]}")
        File.open(file_path, 'w'){|f| f << file}
      end
    end
  end

  # Delete bin_path if it's empty, and any empty parents (#612)
  # Above, we mkdir_p bin_path because rubygems aborts if the parent
  # directory doesn't exist, for example:
  #   ERROR:  While executing gem ... (Errno::ENOENT)
  #       No such file or directory - /tmp/something/weird/bin
  tmp = bin_path
  while ::Dir.entries(tmp).size == 2 || tmp == "/"  # just [ "..", "." ] is an empty directory
    logger.info("Deleting empty bin_path", :path => tmp)
    ::Dir.rmdir(tmp)
    tmp = File.dirname(tmp)
  end
  if attributes[:gem_version_bins?] and File.directory?(bin_path)
    (::Dir.entries(bin_path) - ['.','..']).each do |bin|
      FileUtils.mv("#{bin_path}/#{bin}", "#{bin_path}/#{bin}-#{self.version}")
    end
  end

  if attributes[:source_date_epoch_from_changelog?]
    detect_source_date_from_changelog(installdir)
  end

  # Remove generated Makefile and gem_make.out files, if any; they
  # are not needed, and may contain generated paths that cause
  # different output on successive runs.
  Find.find(installdir) do |path|
    if path =~ /.*(gem_make.out|Makefile|mkmf.log)$/
      logger.info("Removing no longer needed file %s to reduce nondeterminism" % path)
      File.unlink(path)
    end
  end

end
load_package_info(gem_path) click to toggle source
# File lib/fpm/package/gem.rb, line 138
def load_package_info(gem_path)

  spec = YAML.load(%x{#{attributes[:gem_gem]} specification #{gem_path} --yaml})

  if !attributes[:gem_package_prefix].nil?
    attributes[:gem_package_name_prefix] = attributes[:gem_package_prefix]
  end

  # name prefixing is optional, if enabled, a name 'foo' will become
  # 'rubygem-foo' (depending on what the gem_package_name_prefix is)
  self.name = spec.name
  if attributes[:gem_fix_name?]
    self.name = fix_name(spec.name)
  end

  #self.name = [attributes[:gem_package_name_prefix], spec.name].join("-")
  self.license = (spec.license or "no license listed in #{File.basename(gem_path)}")

  # expand spec's version to match RationalVersioningPolicy to prevent cases
  # where missing 'build' number prevents correct dependency resolution by target
  # package manager. Ie. for dpkg 1.1 != 1.1.0
  m = spec.version.to_s.scan(/(\d+)\.?/)
  self.version = m.flatten.fill('0', m.length..2).join('.')

  self.vendor = spec.author
  self.url = spec.homepage
  self.category = "Languages/Development/Ruby"

  # if the gemspec has C extensions defined, then this should be a 'native' arch.
  if !spec.extensions.empty?
    self.architecture = "native"
  else
    self.architecture = "all"
  end

  # make sure we have a description
  description_options = [ spec.description, spec.summary, "#{spec.name} - no description given" ]
  self.description = description_options.find { |d| !(d.nil? or d.strip.empty?) }

  # Upstream rpms seem to do this, might as well share.
  # TODO(sissel): Figure out how to hint this only to rpm?
  # maybe something like attributes[:rpm_provides] for rpm specific stuff?
  # Or just ignore it all together.
  #self.provides << "rubygem(#{self.name})"

  # By default, we'll usually automatically provide this, but in the case that we are
  # composing multiple packages, it's best to explicitly include it in the provides list.
  self.provides << "#{self.name} = #{self.version}"

  if !attributes[:no_auto_depends?] && !attributes[:gem_embed_dependencies?]
    spec.runtime_dependencies.map do |dep|
      # rubygems 1.3.5 doesn't have 'Gem::Dependency#requirement'
      if dep.respond_to?(:requirement)
        reqs = dep.requirement.to_s
      else
        reqs = dep.version_requirements
      end

      # Some reqs can be ">= a, < b" versions, let's handle that.
      reqs.to_s.split(/, */).each do |req|
        if attributes[:gem_disable_dependencies]
          next if attributes[:gem_disable_dependencies].include?(dep.name)
        end

        if attributes[:gem_fix_dependencies?]
          name = fix_name(dep.name)
        else
          name = dep.name
        end
        self.dependencies << "#{name} #{req}"
      end
    end # runtime_dependencies
  end #no_auto_depends
end
staging_path(path=nil) click to toggle source

Override parent method

# File lib/fpm/package/gem.rb, line 67
def staging_path(path=nil)
  @gem_staging_path ||= attributes[:gem_stagingdir] || Stud::Temporary.directory("package-#{type}-staging")
  @staging_path = @gem_staging_path

  if path.nil?
    return @staging_path
  else
    return File.join(@staging_path, path)
  end
end