#Copyright ReportLab Europe Ltd. 2000-2004
#see license.txt for license details
#history www.reportlab.co.uk/rl-cgi/viewcvs.cgi/rlextra/utils/buildutils.py
__version__=''' $Id: buildutils.py 15257 2004-11-04 15:30:08Z rgbecker $ '''
import os, sys, string, traceback, re, glob, struct
from shutil import copy2
path_join = os.path.join
path_basename = os.path.basename
path_dirname = os.path.dirname
isFile = os.path.isfile
isDir = os.path.isdir
normPath = os.path.normpath
os_sep = os.sep

if hasattr(sys, 'version_info'):
	_pyvers = sys.version_info[0]*10 + sys.version_info[1]
else:
	toks = string.split(sys.version, '.', 2)
	_pyvers = int(toks[0])*10 + int(toks[1])

def errorMsg(msg,exit=1):
	print >>sys.stderr, msg
	if exit is not None: sys.exit(exit)

def getFlag(name,nextVal=0,default=None):
	r = name in sys.argv
	if nextVal and not r:
		name += '='
		for x in sys.argv[1:]:
			if x.find(name)==0:
				name = x
				r = '='.join(name.split('=')[1:])
				nextVal = 0
				break
	if r:
		x = sys.argv.index(name)
		del sys.argv[x]
		if nextVal:
			try:
				r = sys.argv[x]
				del sys.argv[x]
			except:
				r = (nextVal>>1)&1
	else:
		r = default
	return r
_verbose = os.environ.get('RL_verbose',0)

def invalidFlags(allowed,trailersOK=0):
	I = []	#invalid flags
	for a in sys.argv[1:]:
		if a[0]=='-':
			if a[:2]=='--' and a.find('=')>0: a = a.split('=')[0]
		elif trailersOK: continue
		if a not in allowed: I.append(a)
	return I

class STDLOG:
	def __init__(self,fn='log.txt'):
		self.log = [sys.__stdout__,open(path_join(path_dirname(sys.argv[0]),fn),'w')]
		sys.stderr = sys.stdout = self

	def write(self,msg):
		for log in self.log:
			log.write(msg)

	def close(self):
		sys.stderr = sys.__stderr__
		sys.stdout = sys.__stdout__
		self.log[1].close()

	def __del__(self):
		self.close()

def installerDir(P=None):
	import rlextra
	D = path_join(path_dirname(rlextra.__file__),'utils','installer')
	if P:
		if type(P) not in (type(()),type([])):
			D = path_join(D,P)
		else:
			for p in P:
				D = path_join(D,p)
	return normPath(D)

def do_exec(cmd, fout=None, sys_exit=1, verbose=None, fail=1):
	if verbose is None: verbose = _verbose
	i=os.popen(cmd,'r')
	o = i.read()
	if fout: print >>fout, o
	i = i.close()
	if fail and i is not None:
		errorMsg('Error: %s\n%s\n' % (cmd,o),sys_exit)
		raise ValueError, 'external command failure'
	elif verbose>8: print >>sys.stderr, 'do_exec(%s)' % repr(cmd)

def get_path():
	for i in os.environ.keys():
		if string.upper(i)=='PATH':
			return string.split(os.environ[i].replace('"',''),os.pathsep)
	return []

_find_exe_cache = {}
def find_exe(name):
	global _find_exe_cache
	if sys.platform=='win32':
		name = '%s.exe' % name

	exe = _find_exe_cache.get(name,None)
	if exe: return exe

	for p in get_path():
		f = path_join(p,name)
		if isFile(f):
			if ' ' in f: f = '"%s"' % f
			_find_exe_cache[name] = f
			return f

	errorMsg("Can't find %s anywhere on the path" % name)

def ext_remove(d,ext):
	'remove .ext n d and subtree'
	if isDir(d):
		for p in os.listdir(d):
			fn = path_join(d,p)
			if isFile(fn) and fn[-len(ext):]==ext:
				os.remove(fn)
			else:
				ext_remove(fn,ext)

def rmdir(d):
	'destroy directory d'
	if isDir(d):
		try:
			os.chmod(d,0777)
		except:
			pass
		for p in os.listdir(d):
			fn = path_join(d,p)
			if isDir(fn):
				rmdir(fn)
			else:
				remove(fn)
		os.rmdir(d)

def remove(f):
	'destroy an existing file'
	try:
		try:
			os.chmod(f,0666)
		except:
			pass
		os.remove(f)
	except OSError:
		pass

def _kill(f):
	'remove a single directory or file unconditionally'
	if isFile(f): remove(f)
	elif isDir(f): rmdir(f)

def kill(f):
	'remove directory or file unconditionally'
	map(_kill,glob.glob(f))

def filesMatch(patterns):
	L = []
	a = L.append
	for p in patterns:
		G = glob.glob(p)
		while G:
			g = G[-1]
			del G[-1]
			if isDir(g):
				G.extend(listAllFiles(g,rel=0))
			else:
				L.append(g.replace(os.sep,'/'))
	return L

