Blame third_party/waf/waflib/extras/fast_partial.py

rpm-build 95f51c
#! /usr/bin/env python
rpm-build 95f51c
# encoding: utf-8
rpm-build 95f51c
# Thomas Nagy, 2017-2018 (ita)
rpm-build 95f51c
rpm-build 95f51c
"""
rpm-build 95f51c
A system for fast partial rebuilds
rpm-build 95f51c
rpm-build 95f51c
Creating a large amount of task objects up front can take some time.
rpm-build 95f51c
By making a few assumptions, it is possible to avoid posting creating
rpm-build 95f51c
task objects for targets that are already up-to-date.
rpm-build 95f51c
rpm-build 95f51c
On a silly benchmark the gain observed for 1M tasks can be 5m->10s
rpm-build 95f51c
for a single file change.
rpm-build 95f51c
rpm-build 95f51c
Usage::
rpm-build 95f51c
rpm-build 95f51c
	def options(opt):
rpm-build 95f51c
		opt.load('fast_partial')
rpm-build 95f51c
rpm-build 95f51c
Assumptions:
rpm-build 95f51c
* Start with a clean build (run "waf distclean" after enabling)
rpm-build 95f51c
* Mostly for C/C++/Fortran targets with link tasks (object-only targets are not handled)
rpm-build 95f51c
  try it in the folder generated by utils/genbench.py
rpm-build 95f51c
* For full project builds: no --targets and no pruning from subfolders
rpm-build 95f51c
* The installation phase is ignored
rpm-build 95f51c
* `use=` dependencies are specified up front even across build groups
rpm-build 95f51c
* Task generator source files are not obtained from globs
rpm-build 95f51c
rpm-build 95f51c
Implementation details:
rpm-build 95f51c
* The first layer obtains file timestamps to recalculate file hashes only
rpm-build 95f51c
  when necessary (similar to md5_tstamp); the timestamps are then stored
rpm-build 95f51c
  in a dedicated pickle file
rpm-build 95f51c
* A second layer associates each task generator to a file set to help
rpm-build 95f51c
  detecting changes. Task generators are to create their tasks only when
rpm-build 95f51c
  the related files have been modified. A specific db file is created
rpm-build 95f51c
  to store such data (5m -> 1m10)
rpm-build 95f51c
* A third layer binds build context proxies onto task generators, replacing
rpm-build 95f51c
  the default context. While loading data for the full build uses more memory
rpm-build 95f51c
  (4GB -> 9GB), partial builds are then much faster (1m10 -> 13s)
rpm-build 95f51c
* A fourth layer enables a 2-level cache on file signatures to
rpm-build 95f51c
  reduce the size of the main pickle file (13s -> 10s)
rpm-build 95f51c
"""
rpm-build 95f51c
rpm-build 95f51c
import os
rpm-build 95f51c
from waflib import Build, Context, Errors, Logs, Task, TaskGen, Utils
rpm-build 95f51c
from waflib.TaskGen import feature, after_method, taskgen_method
rpm-build 95f51c
import waflib.Node
rpm-build 95f51c
rpm-build 95f51c
DONE = 0
rpm-build 95f51c
DIRTY = 1
rpm-build 95f51c
NEEDED = 2
rpm-build 95f51c
rpm-build 95f51c
SKIPPABLE = ['cshlib', 'cxxshlib', 'cstlib', 'cxxstlib', 'cprogram', 'cxxprogram']
rpm-build 95f51c
rpm-build 95f51c
TSTAMP_DB = '.wafpickle_tstamp_db_file'
rpm-build 95f51c
rpm-build 95f51c
SAVED_ATTRS = 'root node_sigs task_sigs imp_sigs raw_deps node_deps'.split()
rpm-build 95f51c
rpm-build 95f51c
class bld_proxy(object):
rpm-build 95f51c
	def __init__(self, bld):
rpm-build 95f51c
		object.__setattr__(self, 'bld', bld)
