#! /usr/bin/env python
# encoding: utf-8

import os.path,glob,types,re,sys
import Configure,config_c,Options,Utils,Logs
from Logs import warn
from Configure import conf
boost_code='''
#include <iostream>
#include <boost/version.hpp>
int main() { std::cout << BOOST_VERSION << std::endl; }
'''
boost_libpath=['/usr/lib','/usr/local/lib','/opt/local/lib','/sw/lib','/lib']
boost_cpppath=['/usr/include','/usr/local/include','/opt/local/include','/sw/include']
STATIC_NOSTATIC='nostatic'
STATIC_BOTH='both'
STATIC_ONLYSTATIC='onlystatic'
is_versiontag=re.compile('^\d+_\d+_?\d*$')
is_threadingtag=re.compile('^mt$')
is_abitag=re.compile('^[sgydpn]+$')
is_toolsettag=re.compile('^(acc|borland|como|cw|dmc|darwin|gcc|hp_cxx|intel|kylix|vc|mgw|qcc|sun|vacpp)\d*$')
def set_options(opt):
	opt.add_option('--boost-includes',type='string',default='',dest='boostincludes',help='path to the boost directory where the includes are e.g. /usr/local/include/boost-1_35')
	opt.add_option('--boost-libs',type='string',default='',dest='boostlibs',help='path to the directory where the boost libs are e.g. /usr/local/lib')
def string_to_version(s):
	version=s.split('.')
	if len(version)<3:return 0
	return int(version[0])*100000+int(version[1])*100+int(version[2])
def version_string(version):
	major=version/100000
	minor=version/100%1000
	minor_minor=version%100
	if minor_minor==0:
		return"%d_%d"%(major,minor)
	else:
		return"%d_%d_%d"%(major,minor,minor_minor)
def libfiles(lib,pattern,lib_paths):
	result=[]
	for lib_path in lib_paths:
		libname=pattern%('boost_'+lib+'*')
		result+=glob.glob(lib_path+'/'+libname)
	return result
def get_boost_version_number(self,dir):
	try:
		return self.run_c_code(compiler='cxx',code=boost_code,includes=dir,execute=1,env=self.env.copy(),type='cprogram',compile_mode='cxx',compile_filename='test.cpp')
	except Configure.ConfigurationError,e:
		return-1
def set_default(kw,var,val):
	if not var in kw:
		kw[var]=val
def tags_score(tags,kw):
	score=0
	needed_tags={'threading':kw['tag_threading'],'abi':kw['tag_abi'],'toolset':kw['tag_toolset'],'version':kw['tag_version']}
	if kw['tag_toolset']is None:
		v=kw['env']
		toolset=v['CXX_NAME']
		if v['CXX_VERSION']:
			version_no=v['CXX_VERSION'].split('.')
			toolset+=version_no[0]
			if len(version_no)>1:
				toolset+=version_no[1]
		needed_tags['toolset']=toolset
	found_tags={}
	for tag in tags:
		if is_versiontag.match(tag):found_tags['version']=tag
		if is_threadingtag.match(tag):found_tags['threading']=tag
		if is_abitag.match(tag):found_tags['abi']=tag
		if is_toolsettag.match(tag):found_tags['toolset']=tag
	for tagname in needed_tags.iterkeys():
		if needed_tags[tagname]is not None and tagname in found_tags:
			if re.compile(needed_tags[tagname]).match(found_tags[tagname]):
				score+=kw['score_'+tagname][0]
			else:
				score+=kw['score_'+tagname][1]
	return score
def validate_boost(self,kw):
	ver=kw.get('version','')
	for x in'min_version max_version version'.split():
		set_default(kw,x,ver)
	set_default(kw,'lib','')
	kw['lib']=Utils.to_list(kw['lib'])
	set_default(kw,'env',self.env)
	set_default(kw,'libpath',boost_libpath)
	set_default(kw,'cpppath',boost_cpppath)
	for x in'tag_threading tag_version tag_toolset'.split():
		set_default(kw,x,None)
	set_default(kw,'tag_abi','^[^d]*$')
	set_default(kw,'score_threading',(10,-10))
	set_default(kw,'score_abi',(10,-10))
	set_default(kw,'score_toolset',(1,-1))
	set_default(kw,'score_version',(100,-100))
	set_default(kw,'score_min',0)
	set_default(kw,'static',STATIC_NOSTATIC)
	set_default(kw,'found_includes',False)
	set_default(kw,'min_score',0)
	set_default(kw,'errmsg','not found')
	set_default(kw,'okmsg','ok')