def copyTree(src, dst, symlinks=0,excludeSVN=1,exclude=[]):
	"""Recursively copy a directory tree using copy2."""
	if not isDir(dst): os.mkdir(dst)
	for f in os.listdir(src):
		if f in exclude: continue
		s = path_join(src, f)
		d = path_join(dst, f)
		try:
			if symlinks and os.path.islink(s):
				linkto = os.readlink(s)
				os.symlink(linkto, d)
			elif isDir(s):
				if not (excludeSVN and path_basename(s)=='.svn'): copyTree(s, d, symlinks,excludeSVN)
			else:
				if not (excludeSVN and path_basename(s)[:4]=='.svn'): copy2(s, d)
		except (IOError, os.error), why:
			print "Can't copy %s to %s: %s" % (`s`, `d`, str(why))
			raise


def safe_umask(mask):
	'''non erroring umask'''
	if mask is None: return None
	try:
		return os.umask(mask)
	except:
		return None

def safe_makedirs(dir,mode=0777):
	'''non erroring makedirs'''
	try:
		um = safe_umask(0)
		os.makedirs(dir,mode)
	except:
		pass
	safe_umask(um)

def _src2dst(src,dst,excludeSVN=1):
	if isFile(src):
		if _verbose>1: print ' %s --> ' % src,
		D = path_dirname(dst)
		if not isDir(D): os.makedirs(D)
		copy2(src,dst)
		if _verbose>1: print dst
	elif isDir(src):
		if _verbose>1: print ' %s --> ' % src,
		if not isDir(dst): os.makedirs(dst)
		copyTree(src,dst)
		if _verbose>1: print dst

def copyOver(L,src,dst,xtra=None,excludeSVN=1):
	assert type(L) in (type(()),type([])), 'L should be list or tuple'
	for x in L:
		d = path_join(dst,x)
		_src2dst(path_join(src,x),d)
		if xtra: _src2dst(path_join(xtra,x),d)	#overwrite with any specials

_bpath=None
_ppath=None
def getExpandedPath():
	"""Return sys.path as expanded by packages"""
	global _ppath
	if _ppath is None:
		_ppath = expand(sys.path)
	return _ppath

def _locate(nm, xtrapath=None, base=None):
	"""Find a file / directory named NM in likely places.
	
	   XTRAPATH is a list of paths to prepend to BASE.
	   If BASE is None, sys.path (as extended by packages) is used."""
	ppath = base
	if base is None:
		ppath = getExpandedPath()
	if xtrapath:
		ppath = xtrapath + ppath
	for pth in ppath:
		fullnm = path_join(pth, nm)
		#print " _locate trying", fullnm
		if os.path.exists(fullnm):
			break
	else:
		return ''
	return fullnm

def getWindowsPath():
	"""Return the path that Windows will search for dlls."""
	global _bpath
	if _bpath is None:
		try:
			import win32api
		except ImportError:
			print "W: Cannot determine your Windows or System directories"
			print "W: Please add them to your PATH if .dlls are not found"
			_bpath = []
		else:
			sysdir = win32api.GetSystemDirectory()
			sysdir2 = path_join(sysdir, '../SYSTEM')
			windir = win32api.GetWindowsDirectory()
			_bpath = [sysdir, sysdir2, windir]
		_bpath.extend(string.split(os.environ.get('PATH', ''), ';'))
	return _bpath

def _locate(nm, xtrapath=None, base=None):
	"""Find a file / directory named NM in likely places.
	
	   XTRAPATH is a list of paths to prepend to BASE.
	   If BASE is None, sys.path (as extended by packages) is used."""
	ppath = base
	if base is None:
		ppath = getExpandedPath()
	if xtrapath:
		ppath = xtrapath + ppath
	for pth in ppath:
		fullnm = path_join(pth, nm)
		#print " _locate trying", fullnm
		if os.path.exists(fullnm):
			break
	else:
		return ''
	return fullnm

def _locateVerDLL(name):
	return _locate('%s%2d.dll'%(name,_pyvers),[],getWindowsPath())

def chdir(dir):
	'''chdir to dir and return old cwd'''
	cwd = os.path.abspath(os.getcwd())
	os.chdir(dir)
	return cwd

class PythonCopier:
	sources = ['py.ico','pyc.ico','pycon.ico','w9xpopen.exe', 'Lib', 'DLLs']
	nocopy = [	'Lib/lib-tk', 'Lib/test', 'Lib/imputil.py', 'Libs',
				'Lib/lib-old', 'Lib/site-packages', 'Lib/email/test',
				'DLLs/tcl83.dll', 'DLLs/tk83.dll',
				'DLLs/cCopy.pyd', 'Dlls/calldll.pyd', 'DLLs/npstruct.pyd',
				'DLLs/_functional.pyd','DLLs/_pyfribidi.pyd','DLLs/fribidi.dll', 'DLLs/libeay32.dll',
				'DLLs/_tkinter.pyd', 'DLLs/gaxtra.pyd','DLLs/old','Dlls/_grabscreen.pyd',
				'DLLs/m2crypto.dll', 'DLLs/_imagingtk.pyd', 'Doc/XTRA','Doc/Grimoire','Doc/Zope',
				]
	
	_pythonDir = path_dirname(sys.executable)

	def doCopy(self,where, srcDir, pythonDir=None):
		'''make a reduced copy of python'''
		if _verbose: print 'Copying python from %s & %s\n  --> %s' % (pythonDir,srcDir,where)
		cwd = chdir(where)
		copyOver(self.sources,pythonDir or self._pythonDir,where,srcDir)

		#clean up unwanted things
		for x in self.nocopy:
			p = path_join(where,x)
			kill(p)
			if _verbose>1: print '	   kill(%s)' % p
		if not isDir('Doc'): os.mkdir('Doc')
		p = _locateVerDLL('Python')
		copy2(p,'DLLs')
		if _verbose>1: print '  %s --> DLLs' % p
		chdir(cwd)