rpm-build 95f51c
rpm-build 95f51c
		object.__setattr__(self, 'node_class', type('Nod3', (waflib.Node.Node,), {}))
rpm-build 95f51c
		self.node_class.__module__ = 'waflib.Node'
rpm-build 95f51c
		self.node_class.ctx = self
rpm-build 95f51c
rpm-build 95f51c
		object.__setattr__(self, 'root', self.node_class('', None))
rpm-build 95f51c
		for x in SAVED_ATTRS:
rpm-build 95f51c
			if x != 'root':
rpm-build 95f51c
				object.__setattr__(self, x, {})
rpm-build 95f51c
rpm-build 95f51c
		self.fix_nodes()
rpm-build 95f51c
rpm-build 95f51c
	def __setattr__(self, name, value):
rpm-build 95f51c
		bld = object.__getattribute__(self, 'bld')
rpm-build 95f51c
		setattr(bld, name, value)
rpm-build 95f51c
rpm-build 95f51c
	def __delattr__(self, name):
rpm-build 95f51c
		bld = object.__getattribute__(self, 'bld')
rpm-build 95f51c
		delattr(bld, name)
rpm-build 95f51c
rpm-build 95f51c
	def __getattribute__(self, name):
rpm-build 95f51c
		try:
rpm-build 95f51c
			return object.__getattribute__(self, name)
rpm-build 95f51c
		except AttributeError:
rpm-build 95f51c
			bld = object.__getattribute__(self, 'bld')
rpm-build 95f51c
			return getattr(bld, name)
rpm-build 95f51c
rpm-build 95f51c
	def __call__(self, *k, **kw):
rpm-build 95f51c
		return self.bld(*k, **kw)
rpm-build 95f51c
rpm-build 95f51c
	def fix_nodes(self):
rpm-build 95f51c
		for x in ('srcnode', 'path', 'bldnode'):
rpm-build 95f51c
			node = self.root.find_dir(getattr(self.bld, x).abspath())
rpm-build 95f51c
			object.__setattr__(self, x, node)
rpm-build 95f51c
rpm-build 95f51c
	def set_key(self, store_key):
rpm-build 95f51c
		object.__setattr__(self, 'store_key', store_key)
rpm-build 95f51c
rpm-build 95f51c
	def fix_tg_path(self, *tgs):
rpm-build 95f51c
		# changing Node objects on task generators is possible
rpm-build 95f51c
		# yet, all Node objects must belong to the same parent
rpm-build 95f51c
		for tg in tgs:
rpm-build 95f51c
			tg.path = self.root.make_node(tg.path.abspath())
rpm-build 95f51c
rpm-build 95f51c
	def restore(self):
rpm-build 95f51c
		dbfn = os.path.join(self.variant_dir, Context.DBFILE + self.store_key)
rpm-build 95f51c
		Logs.debug('rev_use: reading %s', dbfn)
rpm-build 95f51c
		try:
rpm-build 95f51c
			data = Utils.readf(dbfn, 'rb')
rpm-build 95f51c
		except (EnvironmentError, EOFError):
rpm-build 95f51c
			# handle missing file/empty file
rpm-build 95f51c
			Logs.debug('rev_use: Could not load the build cache %s (missing)', dbfn)
rpm-build 95f51c
		else:
rpm-build 95f51c
			try:
rpm-build 95f51c
				waflib.Node.pickle_lock.acquire()
rpm-build 95f51c
				waflib.Node.Nod3 = self.node_class
rpm-build 95f51c
				try:
rpm-build 95f51c
					data = Build.cPickle.loads(data)
rpm-build 95f51c
				except Exception as e:
rpm-build 95f51c
					Logs.debug('rev_use: Could not pickle the build cache %s: %r', dbfn, e)
rpm-build 95f51c
				else:
rpm-build 95f51c
					for x in SAVED_ATTRS:
rpm-build 95f51c
						object.__setattr__(self, x, data.get(x, {}))
rpm-build 95f51c
			finally:
rpm-build 95f51c
				waflib.Node.pickle_lock.release()
