class MiniPortile

Constants

DEFAULT_TIMEOUT
KEYRING_NAME
TAR_EXECUTABLES
VERSION

Attributes

configure_options[W]
files[RW]
host[RW]
logger[RW]
name[R]
original_host[R]
patch_files[RW]
source_directory[RW]
target[RW]
version[R]

Public Class Methods

mingw?() click to toggle source

GNU MinGW compiled Ruby?

# File lib/mini_portile2/mini_portile.rb, line 42
def self.mingw?
  RbConfig::CONFIG['target_os'] =~ /mingw/
end
mswin?() click to toggle source

MS Visual-C compiled Ruby?

# File lib/mini_portile2/mini_portile.rb, line 47
def self.mswin?
  RbConfig::CONFIG['target_os'] =~ /mswin/
end
new(name, version, **kwargs) click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 51
def initialize(name, version, **kwargs)
  @name = name
  @version = version
  @target = 'ports'
  @files = []
  @patch_files = []
  @log_files = {}
  @logger = STDOUT
  @source_directory = nil

  @original_host = @host = detect_host

  @gcc_command = kwargs[:gcc_command]
  @make_command = kwargs[:make_command]
  @open_timeout = kwargs[:open_timeout] || DEFAULT_TIMEOUT
  @read_timeout = kwargs[:read_timeout] || DEFAULT_TIMEOUT
end
windows?() click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 37
def self.windows?
  RbConfig::CONFIG['target_os'] =~ /mswin|mingw/
end

Public Instance Methods

activate() click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 193
def activate
  lib_path = File.join(port_path, "lib")
  vars = {
    'PATH'          => File.join(port_path, 'bin'),
    'CPATH'         => File.join(port_path, 'include'),
    'LIBRARY_PATH'  => lib_path
  }.reject { |env, path| !File.directory?(path) }

  output "Activating #{@name} #{@version} (from #{port_path})..."
  vars.each do |var, path|
    full_path = File.expand_path(path)

    # turn into a valid Windows path (if required)
    full_path.gsub!(File::SEPARATOR, File::ALT_SEPARATOR) if File::ALT_SEPARATOR

    # save current variable value
    old_value = ENV[var] || ''

    unless old_value.include?(full_path)
      ENV[var] = "#{full_path}#{File::PATH_SEPARATOR}#{old_value}"
    end
  end

  # rely on LDFLAGS when cross-compiling
  if File.exist?(lib_path) && (@host != @original_host)
    full_path = File.expand_path(lib_path)

    old_value = ENV.fetch("LDFLAGS", "")

    unless old_value.include?(full_path)
      ENV["LDFLAGS"] = "-L#{full_path} #{old_value}".strip
    end
  end
end
apply_patch(patch_file) click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 94
def apply_patch(patch_file)
  (
    # Not a class variable because closures will capture self.
    @apply_patch ||=
    case
    when which('git')
      lambda { |file|
        message "Running git apply with #{file}... "
        Dir.mktmpdir do |tmp_git_dir|
          execute('patch', ["git", "--git-dir=#{tmp_git_dir}", "--work-tree=.", "apply", "--whitespace=warn", file], :initial_message => false)
        end
      }
    when which('patch')
      lambda { |file|
        message "Running patch with #{file}... "
        execute('patch', ["patch", "-p1", "-i", file], :initial_message => false)
      }
    else
      raise "Failed to complete patch task; patch(1) or git(1) is required."
    end
  ).call(patch_file)
end
compile() click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 143
def compile
  execute('compile', make_cmd)
end
configure() click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 128
def configure
  return if configured?

  FileUtils.mkdir_p(tmp_path)
  cache_file = File.join(tmp_path, 'configure.options_cache')
  File.open(cache_file, "w") { |f| f.write computed_options.to_s }

  command = Array(File.join((source_directory || "."), "configure"))
  if RUBY_PLATFORM=~/mingw|mswin/
    # Windows doesn't recognize the shebang.
    command.unshift("sh")
  end
  execute('configure', command + computed_options)
end
configure_options() click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 124
def configure_options
  @configure_options ||= configure_defaults
end
configured?() click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 160
def configured?
  configure = File.join((source_directory || work_path), 'configure')
  makefile  = File.join(work_path, 'Makefile')
  cache_file  = File.join(tmp_path, 'configure.options_cache')

  stored_options  = File.exist?(cache_file) ? File.read(cache_file) : ""
  current_options = computed_options.to_s

  (current_options == stored_options) && newer?(makefile, configure)
end
cook() click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 178
def cook
  if source_directory
    prepare_build_directory
  else
    download unless downloaded?
    extract
    patch
  end
  configure unless configured?
  compile
  install unless installed?

  return true