def copyPIL(where, tk=0):
	import _imaging, PIL
	src = path_dirname(_imaging.__file__)
	for F in ('_imaging.pyd','libjpeg.dll','libz.dll') + (tk and ('_imagingtk.pyd',) or ()):
		condCopyF(F,src,path_join(where,'Dlls'))
	src = path_join(path_dirname(PIL.__file__),'..')
	copyOver(('PIL',),src,path_join(where,'Lib','site-packages'))

def copy_rl_remove(where):
	from rlextra.utils import _rl_remove
	src = path_dirname(_rl_remove.__file__)
	copyOver(('_rl_remove.py',),src,path_join(where,'Lib'))

def _svnUpdate(svn, url, opts='',target=''):
	url = string.replace(url,'\\','/')
	if url.endswith('/'): url = url[:-1]
	if target:
		module = target
	else:
		module = url.split('/')[-1]
	if os.path.exists(module):
		act = 'up'
		url = module
	else:
		act = 'co'
	if opts: act = '%s %s' % (act,opts)
	act = '%s %s %s' % (svn, act, url)
	if target: act = '%s %s' % (act,target)
	do_exec(act)

def svnUpdate(svn, url, L, opts='',target=''):
	if not L:
		_svnUpdate(svn, url, opts=opts,target=target)
	else:
		for x in L:
			_svnUpdate(svn, '%s/%s' % (url,x), opts=opts, target=target)

def svn_update(workDir, url=None, L=None,opts='',target=''):
	svn = find_exe('svn')
	try:
		os.makedirs(workDir)
	except:
		pass
	try:
		cwd = chdir(workDir)
		svnUpdate(svn, url, L, opts=opts,target=target)
	finally:
		chdir(cwd)

def svn_info(what):
	'''return the svn info output for a working file/folder as a dictionary'''
	cwd = os.getcwd()
	if not what: what = cwd
	if isDir(what):
		wd = what
		obj = ''
	elif isFile(what):
		wd = path_dirname(what)
		obj = '"%s"' % path_filename(what)
	else:
		raise ValueError('Not file or folder: "%s"' % what)
	from StringIO import StringIO
	out = StringIO()
	try:
		chdir(wd)
		do_exec('%s info %s' % (find_exe('svn'), obj), out)
	finally:
		chdir(cwd)
	D = {}
	out = out.getvalue().split('\n')
	for o in out:
		o = o.split(':')
		D[o[0].strip()] = ':'.join(o[1:]).strip()
	return D

def findSVNURL(W):
	'''return the URL corresponding to working folder W'''
	try:
		f = open(path_join(W,'.svn','entries'),'r')
		lines = f.read().replace('\r','\n').split('\n')
		f.close()
		for l in lines:
			l = l.strip()
			if l.startswith('url="'):
				return l.split('"')[1]
	except:
		pass
	raise ValueError('Can\'t find SVN URL for working dir '+W)

def import_free_call(func,*args,**kw):
	'''eliminate unwanted import side effects and return func()'''
	try:
		OLD = {}
		for x in 'path','modules','meta_path','path_hooks','path_importer_cache':
			try:
				_ = getattr(sys,x)
				if hasattr(_,'copy'): _ = _.copy()
				else: _ = _[:]
				OLD[x] = _
			except:
				pass
		return func(*args,**kw)
	finally:
		for x,v in OLD.items():
			_ = getattr(sys,x)
			if hasattr(_,'copy'):
				_.clear()
				_.update(v)
			else:
				_[:] = v

def inferSVNURL(URL,modulename):
	'''try to infer the SVN URL from a module import location'''
	def func(URL,modulename):
		try:
			exec 'import %s as mod' % modulename in locals()
			from urlparse import urlparse, urlunparse
			host = urlparse(findSVNURL(path_dirname(mod.__file__)),allow_fragments=0)
			url = urlparse(URL)
			return urlunparse((url[0],host[1])+url[2:])
		except:
			return URL
	return import_free_call(func,URL,modulename)

def fetchRL(srcDir,rl=1,rlx=1,rla=None,report=None,pfb=None, branchName=None, branchNameX=None):
	'''convenience routine assumes our reportlab & rlextra are SVN controlled'''
	if not branchName: branchName = 'trunk'
	elif not (branchName.startswith('branches/') or branchName.startswith('tags/')):
		raise ValueError('branchName should start with "branches/" or "tags/" not "%s"' % branchName)
	if rl or rla:
		url = inferSVNURL('https://www.reportlab.co.uk/svn/public/reportlab/%s' % branchName,'reportlab')
		if rl:
			svn_update(srcDir,url=url,L=['reportlab'])
			if report: report('reportlab fetched')
			if pfb:
				#fetch the standard pfb files into ./fonts
				from reportlab.pdfbase._fontdata import _font2fnrMapWin32, findT1File
				N = []
				for fontName in _font2fnrMapWin32.keys():
					try:
						fn = findT1File(fontName)
					except:
						N.append(fontName)
					_src2dst(fn,path_join(srcDir,'reportlab','fonts'))
				if N: raise ValueError('Can\'t copy pfb fonts '+ ' '.join(N)) 
				if report: report('pfb font files copied to fonts')
		if rla:
			svn_update(srcDir,url=url,L=['rl_addons'])
			if report: report('rl_addons fetched')
	if rlx:
		if not branchNameX: branchNameX = branchName
		elif not (branchNameX.startswith('branches/') or branchNameX.startswith('tags/')):
			raise ValueError('branchNameX should start with "branches/" or "tags/" not "%s"' % branchNameX)
		url = inferSVNURL('https://www.reportlab.co.uk/svn/private/%s/rlextra' % branchNameX,'rlextra')
		svn_update(srcDir,url=url,L=None)
		if report: report('rlextra fetched')