rpm-build 95f51c
		self.fix_nodes()
rpm-build 95f51c
rpm-build 95f51c
	def store(self):
rpm-build 95f51c
		data = {}
rpm-build 95f51c
		for x in Build.SAVED_ATTRS:
rpm-build 95f51c
			data[x] = getattr(self, x)
rpm-build 95f51c
		db = os.path.join(self.variant_dir, Context.DBFILE + self.store_key)
rpm-build 95f51c
rpm-build 95f51c
		with waflib.Node.pickle_lock:
rpm-build 95f51c
			waflib.Node.Nod3 = self.node_class
rpm-build 95f51c
			try:
rpm-build 95f51c
				x = Build.cPickle.dumps(data, Build.PROTOCOL)
rpm-build 95f51c
			except Build.cPickle.PicklingError:
rpm-build 95f51c
				root = data['root']
rpm-build 95f51c
				for node_deps in data['node_deps'].values():
rpm-build 95f51c
					for idx, node in enumerate(node_deps):
rpm-build 95f51c
						# there may be more cross-context Node objects to fix,
rpm-build 95f51c
						# but this should be the main source
rpm-build 95f51c
						node_deps[idx] = root.find_node(node.abspath())
rpm-build 95f51c
				x = Build.cPickle.dumps(data, Build.PROTOCOL)
rpm-build 95f51c
rpm-build 95f51c
		Logs.debug('rev_use: storing %s', db)
rpm-build 95f51c
		Utils.writef(db + '.tmp', x, m='wb')
rpm-build 95f51c
		try:
rpm-build 95f51c
			st = os.stat(db)
rpm-build 95f51c
			os.remove(db)
rpm-build 95f51c
			if not Utils.is_win32:
rpm-build 95f51c
				os.chown(db + '.tmp', st.st_uid, st.st_gid)
rpm-build 95f51c
		except (AttributeError, OSError):
rpm-build 95f51c
			pass
rpm-build 95f51c
		os.rename(db + '.tmp', db)
rpm-build 95f51c
rpm-build 95f51c
class bld(Build.BuildContext):
rpm-build 95f51c
	def __init__(self, **kw):
rpm-build 95f51c
		super(bld, self).__init__(**kw)
rpm-build 95f51c
		self.hashes_md5_tstamp = {}
rpm-build 95f51c
rpm-build 95f51c
	def __call__(self, *k, **kw):
rpm-build 95f51c
		# this is one way of doing it, one could use a task generator method too
rpm-build 95f51c
		bld = kw['bld'] = bld_proxy(self)
rpm-build 95f51c
		ret = TaskGen.task_gen(*k, **kw)
rpm-build 95f51c
		self.task_gen_cache_names = {}
rpm-build 95f51c
		self.add_to_group(ret, group=kw.get('group'))
rpm-build 95f51c
		ret.bld = bld
rpm-build 95f51c
		bld.set_key(ret.path.abspath().replace(os.sep, '') + str(ret.idx))
rpm-build 95f51c
		return ret
rpm-build 95f51c
rpm-build 95f51c
	def is_dirty(self):
rpm-build 95f51c
		return True
rpm-build 95f51c
rpm-build 95f51c
	def store_tstamps(self):
rpm-build 95f51c
		# Called after a build is finished
rpm-build 95f51c
		# For each task generator, record all files involved in task objects
rpm-build 95f51c
		# optimization: done only if there was something built
rpm-build 95f51c
		do_store = False
rpm-build 95f51c
		try:
rpm-build 95f51c
			f_deps = self.f_deps
rpm-build 95f51c
		except AttributeError:
rpm-build 95f51c
			f_deps = self.f_deps = {}
rpm-build 95f51c
			self.f_tstamps = {}
rpm-build 95f51c
rpm-build 95f51c
		allfiles = set()
rpm-build 95f51c
		for g in self.groups:
rpm-build 95f51c
			for tg in g:
rpm-build 95f51c
				try:
