Skip to content

Commit 86f7cf4

Browse files
Merge pull request #4426 from theotherjimmy/faster-scan
Improve directory scanning performance
2 parents 50ec6db + bf1a69b commit 86f7cf4

File tree

1 file changed

+89
-11
lines changed

1 file changed

+89
-11
lines changed

tools/toolchains/__init__.py

Lines changed: 89 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
from time import time, sleep
2323
from types import ListType
2424
from shutil import copyfile
25-
from os.path import join, splitext, exists, relpath, dirname, basename, split, abspath, isfile, isdir
25+
from os.path import join, splitext, exists, relpath, dirname, basename, split, abspath, isfile, isdir, normcase
26+
from itertools import chain
2627
from inspect import getmro
2728
from copy import deepcopy
2829
from tools.config import Config
@@ -42,6 +43,78 @@
4243
CPU_COUNT_MIN = 1
4344
CPU_COEF = 1
4445

46+
class LazyDict(dict):
47+
def __init__(self):
48+
self.eager = {}
49+
self.lazy = {}
50+
51+
def add_lazy(self, key, thunk):
52+
if key in self.eager:
53+
del self.eager[key]
54+
self.lazy[key] = thunk
55+
56+
def __getitem__(self, key):
57+
if (key not in self.eager
58+
and key in self.lazy):
59+
self.eager[key] = self.lazy[key]()
60+
del self.lazy[key]
61+
return self.eager[key]
62+
63+
def __setitem__(self, key, value):
64+
self.eager[key] = value
65+
66+
def __delitem__(self, key):
67+
if key in self.eager:
68+
del self.eager[key]
69+
else:
70+
del self.lazy[key]
71+
72+
def __contains__(self, key):
73+
return key in self.eager or key in self.lazy
74+
75+
def __iter__(self):
76+
return chain(iter(self.eager), iter(self.lazy))
77+
78+
def __len__(self):
79+
return len(self.eager) + len(self.lazy)
80+
81+
def __str__(self):
82+
return "Lazy{%s}" % (
83+
", ".join("%r: %r" % (k, v) for k, v in
84+
chain(self.eager.iteritems(), ((k, "not evaluated")
85+
for k in self.lazy))))
86+
87+
def update(self, other):
88+
if isinstance(other, LazyDict):
89+
self.eager.update(other.eager)
90+
self.lazy.update(other.lazy)
91+
else:
92+
self.eager.update(other)
93+
94+
def iteritems(self):
95+
"""Warning: This forces the evaluation all of the items in this LazyDict
96+
that are iterated over."""
97+
for k, v in self.eager.iteritems():
98+
yield k, v
99+
for k in self.lazy.keys():
100+
yield k, self[k]
101+
102+
def apply(self, fn):
103+
"""Delay the application of a computation to all items of the lazy dict.
104+
Does no computation now. Instead the comuptation is performed when a
105+
consumer attempts to access a value in this LazyDict"""
106+
new_lazy = {}
107+
for k, f in self.lazy.iteritems():
108+
def closure(f=f):
109+
return fn(f())
110+
new_lazy[k] = closure
111+
for k, v in self.eager.iteritems():
112+
def closure(v=v):
113+
return fn(v)
114+
new_lazy[k] = closure
115+
self.lazy = new_lazy
116+
self.eager = {}
117+
45118
class Resources:
46119
def __init__(self, base_path=None):
47120
self.base_path = base_path
@@ -74,7 +147,7 @@ def __init__(self, base_path=None):
74147
self.json_files = []
75148

76149
# Features
77-
self.features = {}
150+
self.features = LazyDict()
78151

79152
def __add__(self, resources):
80153
if resources is None:
@@ -165,7 +238,9 @@ def relative_to(self, base, dot=False):
165238
v = [rel_path(f, base, dot) for f in getattr(self, field)]
166239
setattr(self, field, v)
167240

168-
self.features = {k: f.relative_to(base, dot) for k, f in self.features.iteritems() if f}
241+
def to_apply(feature, base=base, dot=dot):
242+
feature.relative_to(base, dot)
243+
self.features.apply(to_apply)
169244

170245
if self.linker_script is not None:
171246
self.linker_script = rel_path(self.linker_script, base, dot)
@@ -178,7 +253,9 @@ def win_to_unix(self):
178253
v = [f.replace('\\', '/') for f in getattr(self, field)]
179254
setattr(self, field, v)
180255

181-
self.features = {k: f.win_to_unix() for k, f in self.features.iteritems() if f}
256+
def to_apply(feature):
257+
feature.win_to_unix()
258+
self.features.apply(to_apply)
182259

183260
if self.linker_script is not None:
184261
self.linker_script = self.linker_script.replace('\\', '/')
@@ -306,6 +383,7 @@ def __init__(self, target, notify=None, macros=None, silent=False,
306383

307384
# Ignore patterns from .mbedignore files
308385
self.ignore_patterns = []
386+
self._ignore_regex = re.compile("$^")
309387

310388
# Pre-mbed 2.0 ignore dirs
311389
self.legacy_ignore_dirs = (LEGACY_IGNORE_DIRS | TOOLCHAINS) - set([target.name, LEGACY_TOOLCHAIN_NAMES[self.name]])
@@ -511,10 +589,7 @@ def need_update(self, target, dependencies):
511589

512590
def is_ignored(self, file_path):
513591
"""Check if file path is ignored by any .mbedignore thus far"""
514-
for pattern in self.ignore_patterns:
515-
if fnmatch.fnmatch(file_path, pattern):
516-
return True
517-
return False
592+
return self._ignore_regex.match(normcase(file_path))
518593

519594
def add_ignore_patterns(self, root, base_path, patterns):
520595
"""Add a series of patterns to the ignored paths
@@ -526,9 +601,10 @@ def add_ignore_patterns(self, root, base_path, patterns):
526601
"""
527602
real_base = relpath(root, base_path)
528603
if real_base == ".":
529-
self.ignore_patterns.extend(patterns)
604+
self.ignore_patterns.extend(normcase(p) for p in patterns)
530605
else:
531-
self.ignore_patterns.extend(join(real_base, pat) for pat in patterns)
606+
self.ignore_patterns.extend(normcase(join(real_base, pat)) for pat in patterns)
607+
self._ignore_regex = re.compile("|".join(fnmatch.translate(p) for p in self.ignore_patterns))
532608

533609
# Create a Resources object from the path pointed to by *path* by either traversing a
534610
# a directory structure, when *path* is a directory, or adding *path* to the resources,
@@ -604,7 +680,9 @@ def _add_dir(self, path, resources, base_path, exclude_paths=None):
604680
elif d.startswith('FEATURE_'):
605681
# Recursively scan features but ignore them in the current scan.
606682
# These are dynamically added by the config system if the conditions are matched
607-
resources.features[d[8:]] = self.scan_resources(dir_path, base_path=base_path)
683+
def closure (dir_path=dir_path, base_path=base_path):
684+
return self.scan_resources(dir_path, base_path=base_path)
685+
resources.features.add_lazy(d[8:], closure)
608686
dirs.remove(d)
609687
elif exclude_paths:
610688
for exclude_path in exclude_paths:

0 commit comments

Comments
 (0)