
def getRealVersion():
	"Attempts to get yade version from RELEASE file if it exists or from bzr/svn, or from VERSION"
	import os.path,re,os
	if os.path.exists('RELEASE'):
		return file('RELEASE').readline().strip()
	if os.path.exists('.bzr'):
		for l in os.popen("LC_ALL=C bzr revno 2>/dev/null").readlines():
			return 'bzr'+l[:-1]
	if os.path.exists('VERSION'):
		return file('VERSION').readline().strip()
	return None


class Plugin:
	def __init__(self,name,src,deps,feats,module):
		self.name,self.src,self.deps,self.feats,self.module=name,src,deps,feats,module
	def __str__(self):
		return '%s/%s [%s] (%s)'%(self.module,self.name,','.join(self.feats),','.join(self.libs))

def grepForIncludes(root,f):
	import re
	ret=set()
	skipping=False
	lineNo=0
	for l in open(root+'/'+f):
		if re.match(r'\s*#endif.*$',l): skipping=False; continue
		if skipping: continue
		m=re.match(r'^\s*#include\s*<yade/([^/]*)/(.*)>.*$',l)
		if m:
			incMod=m.group(1); baseName=m.group(2).split('.')[0];
			if incMod=='core' or incMod.startswith('lib-'): continue
			if skipping: continue
			ret.add(baseName)
			continue
		m=re.match(r'\s*#ifdef\s*YADE_(.*)\s*$',l)
		if m:
			feat=m.group(1).lower()
			if feat not in features: skipping=True; continue
	return ret

features=[]

def scanAllPlugins(cacheFile,feats):
	"""Traverse all files in pkg/, recording what plugins they link with and what features they require.
	Save the result in a cache file and only regenerate the information if the cache file is missing."""
	import os, os.path, re, shelve
	features=feats # update the module-level var
	if cacheFile:
		refresh=os.path.exists(cacheFile)
		plugInfo=shelve.open(cacheFile)
	else:
		plugInfo={}; refresh=True
	if refresh:
		for root, dirs, files in os.walk('pkg/',topdown=True):
			for f in files:
				if not (f.endswith('.cpp') or f.endswith('.cc') or f.endswith('C')): continue
				ff=root+'/'+f
				linkDeps,featureDeps=set(),set()
				isPlugin=True # False
				skipping=False
				for l in open(ff):
					if re.match(r'\s*#endif.*$',l): skipping=False; continue
					if skipping: continue
					m=re.match(r'\s*#(ifdef|ifndef)\s*YADE_(.*)\s*$',l)
					if m:
						cond,feat=m.group(1),m.group(2).lower()
						if (cond=='ifdef' and feat not in features) or (cond=='ifndef' and feat in features): skipping=True
					if re.match(r'\s*YADE_PLUGIN\(.*',l): isPlugin=True
					m=re.match(r'^\s*#include\s*<yade/([^/]*)/(.*)>.*$',l)
					if m:
						incMod=m.group(1); incHead=m.group(2); baseName=incHead.split('.')[0]; assert(len(incHead.split('.'))==2)
						if incMod=='core' or incMod.startswith('lib-'): continue
						if os.path.exists(root+'/'+m.group(2)):
							linkDeps.update(grepForIncludes(root,m.group(2)))
						linkDeps.add(incHead.split('.')[0])
						continue
					m=re.match('^\s*YADE_REQUIRE_FEATURE\s*\(\s*(.*)\s*\).*$',l)
					if m:
						featureDeps.add(m.group(1).upper())
					m=re.match('^s*YADE_LINK_EXTRA_LIB\((.*)\).*$',l)
					if m:
						linkDeps.add('!'+m.group(1)) # hack!! see getPLuginLibs
					m=re.match('^\s*#include\s*"([^/]*)".*$',l)
					if m:
						inc=m.group(1); incBaseName=m.group(1).split('.')[0]
						if not os.path.exists(root+'/'+m.group(1)):
							print "WARNING: file %s included from %s doesn't exist"%(m.group(1),ff)
						else:
							linkDeps.update(grepForIncludes(root,m.group(1)))
							continue
				if isPlugin:
					plugin=f.split('.')[0]
					m=re.match(r'.*pkg/([^/]*)(/.*|)$',root)
					plugInfo[plugin]=Plugin(plugin,ff,linkDeps,featureDeps,m.group(1))
	pp={}
	for p in plugInfo.keys(): pp[p]=plugInfo[p]
	if cacheFile: plugInfo.close()
	return pp

def getWantedPlugins(plugInfo,excludes,features,chunkSize,hotPlugins):
	"""Use pluginInfo (generated by scanAllPlugins) and return only plugins that we should build,
	based on excludes and available features.
	
	Set the plugin object according to chunkSize and set also other plugins this one should link to."""
	ret={}
	feats=set([feat.upper() for feat in features])
	excludes=set(excludes)
	for p in plugInfo:
		plug=plugInfo[p]
		if plug.module in excludes: continue
		if not plug.feats<=feats:
			continue # plugin needs more feature than we have
		ret[plug.name]=plug
	for p in plugInfo.values(): p.obj=getPluginObj(p,chunkSize,hotPlugins)
	for p in plugInfo.values(): p.libs=getPluginLibs(p,plugInfo)
	return ret

def getPluginObj(plug,chunkSize,hotPlugins):
	"""Return name of library this plugin will be compiled into, based on current chunkSize."""
	if   chunkSize==1: return plug.name
	#elif linkStrategy=='per-pkg': return plug.module
	elif chunkSize>1 or chunkSize<=0:
		if plug.name in hotPlugins: return plug.name
		else: return 'plugins'
	#elif linkStrategy=='static': return 'plugins'

def getPluginLibs(p,plugInfo):
	"""Returns library names this plugin should link to, based on current information about other plugins."""
	ret=set()
	for dep in p.deps:
		if dep in plugInfo.keys():
			ret.add(plugInfo[dep].obj)
		elif dep.startswith('!'):
			ret.add(dep[1:])
		else:
			pass
			#print p.src+':',dep,"not a plugin?"
	ret.discard(p.obj)
	return ret

def buildPluginLibs(env,plugInfo):
	objs={}
	chunkSize=env['chunkSize']
	for p in plugInfo.values():
		if not objs.has_key(p.obj): objs[p.obj]=(set(),set())
		objs[p.obj][0].add(p.src)
		objs[p.obj][1].update(p.libs)
	for obj in objs.keys():
		srcs=list(objs[obj][0])
		if len(srcs)>1:
			if len(srcs)<chunkSize or chunkSize<=0: srcs=env.CombineWrapper('$buildDir/'+obj+'.cpp',srcs)
			# thanks to http://stackoverflow.com/questions/312443/how-do-you-split-a-list-into-evenly-sized-chunks-in-python :
			else: srcs=[env.CombineWrapper('$buildDir/'+obj+'%d.cpp'%j,srcs[i:i+chunkSize]) for j,i in enumerate(range(0,len(srcs),chunkSize))]
		#if linkStrategy!='static':
		env.Install('$LIBDIR/plugins',env.SharedLibrary(obj,srcs,LIBS=env['LIBS']+['yade-support','core']+list(objs[obj][1])))
		#else:
		#	env.Install('$LIBDIR/plugins',env.StaticLibrary(obj,srcs,LIBS=env['LIBS']+['yade-support','core']+list(objs[obj][1])))
	

	




