diff --git a/cabal-install/src/Distribution/Client/CmdInstall.hs b/cabal-install/src/Distribution/Client/CmdInstall.hs index 1215af9fc7b..fea80d59990 100644 --- a/cabal-install/src/Distribution/Client/CmdInstall.hs +++ b/cabal-install/src/Distribution/Client/CmdInstall.hs @@ -112,6 +112,7 @@ import Distribution.Package ( Package (..) , PackageName , mkPackageName + , packageName , unPackageName ) import Distribution.Simple.BuildPaths @@ -775,13 +776,19 @@ getSpecsAndTargetSelectors verbosity reducedVerbosity sourcePkgDb targetSelector localPkgs = sdistize <$> localPackages baseCtx - gatherTargets :: UnitId -> TargetSelector - gatherTargets targetId = TargetPackageNamed pkgName targetFilter - where - targetUnit = Map.findWithDefault (error "cannot find target unit") targetId planMap - PackageIdentifier{..} = packageId targetUnit - - localTargets = map gatherTargets (Map.keys targetsMap) + localTargets = + ordNub + [ gatherTarget pkgName ts + | (unitId, components) <- Map.toList targetsMap + , let pkgName = packageName (planMap Map.! unitId) + , (_, selectors) <- components + , ts <- NE.toList selectors + ] + + gatherTarget pkgName ts = case ts of + TargetComponent{} -> ts + TargetComponentUnknown{} -> ts + _ -> TargetPackageNamed pkgName targetFilter hackagePkgs :: [PackageSpecifier UnresolvedSourcePackage] hackagePkgs = [NamedPackage pn [] | pn <- hackageNames] diff --git a/cabal-testsuite/PackageTests/Install/T8614/cabal.project b/cabal-testsuite/PackageTests/Install/T8614/cabal.project new file mode 100644 index 00000000000..e6fdbadb439 --- /dev/null +++ b/cabal-testsuite/PackageTests/Install/T8614/cabal.project @@ -0,0 +1 @@ +packages: . diff --git a/cabal-testsuite/PackageTests/Install/T8614/cabal.test.hs b/cabal-testsuite/PackageTests/Install/T8614/cabal.test.hs new file mode 100644 index 00000000000..aa0bd365c9a --- /dev/null +++ b/cabal-testsuite/PackageTests/Install/T8614/cabal.test.hs @@ -0,0 +1,21 @@ +import Test.Cabal.Prelude + +import System.Directory (doesFileExist) +import System.FilePath ((<.>), ()) + +-- Test that `cabal install ` installs only the named executable, +-- not every executable in the package. See #8614. +main = cabalTest $ do + env <- getTestEnv + recordMode DoNotRecord $ do + let installdir = testPrefixDir env "bin" + exeExt = if isWindows then "exe" else "" + + cabal "install" + ["example1", "--installdir", installdir, "--overwrite-policy=always"] + + example1Installed <- liftIO $ doesFileExist (installdir "example1" <.> exeExt) + example2Installed <- liftIO $ doesFileExist (installdir "example2" <.> exeExt) + + assertBool "example1 should have been installed" example1Installed + assertBool "example2 should not have been installed" (not example2Installed) diff --git a/cabal-testsuite/PackageTests/Install/T8614/example.cabal b/cabal-testsuite/PackageTests/Install/T8614/example.cabal new file mode 100644 index 00000000000..30282b4af67 --- /dev/null +++ b/cabal-testsuite/PackageTests/Install/T8614/example.cabal @@ -0,0 +1,12 @@ +name: example +version: 1.0 +build-type: Simple +cabal-version: >= 1.2 + +executable example1 + main-is: example1.hs + build-depends: base + +executable example2 + main-is: example2.hs + build-depends: base diff --git a/cabal-testsuite/PackageTests/Install/T8614/example1.hs b/cabal-testsuite/PackageTests/Install/T8614/example1.hs new file mode 100644 index 00000000000..66b666bc153 --- /dev/null +++ b/cabal-testsuite/PackageTests/Install/T8614/example1.hs @@ -0,0 +1,4 @@ +module Main (main) where + +main :: IO () +main = putStrLn "example1" diff --git a/cabal-testsuite/PackageTests/Install/T8614/example2.hs b/cabal-testsuite/PackageTests/Install/T8614/example2.hs new file mode 100644 index 00000000000..754dd951819 --- /dev/null +++ b/cabal-testsuite/PackageTests/Install/T8614/example2.hs @@ -0,0 +1,4 @@ +module Main (main) where + +main :: IO () +main = putStrLn "example2" diff --git a/changelog.d/12277.md b/changelog.d/12277.md new file mode 100644 index 00000000000..5e8a5c827bd --- /dev/null +++ b/changelog.d/12277.md @@ -0,0 +1,29 @@ +--- +synopsis: Fix `cabal install ` installing every executable in the package +packages: [cabal-install] +prs: 12277 +issues: 8614 +--- + +`cabal install` used to collapse a component target into a whole-package target, +so naming a single executable would build and install *all* of the package's +executables: + +```diff + $ cabal install example1 --installdir /tmp/executables + Symlinking 'example1' to '/tmp/executables/example1' +- Symlinking 'example2' to '/tmp/executables/example2' +``` + +Now the component selector is preserved and only the requested executable is +installed: + +```diff + $ cabal install example1 --installdir /tmp/executables + Symlinking 'example1' to '/tmp/executables/example1' +``` + +This applies to every way of naming a single executable (`example1`, +`exe:example1`, `example:exe:example1`). Whole-package targets such as a bare +package name, `all` or `all:exes` keep their existing behaviour and continue to +install all of the package's executables.