+universal していないパッケージをぜんぶ入れ替える Ruby スクリプト

昨日のエントリではワンライナでやっていて、依存関係を考えてなかったので大変なことになった。2パスでいいだろうとか考えていたけど甘かった。

ということで、Ruby で依存関係に従った順序で置き換えるスクリプトを書きました

def active_version_variants(x, installed)
/@[^+]+/.match(installed[x].keys.select{|var| installed[x][var] }[0])
  return [$&, $' ? $'.scan(/\+[^+]+/) : []]
end
 
def active_variants(x, installed)
  return active_version_variants(x, installed)[1]
end
 
def active_deps(x, installed)
  var = active_variants(x, installed)
  deps = []
  open("| port deps #{x} #{var.join}") do |f|
    f.each_line do |line|
      line.strip!
      deps << line if line !~ /:/
    end
  end
  return deps
end
 
def has_universal?(x)
  open("| port variants #{x}") do |f|
    return f.read =~ /\s+universal:/
  end
end
 
def is_active_universal?(x, installed)
  return active_variants(x, installed).include?('+universal')
end
 
def has_active?(x, installed)
  return installed[x].keys.any?{|var| installed[x][var] }
end
 
def is_installed(x, installed)
  return installed.has_key?(x) && !installed[x].empty?
end
 
def deactivate(x, vv)
  puts "sudo port deactivate #{x} #{vv}"
  system('sudo', 'port', 'deactivate', x, vv)
end
 
def install(x, v)
  puts "sudo port install #{x} #{v}"
  system('sudo', 'port', 'install', x, v)
end
 
def uninstall(x, vv)
  puts "sudo port uninstall #{x} #{vv}"
  system('sudo', 'port', 'uninstall', x, vv)
end
 
def universalize(x, remained, installed)
  remained.delete(x)
  if has_universal?(x) && !is_active_universal?(x, installed)
    active_deps(x, installed).each {|y|
      universalize(y, remained, installed) if remained.include?(y) }
 
    if has_active?(x, installed)
      vv = active_version_variants(x, installed)
      deactivate(x, vv.join)
    elsif installed[x].length == 1
      /@[^+]+/.match(installed[x].keys[0])
      vv = [$&, $'.scan(/\+[^+]+/)]
    else
      raise "there are multiple deactivated ports: #{x}"
    end
    install(x, [vv[1], '+universal'].join)
    uninstall(x, vv.join) if has_active?(x, installed) || installed[x].length == 1
    installed[x].delete(vv.join)
    vv[1] << '+universal'
    vv[1].sort!
    installed[x][vv.join] = '(active)'
  end
  return true
end
 
installed_ports = {}
open('| port installed') do |f|
  f.gets # drop the first line
  f.each_line do |line|
    name, ver, active = line.split
    installed_ports[name] ||= {}
    installed_ports[name][ver] = active
  end
end
 
remained = installed_ports.keys
until remained.empty?
  universalize(remained[0], remained, installed_ports)
end