rpm-build 95f51c
					staleness = tg.staleness
rpm-build 95f51c
				except AttributeError:
rpm-build 95f51c
					staleness = DIRTY
rpm-build 95f51c
rpm-build 95f51c
				if staleness != DIRTY:
rpm-build 95f51c
					# DONE case: there was nothing built
rpm-build 95f51c
					# NEEDED case: the tg was brought in because of 'use' propagation
rpm-build 95f51c
					# but nothing really changed for them, there may be incomplete
rpm-build 95f51c
					# tasks (object files) and in this case it is best to let the next build
rpm-build 95f51c
					# figure out if an input/output file changed
rpm-build 95f51c
					continue
rpm-build 95f51c
rpm-build 95f51c
				do_cache = False
rpm-build 95f51c
				for tsk in tg.tasks:
rpm-build 95f51c
					if tsk.hasrun == Task.SUCCESS:
rpm-build 95f51c
						do_cache = True
rpm-build 95f51c
						pass
rpm-build 95f51c
					elif tsk.hasrun == Task.SKIPPED:
rpm-build 95f51c
						pass
rpm-build 95f51c
					else:
rpm-build 95f51c
						# one failed task, clear the cache for this tg
rpm-build 95f51c
						try:
rpm-build 95f51c
							del f_deps[(tg.path.abspath(), tg.idx)]
rpm-build 95f51c
						except KeyError:
rpm-build 95f51c
							pass
rpm-build 95f51c
						else:
rpm-build 95f51c
							# just store the new state because there is a change
rpm-build 95f51c
							do_store = True
rpm-build 95f51c
rpm-build 95f51c
						# skip the rest because there is no valid cache possible
rpm-build 95f51c
						break
rpm-build 95f51c
				else:
rpm-build 95f51c
					if not do_cache:
rpm-build 95f51c
						# all skipped, but is there anything in cache?
rpm-build 95f51c
						try:
rpm-build 95f51c
							f_deps[(tg.path.abspath(), tg.idx)]
rpm-build 95f51c
						except KeyError:
rpm-build 95f51c
							# probably cleared because a wscript file changed
rpm-build 95f51c
							# store it
rpm-build 95f51c
							do_cache = True
rpm-build 95f51c
rpm-build 95f51c
					if do_cache:
rpm-build 95f51c
rpm-build 95f51c
						# there was a rebuild, store the data structure too
rpm-build 95f51c
						tg.bld.store()
rpm-build 95f51c
rpm-build 95f51c
						# all tasks skipped but no cache
rpm-build 95f51c
						# or a successful task build
rpm-build 95f51c
						do_store = True
rpm-build 95f51c
						st = set()
rpm-build 95f51c
						for tsk in tg.tasks:
rpm-build 95f51c
							st.update(tsk.inputs)
rpm-build 95f51c
							st.update(self.node_deps.get(tsk.uid(), []))
rpm-build 95f51c
rpm-build 95f51c
						# TODO do last/when loading the tgs?
rpm-build 95f51c
						lst = []
rpm-build 95f51c
						for k in ('wscript', 'wscript_build'):
rpm-build 95f51c
							n = tg.path.find_node(k)
rpm-build 95f51c
							if n:
rpm-build 95f51c
								n.get_bld_sig()
rpm-build 95f51c
								lst.append(n.abspath())
rpm-build 95f51c
rpm-build 95f51c
						lst.extend(sorted(x.abspath() for x in st))
rpm-build 95f51c
						allfiles.update(lst)
rpm-build 95f51c
						f_deps[(tg.path.abspath(), tg.idx)] = lst
rpm-build 95f51c
rpm-build 95f51c
		for x in allfiles:
rpm-build 95f51c
			# f_tstamps has everything, while md5_tstamp can be relatively empty on partial builds
rpm-build 95f51c
			self.f_tstamps[x] = self.hashes_md5_tstamp[x][0]
rpm-build 95f51c
rpm-build 95f51c
		if do_store:
