
import glob, os, re, sys, time

from stat import ST_MODE

try:
  from distutils import log
  from distutils.core import setup
  from distutils.ccompiler import *
  from distutils.command.build import build as _build
  from distutils.command.clean import clean as _clean
  from distutils.command.install import install as _install
  from distutils.command.install_egg_info import install_egg_info as _install_egg_info
  from distutils.command.sdist import sdist as _sdist
  from distutils.dep_util import newer
  from distutils.dir_util import copy_tree, remove_tree
  from distutils.errors import DistutilsFileError
  from distutils.versionpredicate import VersionPredicate
  import distutils.filelist
except:
  sys.exit("Your python distutils version is too old, please try a newer one")

## Basics ...
package_nam = 'Mobyle'
package_ver = '1.0.4'
package_url = 'https://projets.pasteur.fr/wiki/mobyle/'
package_aut = 'The Mobyle team'
package_mel = 'mobyle@pasteur.fr'

## Prerequisites ...
require_pyt = [ 'python (>=2.5, <3.0)' ]
require_mod = [ 'Image (>=1.1.5)', 'lxml (>=2.2.4)',
		'simpletal (>=4.1, <5.0)', 'simplejson (>=1.7.1)' ]

## Installation ...
core_dat = [ 'Src/Mobyle/*.py', 'Src/Mobyle/Classes/*.py',
             'Src/Mobyle/Execution/*.py', 'Src/Mobyle/Converter/*.py', 'Src/Mobyle/Captcha/*',
             'Src/Mobyle/Captcha/data/fonts/vera/*', 'Src/Mobyle/Captcha/data/pictures/abstract/*',
             'Src/Mobyle/Captcha/data/pictures/nature/*', 'Src/Mobyle/Captcha/data/words/*',
             'Schema/*', 'Local/*.py', 'Local/Config/Execution/*.py',
             'Local/*/*.py', 'Tools/validation/*.xsl',
             'Tools/README', 'Example/Local/*.py', 'Example/Local/*/*.py',
             'Doc/Admin/*.pdf', 'Doc/*.pdf', 'Doc/*.html',
             'Doc/Admin/*.tex', 'Doc/Admin/*.png', 'Doc/*.tex', 'Doc/*.jpeg']
core_cfg = [ 'Local/Policy.py', 'Local/black_list.py', 'Local/mailTemplate.py' ]
core_scr = [ 'Src/Mobyle/RunnerChild.py',
             'Tools/mob*',
             'Tools/job_updater.py',
             'Tools/session_updater.py',
             'Local/Config/Execution/__init__.py' ]
core_tst = [ 'Src/Mobyle/Test/*.py', 'Src/Mobyle/Test/Converter/*.py',
             'Src/Mobyle/Test/Converter/DataSequences/*',
             'Src/Mobyle/Test/Converter/DataAlignments/*' ]
core_exe = [ 'Tools/setsid.c' ]
core_fix = core_scr + [ 'Src/Mobyle/ConfigManager.py',
                        'Src/Mobyle/Converter/__init__.py',
                        'Src/Mobyle/Execution/__init__.py' ]
core_tre = [ 'Services', 'Services/Programs', 'Services/Viewers', 'Services/Workflows', 
             'Local/Services', 'Local/Services/Programs', 'Local/Services/Workflows',
             'Local/Services/Viewers' ]

cgis_scr = [ 'Src/Portal/cgi-bin/MobylePortal/*' ]
cgis_fix = [ 'Src/Portal/cgi-bin/MobylePortal/mb_cgi.py' ]

html_dat = [ 'Src/Portal/htdocs/MobylePortal/*/*',
             'Src/Portal/htdocs/MobylePortal/*/openid/*' ]
html_cfg = [ 'Src/Portal/htdocs/MobylePortal/css/local.css',
             'Src/Portal/htdocs/MobylePortal/html/announcement.txt' ]
html_tre = [ 'sessions/anonymous', 'sessions/authentified',
             'jobs/ADMINDIR' ]

misc_dat = [ 'AUTHORS', 'COPYING', 'INSTALL', 'NEWS' ]

## Distribution ...
dist_lst  = core_dat + core_cfg + core_scr + core_exe + core_tst
dist_lst += cgis_scr + html_dat + html_cfg + misc_dat

excl_lst  = [ 'setup.cfg', 
              'Local/Policy.pasteur.py', 'Local/Config/Config.py' ]

## Let the hard code begins ...

core_dir = 'core'
cgis_dir = 'cgis'
html_dir = 'html'

