#!/usr/local/bin/python

# $Id: buildport.py,v 1.5 2005/01/21 16:38:57 john Exp $
#
# JW.
#
# Given a port's magic string, install any dependencies which are already
# built either on an ftp server or in /usr/ports/packages.
# For all remaining packages which are not already installed, extract their
# port directory and invoke make install on the given port.

# Magic: category/name[/version][,-subpackage]{,flavour}*
# Taken from the second column in ports/INDEX.

import csv, re, sys, os

make_path = [
   '/bin', '/sbin', '/usr/bin', '/usr/sbin', '/usr/local/bin',
   '/usr/local/sbin'
]
http_proxy = os.environ.get('http_proxy')
ftp_proxy = os.environ.get('ftp_proxy')
ftpsite = 'ftp://xxxxxxxxxxxxxxxxxx/3.6CD1/3.6/packages/i386'
ftpdb = map(lambda entry: entry.split()[-1].strip(), \
         file('/var/db/packages_3.6_i386').readlines())
portsdb = '/var/db/ports_3.6'
portsdb_cache = {}
packages_cache = {}
portsbuilt = '/usr/ports/packages/i386/all'
installed = os.listdir('/var/db/pkg')
portstar = '/home/john/openbsd/ports.tar.gz'
portsdir = '/usr/ports'
mopts = os.readlink('/etc/malloc.conf').lower()

def pkg_add(location):
   os.spawnl(os.P_WAIT, "/usr/bin/sudo", "sudo", "/usr/sbin/pkg_add", location)

def make(dir, env=[], target='install'):
   env.append("SUDO=sudo")
   env.append("MALLOC_OPTIONS=" + mopts)
   env.append("PATH=" + ':'.join(make_path))
   if http_proxy:
      env.append("http_proxy=" + http_proxy)
   if ftp_proxy:
      env.append("ftp_proxy=" + ftp_proxy)
   args = [ 'env', '-i' ] + env + [ '/usr/bin/make', target ]
   os.chdir(os.path.join(portsdir, dir))
   #print os.getcwd(), '|'.join(args)
   os.spawnv(os.P_WAIT, "/usr/bin/env", args)

def tar_extract(paths):
   if not paths:
      return
   os.chdir(os.path.split(portsdir)[0])
   args = [ 'tar', 'xzf', portstar ] + paths
   os.spawnv(os.P_WAIT, "/bin/tar", args)

def Package(pkgident):
   global packages_cache
   if pkgident not in packages_cache:
      packages_cache[pkgident] = _Package(pkgident)
   return packages_cache[pkgident]

class _Package:
   def __init__(self, pkgident):
      self.pkgident = pkgident
      self.pkg = findpkg(pkgident)
      self.depends = depends(pkgident)
      self.pkgdir, self.subpackage, self.flavors = decodepkgident(pkgident)
      self.pkgtgz = self.pkg[0] + '.tgz'
      self.isonftp = self.pkgtgz in ftpdb
      self.isbuilt = os.path.exists(os.path.join(portsbuilt, self.pkgtgz))
      self.isinstalled = self.pkg[0] in installed

   def __repr__(self):
      s = '<Package \"%s\"' % self.pkgident
      if self.subpackage:
         s += ' subpackage=%s' % self.subpackage
      if self.flavors:
         s += ' flavors=%s' % self.flavors
      if self.depends:
         s += ' depends=%d' % len(self.depends)
      s += '>'
      return s

   def _createlistthrurecursion(self, fn, init, cond=True):
      l = []
      if cond:
         l.append(init)
      for d in self.depends:
         _dirs = getattr(d, fn)()
         for _d in _dirs:
            if _d not in l:
               l.append(_d)
      return l

   def gettgzs(self):
      return self._createlistthrurecursion('gettgzs', self.pkgtgz)

   def getdirs(self):
      return self._createlistthrurecursion('getdirs', self.pkgdir)

   def getbuilt(self):
      return self._createlistthrurecursion('getbuilt', self.pkgtgz, self.isbuilt)

   def getftp(self):
      return self._createlistthrurecursion('getftp', self.pkgtgz, self.isonftp)

   def getinstalled(self):
      return self._createlistthrurecursion('getinstalled', self.pkgtgz, self.isinstalled)

   def _finddepends(self):
      inst = []
      installed = self.getinstalled()
      onftp = self.getftp()
      built = self.getbuilt()
      deps = self.gettgzs()
      for i in installed:
         if i in onftp:
            onftp.remove(i)
         if i in built:
            built.remove(i)
      for i in built:
         if i in onftp:
            onftp.remove(i)
      for i in installed:
         inst.append(( 'none', i ))
         inst.append(( 'port', i ))
         deps.remove(i)
      for i in built:
         inst.append(( 'built', i ))
         inst.append(( 'port', i ))
         deps.remove(i)
      for i in onftp:
         inst.append(( 'ftp', i ))
         inst.append(( 'port', i ))
         deps.remove(i)
      for i in deps:
         inst.append(( 'port', i ))
      # Put ports first.
      inst.reverse()
      return inst

   def preinstall(self):
      recipe = self._finddepends()
      for method, location in recipe:
         globals()['install_' + method](location)
      extract = []
      for dir in extract_ports:
         if not os.path.exists(os.path.join(portsdir, dir)):
            print "Will extract `%s' from ports.tar.gz." % dir
            extract.append('ports/' + dir)
      tar_extract(extract)

   def environment(self):
      env = []
      if self.subpackage:
         env.append('SUBPACKAGE=%s' % self.subpackage)
      if self.flavors:
         env.append('FLAVOR=%s' % ' '.join(self.flavors))
      return env

   def build(self):
      make(self.pkgdir, self.environment())