rpm-build 95f51c
			dbfn = os.path.join(self.variant_dir, TSTAMP_DB)
rpm-build 95f51c
			Logs.debug('rev_use: storing %s', dbfn)
rpm-build 95f51c
			dbfn_tmp = dbfn + '.tmp'
rpm-build 95f51c
			x = Build.cPickle.dumps([self.f_tstamps, f_deps], Build.PROTOCOL)
rpm-build 95f51c
			Utils.writef(dbfn_tmp, x, m='wb')
rpm-build 95f51c
			os.rename(dbfn_tmp, dbfn)
rpm-build 95f51c
			Logs.debug('rev_use: stored %s', dbfn)
rpm-build 95f51c
rpm-build 95f51c
	def store(self):
rpm-build 95f51c
		self.store_tstamps()
rpm-build 95f51c
		if self.producer.dirty:
rpm-build 95f51c
			Build.BuildContext.store(self)
rpm-build 95f51c
rpm-build 95f51c
	def compute_needed_tgs(self):
rpm-build 95f51c
		# assume the 'use' keys are not modified during the build phase
rpm-build 95f51c
rpm-build 95f51c
		dbfn = os.path.join(self.variant_dir, TSTAMP_DB)
rpm-build 95f51c
		Logs.debug('rev_use: Loading %s', dbfn)
rpm-build 95f51c
		try:
rpm-build 95f51c
			data = Utils.readf(dbfn, 'rb')
rpm-build 95f51c
		except (EnvironmentError, EOFError):
rpm-build 95f51c
			Logs.debug('rev_use: Could not load the build cache %s (missing)', dbfn)
rpm-build 95f51c
			self.f_deps = {}
rpm-build 95f51c
			self.f_tstamps = {}
rpm-build 95f51c
		else:
rpm-build 95f51c
			try:
rpm-build 95f51c
				self.f_tstamps, self.f_deps = Build.cPickle.loads(data)
rpm-build 95f51c
			except Exception as e:
rpm-build 95f51c
				Logs.debug('rev_use: Could not pickle the build cache %s: %r', dbfn, e)
rpm-build 95f51c
				self.f_deps = {}
rpm-build 95f51c
				self.f_tstamps = {}
rpm-build 95f51c
			else:
rpm-build 95f51c
				Logs.debug('rev_use: Loaded %s', dbfn)
rpm-build 95f51c
rpm-build 95f51c
rpm-build 95f51c
		# 1. obtain task generators that contain rebuilds
rpm-build 95f51c
		# 2. obtain the 'use' graph and its dual
rpm-build 95f51c
		stales = set()
rpm-build 95f51c
		reverse_use_map = Utils.defaultdict(list)
rpm-build 95f51c
		use_map = Utils.defaultdict(list)
rpm-build 95f51c
rpm-build 95f51c
		for g in self.groups:
rpm-build 95f51c
			for tg in g:
rpm-build 95f51c
				if tg.is_stale():
rpm-build 95f51c
					stales.add(tg)
rpm-build 95f51c
rpm-build 95f51c
				try:
rpm-build 95f51c
					lst = tg.use = Utils.to_list(tg.use)
rpm-build 95f51c
				except AttributeError:
rpm-build 95f51c
					pass
rpm-build 95f51c
				else:
rpm-build 95f51c
					for x in lst:
rpm-build 95f51c
						try:
rpm-build 95f51c
							xtg = self.get_tgen_by_name(x)
rpm-build 95f51c
						except Errors.WafError:
rpm-build 95f51c
							pass
rpm-build 95f51c
						else:
rpm-build 95f51c
							use_map[tg].append(xtg)
rpm-build 95f51c
							reverse_use_map[xtg].append(tg)
rpm-build 95f51c
rpm-build 95f51c
		Logs.debug('rev_use: found %r stale tgs', len(stales))
rpm-build 95f51c
rpm-build 95f51c
		# 3. dfs to post downstream tg as stale
rpm-build 95f51c
		visited = set()
