class Dpkg::S3::Package

Package class inlcudes methods for extracting the control file of a debian archive and extracting information required for publishing package

Attributes

architecture[RW]
attributes[RW]

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

category[RW]
dependencies[RW]
description[RW]
epoch[RW]
filename[RW]
iteration[RW]
license[RW]
maintainer[RW]
md5[RW]
name[RW]
sha1[RW]
sha256[RW]
size[RW]
url[RW]
url_filename[RW]
vendor[RW]
version[RW]

Public Class Methods

extract_control(package) click to toggle source
# File lib/dpkg/s3/package.rb, line 46
def extract_control(package)
  if system('which dpkg > /dev/null 2>&1')
    `dpkg -f #{package}`
  else
    # use ar to determine control file name (control.ext)
    package_files = `ar t #{package}`
    control_file = package_files.split("\n").select do |file|
      file.start_with?('control.')
    end.first
    compression = if control_file == 'control.tar.gz'
                    'z'
                  else
                    'J'
                  end

    # ar fails to find the control.tar.gz tarball within the .deb
    # on Mac OS. Try using ar to list the control file, if found,
    # use ar to extract, otherwise attempt with tar which works on OS X.
    extract_control_tarball_cmd = "ar p #{package} #{control_file}"

    begin
      safesystem("ar t #{package} #{control_file} &> /dev/null")
    rescue SafeSystemError
      warn 'Failed to find control data in .deb with ar, trying tar.'
      extract_control_tarball_cmd = "tar #{compression}xf #{package} --to-stdout #{control_file}"
    end

    Dir.mktmpdir do |path|
      safesystem("#{extract_control_tarball_cmd} | tar -#{compression}xf - -C #{path}")
      File.read(File.join(path, 'control'), encoding: 'UTF-8')
    end
  end
end
new() click to toggle source
# File lib/dpkg/s3/package.rb, line 81
def initialize
  @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
  @maintainer = if ENV.include?('DEBEMAIL') && ENV.include?('DEBFULLNAME')
                  # Use DEBEMAIL and DEBFULLNAME as the default maintainer if available.
                  "#{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
                  "<#{ENV['USER']}@#{Socket.gethostname}>"
                end

  @name = nil
  @architecture = 'native'
  @description = 'no description given'
  @version = nil
  @epoch = nil
  @iteration = nil
  @url = nil
  @category = 'default'
  @license = 'unknown'
  @vendor = 'none'
  @sha1 = nil
  @sha256 = nil
  @md5 = nil
  @size = nil
  @filename = nil
  @url_filename = nil

  @dependencies = []
end
parse_file(package) click to toggle source
# File lib/dpkg/s3/package.rb, line 32
def parse_file(package)
  p = new
  p.extract_info(extract_control(package))
  p.apply_file_info(package)
  p.filename = package
  p
end
parse_string(str) click to toggle source
# File lib/dpkg/s3/package.rb, line 40
def parse_string(str)
  p = new
  p.extract_info(str)
  p
end

Public Instance Methods

apply_file_info(file) click to toggle source
# File lib/dpkg/s3/package.rb, line 262
def apply_file_info(file)
  self.size = File.size(file)
  self.sha1 = Digest::SHA1.file(file).hexdigest
  self.sha256 = Digest::SHA2.file(file).hexdigest
  self.md5 = Digest::MD5.file(file).hexdigest
end
extract_info(control) click to toggle source

from fpm

# File lib/dpkg/s3/package.rb, line 214
def extract_info(control)
  fields = parse_control(control)

  # Parse 'epoch:version-iteration' in the version string
  full_version = fields.delete('Version')
  raise "Unsupported version string '#{full_version}'" if full_version !~ /^(?:([0-9]+):)?(.+?)(?:-(.*))?$/

  self.epoch, self.version, self.iteration = $LAST_MATCH_INFO.captures

  self.architecture = fields.delete('Architecture')
  self.category = fields.delete('Section')
  self.license = fields.delete('License') || license
  self.maintainer = fields.delete('Maintainer')
  self.name = fields.delete('Package')
  self.url = fields.delete('Homepage')
  self.vendor = fields.delete('Vendor') || vendor
  attributes[:deb_priority] = fields.delete('Priority')
  attributes[:deb_origin] = fields.delete('Origin')
  attributes[:deb_installed_size] = fields.delete('Installed-Size')

  # Packages manifest fields
  filename = fields.delete('Filename')
  self.url_filename = filename
  self.sha1 = fields.delete('SHA1')
  self.sha256 = fields.delete('SHA256')
  self.md5 = fields.delete('MD5sum')
  self.size = fields.delete('Size')
  self.description = fields.delete('Description')

  # self.config_files = config_files

  self.dependencies += Array(parse_depends(fields.delete('Depends')))

  attributes[:deb_recommends] = fields.delete('Recommends')
  attributes[:deb_suggests]   = fields.delete('Suggests')
  attributes[:deb_enhances]   = fields.delete('Enhances')
  attributes[:deb_pre_depends] = fields.delete('Pre-Depends')

  attributes[:deb_breaks]    = fields.delete('Breaks')
  attributes[:deb_conflicts] = fields.delete('Conflicts')
  attributes[:deb_provides]  = fields.delete('Provides')
  attributes[:deb_replaces]  = fields.delete('Replaces')

  attributes[:deb_field] = fields.map do |k, v|
    [k.sub(/\AX[BCS]{0,3}-/, ''), v]
  end.to_h