def stagingArea(suffix=''):
	from reportlab.lib.utils import get_rl_tempdir
	return get_rl_tempdir('staging_area'+suffix)

def copyRL(where,srcDir,rl=1,rlx=1,noFetch=0):
	'''copy reportlab or rlextra from the staging area to the result area'''
	if not noFetch: fetchRL(srcDir,rl=rl,rlx=rlx)	#get export to srcDir
	cwd = chdir(where)
	if _verbose: print 'Copying RL from %s\n  --> %s' % (srcDir,where)
	copyOver((rl and ('reportlab',) or ())+(rlx and ('rlextra',) or ()), srcDir, where,None)
	chdir(cwd)

def make_stub(stubname,fn,unix=0):
	stubs= {'rml2pdf':'''import sys
from rlextra.rml2pdf import rml2pdf
from reportlab import rl_config
fn=sys.argv[1:]
x = filter(lambda x:x[:9]=='--outdir=',fn)
outDir=None
if x:
	outDir=x[-1][9:]
	map(fn.remove,x)
rml2pdf.main(quiet=rl_config.verbose==0,outDir=outDir,exe=1,fn=fn)
''',
	'pagecatcher': 'from rlextra.pageCatcher.pageCatcher import scriptInterp\nscriptInterp()\n',
	'preppy': 'from rlextra.preppy.preppy import main\nmain()\n',
	'pythonpoint': 'from reportlab.tools.pythonpoint.pythonpoint import main\nmain()\n',
	}
	open(fn,'w').write((unix and '#!env python\n' or '') + stubs[stubname])

def postCopyFixupRL(where):
	cwd = chdir(where)
	if sys.hexversion>=0x02020000:
		src = path_join(PythonCopier._pythonDir,'Lib','site-packages')
		for F in ('_renderPM.pyd','_rl_accel.pyd','pyRXP.pyd','sgmlop.pyd','pyHnj.pyd'):
			condCopyF(F,src,'Dlls')
	sys.path.insert(0,where)
	import reportlab.rl_config
	sys.path.remove(where)
	L = []
	for d in dir(reportlab.rl_config):
		if d in ('__builtins__','sys','os','string'): continue
		L.append(d)
	src = path_join(where,'reportlab','rl_config.py')
	dst = path_join(where,'_rl_config.py')
	rename(src,dst)
	open(src,'w').write('from _rl_config import %s\n' % ', '.join(L))
	make_stub('rml2pdf',path_join(where,'rlextra','rml2pdf','exe_stub_rml2pdf_distro.py'))
	make_stub('pagecatcher', path_join(where,'rlextra','pageCatcher','exe_stub_pageCatcher_distro.py'))
	make_stub('preppy',path_join(where,'rlextra','preppy','exe_stub_preppy_distro.py'))
	make_stub('pythonpoint',path_join(where,'reportlab','tools','pythonpoint', 'exe_stub_pythonpoint_distro.py'))
	chdir(cwd)

def makePat(P):
	'''
	P is a list of	glob style patterns
	return the corresponding compiled re
	'''
	import re, fnmatch
	L=[]
	for p in P:
		L.append(fnmatch.translate(p))
	return re.compile('^(?:%s)$'%string.join(L,'|'))

def makeRLDocs(where):
	cwd = chdir(path_join(where,'reportlab','docs'))
	opp = os.environ.get('PYTHONPATH',None)
	pp = where
	if opp: pp += ';'+opp
	os.environ['PYTHONPATH'] = pp
	do_exec(sys.executable + ' genAll.py')
	PDF = glob.glob('*.pdf')
	if _verbose>2: print 'pdf=',PDF
	if opp: os.environ['PYTHONPATH'] = opp
	else: del os.environ['PYTHONPATH']
	chdir(cwd)
	chdir(path_join(where,'rlextra','graphics','doc'))
	do_exec('%s %s diagradoc.rml' % (sys.executable, path_join(where,'rlextra','rml2pdf','rml2pdf.py')))
	chdir(cwd)
	return PDF

_binexts = ('dll', 'pyc', 'pyo', 'pyz', 'exe')
def _resource_fixcase(name, path):
	parts = string.split(name, '.')
	name = parts[-1]
	if name in _binexts:
		name = parts[-2]
	d, bnm = os.path.split(path)
	nm, ext = os.path.splitext(bnm)
	if string.lower(name) == string.lower(nm):
		return path_join(d, name + ext)
	return path