rpm-build 95f51c
		def mark_down(tg):
rpm-build 95f51c
			if tg in visited:
rpm-build 95f51c
				return
rpm-build 95f51c
			visited.add(tg)
rpm-build 95f51c
			Logs.debug('rev_use: marking down %r as stale', tg.name)
rpm-build 95f51c
			tg.staleness = DIRTY
rpm-build 95f51c
			for x in reverse_use_map[tg]:
rpm-build 95f51c
				mark_down(x)
rpm-build 95f51c
		for tg in stales:
rpm-build 95f51c
			mark_down(tg)
rpm-build 95f51c
rpm-build 95f51c
		# 4. dfs to find ancestors tg to mark as needed
rpm-build 95f51c
		self.needed_tgs = needed_tgs = set()
rpm-build 95f51c
		def mark_needed(tg):
rpm-build 95f51c
			if tg in needed_tgs:
rpm-build 95f51c
				return
rpm-build 95f51c
			needed_tgs.add(tg)
rpm-build 95f51c
			if tg.staleness == DONE:
rpm-build 95f51c
				Logs.debug('rev_use: marking up %r as needed', tg.name)
rpm-build 95f51c
				tg.staleness = NEEDED
rpm-build 95f51c
			for x in use_map[tg]:
rpm-build 95f51c
				mark_needed(x)
rpm-build 95f51c
		for xx in visited:
rpm-build 95f51c
			mark_needed(xx)
rpm-build 95f51c
rpm-build 95f51c
		# so we have the whole tg trees to post in the set "needed"
rpm-build 95f51c
		# load their build trees
rpm-build 95f51c
		for tg in needed_tgs:
rpm-build 95f51c
			tg.bld.restore()
rpm-build 95f51c
			tg.bld.fix_tg_path(tg)
rpm-build 95f51c
rpm-build 95f51c
		# the stale ones should be fully build, while the needed ones
rpm-build 95f51c
		# may skip a few tasks, see create_compiled_task and apply_link_after below
rpm-build 95f51c
		Logs.debug('rev_use: amount of needed task gens: %r', len(needed_tgs))
rpm-build 95f51c
rpm-build 95f51c
	def post_group(self):
rpm-build 95f51c
		# assumption: we can ignore the folder/subfolders cuts
rpm-build 95f51c
		def tgpost(tg):
rpm-build 95f51c
			try:
rpm-build 95f51c
				f = tg.post
rpm-build 95f51c
			except AttributeError:
rpm-build 95f51c
				pass
rpm-build 95f51c
			else:
rpm-build 95f51c
				f()
rpm-build 95f51c
rpm-build 95f51c
		if not self.targets or self.targets == '*':
rpm-build 95f51c
			for tg in self.groups[self.current_group]:
rpm-build 95f51c
				# this can cut quite a lot of tg objects
rpm-build 95f51c
				if tg in self.needed_tgs:
rpm-build 95f51c
					tgpost(tg)
rpm-build 95f51c
		else:
rpm-build 95f51c
			# default implementation
rpm-build 95f51c
			return Build.BuildContext.post_group()
rpm-build 95f51c
rpm-build 95f51c
	def get_build_iterator(self):
rpm-build 95f51c
		if not self.targets or self.targets == '*':
rpm-build 95f51c
			self.compute_needed_tgs()
rpm-build 95f51c
		return Build.BuildContext.get_build_iterator(self)
rpm-build 95f51c
rpm-build 95f51c
@taskgen_method
rpm-build 95f51c
def is_stale(self):
rpm-build 95f51c
	# assume no globs
rpm-build 95f51c
	self.staleness = DIRTY
rpm-build 95f51c
rpm-build 95f51c
	# 1. the case of always stale targets
rpm-build 95f51c
	if getattr(self, 'always_stale', False):
rpm-build 95f51c
		return True
rpm-build 95f51c
rpm-build 95f51c
	# 2. check if the db file exists
rpm-build 95f51c
	db = os.path.join(self.bld.variant_dir, Context.DBFILE)
