diff --git a/cabal-install/src/Distribution/Client/CmdListBin.hs b/cabal-install/src/Distribution/Client/CmdListBin.hs index 7a91f87b971..d5787810936 100644 --- a/cabal-install/src/Distribution/Client/CmdListBin.hs +++ b/cabal-install/src/Distribution/Client/CmdListBin.hs @@ -6,6 +6,14 @@ module Distribution.Client.CmdListBin ( listbinCommand, listbinAction, + + -- * Internals exposed for testing + selectPackageTargets, + selectComponentTarget, + noComponentsProblem, + matchesMultipleProblem, + multipleTargetsProblem, + componentNotRightKindProblem ) where import Distribution.Client.Compat.Prelude @@ -210,17 +218,21 @@ selectPackageTargets :: TargetSelector -> [AvailableTarget k] -> Either ListBinTargetProblem [k] selectPackageTargets targetSelector targets - -- If there is exactly one buildable executable then we select that + -- If there is a single executable component, select that. See #7403 | [target] <- targetsExesBuildable + = Right [target] + + -- Otherwise, if there is a single executable-like component left, select that. + | [target] <- targetsExeLikesBuildable = Right [target] -- but fail if there are multiple buildable executables. - | not (null targetsExesBuildable) - = Left (matchesMultipleProblem targetSelector targetsExesBuildable') + | not (null targetsExeLikesBuildable) + = Left (matchesMultipleProblem targetSelector targetsExeLikesBuildable') -- If there are executables but none are buildable then we report those - | not (null targetsExes) - = Left (TargetProblemNoneEnabled targetSelector targetsExes) + | not (null targetsExeLikes') + = Left (TargetProblemNoneEnabled targetSelector targetsExeLikes') -- If there are no executables but some other targets then we report that | not (null targets) @@ -230,14 +242,19 @@ selectPackageTargets targetSelector targets | otherwise = Left (TargetProblemNoTargets targetSelector) where - -- Targets that can be executed - targetsExecutableLike = - concatMap (\kind -> filterTargetsKind kind targets) - [ExeKind, TestKind, BenchKind] - (targetsExesBuildable, - targetsExesBuildable') = selectBuildableTargets' targetsExecutableLike - - targetsExes = forgetTargetsDetail targetsExecutableLike + -- Targets that are precisely executables + targetsExes = filterTargetsKind ExeKind targets + targetsExesBuildable = selectBuildableTargets targetsExes + + -- Any target that could be executed + targetsExeLikes = targetsExes + ++ filterTargetsKind TestKind targets + ++ filterTargetsKind BenchKind targets + + (targetsExeLikesBuildable, + targetsExeLikesBuildable') = selectBuildableTargets' targetsExeLikes + + targetsExeLikes' = forgetTargetsDetail targetsExeLikes -- | For a 'TargetComponent' 'TargetSelector', check if the component can be diff --git a/cabal-install/src/Distribution/Client/CmdRun.hs b/cabal-install/src/Distribution/Client/CmdRun.hs index ac4783554fb..38da29108db 100644 --- a/cabal-install/src/Distribution/Client/CmdRun.hs +++ b/cabal-install/src/Distribution/Client/CmdRun.hs @@ -121,8 +121,9 @@ runCommand = CommandUI ++ "Any executable-like component in any package in the project can be " ++ "specified. A package can be specified if contains just one " - ++ "executable-like. The default is to use the package in the current " - ++ "directory if it contains just one executable-like.\n\n" + ++ "executable-like, preferring a single executable. The default is to " + ++ "use the package in the current directory if it contains just one " + ++ "executable-like.\n\n" ++ "Extra arguments can be passed to the program, but use '--' to " ++ "separate arguments for the program from arguments for " ++ pname @@ -480,17 +481,21 @@ selectPackageTargets :: TargetSelector -> [AvailableTarget k] -> Either RunTargetProblem [k] selectPackageTargets targetSelector targets - -- If there is exactly one buildable executable then we select that + -- If there is a single executable component, select that. See #7403 | [target] <- targetsExesBuildable = Right [target] - -- but fail if there are multiple buildable executables. - | not (null targetsExesBuildable) - = Left (matchesMultipleProblem targetSelector targetsExesBuildable') + -- Otherwise, if there is a single executable-like component left, select that. + | [target] <- targetsExeLikesBuildable + = Right [target] + + -- but fail if there are multiple buildable executables. + | not (null targetsExeLikesBuildable) + = Left (matchesMultipleProblem targetSelector targetsExeLikesBuildable') -- If there are executables but none are buildable then we report those - | not (null targetsExes) - = Left (TargetProblemNoneEnabled targetSelector targetsExes) + | not (null targetsExeLikes') + = Left (TargetProblemNoneEnabled targetSelector targetsExeLikes') -- If there are no executables but some other targets then we report that | not (null targets) @@ -500,14 +505,19 @@ selectPackageTargets targetSelector targets | otherwise = Left (TargetProblemNoTargets targetSelector) where - -- Targets that can be executed - targetsExecutableLike = - concatMap (\kind -> filterTargetsKind kind targets) - [ExeKind, TestKind, BenchKind] - (targetsExesBuildable, - targetsExesBuildable') = selectBuildableTargets' targetsExecutableLike - - targetsExes = forgetTargetsDetail targetsExecutableLike + -- Targets that are precisely executables + targetsExes = filterTargetsKind ExeKind targets + targetsExesBuildable = selectBuildableTargets targetsExes + + -- Any target that could be executed + targetsExeLikes = targetsExes + ++ filterTargetsKind TestKind targets + ++ filterTargetsKind BenchKind targets + + (targetsExeLikesBuildable, + targetsExeLikesBuildable') = selectBuildableTargets' targetsExeLikes + + targetsExeLikes' = forgetTargetsDetail targetsExeLikes -- | For a 'TargetComponent' 'TargetSelector', check if the component can be diff --git a/cabal-install/tests/IntegrationTests2.hs b/cabal-install/tests/IntegrationTests2.hs index 4ca346930ed..a2879a6f2be 100644 --- a/cabal-install/tests/IntegrationTests2.hs +++ b/cabal-install/tests/IntegrationTests2.hs @@ -43,6 +43,7 @@ import qualified Distribution.Client.CmdRun as CmdRun import qualified Distribution.Client.CmdTest as CmdTest import qualified Distribution.Client.CmdBench as CmdBench import qualified Distribution.Client.CmdHaddock as CmdHaddock +import qualified Distribution.Client.CmdListBin as CmdListBin import Distribution.Package import Distribution.PackageDescription @@ -109,6 +110,7 @@ tests config = , testCaseSteps "problems (build)" (testTargetProblemsBuild config) , testCaseSteps "problems (repl)" (testTargetProblemsRepl config) , testCaseSteps "problems (run)" (testTargetProblemsRun config) + , testCaseSteps "problems (list-bin)" (testTargetProblemsListBin config) , testCaseSteps "problems (test)" (testTargetProblemsTest config) , testCaseSteps "problems (bench)" (testTargetProblemsBench config) , testCaseSteps "problems (haddock)" (testTargetProblemsHaddock config) @@ -861,9 +863,85 @@ testTargetProblemsRepl config reportSubCase = do [ TargetPackage TargetExplicitNamed ["p-0.1"] (Just BenchKind) ] [ ("p-0.1-inplace-a-benchmark", CBenchName "a-benchmark") ] +testTargetProblemsListBin :: ProjectConfig -> (String -> IO ()) -> Assertion +testTargetProblemsListBin config reportSubCase = do + reportSubCase "one-of-each" + do (_,elaboratedPlan,_) <- planProject "targets/one-of-each" config + assertProjectDistinctTargets + elaboratedPlan + CmdListBin.selectPackageTargets + CmdListBin.selectComponentTarget + [ TargetPackage TargetExplicitNamed ["p-0.1"] Nothing + ] + [ ("p-0.1-inplace-p1", CExeName "p1") + ] + + reportSubCase "multiple-exes" + assertProjectTargetProblems + "targets/multiple-exes" config + CmdListBin.selectPackageTargets + CmdListBin.selectComponentTarget + [ ( flip CmdListBin.matchesMultipleProblem + [ AvailableTarget "p-0.1" (CExeName "p2") + (TargetBuildable () TargetRequestedByDefault) True + , AvailableTarget "p-0.1" (CExeName "p1") + (TargetBuildable () TargetRequestedByDefault) True + ] + , mkTargetPackage "p-0.1" ) + ] + + reportSubCase "multiple targets" + do (_,elaboratedPlan,_) <- planProject "targets/multiple-exes" config + assertProjectDistinctTargets + elaboratedPlan + CmdListBin.selectPackageTargets + CmdListBin.selectComponentTarget + [ mkTargetComponent "p-0.1" (CExeName "p1") + , mkTargetComponent "p-0.1" (CExeName "p2") + ] + [ ("p-0.1-inplace-p1", CExeName "p1") + , ("p-0.1-inplace-p2", CExeName "p2") + ] + + reportSubCase "exes-disabled" + assertProjectTargetProblems + "targets/exes-disabled" config + CmdListBin.selectPackageTargets + CmdListBin.selectComponentTarget + [ ( flip TargetProblemNoneEnabled + [ AvailableTarget "p-0.1" (CExeName "p") TargetNotBuildable True + ] + , mkTargetPackage "p-0.1" ) + ] + + reportSubCase "empty-pkg" + assertProjectTargetProblems + "targets/empty-pkg" config + CmdListBin.selectPackageTargets + CmdListBin.selectComponentTarget + [ ( TargetProblemNoTargets, mkTargetPackage "p-0.1" ) + ] + + reportSubCase "lib-only" + assertProjectTargetProblems + "targets/lib-only" config + CmdListBin.selectPackageTargets + CmdListBin.selectComponentTarget + [ (CmdListBin.noComponentsProblem, mkTargetPackage "p-0.1" ) + ] testTargetProblemsRun :: ProjectConfig -> (String -> IO ()) -> Assertion testTargetProblemsRun config reportSubCase = do + reportSubCase "one-of-each" + do (_,elaboratedPlan,_) <- planProject "targets/one-of-each" config + assertProjectDistinctTargets + elaboratedPlan + CmdRun.selectPackageTargets + CmdRun.selectComponentTarget + [ TargetPackage TargetExplicitNamed ["p-0.1"] Nothing + ] + [ ("p-0.1-inplace-p1", CExeName "p1") + ] reportSubCase "multiple-exes" assertProjectTargetProblems diff --git a/cabal-install/tests/IntegrationTests2/targets/one-of-each/cabal.project b/cabal-install/tests/IntegrationTests2/targets/one-of-each/cabal.project new file mode 100644 index 00000000000..6f920794c80 --- /dev/null +++ b/cabal-install/tests/IntegrationTests2/targets/one-of-each/cabal.project @@ -0,0 +1 @@ +packages: ./ diff --git a/cabal-install/tests/IntegrationTests2/targets/one-of-each/p.cabal b/cabal-install/tests/IntegrationTests2/targets/one-of-each/p.cabal new file mode 100644 index 00000000000..f93171a8b87 --- /dev/null +++ b/cabal-install/tests/IntegrationTests2/targets/one-of-each/p.cabal @@ -0,0 +1,26 @@ +name: p +version: 0.1 +build-type: Simple +cabal-version: >= 1.10 + +executable p1 + main-is: P1.hs + build-depends: base + +benchmark p2 + type: exitcode-stdio-1.0 + main-is: P2.hs + build-depends: base + +test-suite p3 + type: exitcode-stdio-1.0 + main-is: P3.hs + build-depends: base + +library p4 + exposed-modules: P4 + build-depends: base + +foreign-library libp + type: native-shared + other-modules: FLib diff --git a/doc/cabal-commands.rst b/doc/cabal-commands.rst index 58f36d4b276..b3c9abb2f1e 100644 --- a/doc/cabal-commands.rst +++ b/doc/cabal-commands.rst @@ -194,6 +194,19 @@ Tests and benchmarks are also treated as executables. See `the v2-build section <#cabal-v2-build>`__ for the target syntax. +When ``TARGET`` is one of the following: + +- A component target: execute the specified executable, benchmark or test suite + +- A package target: + 1. If the package has exactly one executable component, it will be selected. + 2. If the package has multiple executable components, an error is raised. + 3. If the package has exactly one test or benchmark component, it will be selected. + 4. Otherwise an issue is raised + +- Empty target: Same as package target, implicitly using the package from the current + working directory. + Except in the case of the empty target, the strings after it will be passed to the executable as arguments.