def GetCompiled(seq, lvl='c'):
	"""SEQ is a list of .py files, or a logical TOC.
	 Return as .pyc or .pyo files (LVL) after ensuring their existence"""
	if len(seq) == 0: return seq
	rslt = []
	isTOC = type(seq[0])==type(())
	for py in seq:
		if isTOC:
			(nm, fnm), rest = py[:2], py[2:]
		else:
			fnm = py
			nm = os.path.splitext(fnm)[0]
		fnm = os.path.splitext(fnm)[0] + '.py'
		cmpl = 1
		pyc = fnm + lvl
		if os.path.exists(pyc):
			pytm = long(os.stat(fnm)[8])
			ctm = long(os.stat(pyc)[8])
			if pytm < ctm: cmpl = 0
		if cmpl:
			fnm = _resource_fixcase(nm, fnm)
			py_compile.compile(fnm, pyc)
		if isTOC:
			rslt.append((nm, pyc)+rest)
		else:
			rslt.append(pyc)
	return rslt

def assembleArchive(pkg,toc,fullName=0):
	'''
	pkg is the name of the archive file
	toc is the list of contents path, type
	'''
	from installer import CArchive
	A = CArchive()
	L = []
	for p, t in toc:
		n = os.path.splitext(path_basename(p))[0]
		c = {'s':2,'m':0,'b':1,'x':1,'a':0,'z':0}[t]
		if t=='b' or (fullName and t in 'ms'): n = n + os.path.splitext(p)[1]
		if t=='m': L += GetCompiled([(n, p, c, t)])
		else: L.append((n, p, c, t))
	A.build(pkg,L)
	return A

def copyFiles(src,dst,mode='wb'):
	w = open(dst,mode)
	for r in src:
		w.write(open(r,'rb').read())

_MAGIC = {'RLP':"RLP\02\04\03\014\015", 'RLW':"RLW\01\02\03\017\016", 'RLE':"RLE\07\04\01\013\012"}
def _wrapEXE(src,dst,magic,project,script=None,version=None,icon=None):
	v = sys.version
	v = int(v[0])*10+int(v[2])
	if _verbose: print 'making %s(%d) from %s icon=%s version=%s' % (n,v,w,icon,version)
	d = open(dst,'wb')
	d.write(open(src,'rb').read())
	if script:
		d.write(script)
		s = struct.pack('<ihh',len(script),len(project),v)
	else:
		s = struct.pack('<hh',len(project),v)
	d.write(project)
	d.write(s)
	d.write(magic)
	d.close()
	if icon:
		odst = dst+'.tmp'
		os.rename(dst,odst)
		import pywintypes
		try:
			from installer.icon import CopyIcons
			CopyIcons(dst, icon)
		except ImportError:
			raise ValueError, "win32api & NT/W2K are required for updating icons."
		except pywintypes.error:
			raise ValueError, "NT/Win2k required to update icons"
		os.remove(odst)
	if version:
		odst = dst+'.tmp'
		os.rename(dst,odst)
		import pywintypes
		try:
			from installer.versionInfo import SetVersion
			SetVersion(vexe, version)
		except ImportError:
			raise ValueError, "win32api & NT/W2K are required for setting Version resources."
		except pywintypes.error:
			raise ValueError, "NT/Win2k required to update version resource"
		os.remove(odst)

def _makeEXE(where,n,debug,magic,project,script=None,version=None,icon=None,stub=None,ext='exe'):
	supDir = installerDir('support')
	if ext and ext[0]!='.': ext = '.'+ext
	w = (stub or n)+(debug and '_d' or '')+ext
	n = n+ext
	src = path_join(supDir,w)
	dst = path_join(where,n)
	_wrapEXE(src,dst,magic,project,script=script,version=version,icon=icon)

def	makeEXES(where,PROJECT,RLP=('python','pythonw',),RLW=(),debug=1):
	if _verbose: print 'Making exe files in ',where
	for n,p,w,v,i in RLW:
		_makeEXE(where,n,debug,
				_MAGIC['RLW'],
				PROJECT,
				script=open(p,'r').read(),
				icon=i,
				version=v,
				stub=w and 'rlwrapw' or 'rlwrap')
	for n in RLP:
		_makeEXE(where,n,debug,_MAGIC['RLP'],PROJECT)

def getHere():
	global _cached_getHere
	if '_cached_getHere' not in globals().keys():
		_cached_getHere = getFlag('--where',1,sys.argv[0])
		if not os.path.isabs(_cached_getHere): _cached_getHere = os.path.abspath(_cached_getHere)
		if not isDir(_cached_getHere): _cached_getHere = path_dirname(_cached_getHere)
	return _cached_getHere

def listAllFiles(dir,rel=1):
	def record(F,dir,names):
		F.extend(filter(isFile,map(lambda x, dir=dir: path_join(dir,x),names)))
	F = []
	os.path.walk(dir,record,F)
	if rel:
		n = len(dir)+len(os.sep)
		F = map(lambda x,n=n: x[n:],F)
	return F

def listAllDirs(dir,rel=1):
	def record(F,dir,names):
		F.extend(filter(isDir,map(lambda x, dir=dir: path_join(dir,x),names)))
	F = []
	os.path.walk(dir,record,F)
	if rel:
		n = len(dir)+len(os.sep)
		F = map(lambda x,n=n: x[n:],F)
	return F

