#! /usr/bin/env python
# encoding: utf-8
# WARNING! Do not edit! http://waf.googlecode.com/git/docs/wafbook/single.html#_obtaining_the_waf_file

'''
Off-load dependency scanning from Python code to MSVC compiler

This tool is safe to load in any environment; it will only activate the
MSVC exploits when it finds that a particular taskgen uses MSVC to
compile.

Empirical testing shows about a 10% execution time savings from using
this tool as compared to c_preproc.

The technique of gutting scan() and pushing the dependency calculation
down to post_run() is cribbed from gccdeps.py.
'''
import os
import sys
import tempfile
import threading
from waflib import Context,Errors,Logs,Task,Utils
from waflib.Tools import c_preproc,c,cxx,msvc
from waflib.TaskGen import feature,before_method
lock=threading.Lock()
nodes={}
PREPROCESSOR_FLAG='/showIncludes'
INCLUDE_PATTERN='Note: including file:'
supported_compilers=['msvc']
@feature('c','cxx')
@before_method('process_source')
def apply_msvcdeps_flags(taskgen):
	if taskgen.env.CC_NAME not in supported_compilers:
		return
	for flag in('CFLAGS','CXXFLAGS'):
		if taskgen.env.get_flat(flag).find(PREPROCESSOR_FLAG)<0:
			taskgen.env.append_value(flag,PREPROCESSOR_FLAG)
	(drive,_)=os.path.splitdrive(taskgen.bld.srcnode.abspath())
	taskgen.msvcdeps_drive_lowercase=drive==drive.lower()
def path_to_node(base_node,path,cached_nodes):
	if getattr(path,'__hash__'):
		node_lookup_key=(base_node,path)
	else:
		node_lookup_key=(base_node,os.path.sep.join(path))
	try:
		lock.acquire()
		node=cached_nodes[node_lookup_key]
	except KeyError:
		node=base_node.find_resource(path)
		cached_nodes[node_lookup_key]=node
	finally:
		lock.release()
	return node
'''
Register a task subclass that has hooks for running our custom
dependency calculations rather than the C/C++ stock c_preproc
method.
'''
def wrap_compiled_task(classname):
	derived_class=type(classname,(Task.classes[classname],),{})
	def post_run(self):
		if self.env.CC_NAME not in supported_compilers:
			return super(derived_class,self).post_run()
		if getattr(self,'cached',None):
			return Task.Task.post_run(self)
		bld=self.generator.bld
		unresolved_names=[]
		resolved_nodes=[]
		lowercase=self.generator.msvcdeps_drive_lowercase
		correct_case_path=bld.path.abspath()
		correct_case_path_len=len(correct_case_path)
		correct_case_path_norm=os.path.normcase(correct_case_path)
		try:
			cached_nodes=bld.cached_nodes
		except AttributeError:
			cached_nodes=bld.cached_nodes={}
		for path in self.msvcdeps_paths:
			node=None
			if os.path.isabs(path):
				drive,tail=os.path.splitdrive(path)
				if os.path.normcase(path[:correct_case_path_len])==correct_case_path_norm:
					path=correct_case_path+path[correct_case_path_len:]
				else:
					if lowercase and(drive!=drive.lower()):
						path=drive.lower()+tail
					elif(not lowercase)and(drive!=drive.upper()):
						path=drive.upper()+tail
				node=path_to_node(bld.root,path,cached_nodes)
			else:
				base_node=bld.bldnode
				path=[k for k in Utils.split_path(path)if k and k!='.']
				while path[0]=='..':
					path=path[1:]
					base_node=base_node.parent
				node=path_to_node(base_node,path,cached_nodes)
			if not node:
				raise ValueError('could not find %r for %r'%(path,self))
			else:
				if not c_preproc.go_absolute:
					if not(node.is_child_of(bld.srcnode)or node.is_child_of(bld.bldnode)):
						Logs.debug('msvcdeps: Ignoring system include %r'%node)
						continue
				if id(node)==id(self.inputs[0]):
					continue
				resolved_nodes.append(node)
		bld.node_deps[self.uid()]=resolved_nodes
		bld.raw_deps[self.uid()]=unresolved_names
		try:
			del self.cache_sig
		except:
			pass
		Task.Task.post_run(self)
	def scan(self):
		if self.env.CC_NAME not in supported_compilers:
			return super(derived_class,self).scan()
		resolved_nodes=self.generator.bld.node_deps.get(self.uid(),[])
		unresolved_names=[]
		return(resolved_nodes,unresolved_names)
	def sig_implicit_deps(self):
		if self.env.CC_NAME not in supported_compilers:
			return super(derived_class,self).sig_implicit_deps()
		try:
			return Task.Task.sig_implicit_deps(self)
		except Errors.WafError:
			return Utils.SIG_NIL
	def exec_response_command(self,cmd,**kw):
		assert self.env.CC_NAME in supported_compilers
		if isinstance(self,(c.c,cxx.cxx)):
			try:
				assert'VS_UNICODE_OUTPUT'not in kw['env']
				tmp=None
				if sys.platform.startswith('win')and isinstance(cmd,list)and len(' '.join(cmd))>=8192:
					program=cmd[0]
					cmd=[self.quote_response_command(x)for x in cmd]
					(fd,tmp)=tempfile.mkstemp()
					os.write(fd,'\r\n'.join(i.replace('\\','\\\\')for i in cmd[1:]))
					os.close(fd)
					cmd=[program,'@'+tmp]
				self.msvcdeps_paths=[]
				kw['env']=kw.get('env',os.environ.copy())
				kw['cwd']=kw.get('cwd',os.getcwd())
				kw['quiet']=Context.STDOUT
				kw['output']=Context.STDOUT
				out=[]
				try:
					raw_out=self.generator.bld.cmd_and_log(cmd,**kw)
					ret=0
				except Errors.WafError ,e:
					raw_out=e.stdout
					ret=e.returncode
				for line in raw_out.splitlines():
					if line.startswith(INCLUDE_PATTERN):
						inc_path=line[len(INCLUDE_PATTERN):].strip()
						Logs.debug('msvcdeps: Regex matched %s'%inc_path)
						self.msvcdeps_paths.append(inc_path)
					else:
						out.append(line)
				if self.generator.bld.logger:
					self.generator.bld.logger.debug('out: %s'%os.linesep.join(out))
				else:
					sys.stdout.write(os.linesep.join(out)+os.linesep)
			finally:
				if tmp:
					try:
						os.remove(tmp)
					except OSError:
						pass
			return ret
		else:
			return super(derived_class,self).exec_response_command(cmd,**kw)
	def can_retrieve_cache(self):
		if self.env.CC_NAME not in supported_compilers:
			return super(derived_class,self).can_retrieve_cache()
		self.nocache=True
		return False
	derived_class.post_run=post_run
	derived_class.scan=scan
	derived_class.sig_implicit_deps=sig_implicit_deps
	derived_class.exec_response_command=exec_response_command
	derived_class.can_retrieve_cache=can_retrieve_cache
for k in('c','cxx'):
	wrap_compiled_task(k)