end
fix_dependency(dep) click to toggle source

from fpm

# File lib/dpkg/s3/package.rb, line 165
def fix_dependency(dep)
  # Deb dependencies are: NAME (OP VERSION), like "zsh (> 3.0)"
  # Convert anything that looks like 'NAME OP VERSION' to this format.
  if dep =~ /[(,|]/
    # Don't "fix" ones that could appear well formed already.
  else
    # Convert ones that appear to be 'name op version'
    name, op, version = dep.split(/ +/)
    unless version.nil?
      # Convert strings 'foo >= bar' to 'foo (>= bar)'
      dep = "#{name} (#{debianize_op(op)} #{version})"
    end
  end

  name_re = /^[^ (]+/
  name = dep[name_re]
  dep = dep.gsub(name_re, &:downcase) if name =~ /[A-Z]/

  dep = dep.gsub('_', '-') if dep.include?('_')

  # Convert gem ~> X.Y.Z to '>= X.Y.Z' and << X.Y+1.0
  if dep =~ /\(~>/
    name, version = dep.gsub(/[()~>]/, '').split(/ +/)[0..1]
    nextversion = version.split('.').collect(&:to_i)
    l = nextversion.length
    nextversion[l - 2] += 1
    nextversion[l - 1] = 0
    nextversion = nextversion.join('.')
    ["#{name} (>= #{version})", "#{name} (<< #{nextversion})"]
  elsif (m = dep.match(/(\S+)\s+\(!= (.+)\)/))
    # Append this to conflicts
    self.conflicts += [dep.gsub(/!=/, '=')]
    []
  elsif (m = dep.match(/(\S+)\s+\(= (.+)\)/)) &&
        attributes[:deb_ignore_iteration_in_dependencies?]
    # Convert 'foo (= x)' to 'foo (>= x)' and 'foo (<< x+1)'
    # but only when flag --ignore-iteration-in-dependencies is passed.
    name, version = m[1..2]
    nextversion = version.split('.').collect(&:to_i)
    nextversion[-1] += 1
    nextversion = nextversion.join('.')
    ["#{name} (>= #{version})", "#{name} (<< #{nextversion})"]
  else
    # otherwise the dep is probably fine
    dep.rstrip
  end
end
full_version() click to toggle source
# File lib/dpkg/s3/package.rb, line 119
def full_version
  return nil if [epoch, version, iteration].all?(&:nil?)

  [[epoch, version].compact.join(':'), iteration].compact.join('-')
end
generate(codename = nil) click to toggle source
# File lib/dpkg/s3/package.rb, line 133
def generate(codename = nil)
  template('package.erb').result(binding)
end
parse_control(control) click to toggle source
# File lib/dpkg/s3/package.rb, line 269
def parse_control(control)
  field = nil
  value = ''
  {}.tap do |fields|
    control.each_line do |line|
      case line
      when /^(\s+)(\S.*)$/
        indent = Regexp.last_match(1)
        rest = Regexp.last_match(2)
        # Continuation
        if indent.size == 1 && rest == '.'
          value << "\n"
          rest = ''
        elsif value.size.positive?
          value << "\n"
        end
        value << rest
      when /^([-\w]+):(.*)$/
        fields[field] = value if field
        field = Regexp.last_match(1)
        value = Regexp.last_match(2).strip
      end
    end
    fields[field] = value if field
  end
end
parse_depends(data) click to toggle source

from fpm

# File lib/dpkg/s3/package.rb, line 138
def parse_depends(data)
  return [] if data.nil? || data.empty?

  # parse dependencies. Debian dependencies come in one of two forms:
  # * name
  # * name (op version)
  # They are all on one line, separated by ", "

  dep_re = /^([^ ]+)(?: \(([>=<]+) ([^)]+)\))?$/
  data.split(/, */).collect do |dep|
    m = dep_re.match(dep)
    if m
      name, op, version = m.captures
      # this is the proper form of dependency
      if op && version && op != '' && version != ''
        "#{name} (#{op} #{version})".strip
      else
        name.strip
      end
    else
      # Assume normal form dependency, "name op version".
      dep
    end
  end
end
url_filename_encoded(codename) click to toggle source
# File lib/dpkg/s3/package.rb, line 129
def url_filename_encoded(codename)
  @url_filename || "pool/#{codename}/#{name[0]}/#{name[0..1]}/#{s3_escape(File.basename(filename))}"
end