def copy_script(src, dst, exe):
  ## Mostly stolen from distutils.build_scripts.run() ...
  if not newer(src, dst): return
  dir = os.path.dirname(dst)
  log.info("copying and adjusting %s -> %s", src, dir)
  try:
    f = open(src, "r")
  except os.error, (errstr):
    raise DistutilsFileError, "could not open '%s': %s" % (src, errstr)
  try:
    o = open(dst, "w")
  except:
    raise DistutilsFileError, "could not create '%s': %s" % (dst, errstr)
  l = f.readline()
  l = re.sub("^#!.*python", "#!" + exe, l)
  o.write(l)
  o.writelines(f.readlines())
  o.close()
  f.close()
  omod = os.stat(dst)[ST_MODE] & 07777
  nmod = (omod | 0555) & 07777
  if nmod != omod:
    log.info("changing mode of %s from %o to %o", dst, omod, nmod)
    os.chmod(dst, nmod)

def fix_script(src, dst, mob, htm):
  log.info("adjusting %s -> %s", src, dst)
  try:
    f = open(src, "r")
  except os.error, (errstr):
    raise DistutilsFileError, "could not open '%s': %s" % (src, errstr)
  try:
    g = open(dst, "w")
  except os.error, (errstr):
    raise DistutilsFileError, "could not create '%s': %s" % (dst, errstr)
  while True:
    l = f.readline()
    if not l: break
    if mob and l == 'MOBYLEHOME = None\n':
      l = l.replace('None', "'"+mob+"'", 1)
    if htm and l == 'MOBYLEHTDOCS = None\n':
      l = l.replace('None', "'"+htm+"'", 1)
    g.write(l)
  g.close()
  f.close()
  mod = os.stat(src)[ST_MODE] & 07777
  os.chmod(dst, mod)

def find_file(src, msk):
  lst = distutils.filelist.FileList()
  lst.findall(src)
  lst.include_pattern(msk, anchor=False)
  return lst.files

class build(_build):
  def run(self):
    chk = True
    for req in require_pyt:
      chk &= self.chkpython(req)
    for req in require_mod:
      chk &= self.chkmodule(req)
    if not chk: sys.exit(1)
    _build.run(self)
  def chkpython(self, req):
    chk = VersionPredicate(req)
    ver = '.'.join([str(v) for v in sys.version_info[:2]])
    if not chk.satisfied_by(ver):
      print >> sys.stderr, "Invalid python version, expected %s" % req
      return False
    return True
  def chkmodule(self, req):
    chk = VersionPredicate(req)
    try:
      mod = __import__(chk.name)
    except:
      print >> sys.stderr, "Missing mandatory %s python module" % chk.name
      return False
    for v in [ '__version__', 'version' ]:
      ver = getattr(mod, v, None)
      break
    try:
      if ver and not chk.satisfied_by(ver):
        print >> sys.stderr, "Invalid module version, expected %s" % req
        return False
    except:
      pass
    return True

class build_core(_build):
  _build.sub_commands += [('build_core', lambda self:True)]
  def run(self):
    base = os.path.join(self.build_base, core_dir)
    self.mkpath(base)
    (dat, cfg, scr, exe) = ([], [], [], [])
    for m in core_dat: dat += glob.glob(m)
    for m in core_cfg: cfg += glob.glob(m)
    for m in core_scr: scr += glob.glob(m)
    for m in core_exe: exe += glob.glob(m)
    for f in dat:
      if f in scr + cfg + exe: continue
      if not os.path.isfile(f): continue
      dst = os.path.join(base, f)
      self.mkpath(os.path.dirname(dst))
      self.copy_file(f, dst, preserve_mode=0)
    for f in cfg:
      if not os.path.isfile(f): continue
      dst = os.path.join(base, f + '.new')
      self.mkpath(os.path.dirname(dst))
      self.copy_file(f, dst, preserve_mode=0)
    for f in scr:
      if f in exe: continue
      if not os.path.isfile(f): continue
      dst = os.path.join(base, f)
      self.mkpath(os.path.dirname(dst))
      copy_script(f, dst, self.executable)
    cc = new_compiler()
    for f in core_exe:
      objs = cc.compile([f], self.build_temp)
      cc.link_executable(objs, f[0:-2], base)
    for f in core_tre:
      dir = os.path.join(base, f)
      self.mkpath(dir)

class install_core(_install):
  _install.sub_commands += [('install_core', lambda self:True)]
  setattr(_install, 'install_core', None)
  _install.user_options.append(
    ('install-core=', None, 'installation directory for Core files'))
  def run(self):
    base = os.path.join(self.build_base, core_dir)
    inst = self.distribution.command_options.get('install')
    if not inst.has_key('install_core'):
      sys.exit('Missing mandatory Core installation directory')
    if not inst.has_key('install_htdocs'):
      sys.exit('Missing mandatory htdocs installation directory')
    self.install_dir = inst['install_core'][1]
    copy_tree(base, self.install_dir)
    for f in find_file(base, '*.new'):
      dst = f.replace(base, self.install_dir, 1)[:-4]
      if os.path.lexists(dst): continue
      move_file(dst + '.new', dst)
    fix = []
    for m in core_fix: fix += glob.glob(m)
    for f in fix:
      f = os.path.join(self.install_dir, f)
      n = f + '.tmp'
      fix_script(f, n, self.install_dir, inst['install_htdocs'][1])
      os.unlink(f)
      move_file(n, f)

