| #!/usr/bin/env python |
| # Copyright 2014 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import os |
| import subprocess |
| import sys |
| |
| |
| def GetFdCommand(): |
| """Returns the name of the 'fd' command if it is available and suitable.""" |
| for cmd in ('fdfind', 'fd'): |
| try: |
| # We check for --help output to ensure it's the right program and |
| # supports the flags we need. |
| res = subprocess.run([cmd, '--help'], |
| capture_output=True, |
| text=True, |
| check=False) |
| if (res.returncode == 0 and '--glob' in res.stdout |
| and '--print0' in res.stdout and '--no-ignore' in res.stdout): |
| return cmd |
| except OSError: |
| continue |
| return None |
| |
| |
| def RemoveIfStale(pyc_path): |
| """Deletes pyc_path if it doesn't have a corresponding .py file.""" |
| dirname, filename = os.path.split(pyc_path) |
| if os.path.basename(dirname) == '__pycache__': |
| # We have something like 'foo/__pycache__/module.cpython-313.pyc', we want |
| # to check if 'foo/module.py' exists. |
| |
| # Remove .pyc |
| root, _ = os.path.splitext(filename) |
| # Remove version indicator like ".cpython-313." |
| root, _ = os.path.splitext(root) |
| py_path = os.path.join(os.path.dirname(dirname), root + '.py') |
| else: |
| # We have something like 'foo/module.pyc', we want to check if |
| # 'foo/module.py' exists. |
| |
| root, _ = os.path.splitext(pyc_path) |
| py_path = root + '.py' |
| try: |
| if not os.path.exists(py_path): |
| os.remove(pyc_path) |
| except OSError: |
| # Wrap OS calls in try/except in case another process touched this file. |
| pass |
| |
| |
| def RemoveAllStalePycFiles(base_dirs): |
| """Scan directories for old .pyc files without a .py file and delete them.""" |
| base_dirs = [d for d in base_dirs if os.path.exists(d)] |
| if not base_dirs: |
| return |
| fd_cmd = GetFdCommand() |
| if fd_cmd: |
| output = None |
| try: |
| res = subprocess.run( |
| [fd_cmd, '--no-ignore', '--print0', '--glob', '*.pyc'] + base_dirs, |
| capture_output=True, |
| check=True) |
| output = res.stdout |
| except (subprocess.CalledProcessError, OSError): |
| # Fall back to os.walk if fd fails. |
| pass |
| |
| if output is not None: |
| for pyc_path in output.split(b'\0'): |
| if pyc_path: |
| RemoveIfStale(os.fsdecode(pyc_path)) |
| return |
| |
| for base_dir in base_dirs: |
| for dirname, _, filenames in os.walk(base_dir, topdown=False): |
| if '.svn' in dirname or '.git' in dirname: |
| continue |
| for filename in filenames: |
| if filename.endswith('.pyc'): |
| RemoveIfStale(os.path.join(dirname, filename)) |
| |
| |
| if __name__ == '__main__': |
| RemoveAllStalePycFiles(sys.argv[1:]) |