From 460375d72e6e080e17d0afa60144fa7f445a6883 Mon Sep 17 00:00:00 2001
From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com>
Date: Tue, 5 Nov 2024 21:27:18 +0000
Subject: [PATCH] fix `cabal install --program-suffix/prefix` (fix #10290 and
 #10476) (#10483) (#10510)

* fix cabal install --program-suffix/prefix (fix #10290 and #10476)

When checking for existing installations, cabal would not account for
an affix (suffix or prefix). So, if you had a `hello` binary installed, installing a
second one with a non-empty affix (a perfectly legal operation) would
fail.

The reason seemed to be a typo in
09c04e9aca5de2ca391eb859a5b295fdd617f5c6, which passed the arguments to
the Symlink structure in a wrong order.

When failing to install a binary because of an existing one, cabal would
report suffix-less existing target even if a suffix was set.

* Add regression tests for overwrite policies and porgram-affixes

Add regression tests for the `program-prefix` and `program-suffix` flags
combined with the overwrite-policy.
In short, the overwrite-policy needs to take potential program affixes
into account when deciding whether it will need to overwrite a program
path during installation.

---------

Co-authored-by: Fendor <fendor@posteo.de>
(cherry picked from commit ee3c31398c776ce210fd6bbb5000a7532ea43c31)

Co-authored-by: Artem Pelenitsyn <a.pelenitsyn@gmail.com>
---
 .../src/Distribution/Client/CmdInstall.hs     |   4 +-
 .../ProgramAffixes/overwrite-policy.out       | 160 ++++++++++++++++++
 .../ProgramAffixes/overwrite-policy.test.hs   |  66 ++++++++
 3 files changed, 228 insertions(+), 2 deletions(-)
 create mode 100644 cabal-testsuite/PackageTests/Install/ProgramAffixes/overwrite-policy.out
 create mode 100644 cabal-testsuite/PackageTests/Install/ProgramAffixes/overwrite-policy.test.hs

diff --git a/cabal-install/src/Distribution/Client/CmdInstall.hs b/cabal-install/src/Distribution/Client/CmdInstall.hs
index 210ac78ca0..1587b20a44 100644
--- a/cabal-install/src/Distribution/Client/CmdInstall.hs
+++ b/cabal-install/src/Distribution/Client/CmdInstall.hs
@@ -1126,8 +1126,8 @@ symlink
       overwritePolicy
       installDir
       (mkSourceBinDir unit)
-      (mkExeName exe)
       (mkFinalExeName exe)
+      (mkExeName exe)
 
 -- |
 -- -- * When 'InstallCheckOnly', warn if install would fail overwrite policy
@@ -1172,7 +1172,7 @@ installCheckUnitExes
       errorMessage installdir exe = case overwritePolicy of
         NeverOverwrite ->
           "Path '"
-            <> (installdir </> prettyShow exe)
+            <> (installdir </> mkFinalExeName exe)
             <> "' already exists. "
             <> "Use --overwrite-policy=always to overwrite."
         -- This shouldn't even be possible, but we keep it in case symlinking or
diff --git a/cabal-testsuite/PackageTests/Install/ProgramAffixes/overwrite-policy.out b/cabal-testsuite/PackageTests/Install/ProgramAffixes/overwrite-policy.out
new file mode 100644
index 0000000000..cf1c952c9d
--- /dev/null
+++ b/cabal-testsuite/PackageTests/Install/ProgramAffixes/overwrite-policy.out
@@ -0,0 +1,160 @@
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Build profile: -w ghc-<GHCVER> -O1
+In order, the following will be built:
+ - p-1.0 (exe:p) (requires build)
+Configuring p-1.0...
+Preprocessing executable 'p' for p-1.0...
+Building executable 'p' for p-1.0...
+Installing executable p in <PATH>
+Warning: The directory <ROOT>/overwrite-policy.dist/home/.cabal/store/ghc-<GHCVER>/incoming/new-<RAND><ROOT>/overwrite-policy.dist/home/.cabal/store/ghc-<GHCVER>/<PACKAGE>-<HASH>/bin is not in the system search path.
+Symlinking 'p' to '<ROOT>/overwrite-policy.dist/usr/bin/p'
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Error: [Cabal-7149]
+Path '<ROOT>/overwrite-policy.dist/usr/bin/p' already exists. Use --overwrite-policy=always to overwrite.
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Symlinking 'p' to '<ROOT>/overwrite-policy.dist/usr/bin/p'
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Symlinking 'p' to '<ROOT>/overwrite-policy.dist/usr/bin/p'
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Symlinking 'p' to '<ROOT>/overwrite-policy.dist/usr/bin/p-my-suffix'
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Error: [Cabal-7149]
+Path '<ROOT>/overwrite-policy.dist/usr/bin/p-my-suffix' already exists. Use --overwrite-policy=always to overwrite.
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Symlinking 'p' to '<ROOT>/overwrite-policy.dist/usr/bin/p-my-suffix'
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Symlinking 'p' to '<ROOT>/overwrite-policy.dist/usr/bin/p'
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Symlinking 'p' to '<ROOT>/overwrite-policy.dist/usr/bin/p'
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Symlinking 'p' to '<ROOT>/overwrite-policy.dist/usr/bin/my-prefix-p'
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Error: [Cabal-7149]
+Path '<ROOT>/overwrite-policy.dist/usr/bin/my-prefix-p' already exists. Use --overwrite-policy=always to overwrite.
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Symlinking 'p' to '<ROOT>/overwrite-policy.dist/usr/bin/my-prefix-p'
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Symlinking 'p' to '<ROOT>/overwrite-policy.dist/usr/bin/p'
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Symlinking 'p' to '<ROOT>/overwrite-policy.dist/usr/bin/p'
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Symlinking 'p' to '<ROOT>/overwrite-policy.dist/usr/bin/my-prefix-p-my-suffix'
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Error: [Cabal-7149]
+Path '<ROOT>/overwrite-policy.dist/usr/bin/my-prefix-p-my-suffix' already exists. Use --overwrite-policy=always to overwrite.
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Symlinking 'p' to '<ROOT>/overwrite-policy.dist/usr/bin/my-prefix-p-my-suffix'
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Symlinking 'p' to '<ROOT>/overwrite-policy.dist/usr/bin/p'
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Copying 'p' to '<ROOT>/overwrite-policy.dist/usr/bin/p'
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Error: [Cabal-7149]
+Path '<ROOT>/overwrite-policy.dist/usr/bin/p' already exists. Use --overwrite-policy=always to overwrite.
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Copying 'p' to '<ROOT>/overwrite-policy.dist/usr/bin/p'
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Copying 'p' to '<ROOT>/overwrite-policy.dist/usr/bin/p'
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Copying 'p' to '<ROOT>/overwrite-policy.dist/usr/bin/p-my-suffix'
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Error: [Cabal-7149]
+Path '<ROOT>/overwrite-policy.dist/usr/bin/p-my-suffix' already exists. Use --overwrite-policy=always to overwrite.
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Copying 'p' to '<ROOT>/overwrite-policy.dist/usr/bin/p-my-suffix'
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Copying 'p' to '<ROOT>/overwrite-policy.dist/usr/bin/p'
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Copying 'p' to '<ROOT>/overwrite-policy.dist/usr/bin/p'
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Copying 'p' to '<ROOT>/overwrite-policy.dist/usr/bin/my-prefix-p'
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Error: [Cabal-7149]
+Path '<ROOT>/overwrite-policy.dist/usr/bin/my-prefix-p' already exists. Use --overwrite-policy=always to overwrite.
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Copying 'p' to '<ROOT>/overwrite-policy.dist/usr/bin/my-prefix-p'
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Copying 'p' to '<ROOT>/overwrite-policy.dist/usr/bin/p'
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Copying 'p' to '<ROOT>/overwrite-policy.dist/usr/bin/p'
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Copying 'p' to '<ROOT>/overwrite-policy.dist/usr/bin/my-prefix-p-my-suffix'
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Error: [Cabal-7149]
+Path '<ROOT>/overwrite-policy.dist/usr/bin/my-prefix-p-my-suffix' already exists. Use --overwrite-policy=always to overwrite.
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Copying 'p' to '<ROOT>/overwrite-policy.dist/usr/bin/my-prefix-p-my-suffix'
+# cabal install
+Wrote tarball sdist to <ROOT>/overwrite-policy.dist/work/./dist/sdist/p-1.0.tar.gz
+Resolving dependencies...
+Copying 'p' to '<ROOT>/overwrite-policy.dist/usr/bin/p'
diff --git a/cabal-testsuite/PackageTests/Install/ProgramAffixes/overwrite-policy.test.hs b/cabal-testsuite/PackageTests/Install/ProgramAffixes/overwrite-policy.test.hs
new file mode 100644
index 0000000000..f94fa43638
--- /dev/null
+++ b/cabal-testsuite/PackageTests/Install/ProgramAffixes/overwrite-policy.test.hs
@@ -0,0 +1,66 @@
+import Test.Cabal.Prelude
+import System.FilePath ((</>))
+import System.Directory (removeFile)
+
+main = cabalTest $ do
+  runTestForInstallMethod "symlink"
+  runTestForInstallMethod "copy"
+
+runTestForInstallMethod :: String -> TestM ()
+runTestForInstallMethod method = do
+  env <- getTestEnv
+  let installdir = testPrefixDir env </> "bin"
+
+  -- install the binary, don't overwrite anything
+  cabal "install"
+    ["p", "--installdir", installdir, "--install-method", method, "--overwrite-policy", "never"]
+  -- install the binary, don't overwrite anything
+  fails $ cabal "install"
+    ["p", "--installdir", installdir, "--install-method", method, "--overwrite-policy", "never"]
+  -- install the binary again, forcing an overwrite, should succeed.
+  cabal "install"
+    ["p", "--installdir", installdir, "--install-method", method, "--overwrite-policy", "always"]
+  -- remove the installed binary.
+  liftIO $ removeFile (installdir </> "p"  <.> exeExt)
+
+  testPolicyForAffix installdir method ["--program-suffix", "-my-suffix"]
+  testPolicyForAffix installdir method ["--program-prefix", "my-prefix-"]
+  testPolicyForAffix installdir method ["--program-prefix", "my-prefix-", "--program-suffix", "-my-suffix"]
+  -- remove the installed binaries.
+  liftIO $ removeFile (installdir </> "p" <.> exeExt)
+  liftIO $ removeFile (installdir </> "p-my-suffix" <.> exeExt)
+  liftIO $ removeFile (installdir </> "my-prefix-p" <.> exeExt)
+  liftIO $ removeFile (installdir </> "my-prefix-p-my-suffix" <.> exeExt)
+
+-- | Run a policy test for a given 'install-method' and program-affix
+-- (i.e., '--program-suffix' or '--program-prefix').
+--
+-- When a program affix is given, the installation should not be affected
+-- by installing the binary with no affix and vice-versa.
+-- So, installing the program without any affix is not affected by installations with
+-- some program affix.
+testPolicyForAffix :: FilePath -> String -> [String] -> TestM ()
+testPolicyForAffix installdir method affixArgs = do
+  -- install the binary again, forcing an overwrite, must succeed.
+  -- The rest of this test assumes the binary has been installed before.
+  cabal "install"
+    ["p", "--installdir", installdir, "--install-method", method, "--overwrite-policy", "always"]
+
+  -- Install the binary with some program affix, don't need overwrite anything
+  cabal "install"
+    (["p", "--installdir", installdir, "--install-method", method, "--overwrite-policy", "never"] ++ affixArgs)
+  -- Once the binary is installed, we can't overwrite it unless we are told so.
+  fails $ cabal "install"
+    (["p", "--installdir", installdir, "--install-method", method, "--overwrite-policy", "never"] ++ affixArgs)
+  -- Successfully overwrite the binary if told so.
+  cabal "install"
+    (["p", "--installdir", installdir, "--install-method", method, "--overwrite-policy", "always"] ++ affixArgs)
+
+  -- remove the installed binary.
+  liftIO $ removeFile (installdir </> "p" <.> exeExt)
+  -- Make sure we can still install the original program with no program affix without overwriting,
+  -- even though, the program is already installed with some affix.
+  cabal "install"
+    ["p", "--installdir", installdir, "--install-method", method, "--overwrite-policy", "never"]
+
+exeExt = if isWindows then "exe" else ""
-- 
GitLab