PageRenderTime 44ms CodeModel.GetById 1ms RepoModel.GetById 0ms app.codeStats 0ms

/couchjs/scons/scons-local-2.0.1/SCons/Executor.py

http://github.com/cloudant/bigcouch
Python | 633 lines | 569 code | 17 blank | 47 comment | 8 complexity | 8d274e5995e62a3278f3ec38199197ed MD5 | raw file
Possible License(s): Apache-2.0
  1. """SCons.Executor
  2. A module for executing actions with specific lists of target and source
  3. Nodes.
  4. """
  5. #
  6. # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 The SCons Foundation
  7. #
  8. # Permission is hereby granted, free of charge, to any person obtaining
  9. # a copy of this software and associated documentation files (the
  10. # "Software"), to deal in the Software without restriction, including
  11. # without limitation the rights to use, copy, modify, merge, publish,
  12. # distribute, sublicense, and/or sell copies of the Software, and to
  13. # permit persons to whom the Software is furnished to do so, subject to
  14. # the following conditions:
  15. #
  16. # The above copyright notice and this permission notice shall be included
  17. # in all copies or substantial portions of the Software.
  18. #
  19. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
  20. # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  21. # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  22. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  23. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  24. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  25. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  26. __revision__ = "src/engine/SCons/Executor.py 5134 2010/08/16 23:02:40 bdeegan"
  27. import collections
  28. from SCons.Debug import logInstanceCreation
  29. import SCons.Errors
  30. import SCons.Memoize
  31. class Batch(object):
  32. """Remembers exact association between targets
  33. and sources of executor."""
  34. def __init__(self, targets=[], sources=[]):
  35. self.targets = targets
  36. self.sources = sources
  37. class TSList(collections.UserList):
  38. """A class that implements $TARGETS or $SOURCES expansions by wrapping
  39. an executor Method. This class is used in the Executor.lvars()
  40. to delay creation of NodeList objects until they're needed.
  41. Note that we subclass collections.UserList purely so that the
  42. is_Sequence() function will identify an object of this class as
  43. a list during variable expansion. We're not really using any
  44. collections.UserList methods in practice.
  45. """
  46. def __init__(self, func):
  47. self.func = func
  48. def __getattr__(self, attr):
  49. nl = self.func()
  50. return getattr(nl, attr)
  51. def __getitem__(self, i):
  52. nl = self.func()
  53. return nl[i]
  54. def __getslice__(self, i, j):
  55. nl = self.func()
  56. i = max(i, 0); j = max(j, 0)
  57. return nl[i:j]
  58. def __str__(self):
  59. nl = self.func()
  60. return str(nl)
  61. def __repr__(self):
  62. nl = self.func()
  63. return repr(nl)
  64. class TSObject(object):
  65. """A class that implements $TARGET or $SOURCE expansions by wrapping
  66. an Executor method.
  67. """
  68. def __init__(self, func):
  69. self.func = func
  70. def __getattr__(self, attr):
  71. n = self.func()
  72. return getattr(n, attr)
  73. def __str__(self):
  74. n = self.func()
  75. if n:
  76. return str(n)
  77. return ''
  78. def __repr__(self):
  79. n = self.func()
  80. if n:
  81. return repr(n)
  82. return ''
  83. def rfile(node):
  84. """
  85. A function to return the results of a Node's rfile() method,
  86. if it exists, and the Node itself otherwise (if it's a Value
  87. Node, e.g.).
  88. """
  89. try:
  90. rfile = node.rfile
  91. except AttributeError:
  92. return node
  93. else:
  94. return rfile()
  95. class Executor(object):
  96. """A class for controlling instances of executing an action.
  97. This largely exists to hold a single association of an action,
  98. environment, list of environment override dictionaries, targets
  99. and sources for later processing as needed.
  100. """
  101. if SCons.Memoize.use_memoizer:
  102. __metaclass__ = SCons.Memoize.Memoized_Metaclass
  103. memoizer_counters = []
  104. def __init__(self, action, env=None, overridelist=[{}],
  105. targets=[], sources=[], builder_kw={}):
  106. if __debug__: logInstanceCreation(self, 'Executor.Executor')
  107. self.set_action_list(action)
  108. self.pre_actions = []
  109. self.post_actions = []
  110. self.env = env
  111. self.overridelist = overridelist
  112. if targets or sources:
  113. self.batches = [Batch(targets[:], sources[:])]
  114. else:
  115. self.batches = []
  116. self.builder_kw = builder_kw
  117. self._memo = {}
  118. def get_lvars(self):
  119. try:
  120. return self.lvars
  121. except AttributeError:
  122. self.lvars = {
  123. 'CHANGED_SOURCES' : TSList(self._get_changed_sources),
  124. 'CHANGED_TARGETS' : TSList(self._get_changed_targets),
  125. 'SOURCE' : TSObject(self._get_source),
  126. 'SOURCES' : TSList(self._get_sources),
  127. 'TARGET' : TSObject(self._get_target),
  128. 'TARGETS' : TSList(self._get_targets),
  129. 'UNCHANGED_SOURCES' : TSList(self._get_unchanged_sources),
  130. 'UNCHANGED_TARGETS' : TSList(self._get_unchanged_targets),
  131. }
  132. return self.lvars
  133. def _get_changes(self):
  134. cs = []
  135. ct = []
  136. us = []
  137. ut = []
  138. for b in self.batches:
  139. if b.targets[0].is_up_to_date():
  140. us.extend(list(map(rfile, b.sources)))
  141. ut.extend(b.targets)
  142. else:
  143. cs.extend(list(map(rfile, b.sources)))
  144. ct.extend(b.targets)
  145. self._changed_sources_list = SCons.Util.NodeList(cs)
  146. self._changed_targets_list = SCons.Util.NodeList(ct)
  147. self._unchanged_sources_list = SCons.Util.NodeList(us)
  148. self._unchanged_targets_list = SCons.Util.NodeList(ut)
  149. def _get_changed_sources(self, *args, **kw):
  150. try:
  151. return self._changed_sources_list
  152. except AttributeError:
  153. self._get_changes()
  154. return self._changed_sources_list
  155. def _get_changed_targets(self, *args, **kw):
  156. try:
  157. return self._changed_targets_list
  158. except AttributeError:
  159. self._get_changes()
  160. return self._changed_targets_list
  161. def _get_source(self, *args, **kw):
  162. #return SCons.Util.NodeList([rfile(self.batches[0].sources[0]).get_subst_proxy()])
  163. return rfile(self.batches[0].sources[0]).get_subst_proxy()
  164. def _get_sources(self, *args, **kw):
  165. return SCons.Util.NodeList([rfile(n).get_subst_proxy() for n in self.get_all_sources()])
  166. def _get_target(self, *args, **kw):
  167. #return SCons.Util.NodeList([self.batches[0].targets[0].get_subst_proxy()])
  168. return self.batches[0].targets[0].get_subst_proxy()
  169. def _get_targets(self, *args, **kw):
  170. return SCons.Util.NodeList([n.get_subst_proxy() for n in self.get_all_targets()])
  171. def _get_unchanged_sources(self, *args, **kw):
  172. try:
  173. return self._unchanged_sources_list
  174. except AttributeError:
  175. self._get_changes()
  176. return self._unchanged_sources_list
  177. def _get_unchanged_targets(self, *args, **kw):
  178. try:
  179. return self._unchanged_targets_list
  180. except AttributeError:
  181. self._get_changes()
  182. return self._unchanged_targets_list
  183. def get_action_targets(self):
  184. if not self.action_list:
  185. return []
  186. targets_string = self.action_list[0].get_targets(self.env, self)
  187. if targets_string[0] == '$':
  188. targets_string = targets_string[1:]
  189. return self.get_lvars()[targets_string]
  190. def set_action_list(self, action):
  191. import SCons.Util
  192. if not SCons.Util.is_List(action):
  193. if not action:
  194. import SCons.Errors
  195. raise SCons.Errors.UserError("Executor must have an action.")
  196. action = [action]
  197. self.action_list = action
  198. def get_action_list(self):
  199. return self.pre_actions + self.action_list + self.post_actions
  200. def get_all_targets(self):
  201. """Returns all targets for all batches of this Executor."""
  202. result = []
  203. for batch in self.batches:
  204. result.extend(batch.targets)
  205. return result
  206. def get_all_sources(self):
  207. """Returns all sources for all batches of this Executor."""
  208. result = []
  209. for batch in self.batches:
  210. result.extend(batch.sources)
  211. return result
  212. def get_all_children(self):
  213. """Returns all unique children (dependencies) for all batches
  214. of this Executor.
  215. The Taskmaster can recognize when it's already evaluated a
  216. Node, so we don't have to make this list unique for its intended
  217. canonical use case, but we expect there to be a lot of redundancy
  218. (long lists of batched .cc files #including the same .h files
  219. over and over), so removing the duplicates once up front should
  220. save the Taskmaster a lot of work.
  221. """
  222. result = SCons.Util.UniqueList([])
  223. for target in self.get_all_targets():
  224. result.extend(target.children())
  225. return result
  226. def get_all_prerequisites(self):
  227. """Returns all unique (order-only) prerequisites for all batches
  228. of this Executor.
  229. """
  230. result = SCons.Util.UniqueList([])
  231. for target in self.get_all_targets():
  232. result.extend(target.prerequisites)
  233. return result
  234. def get_action_side_effects(self):
  235. """Returns all side effects for all batches of this
  236. Executor used by the underlying Action.
  237. """
  238. result = SCons.Util.UniqueList([])
  239. for target in self.get_action_targets():
  240. result.extend(target.side_effects)
  241. return result
  242. memoizer_counters.append(SCons.Memoize.CountValue('get_build_env'))
  243. def get_build_env(self):
  244. """Fetch or create the appropriate build Environment
  245. for this Executor.
  246. """
  247. try:
  248. return self._memo['get_build_env']
  249. except KeyError:
  250. pass
  251. # Create the build environment instance with appropriate
  252. # overrides. These get evaluated against the current
  253. # environment's construction variables so that users can
  254. # add to existing values by referencing the variable in
  255. # the expansion.
  256. overrides = {}
  257. for odict in self.overridelist:
  258. overrides.update(odict)
  259. import SCons.Defaults
  260. env = self.env or SCons.Defaults.DefaultEnvironment()
  261. build_env = env.Override(overrides)
  262. self._memo['get_build_env'] = build_env
  263. return build_env
  264. def get_build_scanner_path(self, scanner):
  265. """Fetch the scanner path for this executor's targets and sources.
  266. """
  267. env = self.get_build_env()
  268. try:
  269. cwd = self.batches[0].targets[0].cwd
  270. except (IndexError, AttributeError):
  271. cwd = None
  272. return scanner.path(env, cwd,
  273. self.get_all_targets(),
  274. self.get_all_sources())
  275. def get_kw(self, kw={}):
  276. result = self.builder_kw.copy()
  277. result.update(kw)
  278. result['executor'] = self
  279. return result
  280. def do_nothing(self, target, kw):
  281. return 0
  282. def do_execute(self, target, kw):
  283. """Actually execute the action list."""
  284. env = self.get_build_env()
  285. kw = self.get_kw(kw)
  286. status = 0
  287. for act in self.get_action_list():
  288. #args = (self.get_all_targets(), self.get_all_sources(), env)
  289. args = ([], [], env)
  290. status = act(*args, **kw)
  291. if isinstance(status, SCons.Errors.BuildError):
  292. status.executor = self
  293. raise status
  294. elif status:
  295. msg = "Error %s" % status
  296. raise SCons.Errors.BuildError(
  297. errstr=msg,
  298. node=self.batches[0].targets,
  299. executor=self,
  300. action=act)
  301. return status
  302. # use extra indirection because with new-style objects (Python 2.2
  303. # and above) we can't override special methods, and nullify() needs
  304. # to be able to do this.
  305. def __call__(self, target, **kw):
  306. return self.do_execute(target, kw)
  307. def cleanup(self):
  308. self._memo = {}
  309. def add_sources(self, sources):
  310. """Add source files to this Executor's list. This is necessary
  311. for "multi" Builders that can be called repeatedly to build up
  312. a source file list for a given target."""
  313. # TODO(batch): extend to multiple batches
  314. assert (len(self.batches) == 1)
  315. # TODO(batch): remove duplicates?
  316. sources = [x for x in sources if x not in self.batches[0].sources]
  317. self.batches[0].sources.extend(sources)
  318. def get_sources(self):
  319. return self.batches[0].sources
  320. def add_batch(self, targets, sources):
  321. """Add pair of associated target and source to this Executor's list.
  322. This is necessary for "batch" Builders that can be called repeatedly
  323. to build up a list of matching target and source files that will be
  324. used in order to update multiple target files at once from multiple
  325. corresponding source files, for tools like MSVC that support it."""
  326. self.batches.append(Batch(targets, sources))
  327. def prepare(self):
  328. """
  329. Preparatory checks for whether this Executor can go ahead
  330. and (try to) build its targets.
  331. """
  332. for s in self.get_all_sources():
  333. if s.missing():
  334. msg = "Source `%s' not found, needed by target `%s'."
  335. raise SCons.Errors.StopError(msg % (s, self.batches[0].targets[0]))
  336. def add_pre_action(self, action):
  337. self.pre_actions.append(action)
  338. def add_post_action(self, action):
  339. self.post_actions.append(action)
  340. # another extra indirection for new-style objects and nullify...
  341. def my_str(self):
  342. env = self.get_build_env()
  343. return "\n".join([action.genstring(self.get_all_targets(),
  344. self.get_all_sources(),
  345. env)
  346. for action in self.get_action_list()])
  347. def __str__(self):
  348. return self.my_str()
  349. def nullify(self):
  350. self.cleanup()
  351. self.do_execute = self.do_nothing
  352. self.my_str = lambda: ''
  353. memoizer_counters.append(SCons.Memoize.CountValue('get_contents'))
  354. def get_contents(self):
  355. """Fetch the signature contents. This is the main reason this
  356. class exists, so we can compute this once and cache it regardless
  357. of how many target or source Nodes there are.
  358. """
  359. try:
  360. return self._memo['get_contents']
  361. except KeyError:
  362. pass
  363. env = self.get_build_env()
  364. result = "".join([action.get_contents(self.get_all_targets(),
  365. self.get_all_sources(),
  366. env)
  367. for action in self.get_action_list()])
  368. self._memo['get_contents'] = result
  369. return result
  370. def get_timestamp(self):
  371. """Fetch a time stamp for this Executor. We don't have one, of
  372. course (only files do), but this is the interface used by the
  373. timestamp module.
  374. """
  375. return 0
  376. def scan_targets(self, scanner):
  377. # TODO(batch): scan by batches
  378. self.scan(scanner, self.get_all_targets())
  379. def scan_sources(self, scanner):
  380. # TODO(batch): scan by batches
  381. if self.batches[0].sources:
  382. self.scan(scanner, self.get_all_sources())
  383. def scan(self, scanner, node_list):
  384. """Scan a list of this Executor's files (targets or sources) for
  385. implicit dependencies and update all of the targets with them.
  386. This essentially short-circuits an N*M scan of the sources for
  387. each individual target, which is a hell of a lot more efficient.
  388. """
  389. env = self.get_build_env()
  390. # TODO(batch): scan by batches)
  391. deps = []
  392. if scanner:
  393. for node in node_list:
  394. node.disambiguate()
  395. s = scanner.select(node)
  396. if not s:
  397. continue
  398. path = self.get_build_scanner_path(s)
  399. deps.extend(node.get_implicit_deps(env, s, path))
  400. else:
  401. kw = self.get_kw()
  402. for node in node_list:
  403. node.disambiguate()
  404. scanner = node.get_env_scanner(env, kw)
  405. if not scanner:
  406. continue
  407. scanner = scanner.select(node)
  408. if not scanner:
  409. continue
  410. path = self.get_build_scanner_path(scanner)
  411. deps.extend(node.get_implicit_deps(env, scanner, path))
  412. deps.extend(self.get_implicit_deps())
  413. for tgt in self.get_all_targets():
  414. tgt.add_to_implicit(deps)
  415. def _get_unignored_sources_key(self, node, ignore=()):
  416. return (node,) + tuple(ignore)
  417. memoizer_counters.append(SCons.Memoize.CountDict('get_unignored_sources', _get_unignored_sources_key))
  418. def get_unignored_sources(self, node, ignore=()):
  419. key = (node,) + tuple(ignore)
  420. try:
  421. memo_dict = self._memo['get_unignored_sources']
  422. except KeyError:
  423. memo_dict = {}
  424. self._memo['get_unignored_sources'] = memo_dict
  425. else:
  426. try:
  427. return memo_dict[key]
  428. except KeyError:
  429. pass
  430. if node:
  431. # TODO: better way to do this (it's a linear search,
  432. # but it may not be critical path)?
  433. sourcelist = []
  434. for b in self.batches:
  435. if node in b.targets:
  436. sourcelist = b.sources
  437. break
  438. else:
  439. sourcelist = self.get_all_sources()
  440. if ignore:
  441. idict = {}
  442. for i in ignore:
  443. idict[i] = 1
  444. sourcelist = [s for s in sourcelist if s not in idict]
  445. memo_dict[key] = sourcelist
  446. return sourcelist
  447. def get_implicit_deps(self):
  448. """Return the executor's implicit dependencies, i.e. the nodes of
  449. the commands to be executed."""
  450. result = []
  451. build_env = self.get_build_env()
  452. for act in self.get_action_list():
  453. deps = act.get_implicit_deps(self.get_all_targets(),
  454. self.get_all_sources(),
  455. build_env)
  456. result.extend(deps)
  457. return result
  458. _batch_executors = {}
  459. def GetBatchExecutor(key):
  460. return _batch_executors[key]
  461. def AddBatchExecutor(key, executor):
  462. assert key not in _batch_executors
  463. _batch_executors[key] = executor
  464. nullenv = None
  465. def get_NullEnvironment():
  466. """Use singleton pattern for Null Environments."""
  467. global nullenv
  468. import SCons.Util
  469. class NullEnvironment(SCons.Util.Null):
  470. import SCons.CacheDir
  471. _CacheDir_path = None
  472. _CacheDir = SCons.CacheDir.CacheDir(None)
  473. def get_CacheDir(self):
  474. return self._CacheDir
  475. if not nullenv:
  476. nullenv = NullEnvironment()
  477. return nullenv
  478. class Null(object):
  479. """A null Executor, with a null build Environment, that does
  480. nothing when the rest of the methods call it.
  481. This might be able to disapper when we refactor things to
  482. disassociate Builders from Nodes entirely, so we're not
  483. going to worry about unit tests for this--at least for now.
  484. """
  485. def __init__(self, *args, **kw):
  486. if __debug__: logInstanceCreation(self, 'Executor.Null')
  487. self.batches = [Batch(kw['targets'][:], [])]
  488. def get_build_env(self):
  489. return get_NullEnvironment()
  490. def get_build_scanner_path(self):
  491. return None
  492. def cleanup(self):
  493. pass
  494. def prepare(self):
  495. pass
  496. def get_unignored_sources(self, *args, **kw):
  497. return tuple(())
  498. def get_action_targets(self):
  499. return []
  500. def get_action_list(self):
  501. return []
  502. def get_all_targets(self):
  503. return self.batches[0].targets
  504. def get_all_sources(self):
  505. return self.batches[0].targets[0].sources
  506. def get_all_children(self):
  507. return self.batches[0].targets[0].children()
  508. def get_all_prerequisites(self):
  509. return []
  510. def get_action_side_effects(self):
  511. return []
  512. def __call__(self, *args, **kw):
  513. return 0
  514. def get_contents(self):
  515. return ''
  516. def _morph(self):
  517. """Morph this Null executor to a real Executor object."""
  518. batches = self.batches
  519. self.__class__ = Executor
  520. self.__init__([])
  521. self.batches = batches
  522. # The following methods require morphing this Null Executor to a
  523. # real Executor object.
  524. def add_pre_action(self, action):
  525. self._morph()
  526. self.add_pre_action(action)
  527. def add_post_action(self, action):
  528. self._morph()
  529. self.add_post_action(action)
  530. def set_action_list(self, action):
  531. self._morph()
  532. self.set_action_list(action)
  533. # Local Variables:
  534. # tab-width:4
  535. # indent-tabs-mode:nil
  536. # End:
  537. # vim: set expandtab tabstop=4 shiftwidth=4: