Return the maximum mtime of any SOURCE directory under *root* (root included). Walk only directory entries (skipping files) to detect whether any subdirectory has been modified since a marker file was written. Adding or removing a file always updates the PARENT directory's mtime o
(root: Path)
| 54 | |
| 55 | |
| 56 | def get_max_dir_mtime(root: Path) -> float: |
| 57 | """ |
| 58 | Return the maximum mtime of any SOURCE directory under *root* (root included). |
| 59 | |
| 60 | Walk only directory entries (skipping files) to detect whether any subdirectory |
| 61 | has been modified since a marker file was written. Adding or removing a file |
| 62 | always updates the PARENT directory's mtime on NTFS, ext4, and APFS, so this |
| 63 | gives an O(#dirs) change proxy vs O(#files) for a full rglob. |
| 64 | |
| 65 | Uses os.scandir for efficiency — on Windows, DirEntry.stat() reuses |
| 66 | FindFirstFile data (no extra syscall per directory entry). |
| 67 | |
| 68 | Excludes non-source directories (``__pycache__``, ``.git``, etc.) that are |
| 69 | updated by tools (Python bytecode caching, version control) without representing |
| 70 | developer code changes. Pruning them prevents false-positive cache invalidation. |
| 71 | |
| 72 | Returns 0.0 when root does not exist or any OS error occurs. |
| 73 | """ |
| 74 | max_mtime = 0.0 |
| 75 | stack = [str(root)] |
| 76 | while stack: |
| 77 | current = stack.pop() |
| 78 | try: |
| 79 | mtime = os.stat(current).st_mtime |
| 80 | if mtime > max_mtime: |
| 81 | max_mtime = mtime |
| 82 | except OSError: |
| 83 | continue |
| 84 | try: |
| 85 | with os.scandir(current) as it: |
| 86 | for entry in it: |
| 87 | try: |
| 88 | if entry.is_dir(follow_symlinks=False): |
| 89 | if not _should_skip_scan_dir(entry.name): |
| 90 | stack.append(entry.path) |
| 91 | except OSError: |
| 92 | pass |
| 93 | except OSError: |
| 94 | pass |
| 95 | return max_mtime |
| 96 | |
| 97 | |
| 98 | def _get_max_source_file_mtime( |
no test coverage detected