def install_none(pkg):
   print "Package `%s' is already installed." % pkg

extract_ports = [ 'infrastructure' ]
def install_port(pkg):
   global extract_ports
   for p in packages_cache.itervalues():
      if p.pkgtgz == pkg:
         print "Package `%s' will be compiled from `%s'." % (pkg, p.pkgdir)
         extract_ports.append(p.pkgdir)
         return
   raise StandardError("Couldn't find port for `%s'" % pkg)

def install_built(pkg):
   print "Installing `%s' from /usr/ports/packages." % pkg
   pkg_add(os.path.join(portsbuilt, pkg))

def install_ftp(pkg):
   print "Installing `%s' from ftp site." % pkg
   pkg_add(os.path.join(ftpsite, pkg))

def findpkg(pkgident):
   global portsdb_cache
   if pkgident in portsdb_cache:
      return portsdb_cache[pkgident]
   for pkg in csv.reader(file(portsdb), delimiter='|'):
      if pkg[1] == pkgident:
         portsdb_cache[pkgident] = pkg
         return pkg
   for pkg in csv.reader(file(portsdb), delimiter='|'):
      if pkg[1].find(pkgident + ',') == 0:
         portsdb_cache[pkgident] = pkg
         return pkg
   return None

def depends(pkgident):
   depends = None
   pkg = findpkg(pkgident)
   if not pkg:
      raise StandardError("Couldn't find package `%s'" % pkgident)
   depends = []
   for dlist in pkg[7:10]:
      for dep in dlist.split():
         p = Package(dep.split(':')[-1])
         if p not in depends:
            depends.append(p)
   return depends

def decodepkgident(pkgident):
   # category/name[/version][,-subpackage]{,flavour}*
   options = pkgident.split(',')
   pkgdir = options[0]
   del(options[0])
   subpackage = ''
   if options and options[0][0] == '-':
      subpackage = options[0]
      del(options[0])
   flavors = options
   return ( pkgdir, subpackage, flavors )

def main():
   target = 'install'
   try:
      me, pkgident, target = sys.argv
   except:
      try:
         me, pkgident = sys.argv
      except:
         print >> sys.stderr, "Usage: %s PKGIDENT [TARGET]" % sys.argv[0]
         sys.exit(1)
   p = Package(pkgident)
   if target == 'clean':
      for pkg in packages_cache.itervalues():
         if os.path.exists(os.path.join(portsdir, pkg.pkgdir)):
            make(pkg.pkgdir, pkg.environment(), target='clean')
   elif target == 'install':
      p.preinstall()
      p.build()

if __name__ == '__main__':
   main()