2011-09-10 11:13:51 +02:00
|
|
|
#! /usr/bin/env python
|
|
|
|
# encoding: utf-8
|
|
|
|
# Thomas Nagy 2011
|
|
|
|
|
|
|
|
import os, shutil, re
|
|
|
|
from waflib import Options, Build, Logs
|
|
|
|
|
|
|
|
"""
|
|
|
|
Apply a least recently used policy to the Waf cache.
|
|
|
|
|
|
|
|
For performance reasons, it is called after the build is complete.
|
|
|
|
|
|
|
|
We assume that the the folders are written atomically
|
|
|
|
|
2011-10-14 20:22:09 +02:00
|
|
|
Do export WAFCACHE=/tmp/foo_xyz where xyz represents the cache size in bytes
|
2011-09-10 11:13:51 +02:00
|
|
|
If missing, the default cache size will be set to 10GB
|
|
|
|
"""
|
|
|
|
|
2011-10-13 08:14:28 +02:00
|
|
|
re_num = re.compile('[a-zA-Z_-]+(\d+)')
|
2011-09-10 11:13:51 +02:00
|
|
|
|
|
|
|
CACHESIZE = 10*1024*1024*1024 # in bytes
|
|
|
|
CLEANRATIO = 0.8
|
|
|
|
DIRSIZE = 4096
|
|
|
|
|
|
|
|
def compile(self):
|
|
|
|
if Options.cache_global and not Options.options.nocache:
|
|
|
|
try:
|
|
|
|
os.makedirs(Options.cache_global)
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
|
|
|
|
try:
|
|
|
|
self.raw_compile()
|
|
|
|
finally:
|
|
|
|
if Options.cache_global and not Options.options.nocache:
|
|
|
|
self.sweep()
|
|
|
|
|
|
|
|
def sweep(self):
|
|
|
|
global CACHESIZE
|
|
|
|
CACHEDIR = Options.cache_global
|
|
|
|
|
|
|
|
# get the cache max size from the WAFCACHE filename
|
|
|
|
re_num = re.compile('[a-zA-Z_]+(\d+)')
|
|
|
|
val = re_num.sub('\\1', os.path.basename(Options.cache_global))
|
|
|
|
try:
|
|
|
|
CACHESIZE = int(val)
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
|
|
|
|
# map folder names to timestamps
|
|
|
|
flist = {}
|
|
|
|
for x in os.listdir(CACHEDIR):
|
|
|
|
j = os.path.join(CACHEDIR, x)
|
|
|
|
if os.path.isdir(j) and len(x) == 64: # dir names are md5 hexdigests
|
|
|
|
flist[x] = [os.stat(j).st_mtime, 0]
|
|
|
|
|
|
|
|
for (x, v) in flist.items():
|
|
|
|
cnt = DIRSIZE # each entry takes 4kB
|
|
|
|
d = os.path.join(CACHEDIR, x)
|
|
|
|
for k in os.listdir(d):
|
|
|
|
cnt += os.stat(os.path.join(d, k)).st_size
|
|
|
|
flist[x][1] = cnt
|
|
|
|
|
|
|
|
total = sum([x[1] for x in flist.values()])
|
|
|
|
Logs.debug('lru: Cache size is %r' % total)
|
|
|
|
|
|
|
|
if total >= CACHESIZE:
|
|
|
|
Logs.debug('lru: Trimming the cache since %r > %r' % (total, CACHESIZE))
|
|
|
|
|
|
|
|
# make a list to sort the folders by timestamp
|
|
|
|
lst = [(p, v[0], v[1]) for (p, v) in flist.items()]
|
|
|
|
lst.sort(key=lambda x: x[1]) # sort by timestamp
|
|
|
|
lst.reverse()
|
|
|
|
|
|
|
|
while total >= CACHESIZE * CLEANRATIO:
|
|
|
|
(k, t, s) = lst.pop()
|
|
|
|
p = os.path.join(CACHEDIR, k)
|
|
|
|
v = p + '.del'
|
|
|
|
try:
|
|
|
|
os.rename(p, v)
|
|
|
|
except:
|
|
|
|
# someone already did it
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
shutil.rmtree(v)
|
|
|
|
except:
|
|
|
|
# this should not happen, but who knows?
|
|
|
|
Logs.warn('If you ever see this message, report it (%r)' % v)
|
|
|
|
total -= s
|
|
|
|
del flist[k]
|
|
|
|
|
|
|
|
Logs.debug('lru: Total at the end %r' % total)
|
|
|
|
|
|
|
|
Build.BuildContext.raw_compile = Build.BuildContext.compile
|
|
|
|
Build.BuildContext.compile = compile
|
|
|
|
Build.BuildContext.sweep = sweep
|
|
|
|
|