diff options
Diffstat (limited to 'lib/spack/llnl/util/filesystem.py')
-rw-r--r-- | lib/spack/llnl/util/filesystem.py | 73 |
1 files changed, 73 insertions, 0 deletions
diff --git a/lib/spack/llnl/util/filesystem.py b/lib/spack/llnl/util/filesystem.py index 179e1703b9..a0129e3550 100644 --- a/lib/spack/llnl/util/filesystem.py +++ b/lib/spack/llnl/util/filesystem.py @@ -1044,6 +1044,79 @@ def traverse_tree(source_root, dest_root, rel_path='', **kwargs): yield (source_path, dest_path) +def lexists_islink_isdir(path): + """Computes the tuple (lexists(path), islink(path), isdir(path)) in a minimal + number of stat calls.""" + # First try to lstat, so we know if it's a link or not. + try: + lst = os.lstat(path) + except (IOError, OSError): + return False, False, False + + is_link = stat.S_ISLNK(lst.st_mode) + + # Check whether file is a dir. + if not is_link: + is_dir = stat.S_ISDIR(lst.st_mode) + return True, is_link, is_dir + + # Check whether symlink points to a dir. + try: + st = os.stat(path) + is_dir = stat.S_ISDIR(st.st_mode) + except (IOError, OSError): + # Dangling symlink (i.e. it lexists but not exists) + is_dir = False + + return True, is_link, is_dir + + +def visit_directory_tree(root, visitor, rel_path='', depth=0): + """ + Recurses the directory root depth-first through a visitor pattern + + The visitor interface is as follows: + - visit_file(root, rel_path, depth) + - before_visit_dir(root, rel_path, depth) -> bool + if True, descends into this directory + - before_visit_symlinked_dir(root, rel_path, depth) -> bool + if True, descends into this directory + - after_visit_dir(root, rel_path, depth) -> void + only called when before_visit_dir returns True + - after_visit_symlinked_dir(root, rel_path, depth) -> void + only called when before_visit_symlinked_dir returns True + """ + dir = os.path.join(root, rel_path) + + if sys.version_info >= (3, 5, 0): + dir_entries = sorted(os.scandir(dir), key=lambda d: d.name) # novermin + else: + dir_entries = os.listdir(dir) + dir_entries.sort() + + for f in dir_entries: + if sys.version_info >= (3, 5, 0): + rel_child = os.path.join(rel_path, f.name) + islink, isdir = f.is_symlink(), f.is_dir() + else: + rel_child = os.path.join(rel_path, f) + lexists, islink, isdir = lexists_islink_isdir(os.path.join(dir, f)) + if not lexists: + continue + + if not isdir: + # Handle files + visitor.visit_file(root, rel_child, depth) + elif not islink and visitor.before_visit_dir(root, rel_child, depth): + # Handle ordinary directories + visit_directory_tree(root, visitor, rel_child, depth + 1) + visitor.after_visit_dir(root, rel_child, depth) + elif islink and visitor.before_visit_symlinked_dir(root, rel_child, depth): + # Handle symlinked directories + visit_directory_tree(root, visitor, rel_child, depth + 1) + visitor.after_visit_symlinked_dir(root, rel_child, depth) + + @system_path_filter def set_executable(path): mode = os.stat(path).st_mode |