mirror of https://github.com/ARMmbed/mbed-os.git
158 lines
6.2 KiB
Python
158 lines
6.2 KiB
Python
#
|
|
# Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
"""Find files in MbedOS program directory."""
|
|
from pathlib import Path
|
|
import fnmatch
|
|
from typing import Callable, Iterable, Optional, List, Tuple
|
|
|
|
from mbed_tools.lib.json_helpers import decode_json_file
|
|
|
|
|
|
def find_files(filename: str, directory: Path) -> List[Path]:
|
|
"""Proxy to `_find_files`, which applies legacy filtering rules."""
|
|
# Temporary workaround, which replicates hardcoded ignore rules from old tools.
|
|
# Legacy list of ignored directories is longer, however "TESTS" and
|
|
# "TEST_APPS" were the only ones that actually exist in the MbedOS source.
|
|
# Ideally, this should be solved by putting an `.mbedignore` file in the root of MbedOS repo,
|
|
# similarly to what the code below pretends is happening.
|
|
legacy_ignore = MbedignoreFilter(("*/TESTS", "*/TEST_APPS"))
|
|
return _find_files(filename, directory, [legacy_ignore])
|
|
|
|
|
|
def _find_files(filename: str, directory: Path, filters: Optional[List[Callable]] = None) -> List[Path]:
|
|
"""Recursively find files by name under a given directory.
|
|
|
|
This function automatically applies rules from .mbedignore files found during traversal.
|
|
|
|
It is important to realise that applied filters are "greedy". The moment a directory is filtered out,
|
|
its children won't be traversed.
|
|
|
|
Args:
|
|
filename: Name of the file to look for.
|
|
directory: Location where search starts.
|
|
filters: Optional list of exclude filters to apply.
|
|
"""
|
|
if filters is None:
|
|
filters = []
|
|
|
|
result: List[Path] = []
|
|
|
|
# Directories and files to process
|
|
children = list(directory.iterdir())
|
|
|
|
# If .mbedignore is one of the children, we need to add it to filter list,
|
|
# as it might contain rules for currently processed directory, as well as its descendants.
|
|
mbedignore = Path(directory, ".mbedignore")
|
|
if mbedignore in children:
|
|
filters = filters + [MbedignoreFilter.from_file(mbedignore)]
|
|
|
|
# Remove files and directories that don't match current set of filters
|
|
filtered_children = filter_files(children, filters)
|
|
|
|
for child in filtered_children:
|
|
if child.is_symlink():
|
|
child = child.absolute().resolve()
|
|
|
|
if child.is_dir():
|
|
# If processed child is a directory, recurse with current set of filters
|
|
result += _find_files(filename, child, filters)
|
|
|
|
if child.is_file() and child.name == filename:
|
|
# We've got a match
|
|
result.append(child)
|
|
|
|
return result
|
|
|
|
|
|
def filter_files(files: Iterable[Path], filters: Iterable[Callable]) -> Iterable[Path]:
|
|
"""Filter given paths to files using filter callables."""
|
|
return [file for file in files if all(f(file) for f in filters)]
|
|
|
|
|
|
class RequiresFilter:
|
|
"""Filter out mbed libraries not needed by application.
|
|
|
|
The 'requires' config option in mbed_app.json can specify list of mbed
|
|
libraries (mbed_lib.json) that application requires. Apply 'requires'
|
|
filter to remove mbed_lib.json files not required by application.
|
|
"""
|
|
|
|
def __init__(self, requires: Iterable[str]):
|
|
"""Initialise the filter attributes.
|
|
|
|
Args:
|
|
requires: List of required mbed libraries.
|
|
"""
|
|
self._requires = requires
|
|
|
|
def __call__(self, path: Path) -> bool:
|
|
"""Return True if no requires are specified or our lib name is in the list of required libs."""
|
|
return decode_json_file(path).get("name", "") in self._requires or not self._requires
|
|
|
|
|
|
class LabelFilter:
|
|
"""Filter out given paths using path labelling rules.
|
|
|
|
If a path is labelled with given type, but contains label value which is
|
|
not allowed, it will be filtered out.
|
|
|
|
An example of labelled path is "/mbed-os/rtos/source/TARGET_CORTEX/mbed_lib.json",
|
|
where label type is "TARGET" and label value is "CORTEX".
|
|
|
|
For example, given a label type "FEATURE" and allowed values ["FOO"]:
|
|
- "/path/FEATURE_FOO/somefile.txt" will not be filtered out
|
|
- "/path/FEATURE_BAZ/somefile.txt" will be filtered out
|
|
- "/path/FEATURE_FOO/FEATURE_BAR/somefile.txt" will be filtered out
|
|
"""
|
|
|
|
def __init__(self, label_type: str, allowed_label_values: Iterable[str]):
|
|
"""Initialise the filter attributes.
|
|
|
|
Args:
|
|
label_type: Type of the label to filter with. In filtered paths, it prefixes the value.
|
|
allowed_label_values: Values which are allowed for the given label type.
|
|
"""
|
|
self._label_type = label_type
|
|
self._allowed_labels = set(f"{label_type}_{label_value}" for label_value in allowed_label_values)
|
|
|
|
def __call__(self, path: Path) -> bool:
|
|
"""Return True if given path contains only allowed labels - should not be filtered out."""
|
|
labels = set(part for part in path.parts if self._label_type in part)
|
|
return labels.issubset(self._allowed_labels)
|
|
|
|
|
|
class MbedignoreFilter:
|
|
"""Filter out given paths based on rules found in .mbedignore files.
|
|
|
|
Patterns in .mbedignore use unix shell-style wildcards (fnmatch). It means
|
|
that functionality, although similar is different to that found in
|
|
.gitignore and friends.
|
|
"""
|
|
|
|
def __init__(self, patterns: Tuple[str, ...]):
|
|
"""Initialise the filter attributes.
|
|
|
|
Args:
|
|
patterns: List of patterns from .mbedignore to filter against.
|
|
"""
|
|
self._patterns = patterns
|
|
|
|
def __call__(self, path: Path) -> bool:
|
|
"""Return True if given path doesn't match .mbedignore patterns - should not be filtered out."""
|
|
stringified = str(path)
|
|
return not any(fnmatch.fnmatch(stringified, pattern) for pattern in self._patterns)
|
|
|
|
@classmethod
|
|
def from_file(cls, mbedignore_path: Path) -> "MbedignoreFilter":
|
|
"""Return new instance with patterns read from .mbedignore file.
|
|
|
|
Constructed patterns are rooted in the directory of .mbedignore file.
|
|
"""
|
|
lines = mbedignore_path.read_text().splitlines()
|
|
pattern_lines = (line for line in lines if line.strip() and not line.startswith("#"))
|
|
ignore_root = mbedignore_path.parent
|
|
patterns = tuple(str(ignore_root.joinpath(pattern)) for pattern in pattern_lines)
|
|
return cls(patterns)
|