end
download() click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 80
def download
  files_hashs.each do |file|
    download_file(file[:url], file[:local_path])
    verify_file(file)
  end
end
downloaded?() click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 152
def downloaded?
  missing = files_hashs.detect do |file|
    !File.exist?(file[:local_path])
  end

  missing ? false : true
end
extract() click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 87
def extract
  files_hashs.each do |file|
    verify_file(file)
    extract_file(file[:local_path], tmp_path)
  end
end
gcc_cmd() click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 232
def gcc_cmd
  (ENV["CC"] || @gcc_command || RbConfig::CONFIG["CC"] || "gcc").dup
end
install() click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 147
def install
  return if installed?
  execute('install', %Q(#{make_cmd} install))
end
installed?() click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 171
def installed?
  makefile  = File.join(work_path, 'Makefile')
  target_dir = Dir.glob("#{port_path}/*").find { |d| File.directory?(d) }

  newer?(target_dir, makefile)
end
make_cmd() click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 236
def make_cmd
  (ENV["MAKE"] || @make_command || ENV["make"] || "make").dup
end
patch() click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 117
def patch
  @patch_files.each do |full_path|
    next unless File.exist?(full_path)
    apply_patch(full_path)
  end
end
path() click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 228
def path
  File.expand_path(port_path)
end
prepare_build_directory() click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 73
def prepare_build_directory
  raise "source_directory is not set" if source_directory.nil?
  output "Building #{@name} #{@version} from source at '#{source_directory}'"
  FileUtils.mkdir_p(File.join(tmp_path, [name, version].join("-")))
  FileUtils.rm_rf(port_path) # make sure we always re-install
end
source_directory=(path) click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 69
def source_directory=(path)
  @source_directory = File.expand_path(path)
end

Private Instance Methods

archives_path() click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 250
def archives_path
  "#{@target}/archives"
end
computed_options() click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 270
def computed_options
  [
    configure_options,     # customized or default options
    configure_prefix,      # installation target
  ].flatten
end
configure_defaults() click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 258
def configure_defaults
  [
    "--host=#{@host}",    # build for specific target (host)
    "--enable-static",    # build static library
    "--disable-shared"    # disable generation of shared object
  ]
end
configure_prefix() click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 266
def configure_prefix
  "--prefix=#{File.expand_path(port_path)}"
end
detect_host() click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 391
def detect_host
  return @detect_host if defined?(@detect_host)

  begin
    ENV["LC_ALL"], old_lc_all = "C", ENV["LC_ALL"]

    output = `#{gcc_cmd} -v 2>&1`
    if m = output.match(/^Target\: (.*)$/)
      @detect_host = m[1]
    end

    @detect_host
  ensure
    ENV["LC_ALL"] = old_lc_all
  end
end
download_file(url, full_path, count = 3) click to toggle source

Slighly modified from RubyInstaller uri_ext, Rubinius configure and adaptations of Wayne’s RailsInstaller

# File lib/mini_portile2/mini_portile.rb, line 487
def download_file(url, full_path, count = 3)
  return if File.exist?(full_path)
  uri = URI.parse(url)

  case uri.scheme.downcase
  when /ftp/
    download_file_ftp(uri, full_path)
  when /http|https/
    download_file_http(url, full_path, count)
  when /file/
    download_file_file(uri, full_path)
  else
    raise ArgumentError.new("Unsupported protocol for #{url}")
  end
rescue Exception => e
  File.unlink full_path if File.exist?(full_path)
  raise e
end
download_file_file(uri, full_path) click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 560
def download_file_file(uri, full_path)
  FileUtils.mkdir_p File.dirname(full_path)
  FileUtils.cp uri.path, full_path
end
download_file_ftp(uri, full_path) click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 565
def download_file_ftp(uri, full_path)
  require "net/ftp"
  filename = File.basename(uri.path)
  with_tempfile(filename, full_path) do |temp_file|
    total = 0
    params = {
      :content_length_proc => lambda{|length| total = length },
      :progress_proc => lambda{|bytes|
        new_progress = (bytes * 100) / total
        message "\rDownloading %s (%3d%%) " % [filename, new_progress]
      },
      :open_timeout => @open_timeout,
      :read_timeout => @read_timeout,
    }
    if ENV["ftp_proxy"]
      _, userinfo, _p_host, _p_port = URI.split(ENV['ftp_proxy'])
      if userinfo
        proxy_user, proxy_pass = userinfo.split(/:/).map{|s| CGI.unescape(s) }
        params[:proxy_http_basic_authentication] =
          [ENV['ftp_proxy'], proxy_user, proxy_pass]
      end
    end
    OpenURI.open_uri(uri, 'rb', params) do |io|
      temp_file << io.read
    end
    output
  end
rescue LoadError
  raise LoadError, "Ruby #{RUBY_VERSION} does not provide the net-ftp gem, please add it as a dependency if you need to use FTP"
rescue Net::FTPError
  return false
end
download_file_http(url, full_path, count = 3) click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 506
def download_file_http(url, full_path, count = 3)
  filename = File.basename(full_path)
  with_tempfile(filename, full_path) do |temp_file|
    total = 0
    params = {
      "Accept-Encoding" => 'identity',
      :content_length_proc => lambda{|length| total = length },
      :progress_proc => lambda{|bytes|
        if total
          new_progress = (bytes * 100) / total
          message "\rDownloading %s (%3d%%) " % [filename, new_progress]
        else
          # Content-Length is unavailable because Transfer-Encoding is chunked
          message "\rDownloading %s " % [filename]
        end
      },
      :open_timeout => @open_timeout,
      :read_timeout => @read_timeout,
    }
    proxy_uri = URI.parse(url).scheme.downcase == 'https' ?
                ENV["https_proxy"] :
                ENV["http_proxy"]
    if proxy_uri
      _, userinfo, _p_host, _p_port = URI.split(proxy_uri)
      if userinfo
        proxy_user, proxy_pass = userinfo.split(/:/).map{|s| CGI.unescape(s) }
        params[:proxy_http_basic_authentication] =
          [proxy_uri, proxy_user, proxy_pass]
      end
    end

    begin
      OpenURI.open_uri(url, 'rb', params) do |io|
        temp_file << io.read
      end
      output
    rescue OpenURI::HTTPRedirect => redirect
      raise "Too many redirections for the original URL, halting." if count <= 0
      count = count - 1
      return download_file(redirect.url, full_path, count-1)
    rescue => e
      count = count - 1
      puts "#{count} retrie(s) left for #{filename} (#{e.message})"
      if count > 0
        sleep 1
        return download_file_http(url, full_path, count)
      end

      output e.message
      return false
    end
  end
end
execute(action, command, command_opts={}) click to toggle source

command could be an array of args, or one string containing a command passed to the shell. See Process.spawn for more information.

# File lib/mini_portile2/mini_portile.rb, line 418
def execute(action, command, command_opts={})
  opt_message = command_opts.fetch(:initial_message, true)
  opt_debug =   command_opts.fetch(:debug, false)
  opt_cd =      command_opts.fetch(:cd) { work_path }
  opt_env =     command_opts.fetch(:env) { Hash.new }

  log_out = log_file(action)

  Dir.chdir(opt_cd) do
    output "DEBUG: env is #{opt_env.inspect}" if opt_debug
    output "DEBUG: command is #{command.inspect}" if opt_debug
    message "Running '#{action}' for #{@name} #{@version}... " if opt_message

    if Process.respond_to?(:spawn) && ! RbConfig.respond_to?(:java)
      options = {[:out, :err]=>[log_out, "a"]}
      output "DEBUG: options are #{options.inspect}" if opt_debug
      args = [opt_env, command, options].flatten
      pid = spawn(*args)
      Process.wait(pid)
    else
      env_args = opt_env.map { |k,v| "#{k}=#{v}".shellescape }.join(" ")
      c = if command.kind_of?(Array)
            command.map(&:shellescape).join(" ")
          else
            command
          end
      redirected = %Q{env #{env_args} #{c} > #{log_out.shellescape} 2>&1}
      output "DEBUG: final command is #{redirected.inspect}" if opt_debug
      system redirected
    end

    if $?.success?
      output "OK"
      return true
    else
      if File.exist? log_out
        output "ERROR, review '#{log_out}' to see what happened. Last lines are:"
        output("=" * 72)
        log_lines = File.readlines(log_out)
        output(log_lines[-[log_lines.length, 20].min .. -1])
        output("=" * 72)
      end
      raise "Failed to complete #{action} task"
    end
  end
end
extract_file(file, target) click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 408
def extract_file(file, target)
  filename = File.basename(file)
  FileUtils.mkdir_p target

  message "Extracting #{filename} into #{target}... "
  execute('extract', [tar_exe, "#{tar_compression_switch(filename)}xf", file, "-C", target], {:cd => Dir.pwd, :initial_message => false})
end
files_hashs() click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 277
def files_hashs
  @files.map do |file|
    hash = case file
    when String
      { :url => file }
    when Hash
      file.dup
    else
      raise ArgumentError, "files must be an Array of Stings or Hashs"
    end

    url = hash.fetch(:url){ raise ArgumentError, "no url given" }
    filename = File.basename(url)
    hash[:local_path] = File.join(archives_path, filename)
    hash
  end
end
log_file(action) click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 343
def log_file(action)
  @log_files[action] ||=
    File.expand_path("#{action}.log", tmp_path).tap { |file|
      File.unlink(file) if File.exist?(file)
    }
end
message(text) click to toggle source

print out a message with the logger

# File lib/mini_portile2/mini_portile.rb, line 474
def message(text)
  @logger.print text
  @logger.flush
end
newer?(target, checkpoint) click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 465
def newer?(target, checkpoint)
  if (target && File.exist?(target)) && (checkpoint && File.exist?(checkpoint))
    File.mtime(target) > File.mtime(checkpoint)
  else
    false
  end
end
output(text = "") click to toggle source

print out a message using the logger but return to a new line

# File lib/mini_portile2/mini_portile.rb, line 480
def output(text = "")
  @logger.puts text
  @logger.flush
end
port_path() click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 246
def port_path
  "#{@target}/#{@host}/#{@name}/#{@version}"
end
tar_compression_switch(filename) click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 359
def tar_compression_switch(filename)
  case File.extname(filename)
    when '.gz', '.tgz'
      'z'
    when '.bz2', '.tbz2'
      'j'
    when '.xz'
      'J'
    when '.Z'
      'Z'
    else
      ''
  end
end
tar_exe() click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 351
def tar_exe
  @@tar_exe ||= begin
    TAR_EXECUTABLES.find { |c|
      which(c)
    } or raise("tar not found - please make sure that one of the following commands is in the PATH: #{TAR_EXECUTABLES.join(", ")}")
  end
end
tmp_path() click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 242
def tmp_path
  "tmp/#{@host}/ports/#{@name}/#{@version}"
end
verify_file(file) click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 297
def verify_file(file)
  if file.has_key?(:gpg)
    gpg = file[:gpg]

    signature_url = gpg[:signature_url] || "#{file[:url]}.asc"
    signature_file = file[:local_path] + ".asc"
    # download the signature file
    download_file(signature_url, signature_file)

    gpg_exe = which('gpg2') || which('gpg') || raise("Neither GPG nor GPG2 is installed")

    # import the key into our own keyring
    gpg_status = IO.popen([gpg_exe, "--status-fd", "1", "--no-default-keyring", "--keyring", KEYRING_NAME, "--import"], "w+") do |io|
      io.write gpg[:key]
      io.close_write
      io.read
    end
    key_ids = gpg_status.scan(/\[GNUPG:\] IMPORT_OK \d+ (?<key_id>[0-9a-f]+)/i).map(&:first)
    raise "invalid gpg key provided" if key_ids.empty?

    # verify the signature against our keyring
    gpg_status = IO.popen([gpg_exe, "--status-fd", "1", "--no-default-keyring", "--keyring", KEYRING_NAME, "--verify", signature_file, file[:local_path]], &:read)

    # remove the key from our keyring
    key_ids.each do |key_id|
      IO.popen([gpg_exe, "--batch", "--yes", "--no-default-keyring", "--keyring", KEYRING_NAME, "--delete-keys", key_id], &:read)
      raise "unable to delete the imported key" unless $?.exitstatus==0
    end

    raise "signature mismatch" unless gpg_status.match(/^\[GNUPG:\] VALIDSIG/)

  else
    digest = case
      when exp=file[:sha256] then Digest::SHA256
      when exp=file[:sha1] then Digest::SHA1
      when exp=file[:md5] then Digest::MD5
    end
    if digest
      is = digest.file(file[:local_path]).hexdigest
      unless is == exp.downcase
        raise "Downloaded file '#{file[:local_path]}' has wrong hash: expected: #{exp} is: #{is}"
      end
    end
  end
end
which(cmd) click to toggle source

From: stackoverflow.com/a/5471032/7672 Thanks, Mislav!

Cross-platform way of finding an executable in the $PATH.

which('ruby') #=> /usr/bin/ruby
# File lib/mini_portile2/mini_portile.rb, line 380
def which(cmd)
  exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
  ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
    exts.each { |ext|
      exe = File.join(path, "#{cmd}#{ext}")
      return exe if File.executable? exe
    }
  end
  return nil
end
with_tempfile(filename, full_path) { |temp_file| ... } click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 598
def with_tempfile(filename, full_path)
  temp_file = Tempfile.new("download-#{filename}")
  temp_file.binmode
  yield temp_file
  temp_file.close
  File.unlink full_path if File.exist?(full_path)
  FileUtils.mkdir_p File.dirname(full_path)
  FileUtils.mv temp_file.path, full_path, :force => true
end
work_path() click to toggle source
# File lib/mini_portile2/mini_portile.rb, line 254
def work_path
  Dir.glob("#{tmp_path}/*").find { |d| File.directory?(d) }
end