def cleanEmpties(dir):
	D = listAllDirs(dir)
	D.reverse()
	cwd = chdir(dir)
	for d in D:
		if not os.listdir(d): os.rmdir(d)
	chdir(cwd)

def _forceDir(d):
	D = path_dirname(d)
	if not isDir(D): os.makedirs(D)

def _copyF(s,d):
	if isFile(d):
		remove(d)
	else:
		_forceDir(d)
	copy2(s,d)

def sitecustomize(where):
	'''for versions < 2.2 put site-packages into the path'''
	if sys.hexversion<0x02020000:
		open(path_join(where,'Lib','sitecustomize.py'),'w').write(
'''import sys, os
from site import addsitedir
addsitedir(path_join(os.path.dirname(__file__),'site-packages'))
'''
		)

def copyF(F,src,dst):
	_copyF(os.path.abspath(path_join(src,F)), os.path.abspath(path_join(dst,F)))

def condCopyF(F,src,dst):
	srcF = os.path.abspath(path_join(src,F))
	if isFile(srcF):
		_copyF(srcF, os.path.abspath(path_join(dst,F)))

class PyComCopier:
	sources=(
		'win32/win32api.pyd', 'win32/win32trace.pyd',
		'win32/lib/commctrl.py', 'win32/lib/win32con.py', 'win32/lib/win32traceutil.py', 'win32/lib/winerror.py',
		'win32com/__init__.py', 'win32com/client/CLSIDToClass.py', 'win32com/client/__init__.py', 'win32com/client/build.py',
		'win32com/client/connect.py', 'win32com/client/dynamic.py', 'win32com/client/gencache.py',
		'win32com/client/genpy.py', 'win32com/client/makepy.py', 'win32com/client/selecttlb.py',
		'win32com/client/util.py', 'win32com/olectl.py', 'win32com/server/__init__.py', 'win32com/server/connect.py',
		'win32com/server/dispatcher.py', 'win32com/server/exception.py', 'win32com/server/factory.py', 'win32com/server/localserver.py',
		'win32com/server/policy.py', 'win32com/server/register.py', 'win32com/server/util.py', 'win32com/storagecon.py',
		'win32com/universal.py', 'win32com/util.py',

		# these are only used by gui type things
		#'Pythonwin/pywin/__init__.py', 'Pythonwin/pywin/dialogs/__init__.py', 'Pythonwin/pywin/dialogs/list.py', 'Pythonwin/pywin/dialogs/status.py',
		#'Pythonwin/pywin/mfc/__init__.py', 'Pythonwin/pywin/mfc/dialog.py', 'Pythonwin/pywin/mfc/object.py', 'Pythonwin/pywin/mfc/window.py',
		#'win32com/client/combrowse.py', 'Pythonwin/win32ui.pyd', 'Pythonwin/win32uiole.pyd', 'win32com/client/tlbrowse.py',
		)

	def doCopy(self,where):
		import win32com
		src = os.path.abspath(path_join(path_dirname(win32com.__file__),'..'))
		sp = os.path.abspath(path_join(where,'Lib','site-packages'))
		d = path_join(sp,'win32com.pth')
		_forceDir(d)
		open(d,'w').write('win32\nwin32\\lib\n')
		map(lambda x,dst=sp,src=src: copyF(x,src,dst),self.sources)
		for f in ('PythonCOM', 'PyWinTypes'):
			s = _locateVerDLL(f)
			d = path_join(where,'Dlls',string.lower(path_basename(s)))
			_copyF(s,d)
		sitecustomize(where)

def findModules(d,wlen,ipfx='',excl_re=None,incl_list=None):
	from py_compile import compile
	cwd = chdir(d)
	here = os.getcwd()
	R = []
	for m in glob.glob('*.py'):
		n = ipfx+m
		if incl_list and n not in incl_list: continue
		n = n[:-3]
		c = normPath(path_join(here,m + 'c'))
		co = '_p:'+c[wlen:-1]
		if n[-9:]=='.__init__': n = n[:-9]
		if excl_re and excl_re.match(n): continue
		if _verbose>9: print 'Adding %s to pyz' % c[:-1]
		compile(m,c,co)
		R.append((n,c))
	P = filter(lambda x, d=d: isDir(x) and isFile(path_join(x,'__init__.py')), os.listdir(here))
	for p in P:
		R.extend(findModules(p,wlen,ipfx+p+'.',excl_re,incl_list))
	chdir(cwd)
	return R

def rename(src,dst):
	remove(dst)
	try:
		os.rename(src,dst)
	except:
		pass

def move_files(root,files):
	for f, d in files.items():
		rename(path_join(root,f),path_join(root,d,path_basename(f)))

def cleanupBeforePYZ(where,movethese={},copythese={},remove_before_pyz=()):
	if _verbose: print 'Moving RL files to', where
	#move to root dir
	move_files(where,movethese)

	for f, d in copythese.items():
		copy2(path_join(where,f),path_join(where,d))

	if _verbose: print 'Removing unwanted files'
	map(kill,remove_before_pyz)

