diff --git a/lib/spack/llnl/util/filesystem.py b/lib/spack/llnl/util/filesystem.py index f280534852b..35c85ba3ae9 100644 --- a/lib/spack/llnl/util/filesystem.py +++ b/lib/spack/llnl/util/filesystem.py @@ -75,7 +75,6 @@ "install_tree", "is_exe", "join_path", - "last_modification_time_recursive", "library_extensions", "mkdirp", "partition_path", @@ -1470,15 +1469,36 @@ def set_executable(path): @system_path_filter -def last_modification_time_recursive(path): - path = os.path.abspath(path) - times = [os.stat(path).st_mtime] - times.extend( - os.lstat(os.path.join(root, name)).st_mtime - for root, dirs, files in os.walk(path) - for name in dirs + files - ) - return max(times) +def recursive_mtime_greater_than(path: str, time: float) -> bool: + """Returns true if any file or dir recursively under `path` has mtime greater than `time`.""" + # use bfs order to increase likelihood of early return + queue: Deque[str] = collections.deque() + + if os.stat(path).st_mtime > time: + return True + + while queue: + current = queue.popleft() + + try: + entries = os.scandir(current) + except OSError: + continue + + with entries: + for entry in entries: + try: + st = entry.stat(follow_symlinks=False) + except OSError: + continue + + if st.st_mtime > time: + return True + + if entry.is_dir(follow_symlinks=False): + queue.append(entry.path) + + return False @system_path_filter diff --git a/lib/spack/spack/package_base.py b/lib/spack/spack/package_base.py index b6dde08638c..0bfd0144989 100644 --- a/lib/spack/spack/package_base.py +++ b/lib/spack/spack/package_base.py @@ -1099,14 +1099,14 @@ def update_external_dependencies(self, extendee_spec=None): """ pass - def detect_dev_src_change(self): + def detect_dev_src_change(self) -> bool: """ Method for checking for source code changes to trigger rebuild/reinstall """ dev_path_var = self.spec.variants.get("dev_path", None) _, record = spack.store.STORE.db.query_by_spec_hash(self.spec.dag_hash()) - mtime = fsys.last_modification_time_recursive(dev_path_var.value) - return mtime > record.installation_time + assert dev_path_var and record, "dev_path variant and record must be present" + return fsys.recursive_mtime_greater_than(dev_path_var.value, record.installation_time) def all_urls_for_version(self, version: StandardVersion) -> List[str]: """Return all URLs derived from version_urls(), url, urls, and