rpm-build 95f51c
	try:
rpm-build 95f51c
		dbstat = os.stat(db).st_mtime
rpm-build 95f51c
	except OSError:
rpm-build 95f51c
		Logs.debug('rev_use: must post %r because this is a clean build')
rpm-build 95f51c
		return True
rpm-build 95f51c
rpm-build 95f51c
	# 3.a check if the configuration exists
rpm-build 95f51c
	cache_node = self.bld.bldnode.find_node('c4che/build.config.py')
rpm-build 95f51c
	if not cache_node:
rpm-build 95f51c
		return True
rpm-build 95f51c
rpm-build 95f51c
	# 3.b check if the configuration changed
rpm-build 95f51c
	if os.stat(cache_node.abspath()).st_mtime > dbstat:
rpm-build 95f51c
		Logs.debug('rev_use: must post %r because the configuration has changed', self.name)
rpm-build 95f51c
		return True
rpm-build 95f51c
rpm-build 95f51c
	# 3.c any tstamp data?
rpm-build 95f51c
	try:
rpm-build 95f51c
		f_deps = self.bld.f_deps
rpm-build 95f51c
	except AttributeError:
rpm-build 95f51c
		Logs.debug('rev_use: must post %r because there is no f_deps', self.name)
rpm-build 95f51c
		return True
rpm-build 95f51c
rpm-build 95f51c
	# 4. check if this is the first build (no cache)
rpm-build 95f51c
	try:
rpm-build 95f51c
		lst = f_deps[(self.path.abspath(), self.idx)]
rpm-build 95f51c
	except KeyError:
rpm-build 95f51c
		Logs.debug('rev_use: must post %r because there it has no cached data', self.name)
rpm-build 95f51c
		return True
rpm-build 95f51c
rpm-build 95f51c
	try:
rpm-build 95f51c
		cache = self.bld.cache_tstamp_rev_use
rpm-build 95f51c
	except AttributeError:
rpm-build 95f51c
		cache = self.bld.cache_tstamp_rev_use = {}
rpm-build 95f51c
rpm-build 95f51c
	# 5. check the timestamp of each dependency files listed is unchanged
rpm-build 95f51c
	f_tstamps = self.bld.f_tstamps
rpm-build 95f51c
	for x in lst:
rpm-build 95f51c
		try:
rpm-build 95f51c
			old_ts = f_tstamps[x]
rpm-build 95f51c
		except KeyError:
rpm-build 95f51c
			Logs.debug('rev_use: must post %r because %r is not in cache', self.name, x)
rpm-build 95f51c
			return True
rpm-build 95f51c
rpm-build 95f51c
		try:
rpm-build 95f51c
			try:
rpm-build 95f51c
				ts = cache[x]
rpm-build 95f51c
			except KeyError:
rpm-build 95f51c
				ts = cache[x] = os.stat(x).st_mtime
rpm-build 95f51c
		except OSError:
rpm-build 95f51c
			del f_deps[(self.path.abspath(), self.idx)]
rpm-build 95f51c
			Logs.debug('rev_use: must post %r because %r does not exist anymore', self.name, x)
rpm-build 95f51c
			return True
rpm-build 95f51c
		else:
rpm-build 95f51c
			if ts != old_ts:
rpm-build 95f51c
				Logs.debug('rev_use: must post %r because the timestamp on %r changed %r %r', self.name, x, old_ts, ts)
rpm-build 95f51c
				return True
rpm-build 95f51c
rpm-build 95f51c
	self.staleness = DONE
rpm-build 95f51c
	return False
rpm-build 95f51c
rpm-build 95f51c
@taskgen_method
rpm-build 95f51c
def create_compiled_task(self, name, node):
rpm-build 95f51c
	# skip the creation of object files
rpm-build 95f51c
	# assumption: object-only targets are not skippable
rpm-build 95f51c
	if self.staleness == NEEDED:
rpm-build 95f51c
		# only libraries/programs can skip object files
