1#!/usr/bin/env python3 2# 3# Copyright (C) 2022 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16"""Module for finding files in a tree""" 17 18from collections.abc import Iterator 19 20import os 21import sys 22 23 24class FileFinder: 25 def __init__(self, filename: str, ignore_paths=(), prune_hidden_dirs=True): 26 """Lightweight Single-threaded Non-cached file finder 27 28 Arguments: 29 filename: filename, should not contain directory prefixes 30 ignore_paths: filepath of dirs to ignore (e.g. 31 $ANDROID_BUILD_TOP/out) 32 prune_hidden_dirs: whether hidden dirs (beginning with a dot 33 character) should be ignored 34 """ 35 self.filename = filename 36 self.ignore_paths = ignore_paths 37 if prune_hidden_dirs and os.name != "posix": 38 raise Exception( 39 "Skipping hidden directories is not supported on this platform" 40 ) 41 self.prune_hidden_dirs = prune_hidden_dirs 42 43 def _is_hidden_dir(self, dirname: str) -> bool: 44 """Return true if directory is a hidden directory""" 45 return self.prune_hidden_dirs and dirname.startswith(".") 46 47 def find(self, path: str, search_depth: int) -> Iterator[str]: 48 """Search directory rooted at <path> 49 50 Arguments: 51 path: Search root 52 search_depth: Search maxdepth (relative to root). Search will be 53 pruned beyond this level 54 55 Returns: 56 An iterator of filepaths that match <filename> 57 """ 58 if search_depth < 0: 59 return 60 61 subdirs = [] 62 with os.scandir(path) as it: 63 for dirent in sorted(it, key=lambda x: x.name): 64 try: 65 if dirent.is_file(): 66 if dirent.name == self.filename: 67 yield dirent.path 68 elif dirent.is_dir(): 69 if (dirent.path not in self.ignore_paths 70 and not self._is_hidden_dir(dirent.name)): 71 subdirs.append(dirent.path) 72 except OSError as ex: 73 # Log the exception, but continue traversing 74 print(ex, file=sys.stderr) 75 for subdir in subdirs: 76 yield from self.find(subdir, search_depth - 1) 77