class build_cgis(_build):
  _build.sub_commands += [('build_cgis', lambda self:True)]
  def run(self):
    base = os.path.join(self.build_base, cgis_dir)
    self.mkpath(base)
    scr = []
    for m in cgis_scr: scr += glob.glob(m)
    for s in scr:
      if not os.path.isfile(s): continue
      dst = os.path.join(base, os.path.basename(s))
      copy_script(s, dst, self.executable)

class install_cgis(_install):
  _install.sub_commands += [('install_cgis', lambda self:True)]
  setattr(_install, 'install_cgis', None)
  _install.user_options.append(
    ('install-cgis=', None, 'installation directory for CGIs files'))
  def run(self):
    base = os.path.join(self.build_base, cgis_dir)
    inst = self.distribution.command_options.get('install')
    if not inst.has_key('install_cgis'):
      sys.exit('Missing mandatory CGIs installation directory')
    if not inst.has_key('install_core'):
      sys.exit('Missing mandatory Core installation directory')
    self.install_dir = inst['install_cgis'][1]
    copy_tree(base, self.install_dir)
    fix = []
    for m in cgis_fix: fix += glob.glob(m)
    for f in fix:
      f = os.path.join(self.install_dir, os.path.basename(f))
      n = f + '.tmp'
      fix_script(f, n, inst['install_core'][1], None)
      os.unlink(f)
      move_file(n, f)

class build_html(_build):
  _build.sub_commands += [('build_html', lambda self:True)]
  def run(self):
    base = os.path.join(self.build_base, html_dir)
    self.mkpath(base)
    (dat, cfg) = ([], [])
    for m in html_dat: dat += glob.glob(m)
    for m in html_cfg: cfg += glob.glob(m)
    com = os.path.commonprefix(dat + cfg)
    for f in dat:
      if f in cfg: continue
      if not os.path.isfile(f): continue
      dst = os.path.join(base, 'portal', f)
      dst = dst.replace(com, '', 1)
      self.mkpath(os.path.dirname(dst))
      self.copy_file(f, dst, preserve_mode=0)
    for f in cfg:
      if not os.path.isfile(f): continue
      dst = os.path.join(base, 'portal', f + '.new')
      dst = dst.replace(com, '', 1)
      self.mkpath(os.path.dirname(dst))
      self.copy_file(f, dst, preserve_mode=0)
    for f in html_tre:
      dir = os.path.join(base, 'data', f)
      self.mkpath(dir)

class install_html(_install):
  _install.sub_commands += [('install_html', lambda self:True)]
  setattr(_install, 'install_htdocs', None)
  _install.user_options.append(
    ('install-htdocs=', None, 'installation directory for HTML files'))
  def run(self):
    base = os.path.join(self.build_base, html_dir)
    inst = self.distribution.command_options.get('install')
    if not inst.has_key('install_htdocs'):
      sys.exit('Missing mandatory htdocs installation directory')
    self.install_dir = inst['install_htdocs'][1]
    copy_tree(base, self.install_dir)
    for f in find_file(base, '*.new'):
      dst = f.replace(base, self.install_dir, 1)[:-4]
      if os.path.lexists(dst): continue
      move_file(dst + '.new', dst)

class sdist(_sdist):
  def run(self):
    self.mktemplate(dist_lst, excl_lst)
    _sdist.run(self)
  def mktemplate(self, incl, excl):
    fd = open('MANIFEST.in', 'w')
    print >> fd, '# WARNING: This is a generated file, *DO NOT* edit.'
    for nam in incl:
      print >> fd, 'include %s' % nam
    for nam in excl:
      print >> fd, 'exclude %s' % nam
    fd.close()

class clean(_clean):
  def run(self):
    for d in [ core_dir, cgis_dir, html_dir ]:
      base = os.path.join(self.build_base, d)
      if not os.path.exists(base): continue
      remove_tree(base)
    _clean.run(self)

class install_egg_info(_install_egg_info):
  def run(self):
    pass

## Overwrite default methods
cmdclass = { 'build':build, 'clean':clean, 'sdist':sdist,
             'install_egg_info':install_egg_info,
             'build_core':build_core, 'install_core':install_core,
             'build_cgis':build_cgis, 'install_cgis':install_cgis,
             'build_html':build_html, 'install_html':install_html }

setup( name=package_nam, version=package_ver, url=package_url,
       author=package_aut, author_email=package_mel,
       cmdclass=cmdclass )