rpm-build 95f51c
		for x in SKIPPABLE:
rpm-build 95f51c
			if x in self.features:
rpm-build 95f51c
				return None
rpm-build 95f51c
rpm-build 95f51c
	out = '%s.%d.o' % (node.name, self.idx)
rpm-build 95f51c
	task = self.create_task(name, node, node.parent.find_or_declare(out))
rpm-build 95f51c
	try:
rpm-build 95f51c
		self.compiled_tasks.append(task)
rpm-build 95f51c
	except AttributeError:
rpm-build 95f51c
		self.compiled_tasks = [task]
rpm-build 95f51c
	return task
rpm-build 95f51c
rpm-build 95f51c
@feature(*SKIPPABLE)
rpm-build 95f51c
@after_method('apply_link')
rpm-build 95f51c
def apply_link_after(self):
rpm-build 95f51c
	# cprogram/cxxprogram might be unnecessary
rpm-build 95f51c
	if self.staleness != NEEDED:
rpm-build 95f51c
		return
rpm-build 95f51c
	for tsk in self.tasks:
rpm-build 95f51c
		tsk.hasrun = Task.SKIPPED
rpm-build 95f51c
rpm-build 95f51c
def path_from(self, node):
rpm-build 95f51c
	# handle nodes of distinct types
rpm-build 95f51c
	if node.ctx is not self.ctx:
rpm-build 95f51c
		node = self.ctx.root.make_node(node.abspath())
rpm-build 95f51c
	return self.default_path_from(node)
rpm-build 95f51c
waflib.Node.Node.default_path_from = waflib.Node.Node.path_from
rpm-build 95f51c
waflib.Node.Node.path_from = path_from
rpm-build 95f51c
rpm-build 95f51c
def h_file(self):
rpm-build 95f51c
	# similar to md5_tstamp.py, but with 2-layer cache
rpm-build 95f51c
	# global_cache for the build context common for all task generators
rpm-build 95f51c
	# local_cache for the build context proxy (one by task generator)
rpm-build 95f51c
	#
rpm-build 95f51c
	# the global cache is not persistent
rpm-build 95f51c
	# the local cache is persistent and meant for partial builds
rpm-build 95f51c
	#
rpm-build 95f51c
	# assume all calls are made from a single thread
rpm-build 95f51c
	#
rpm-build 95f51c
	filename = self.abspath()
rpm-build 95f51c
	st = os.stat(filename)
rpm-build 95f51c
rpm-build 95f51c
	global_cache = self.ctx.bld.hashes_md5_tstamp
rpm-build 95f51c
	local_cache = self.ctx.hashes_md5_tstamp
rpm-build 95f51c
rpm-build 95f51c
	if filename in global_cache:
rpm-build 95f51c
		# value already calculated in this build
rpm-build 95f51c
		cval = global_cache[filename]
rpm-build 95f51c
rpm-build 95f51c
		# the value in global cache is assumed to be calculated once
rpm-build 95f51c
		# reverifying it could cause task generators
rpm-build 95f51c
		# to get distinct tstamp values, thus missing rebuilds
rpm-build 95f51c
		local_cache[filename] = cval
rpm-build 95f51c
		return cval[1]
rpm-build 95f51c
rpm-build 95f51c
	if filename in local_cache:
rpm-build 95f51c
		cval = local_cache[filename]
rpm-build 95f51c
		if cval[0] == st.st_mtime:
rpm-build 95f51c
			# correct value from a previous build
rpm-build 95f51c
			# put it in the global cache
rpm-build 95f51c
			global_cache[filename] = cval
rpm-build 95f51c
			return cval[1]
rpm-build 95f51c
rpm-build 95f51c
	ret = Utils.h_file(filename)
rpm-build 95f51c
	local_cache[filename] = global_cache[filename] = (st.st_mtime, ret)
rpm-build 95f51c
	return ret
rpm-build 95f51c
waflib.Node.Node.h_file = h_file
rpm-build 95f51c