From 0bf14345d86ea85094d63fbeb6925cf2b918847a Mon Sep 17 00:00:00 2001 From: Bodigrim Date: Sat, 6 Jun 2026 00:07:50 +0200 Subject: [PATCH 1/2] Make 'cabal clean' more robust: retry after getting 'Unsatisfied constraints (directory is non empty)' error --- cabal-install/src/Distribution/Client/CmdClean.hs | 13 +++++++++++-- changelog.d/pr-11604.md | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/cabal-install/src/Distribution/Client/CmdClean.hs b/cabal-install/src/Distribution/Client/CmdClean.hs index 21d4b2c9480..e1261f9dacb 100644 --- a/cabal-install/src/Distribution/Client/CmdClean.hs +++ b/cabal-install/src/Distribution/Client/CmdClean.hs @@ -81,6 +81,7 @@ import Control.Monad , mapM ) import qualified Data.Set as Set +import qualified GHC.IO.Exception as GHC import System.Directory ( canonicalizePath , doesDirectoryExist @@ -92,7 +93,8 @@ import System.FilePath ( () ) import System.IO.Error - ( isPermissionError + ( ioeGetErrorType + , isPermissionError ) import qualified System.Process as Process @@ -193,7 +195,14 @@ cleanAction (ProjectFlags{..}, CleanFlags{..}) extraArgs _ = do "attrib -s -h -r " <> distRoot <> "\\*.* /s /d" catch (removePathForcibly distRoot) - (\e -> if isPermissionError e then threadDelay 1000 >> removePathForcibly distRoot else throw e) + ( \e -> + -- Permission error is usually when some files are (temporarily) locked. + -- Unsatisfied constraints (directory is non empty) error happens + -- when some files inside the directory were not removed (perhaps because they are locked). + if isPermissionError e || ioeGetErrorType e == GHC.UnsatisfiedConstraints + then threadDelay 1000 >> removePathForcibly distRoot + else throw e + ) removeEnvFiles $ distProjectRootDirectory distLayout diff --git a/changelog.d/pr-11604.md b/changelog.d/pr-11604.md index 9c05ce1d910..4348c02c892 100644 --- a/changelog.d/pr-11604.md +++ b/changelog.d/pr-11604.md @@ -1,7 +1,7 @@ --- synopsis: Replace removeDirectoryRecursive with removePathForcibly packages: [Cabal, cabal-install] -prs: 11604 +prs: 11604 11938 --- We replace `System.Directory.removeDirectoryRecursive` calls with a more robust `System.Directory.removePathForcibly`. Additionally, some functions (most notably the one responsible for `cabal clean`) now run `removeDirectoryRecursive` twice on all platforms if the first time was not successful (previously only on Windows) and include a small delay in between. From 79e93c6bd0d8ba8cf55600a86015a5e7bc9ae56f Mon Sep 17 00:00:00 2001 From: Bodigrim Date: Sat, 6 Jun 2026 11:32:11 +0200 Subject: [PATCH 2/2] Tests: retry removeDirectoryRecursiveHack a bit more, hopefully to reduce flakiness noticed in #11026 and #11933 --- Cabal-tests/lib/Test/Utils/TempTestDir.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cabal-tests/lib/Test/Utils/TempTestDir.hs b/Cabal-tests/lib/Test/Utils/TempTestDir.hs index af565ff5154..8718440c9fb 100644 --- a/Cabal-tests/lib/Test/Utils/TempTestDir.hs +++ b/Cabal-tests/lib/Test/Utils/TempTestDir.hs @@ -60,7 +60,7 @@ removeDirectoryRecursiveHack :: Verbosity -> FilePath -> IO () removeDirectoryRecursiveHack verbosity dir | isWindows = go 1 where isWindows = System.Info.os == "mingw32" - limit = 3 + limit = 5 go :: Int -> IO () go n = do