def find_boost_includes(self,kw):
	boostPath=getattr(Options.options,'boostincludes','')
	if boostPath:
		boostPath=[os.path.normpath(os.path.expandvars(os.path.expanduser(boostPath)))]
	else:
		boostPath=Utils.to_list(kw['cpppath'])
	min_version=string_to_version(kw.get('min_version',''))
	max_version=string_to_version(kw.get('max_version',''))or(sys.maxint-1)
	version=0
	for include_path in boostPath:
		boost_paths=glob.glob(os.path.join(include_path,'boost*'))
		for path in boost_paths:
			pathname=os.path.split(path)[-1]
			ret=-1
			if pathname=='boost':
				path=include_path
				ret=self.get_boost_version_number(path)
			elif pathname.startswith('boost-'):
				ret=self.get_boost_version_number(path)
			ret=int(ret)
			if ret!=-1 and ret>=min_version and ret<=max_version and ret>version:
				boost_path=path
				version=ret
	if not version:
		self.fatal('boost headers not found! (required version min: %s max: %s)'%(kw['min_version'],kw['max_version']))
		return False
	found_version=version_string(version)
	versiontag='^'+found_version+'$'
	if kw['tag_version']is None:
		kw['tag_version']=versiontag
	elif kw['tag_version']!=versiontag:
		warn('boost header version %r and tag_version %r do not match!'%(versiontag,kw['tag_version']))
	env=self.env
	env['CPPPATH_BOOST']=boost_path
	env['BOOST_VERSION']=found_version
	self.found_includes=1
	ret='Version %s (%s)'%(found_version,boost_path)
	return ret
def find_boost_library(self,lib,kw):
	def find_library_from_list(lib,files):
		lib_pattern=re.compile('.*boost_(.*?)\..*')
		result=(None,None)
		resultscore=kw['min_score']-1
		for file in files:
			m=lib_pattern.search(file,1)
			if m:
				libname=m.group(1)
				libtags=libname.split('-')[1:]
				currentscore=tags_score(libtags,kw)
				if currentscore>resultscore:
					result=(libname,file)
					resultscore=currentscore
		return result
	lib_paths=getattr(Options.options,'boostlibs','')
	if lib_paths:
		lib_paths=[os.path.normpath(os.path.expandvars(os.path.expanduser(lib_paths)))]
	else:
		lib_paths=Utils.to_list(kw['libpath'])
	v=kw.get('env',self.env)
	(libname,file)=(None,None)
	if kw['static']in[STATIC_NOSTATIC,STATIC_BOTH]:
		st_env_prefix='LIB'
		files=libfiles(lib,v['shlib_PATTERN'],lib_paths)
		(libname,file)=find_library_from_list(lib,files)
	if libname is None and kw['static']in[STATIC_ONLYSTATIC,STATIC_BOTH]:
		st_env_prefix='STATICLIB'
		staticLibPattern=v['staticlib_PATTERN']
		if self.env['CC_NAME']=='msvc':
			staticLibPattern='lib'+staticLibPattern
		files=libfiles(lib,staticLibPattern,lib_paths)
		(libname,file)=find_library_from_list(lib,files)
	if libname is not None:
		v['LIBPATH_BOOST_'+lib.upper()]=[os.path.split(file)[0]]
		if self.env['CC_NAME']=='msvc'and os.path.splitext(file)[1]=='.lib':
			v[st_env_prefix+'_BOOST_'+lib.upper()]=['libboost_'+libname]
		else:
			v[st_env_prefix+'_BOOST_'+lib.upper()]=['boost_'+libname]
		return
	self.fatal('lib boost_'+lib+' not found!')
def check_boost(self,*k,**kw):
	if not self.env['CXX']:
		self.fatal('load a c++ compiler tool first, for example conf.check_tool("g++")')
	self.validate_boost(kw)
	ret=None
	try:
		if not kw.get('found_includes',None):
			self.check_message_1(kw.get('msg_includes','boost headers'))
			ret=self.find_boost_includes(kw)
	except Configure.ConfigurationError,e:
		if'errmsg'in kw:
			self.check_message_2(kw['errmsg'],'YELLOW')
		if'mandatory'in kw:
			if Logs.verbose>1:
				raise
			else:
				self.fatal('the configuration failed (see %r)'%self.log.name)
	else:
		if'okmsg'in kw:
			self.check_message_2(kw.get('okmsg_includes',ret))
	for lib in kw['lib']:
		self.check_message_1('library boost_'+lib)
		try:
			self.find_boost_library(lib,kw)
		except Configure.ConfigurationError,e:
			ret=False
			if'errmsg'in kw:
				self.check_message_2(kw['errmsg'],'YELLOW')
			if'mandatory'in kw:
				if Logs.verbose>1:
					raise
				else:
					self.fatal('the configuration failed (see %r)'%self.log.name)
		else:
			if'okmsg'in kw:
				self.check_message_2(kw['okmsg'])
	return ret

conf(get_boost_version_number)
conf(validate_boost)
conf(find_boost_includes)
conf(find_boost_library)
conf(check_boost)
