diff --git a/lib/spack/llnl/util/filesystem.py b/lib/spack/llnl/util/filesystem.py index 0e9708a3c3..a116e2bed2 100644 --- a/lib/spack/llnl/util/filesystem.py +++ b/lib/spack/llnl/util/filesystem.py @@ -63,7 +63,8 @@ 'touchp', 'traverse_tree', 'unset_executable_mode', - 'working_dir' + 'working_dir', + 'keep_modification_time' ] @@ -1819,3 +1820,24 @@ def remove_directory_contents(dir): os.unlink(entry) else: shutil.rmtree(entry) + + +@contextmanager +def keep_modification_time(*filenames): + """ + Context manager to keep the modification timestamps of the input files. + Tolerates and has no effect on non-existent files and files that are + deleted by the nested code. + + Parameters: + *filenames: one or more files that must have their modification + timestamps unchanged + """ + mtimes = {} + for f in filenames: + if os.path.exists(f): + mtimes[f] = os.path.getmtime(f) + yield + for f, mtime in mtimes.items(): + if os.path.exists(f): + os.utime(f, (os.path.getatime(f), mtime)) diff --git a/lib/spack/spack/test/llnl/util/filesystem.py b/lib/spack/spack/test/llnl/util/filesystem.py index 8f64c0907f..5733be6b59 100644 --- a/lib/spack/spack/test/llnl/util/filesystem.py +++ b/lib/spack/spack/test/llnl/util/filesystem.py @@ -588,3 +588,24 @@ def test_content_of_files_with_same_name(tmpdir): # and have not been mixed assert file1.read().strip() == 'file1' assert file2.read().strip() == 'file2' + + +def test_keep_modification_time(tmpdir): + file1 = tmpdir.ensure('file1') + file2 = tmpdir.ensure('file2') + + # Shift the modification time of the file 10 seconds back: + mtime1 = file1.mtime() - 10 + file1.setmtime(mtime1) + + with fs.keep_modification_time(file1.strpath, + file2.strpath, + 'non-existing-file'): + file1.write('file1') + file2.remove() + + # Assert that the modifications took place the modification time has not + # changed; + assert file1.read().strip() == 'file1' + assert not file2.exists() + assert int(mtime1) == int(file1.mtime())