jack2 codebase
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

263 lines
9.8KB

  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. # Copyright Garmin International or its subsidiaries, 2012-2013
  4. '''
  5. Off-load dependency scanning from Python code to MSVC compiler
  6. This tool is safe to load in any environment; it will only activate the
  7. MSVC exploits when it finds that a particular taskgen uses MSVC to
  8. compile.
  9. Empirical testing shows about a 10% execution time savings from using
  10. this tool as compared to c_preproc.
  11. The technique of gutting scan() and pushing the dependency calculation
  12. down to post_run() is cribbed from gccdeps.py.
  13. '''
  14. import os
  15. import sys
  16. import tempfile
  17. import threading
  18. from waflib import Context, Errors, Logs, Task, Utils
  19. from waflib.Tools import c_preproc, c, cxx, msvc
  20. from waflib.TaskGen import feature, before_method
  21. lock = threading.Lock()
  22. nodes = {} # Cache the path -> Node lookup
  23. PREPROCESSOR_FLAG = '/showIncludes'
  24. INCLUDE_PATTERN = 'Note: including file:'
  25. # Extensible by outside tools
  26. supported_compilers = ['msvc']
  27. @feature('c', 'cxx')
  28. @before_method('process_source')
  29. def apply_msvcdeps_flags(taskgen):
  30. if taskgen.env.CC_NAME not in supported_compilers:
  31. return
  32. for flag in ('CFLAGS', 'CXXFLAGS'):
  33. if taskgen.env.get_flat(flag).find(PREPROCESSOR_FLAG) < 0:
  34. taskgen.env.append_value(flag, PREPROCESSOR_FLAG)
  35. # Figure out what casing conventions the user's shell used when
  36. # launching Waf
  37. (drive, _) = os.path.splitdrive(taskgen.bld.srcnode.abspath())
  38. taskgen.msvcdeps_drive_lowercase = drive == drive.lower()
  39. def path_to_node(base_node, path, cached_nodes):
  40. # Take the base node and the path and return a node
  41. # Results are cached because searching the node tree is expensive
  42. # The following code is executed by threads, it is not safe, so a lock is needed...
  43. if getattr(path, '__hash__'):
  44. node_lookup_key = (base_node, path)
  45. else:
  46. # Not hashable, assume it is a list and join into a string
  47. node_lookup_key = (base_node, os.path.sep.join(path))
  48. try:
  49. lock.acquire()
  50. node = cached_nodes[node_lookup_key]
  51. except KeyError:
  52. node = base_node.find_resource(path)
  53. cached_nodes[node_lookup_key] = node
  54. finally:
  55. lock.release()
  56. return node
  57. '''
  58. Register a task subclass that has hooks for running our custom
  59. dependency calculations rather than the C/C++ stock c_preproc
  60. method.
  61. '''
  62. def wrap_compiled_task(classname):
  63. derived_class = type(classname, (Task.classes[classname],), {})
  64. def post_run(self):
  65. if self.env.CC_NAME not in supported_compilers:
  66. return super(derived_class, self).post_run()
  67. if getattr(self, 'cached', None):
  68. return Task.Task.post_run(self)
  69. bld = self.generator.bld
  70. unresolved_names = []
  71. resolved_nodes = []
  72. lowercase = self.generator.msvcdeps_drive_lowercase
  73. correct_case_path = bld.path.abspath()
  74. correct_case_path_len = len(correct_case_path)
  75. correct_case_path_norm = os.path.normcase(correct_case_path)
  76. # Dynamically bind to the cache
  77. try:
  78. cached_nodes = bld.cached_nodes
  79. except AttributeError:
  80. cached_nodes = bld.cached_nodes = {}
  81. for path in self.msvcdeps_paths:
  82. node = None
  83. if os.path.isabs(path):
  84. # Force drive letter to match conventions of main source tree
  85. drive, tail = os.path.splitdrive(path)
  86. if os.path.normcase(path[:correct_case_path_len]) == correct_case_path_norm:
  87. # Path is in the sandbox, force it to be correct. MSVC sometimes returns a lowercase path.
  88. path = correct_case_path + path[correct_case_path_len:]
  89. else:
  90. # Check the drive letter
  91. if lowercase and (drive != drive.lower()):
  92. path = drive.lower() + tail
  93. elif (not lowercase) and (drive != drive.upper()):
  94. path = drive.upper() + tail
  95. node = path_to_node(bld.root, path, cached_nodes)
  96. else:
  97. base_node = bld.bldnode
  98. # when calling find_resource, make sure the path does not begin by '..'
  99. path = [k for k in Utils.split_path(path) if k and k != '.']
  100. while path[0] == '..':
  101. path = path[1:]
  102. base_node = base_node.parent
  103. node = path_to_node(base_node, path, cached_nodes)
  104. if not node:
  105. raise ValueError('could not find %r for %r' % (path, self))
  106. else:
  107. if not c_preproc.go_absolute:
  108. if not (node.is_child_of(bld.srcnode) or node.is_child_of(bld.bldnode)):
  109. # System library
  110. Logs.debug('msvcdeps: Ignoring system include %r' % node)
  111. continue
  112. if id(node) == id(self.inputs[0]):
  113. # Self-dependency
  114. continue
  115. resolved_nodes.append(node)
  116. bld.node_deps[self.uid()] = resolved_nodes
  117. bld.raw_deps[self.uid()] = unresolved_names
  118. try:
  119. del self.cache_sig
  120. except:
  121. pass
  122. Task.Task.post_run(self)
  123. def scan(self):
  124. if self.env.CC_NAME not in supported_compilers:
  125. return super(derived_class, self).scan()
  126. resolved_nodes = self.generator.bld.node_deps.get(self.uid(), [])
  127. unresolved_names = []
  128. return (resolved_nodes, unresolved_names)
  129. def sig_implicit_deps(self):
  130. if self.env.CC_NAME not in supported_compilers:
  131. return super(derived_class, self).sig_implicit_deps()
  132. try:
  133. return Task.Task.sig_implicit_deps(self)
  134. except Errors.WafError:
  135. return Utils.SIG_NIL
  136. def exec_response_command(self, cmd, **kw):
  137. # exec_response_command() is only called from inside msvc.py anyway
  138. assert self.env.CC_NAME in supported_compilers
  139. # Only bother adding '/showIncludes' to compile tasks
  140. if isinstance(self, (c.c, cxx.cxx)):
  141. try:
  142. # The Visual Studio IDE adds an environment variable that causes
  143. # the MS compiler to send its textual output directly to the
  144. # debugging window rather than normal stdout/stderr.
  145. #
  146. # This is unrecoverably bad for this tool because it will cause
  147. # all the dependency scanning to see an empty stdout stream and
  148. # assume that the file being compiled uses no headers.
  149. #
  150. # See http://blogs.msdn.com/b/freik/archive/2006/04/05/569025.aspx
  151. #
  152. # Attempting to repair the situation by deleting the offending
  153. # envvar at this point in tool execution will not be good enough--
  154. # its presence poisons the 'waf configure' step earlier. We just
  155. # want to put a sanity check here in order to help developers
  156. # quickly diagnose the issue if an otherwise-good Waf tree
  157. # is then executed inside the MSVS IDE.
  158. assert 'VS_UNICODE_OUTPUT' not in kw['env']
  159. tmp = None
  160. # This block duplicated from Waflib's msvc.py
  161. if sys.platform.startswith('win') and isinstance(cmd, list) and len(' '.join(cmd)) >= 8192:
  162. program = cmd[0]
  163. cmd = [self.quote_response_command(x) for x in cmd]
  164. (fd, tmp) = tempfile.mkstemp()
  165. os.write(fd, '\r\n'.join(i.replace('\\', '\\\\') for i in cmd[1:]).encode())
  166. os.close(fd)
  167. cmd = [program, '@' + tmp]
  168. # ... end duplication
  169. self.msvcdeps_paths = []
  170. kw['env'] = kw.get('env', os.environ.copy())
  171. kw['cwd'] = kw.get('cwd', os.getcwd())
  172. kw['quiet'] = Context.STDOUT
  173. kw['output'] = Context.STDOUT
  174. out = []
  175. try:
  176. raw_out = self.generator.bld.cmd_and_log(cmd, **kw)
  177. ret = 0
  178. except Errors.WafError as e:
  179. raw_out = e.stdout
  180. ret = e.returncode
  181. for line in raw_out.splitlines():
  182. if line.startswith(INCLUDE_PATTERN):
  183. inc_path = line[len(INCLUDE_PATTERN):].strip()
  184. Logs.debug('msvcdeps: Regex matched %s' % inc_path)
  185. self.msvcdeps_paths.append(inc_path)
  186. else:
  187. out.append(line)
  188. # Pipe through the remaining stdout content (not related to /showIncludes)
  189. if self.generator.bld.logger:
  190. self.generator.bld.logger.debug('out: %s' % os.linesep.join(out))
  191. else:
  192. sys.stdout.write(os.linesep.join(out) + os.linesep)
  193. finally:
  194. if tmp:
  195. try:
  196. os.remove(tmp)
  197. except OSError:
  198. pass
  199. return ret
  200. else:
  201. # Use base class's version of this method for linker tasks
  202. return super(derived_class, self).exec_response_command(cmd, **kw)
  203. def can_retrieve_cache(self):
  204. # msvcdeps and netcaching are incompatible, so disable the cache
  205. if self.env.CC_NAME not in supported_compilers:
  206. return super(derived_class, self).can_retrieve_cache()
  207. self.nocache = True # Disable sending the file to the cache
  208. return False
  209. derived_class.post_run = post_run
  210. derived_class.scan = scan
  211. derived_class.sig_implicit_deps = sig_implicit_deps
  212. derived_class.exec_response_command = exec_response_command
  213. derived_class.can_retrieve_cache = can_retrieve_cache
  214. for k in ('c', 'cxx'):
  215. wrap_compiled_task(k)