def makePYZ(where, pyz_exclude=(), remove_after_pyz=(), pyzName=None, preLoad=1, addLib=1, cleanUp=1, incl_list=None, verbose=None):
	if verbose is None: verbose = _verbose
	if verbose: print 'Locating modules for PYZ'
	from installer._rl_archive import ZlibArchive
	from py_compile import compile
	instDir = installerDir()
	Z = []
	if preLoad:
		for n in ('_rl_archive','_rl_iu','_rl_boot')[:max(2,preLoad)]:
			f = path_join(instDir,n+'.py')
			c = f+'c'
			compile(f,c,f)
			Z.append((n,c))

	excl_re = makePat(pyz_exclude)
	where = os.path.abspath(where)
	wlen = len(where)+len(os.sep)
	lib = path_join(where,'Lib')
	for D in (where,)+(addLib and (lib,) or ()):
		Z.extend(findModules(D,wlen,excl_re=excl_re,incl_list=incl_list))
	if verbose: print 'Building PYZ, number of modules=%d' % len(Z)
	ZlibArchive().build(pyzName or path_join(lib,'reportlab.pyz'),Z)
	if cleanUp:
		for m,p in Z[preLoad and max(preLoad,2) or 0:]:	#first two belong to us
			remove(p)		#x.pyc
			remove(p[:-1])	#x.py
		map(kill,remove_after_pyz)

def compile_files(F,removePy=0):
	'''compile_files(F)
	python compile files in F'''
	from py_compile import compile
	for fn in F:
		compile(fn)
		if removePy: remove(fn)
	return len(F)

def removePYC(F,removePy=0):
	'''removePYC(F)
	remove pyc files parallel to files in F'''
	for f in F:
		f = f.split('/')
		if sys.platform=='mac': f.insert(0,'')
		remove(os.path.splitext(os.sep.join(f))[0]+'.pyc')

def compile_dir(dir, removePy=0, noCompile=[]):
	cwd = os.getcwd()
	if not os.path.isabs(dir): dir = path_join(cwd,dir)
	chdir(dir)
	from superglob import FileSet
	fsPy = FileSet(dir)
	fsPy.include('**/*.py')
	fsPy.scan()
	count = 0
	F = fsPy.getFileNames()
	count = compile_files(filter(lambda x,NC=noCompile: x not in NC,F), removePy=removePy)
	removePYC(noCompile)
	if removePy:
		for f in filter(lambda x: x[-4:]=='.pyc',F):
			remove(f[:-1])
	chdir(cwd)
	return count

def runInno(fileName,where=None):
	Inno = None
	for c in (	'C:\\Program Files\\Inno Setup 4\\Compil32.exe',
				'C:\\Program Files\\Inno\\Compil32.exe',
				'C:\\Program Files\\My Inno Setup Extensions\\Compil32.exe',
				'C:\\Inno\\Compil32.exe',
				'C:\\My Inno Setup Extensions\\Compil32.exe'):
		if isFile(c):
			Inno = c
			break
	if Inno:
		cmd = [Inno,'/cc',where and path_join(where,fileName) or fileName]
		from distutils.spawn import spawn
		rc = spawn(cmd,search_path=0,verbose=_verbose,dry_run=0)
	else:
		print 'Cannot find Inno, run manually'

def makeTGZ(tgzName, archDir,AFL=(),tarName=None):
	#AR the tar module I found is Python 2.2 only and chokes
	#when gzipping; had to handle that myself
	AFL = AFL or listAllFiles(archDir,rel=0)
	import tarfile
	if not tarName:
		from reportlab.lib.utils import get_rl_tempfile
		tarName = get_rl_tempfile()
	arch = tarfile.TarFileCompat(tarName, 'w', compression=tarfile.TAR_PLAIN)
	for fn in AFL: arch.write(fn)
	arch.close()
	import gzip
	if tgzName[:-4]!='.tgz': tgzName = tgzName + '.tgz'
	remove(tgzName)
	g = gzip.GzipFile(tgzName, 'wb', 9)
	f = open(tarName, 'rb')
	while 1:
		chunk = f.read(1024)
		if not chunk: break
		g.write(chunk)
	g.close()
	f.close()
	remove(tarName)

def expandTGZ(tgzName,tarName=None):
	'''Expands tgz archives
	Warning this only handles regular files'''
	import tarfile, gzip
	if not tarName:
		from reportlab.lib.utils import get_rl_tempfile
		tarName = get_rl_tempfile()
	g = gzip.GzipFile(tgzName)
	t = open(tarName, 'wb')
	while 1:
		data = g.read(1024)
		if not data: break
		t.write(data)
	g.close()
	t.close()
	t = tarfile.TarFile(tarName)
	m = t.getmembers()

	for d in filter(lambda x, t=tarfile.REGTYPE: x.type==t, m):
		n = d.name.split('/')
		if n[0]=='.': del n[0]
		if len(n)>1:
			dn = os.sep.join(n[:-1])
			if not isDir(dn): os.makedirs(dn)
		open(os.sep.join(n),'wb').write(t.readstr(d))

	t.close()
	remove(tarName)

def makeZIP(zipName, archDir, AFL=()):
	AFL = AFL or listAllFiles(archDir,rel=0)
	import zipfile
	if zipName[-4:]!='.zip': zipName = zipName + '.zip'
	remove(zipName)
	arch = zipfile.ZipFile(zipName, 'w', compression=zipfile.ZIP_DEFLATED)
	for fn in AFL: arch.write(fn)
	arch.close()

