diff --git a/tools/resources/__init__.py b/tools/resources/__init__.py index 8e0887ae157..eecd60ac9e8 100644 --- a/tools/resources/__init__.py +++ b/tools/resources/__init__.py @@ -434,8 +434,6 @@ def add_directory( root_path = join(relpath(root, base_path)) if self._ignoreset.is_ignored(join(root_path,"")): self.ignore_dir(join(into_path, root_path)) - dirs[:] = [] - continue for d in copy(dirs): dir_path = join(root, d) @@ -451,14 +449,22 @@ def add_directory( relpath(dir_path, base_path) )) dirs.remove(d) - elif (d.startswith('.') or d in self._legacy_ignore_dirs or - self._ignoreset.is_ignored(join(root_path, d, ""))): + + # hardcoded ignore rules -- don't want to visit any of these subdirs + elif d.startswith('.') or d in self._legacy_ignore_dirs: self.ignore_dir(join( into_path, relpath(dir_path, base_path) )) dirs.remove(d) + # manual ignore rules -- may want to visit subdirs if they have unignored files + elif self._ignoreset.is_ignored(join(root_path, d, "")): + self.ignore_dir(join( + into_path, + relpath(dir_path, base_path) + )) + # Add root to include paths root = root.rstrip("/") diff --git a/tools/resources/ignore.py b/tools/resources/ignore.py index b22b281af9c..9ec58cd52c4 100644 --- a/tools/resources/ignore.py +++ b/tools/resources/ignore.py @@ -31,11 +31,50 @@ class MbedIgnoreSet(object): def __init__(self): self._ignore_patterns = [] - self._ignore_regex = re.compile("$^") + self._ignore_regexes = [] + self._unignore_patterns = [] + self._unignore_regexes = [] + + def find_longest_match(self, path, regex_list, pattern_list): + """ + Helper function: find the longest ignore/unignore pattern that matches + the provided path. Returns None if there are no matches. + """ + + longest_matching_pattern = None + + for curr_regex, curr_pattern in zip(regex_list, pattern_list): + this_regex_match = curr_regex.match(path) + if this_regex_match: + if longest_matching_pattern is None: + # no previous match + longest_matching_pattern = curr_pattern + elif len(curr_pattern) > len(longest_matching_pattern): + # found a longer match, indicating a more specific rule which should take precedence + longest_matching_pattern = curr_pattern + + return longest_matching_pattern def is_ignored(self, file_path): """Check if file path is ignored by any .mbedignore thus far""" - return self._ignore_regex.match(normcase(file_path)) + + filepath_normcase = normcase(file_path) + + # find longest ignore and unignore pattern that matches the path + ignore_match_pattern = self.find_longest_match(filepath_normcase, + self._ignore_regexes, self._ignore_patterns) + unignore_match_pattern = self.find_longest_match(filepath_normcase, + self._unignore_regexes, self._unignore_patterns) + + if ignore_match_pattern is None: + # not ignored at all + return False + + if unignore_match_pattern is not None and len(unignore_match_pattern) >= len(ignore_match_pattern): + # unignore rule takes precedence since it is longer than the ignore rule. + return False + + return True def add_ignore_patterns(self, in_name, patterns): """Ignore all files and directories matching the paterns in @@ -45,14 +84,42 @@ def add_ignore_patterns(self, in_name, patterns): in_name - the filename prefix that this ignore will apply to patterns - the list of patterns we will ignore in the future """ + + if len(patterns) == 0: + return + if in_name == ".": - self._ignore_patterns.extend(normcase(p) for p in patterns) + patterns_normpath = list(normcase(p) for p in patterns) else: - self._ignore_patterns.extend( - normcase(join(in_name, pat)) for pat in patterns) - if self._ignore_patterns: - self._ignore_regex = re.compile("|".join( - fnmatch.translate(p) for p in self._ignore_patterns)) + patterns_normpath = list(normcase(join(in_name, pat)) for pat in patterns) + + self._ignore_patterns.extend(patterns_normpath) + self._ignore_regexes.extend(re.compile(fnmatch.translate(p)) for p in patterns_normpath) + + + def add_unignore_patterns(self, in_name, patterns): + """Un-ignore all files and directories matching the patterns in + directories named by in_name. + + Un-ignore rules take precedence based on depth. So ignoring + a/b/* then unignoring a/b/c.cpp would allow c.cpp to build. + But unignoring a/* then ignoring a/b/* would prevent c.cpp from building. + + Positional arguments: + in_name - the filename prefix that this unignore will apply to + patterns - the list of patterns we will unignore in the future + """ + + if len(patterns) == 0: + return + + if in_name == ".": + patterns_normpath = list(normcase(p) for p in patterns) + else: + patterns_normpath = list(normcase(join(in_name, pat)) for pat in patterns) + + self._unignore_patterns.extend(patterns_normpath) + self._unignore_regexes.extend(re.compile(fnmatch.translate(p)) for p in patterns_normpath) def add_mbedignore(self, in_name, filepath): """Add a series of patterns to the ignored paths @@ -62,8 +129,25 @@ def add_mbedignore(self, in_name, filepath): patterns - the list of patterns we will ignore in the future """ with open (filepath) as f: - patterns = [l.strip() for l in f - if l.strip() != "" and not l.startswith("#")] - self.add_ignore_patterns(in_name, patterns) + + ignore_patterns = [] + unignore_patterns = [] + + for line in f: + line_text = line.strip() + + if line_text == "" or line_text.startswith("#"): + # no content on this line + continue + + if line_text.startswith("!"): + # unignore rule + unignore_patterns.append(line_text[1:]) + else: + # ignore rule + ignore_patterns.append(line_text) + + self.add_ignore_patterns(in_name, ignore_patterns) + self.add_unignore_patterns(in_name, unignore_patterns)