def makeArchive(name, where, what='.', convertCRLF=sys.platform=='win32',zipmode='tgz'):
	"Archives file/dir in 'what', or current directory if not specified"
	cwd = chdir(where)
	if name[-4:]=='.tgz' or name[-4:]=='.tar': name = name[:-4]
	name = path_join(os.getcwd(),name)
	if zipmode=='tgz':
		remove(name+'.tar')
		remove(name+'.tgz')
		tar = find_exe('tar')
		do_exec(tar + ' cf %s.tar %s' % (name,what))
		gzip = find_exe('gzip')
		do_exec(gzip + ' %s.tar' % name)
		rename(name+'.tar.gz', name+'.tgz')
	elif zipmode=='zip':
		zip = find_exe('zip')
		remove(name+'.zip')
		do_exec(zip+' -ur %s.zip . -i %s' % (name,what))
	else:
		raise ValueError, 'Illegal zipmode %s' % zipmode
	chdir(cwd)

def generateProductInfo(where):
	cwd = chdir(where)
	python = sys.executable
	for (cmd,dir) in [
		("%(python)s genuserguide.py --outdir=%(where)s/docs", "%(where)s/reportlab/docs/userguide"),
		("%(python)s gen_rmluserguide.py --outdir=%(where)s/docs", "%(where)s/rlextra/rml2pdf/doc"),
		("%(python)s %(where)s/rlextra/rml2pdf/rml2pdf.py %(where)s/rlextra/pageCatcher/PageCatchIntro.rml", "%(where)s/rlextra/pageCatcher"),
		("%(python)s %(where)s/rlextra/rml2pdf/rml2pdf.py %(where)s/rlextra/preppy/preppyspec.rml", "%(where)s/rlextra/preppy"),
		("%(python)s %(where)s/reportlab/tools/pythonpoint/pythonpoint.py %(where)s/reportlab/tools/pythonpoint/demos/pythonpoint.xml", "%(where)s/reportlab/tools/pythonpoint/demos"),
		("%(python)s %(where)s/rlextra/rml2pdf/rml2pdf.py diagradoc.rml", "%(where)s/rlextra/graphics/doc"),
		]:
		cmd = cmd.replace('/',os_sep)
		dir = dir.replace('/',os_sep)
		dir = dir % locals()
		if not isDir(dir): continue
		chdir(dir)
		cmd = cmd % locals()
		if _verbose>5: print '################ %s: start %s' % (time.asctime(time.gmtime()), cmd)
		i, o = os.popen4(cmd,'t')
		t = o.read()
		if _verbose>1 and t: print t
		if _verbose>5: print '################ %s: finished' % time.asctime(time.gmtime())
	chdir(cwd)

_re_exts2del=re.compile(r'.*\.(?:bat|c|cc|cpp|h|py|pyc|pyo|prep)$',re.I)
def cleanProductInfo(dir1, dir2, keep):
	'''
	removes code like files eg, .py, .pyc, .c, .h, etc from underneath dir1
	removes corresponding survivors from under dir2
	'''
	dir1 = os.path.abspath(dir1)
	dir2 = os.path.abspath(dir2)


	K = listAllFiles(dir1)
	D = filter(_re_exts2del.match,K)
	for f in D:
		if f in keep:
			D.remove(f)
		else:
			K.remove(f)
	if _verbose>9:
		import pprint
		print 'dir1=',dir1, '\nK='
		pprint.pprint(K)
		print 'dir2=',dir2, '\nD='
		pprint.pprint(D)
	cwd = chdir(dir1)
	map(kill,D)
	chdir(dir2)
	map(kill,K)
	chdir(cwd)

def getFileActionList(dir,patterns={},L=None,fal=None):
	cwd = chdir(dir)
	if L is None: L = []
	A = map(lambda x: x[1],L)
	for k,p in patterns.items():
		F = filter(lambda x,A=A: x not in A,filesMatch(p))
		A.extend(F)
		for f in F:
			L.append((k,f))
	F = listAllFiles(dir)
	F.sort()
	for f in F:
		fs = f.replace('\\','/')
		if fs in A: continue
		d = path_dirname(f)
		fn, ext = os.path.splitext(f)
		bn = path_basename(fn)
		ext = ext.lower()
		if ext == '.pyc': continue
		if ext=='.py':
			if isFile(path_join(dir,d,'__init__.py')):
				k = 'z'
			else:
				k = 'k'	#keep
		elif ext in ('.bat','.exe'):
			k = 'd'
		elif ext=='.pdf':
			if d!='docs':
				k = 'm', path_join(dir,'docs')
			else:
				k = 'k'
		elif bn.lower() in ('readme', 'changes', '00readme'):
			k = 'k'
		elif ext in ('.txt','.rml'):
			k = 'k'
		else:
			k = 'k'
		L.append(type(k) is type('') and (k,fs) or (k[0],fs,k[1].replace('\\','/')))
	L.sort()
	chdir(cwd)
	return L

def writeFileActionList(dir,patterns={},filename='new_fileactions.txt',L=None):
	fa = open(filename,'w')
	for x in getFileActionList(dir,patterns=patterns,L=L):
		print >>fa, repr(x)[1:-1]

def readFileActionList(filename='fileactions.txt'):
	return map(eval,open(filename,'r').readlines())
