diff --git a/Cabal/Distribution/Simple/BuildTarget.hs b/Cabal/Distribution/Simple/BuildTarget.hs index 0b367db9ac8..8d1ce3687c1 100644 --- a/Cabal/Distribution/Simple/BuildTarget.hs +++ b/Cabal/Distribution/Simple/BuildTarget.hs @@ -553,7 +553,7 @@ ex_cs = -- data ComponentKind = LibKind | FLibKind | ExeKind | TestKind | BenchKind - deriving (Eq, Ord, Show) + deriving (Eq, Ord, Show, Enum, Bounded) componentKind :: ComponentName -> ComponentKind componentKind (CLibName _) = LibKind diff --git a/cabal-install/Distribution/Client/CmdListBin.hs b/cabal-install/Distribution/Client/CmdListBin.hs new file mode 100644 index 00000000000..8b327777f65 --- /dev/null +++ b/cabal-install/Distribution/Client/CmdListBin.hs @@ -0,0 +1,189 @@ +{-# LANGUAGE MultiWayIf #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE TupleSections #-} +module Distribution.Client.CmdListBin ( + listbinCommand, + listbinAction, +) where + +import Distribution.Client.Compat.Prelude +import Prelude () + +import Distribution.Client.DistDirLayout (DistDirLayout (..), ProjectRoot (..)) +import Distribution.Client.NixStyleOptions + (NixStyleFlags (..), defaultNixStyleFlags, nixStyleOptions) +import Distribution.Client.ProjectConfig + (ProjectConfig, projectConfigConfigFile, projectConfigShared, withProjectOrGlobalConfig) +import Distribution.Client.ProjectFlags (ProjectFlags (..)) +import Distribution.Client.ProjectOrchestration +import Distribution.Client.ProjectPlanning.Types +import Distribution.Client.Setup (GlobalFlags (..)) +import Distribution.Simple.BuildPaths (dllExtension, exeExtension) +import Distribution.Simple.Command (CommandUI (..)) +import Distribution.Simple.Setup (configVerbosity, fromFlagOrDefault) +import Distribution.Simple.Utils (die', wrapText) +import Distribution.System (Platform) +import Distribution.Verbosity (silent, verboseStderr) +import System.Directory (getCurrentDirectory) +import System.FilePath ((<.>), ()) + +import qualified Data.Map as Map +import qualified Distribution.Client.InstallPlan as IP +import qualified Distribution.Client.SingleCompTargetProblem as SCTP +import qualified Distribution.Simple.InstallDirs as InstallDirs +import qualified Distribution.Solver.Types.ComponentDeps as CD + +------------------------------------------------------------------------------- +-- Command +------------------------------------------------------------------------------- + +listbinCommand :: CommandUI (NixStyleFlags ()) +listbinCommand = CommandUI + { commandName = "list-bin" + , commandSynopsis = "list path to a single executable." + , commandUsage = \pname -> + "Usage: " ++ pname ++ " list-bin [FLAGS] TARGET\n" + , commandDescription = Just $ \_ -> wrapText + "List path to a build product." + , commandNotes = Nothing + , commandDefaultFlags = defaultNixStyleFlags () + , commandOptions = nixStyleOptions (const []) + } + +------------------------------------------------------------------------------- +-- Action +------------------------------------------------------------------------------- + +listbinAction :: NixStyleFlags () -> [String] -> GlobalFlags -> IO () +listbinAction flags@NixStyleFlags{..} args globalFlags = do + -- fail early if multiple target selectors specified + target <- case args of + [] -> die' verbosity "One target is required, none provided" + [x] -> return x + _ -> die' verbosity "One target is required, given multiple" + + -- configure + (baseCtx, distDirLayout) <- withProjectOrGlobalConfig verbosity ignoreProject globalConfigFlag withProject withoutProject + let localPkgs = localPackages baseCtx + + -- elaborate target selectors + targetSelectors <- either (reportTargetSelectorProblems verbosity) return + =<< readTargetSelectors localPkgs Nothing [target] + + buildCtx <- + runProjectPreBuildPhase verbosity baseCtx $ \elaboratedPlan -> do + -- Interpret the targets on the command line as build targets + -- (as opposed to say repl or haddock targets). + targets <- either (SCTP.reportTargetProblems verbosity "list-bin") return + $ resolveTargets + selectPackageTargets + selectComponentTarget + elaboratedPlan + Nothing + targetSelectors + + -- Reject multiple targets, or at least targets in different + -- components. It is ok to have two module/file targets in the + -- same component, but not two that live in different components. + -- + -- Note that we discard the target and return the whole 'TargetsMap', + -- so this check will be repeated (and must succeed) after + -- the 'runProjectPreBuildPhase'. Keep it in mind when modifying this. + _ <- SCTP.singleComponentOrElse componentKindsListBin + (SCTP.reportTargetProblems + verbosity + "list-bin" + [SCTP.multipleTargetsProblem targets]) + targets + + let elaboratedPlan' = pruneInstallPlanToTargets + TargetActionBuild + targets + elaboratedPlan + return (elaboratedPlan', targets) + + (selectedUnitId, _selectedComponent) <- + -- Slight duplication with 'runProjectPreBuildPhase'. + SCTP.singleComponentOrElse componentKindsListBin + (die' verbosity $ "No or multiple targets given, but the run " + ++ "phase has been reached. This is a bug.") + $ targetsMap buildCtx + + printPlan verbosity baseCtx buildCtx + + binfiles <- case Map.lookup selectedUnitId $ IP.toMap (elaboratedPlanOriginal buildCtx) of + Nothing -> die' verbosity "No or multiple targets given..." + Just gpp -> return $ IP.foldPlanPackage + (const []) -- IPI don't have executables + (elaboratedPackage distDirLayout (elaboratedShared buildCtx)) + gpp + + case binfiles of + [exe] -> putStrLn exe + _ -> die' verbosity "No or multiple targets given" + where + defaultVerbosity = verboseStderr silent + verbosity = fromFlagOrDefault defaultVerbosity (configVerbosity configFlags) + ignoreProject = flagIgnoreProject projectFlags + prjConfig = commandLineFlagsToProjectConfig globalFlags flags mempty -- ClientInstallFlags, not needed here + globalConfigFlag = projectConfigConfigFile (projectConfigShared prjConfig) + + withProject :: IO (ProjectBaseContext, DistDirLayout) + withProject = do + baseCtx <- establishProjectBaseContext verbosity prjConfig OtherCommand + return (baseCtx, distDirLayout baseCtx) + + withoutProject :: ProjectConfig -> IO (ProjectBaseContext, DistDirLayout) + withoutProject config = do + cwd <- getCurrentDirectory + baseCtx <- establishProjectBaseContextWithRoot verbosity (config <> prjConfig) (ProjectRootImplicit cwd) OtherCommand + return (baseCtx, distDirLayout baseCtx) + + -- this is copied from + elaboratedPackage + :: DistDirLayout + -> ElaboratedSharedConfig + -> ElaboratedConfiguredPackage + -> [FilePath] + elaboratedPackage distDirLayout elaboratedSharedConfig elab = case elabPkgOrComp elab of + ElabPackage pkg -> + [ bin + | (c, _) <- CD.toList $ CD.zip (pkgLibDependencies pkg) + (pkgExeDependencies pkg) + , bin <- bin_file c + ] + ElabComponent comp -> bin_file (compSolverName comp) + where + dist_dir = distBuildDirectory distDirLayout (elabDistDirParams elaboratedSharedConfig elab) + + bin_file c = case c of + CD.ComponentExe s -> [bin_file' s] + CD.ComponentTest s -> [bin_file' s] + CD.ComponentBench s -> [bin_file' s] + CD.ComponentFLib s -> [flib_file' s] + _ -> [] + + plat :: Platform + plat = pkgConfigPlatform elaboratedSharedConfig + + bin_file' s = + if elabBuildStyle elab == BuildInplaceOnly + then dist_dir "build" prettyShow s prettyShow s <.> exeExtension plat + else InstallDirs.bindir (elabInstallDirs elab) prettyShow s <.> exeExtension plat + + flib_file' s = + if elabBuildStyle elab == BuildInplaceOnly + then dist_dir "build" prettyShow s ("lib" ++ prettyShow s) <.> dllExtension plat + else InstallDirs.bindir (elabInstallDirs elab) ("lib" ++ prettyShow s) <.> dllExtension plat + +-- | Component kinds we can list-bin +componentKindsListBin :: [ComponentKind] +componentKindsListBin = [ExeKind, TestKind, BenchKind, FLibKind] + +selectPackageTargets :: TargetSelector -> [AvailableTarget k] -> Either SCTP.SingleCompTargetProblem [k] +selectPackageTargets = SCTP.selectPackageTargets componentKindsListBin + +selectComponentTarget :: SubComponentTarget -> AvailableTarget k -> Either SCTP.SingleCompTargetProblem k +selectComponentTarget = SCTP.selectComponentTarget componentKindsListBin diff --git a/cabal-install/Distribution/Client/CmdRun.hs b/cabal-install/Distribution/Client/CmdRun.hs index 3d98380f04c..3efc612aaf0 100644 --- a/cabal-install/Distribution/Client/CmdRun.hs +++ b/cabal-install/Distribution/Client/CmdRun.hs @@ -10,26 +10,15 @@ module Distribution.Client.CmdRun ( runCommand, runAction, handleShebang, validScript, - - -- * Internals exposed for testing - matchesMultipleProblem, - noExesProblem, + -- * Exposed for testing selectPackageTargets, - selectComponentTarget + selectComponentTarget, ) where import Prelude () import Distribution.Client.Compat.Prelude hiding (toList) import Distribution.Client.ProjectOrchestration -import Distribution.Client.CmdErrorMessages - ( renderTargetSelector, showTargetSelector, - renderTargetProblem, - renderTargetProblemNoTargets, plural, targetSelectorPluralPkgs, - targetSelectorFilter, renderListCommaAnd ) -import Distribution.Client.TargetProblem - ( TargetProblem (..) ) - import Distribution.Client.NixStyleOptions ( NixStyleFlags (..), nixStyleOptions, defaultNixStyleFlags ) import Distribution.Client.Setup @@ -40,13 +29,11 @@ import Distribution.Simple.Flag ( fromFlagOrDefault ) import Distribution.Simple.Command ( CommandUI(..), usageAlternatives ) -import Distribution.Types.ComponentName - ( showComponentName ) import Distribution.CabalSpecVersion (CabalSpecVersion (..), cabalSpecLatest) import Distribution.Verbosity ( normal ) import Distribution.Simple.Utils - ( wrapText, warn, die', ordNub, info + ( wrapText, warn, die', info , createTempDirectory, handleDoesNotExist ) import Distribution.Client.ProjectConfig ( ProjectConfig(..), ProjectConfigShared(..) @@ -62,8 +49,6 @@ import Distribution.Client.TargetSelector ( TargetSelectorProblem(..), TargetString(..) ) import Distribution.Client.InstallPlan ( toList, foldPlanPackage ) -import Distribution.Types.UnqualComponentName - ( UnqualComponentName, unUnqualComponentName ) import Distribution.Simple.Program.Run ( runProgramInvocation, ProgramInvocation(..), emptyProgramInvocation ) @@ -100,9 +85,9 @@ import Distribution.Types.PackageName.Magic import Language.Haskell.Extension ( Language(..) ) +import qualified Distribution.Client.SingleCompTargetProblem as SCTP + import qualified Data.ByteString.Char8 as BS -import qualified Data.Map as Map -import qualified Data.Set as Set import qualified Text.Parsec as P import System.Directory ( getTemporaryDirectory, removeDirectoryRecursive, doesFileExist ) @@ -204,7 +189,7 @@ runAction flags@NixStyleFlags {..} targetStrings globalFlags = do -- Interpret the targets on the command line as build targets -- (as opposed to say repl or haddock targets). - targets <- either (reportTargetProblems verbosity) return + targets <- either (SCTP.reportTargetProblems verbosity "run") return $ resolveTargets selectPackageTargets selectComponentTarget @@ -219,10 +204,11 @@ runAction flags@NixStyleFlags {..} targetStrings globalFlags = do -- Note that we discard the target and return the whole 'TargetsMap', -- so this check will be repeated (and must succeed) after -- the 'runProjectPreBuildPhase'. Keep it in mind when modifying this. - _ <- singleExeOrElse - (reportTargetProblems + _ <- SCTP.singleComponentOrElse componentKindsRun + (SCTP.reportTargetProblems verbosity - [multipleTargetsProblem targets]) + "run" + [SCTP.multipleTargetsProblem targets]) targets let elaboratedPlan' = pruneInstallPlanToTargets @@ -233,7 +219,7 @@ runAction flags@NixStyleFlags {..} targetStrings globalFlags = do (selectedUnitId, selectedComponent) <- -- Slight duplication with 'runProjectPreBuildPhase'. - singleExeOrElse + SCTP.singleComponentOrElse componentKindsRun (die' verbosity $ "No or multiple targets given, but the run " ++ "phase has been reached. This is a bug.") $ targetsMap buildCtx @@ -250,7 +236,7 @@ runAction flags@NixStyleFlags {..} targetStrings globalFlags = do selectedUnitId elaboratedPlan - let exeName = unUnqualComponentName selectedComponent + let exeName = prettyShow selectedComponent -- In the common case, we expect @matchingElaboratedConfiguredPackages@ -- to consist of a single element that provides a single way of building @@ -442,14 +428,6 @@ handleScriptCase verbosity pol baseCtx tmpDir scriptContents = do return (baseCtx', targetSelectors) -singleExeOrElse :: IO (UnitId, UnqualComponentName) -> TargetsMap -> IO (UnitId, UnqualComponentName) -singleExeOrElse action targetsMap = - case Set.toList . distinctTargetComponents $ targetsMap - of [(unitId, CExeName component)] -> return (unitId, component) - [(unitId, CTestName component)] -> return (unitId, component) - [(unitId, CBenchName component)] -> return (unitId, component) - _ -> action - -- | Filter the 'ElaboratedInstallPlan' keeping only the -- 'ElaboratedConfiguredPackage's that match the specified -- 'UnitId'. @@ -465,169 +443,12 @@ matchingPackagesByUnitId uid = else Nothing)) . toList --- | This defines what a 'TargetSelector' means for the @run@ command. --- It selects the 'AvailableTarget's that the 'TargetSelector' refers to, --- or otherwise classifies the problem. --- --- For the @run@ command we select the exe if there is only one and it's --- buildable. Fail if there are no or multiple buildable exe components. --- -selectPackageTargets :: TargetSelector - -> [AvailableTarget k] -> Either RunTargetProblem [k] -selectPackageTargets targetSelector targets - - -- If there is exactly one buildable executable then we select that - | [target] <- targetsExesBuildable - = Right [target] - - -- but fail if there are multiple buildable executables. - | not (null targetsExesBuildable) - = Left (matchesMultipleProblem targetSelector targetsExesBuildable') +-- | Component kinds we can run +componentKindsRun :: [ComponentKind] +componentKindsRun = [ExeKind, TestKind, BenchKind] - -- If there are executables but none are buildable then we report those - | not (null targetsExes) - = Left (TargetProblemNoneEnabled targetSelector targetsExes) +selectPackageTargets :: TargetSelector -> [AvailableTarget k] -> Either SCTP.SingleCompTargetProblem [k] +selectPackageTargets = SCTP.selectPackageTargets componentKindsRun - -- If there are no executables but some other targets then we report that - | not (null targets) - = Left (noExesProblem targetSelector) - - -- If there are no targets at all then we report that - | 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 - - --- | For a 'TargetComponent' 'TargetSelector', check if the component can be --- selected. --- --- For the @run@ command we just need to check it is a executable-like --- (an executable, a test, or a benchmark), in addition --- to the basic checks on being buildable etc. --- -selectComponentTarget :: SubComponentTarget - -> AvailableTarget k -> Either RunTargetProblem k -selectComponentTarget subtarget@WholeComponent t - = case availableTargetComponentName t - of CExeName _ -> component - CTestName _ -> component - CBenchName _ -> component - _ -> Left (componentNotExeProblem pkgid cname) - where pkgid = availableTargetPackageId t - cname = availableTargetComponentName t - component = selectComponentTargetBasic subtarget t - -selectComponentTarget subtarget t - = Left (isSubComponentProblem (availableTargetPackageId t) - (availableTargetComponentName t) - subtarget) - --- | The various error conditions that can occur when matching a --- 'TargetSelector' against 'AvailableTarget's for the @run@ command. --- -data RunProblem = - -- | The 'TargetSelector' matches targets but no executables - TargetProblemNoExes TargetSelector - - -- | A single 'TargetSelector' matches multiple targets - | TargetProblemMatchesMultiple TargetSelector [AvailableTarget ()] - - -- | Multiple 'TargetSelector's match multiple targets - | TargetProblemMultipleTargets TargetsMap - - -- | The 'TargetSelector' refers to a component that is not an executable - | TargetProblemComponentNotExe PackageId ComponentName - - -- | Asking to run an individual file or module is not supported - | TargetProblemIsSubComponent PackageId ComponentName SubComponentTarget - deriving (Eq, Show) - -type RunTargetProblem = TargetProblem RunProblem - -noExesProblem :: TargetSelector -> RunTargetProblem -noExesProblem = CustomTargetProblem . TargetProblemNoExes - -matchesMultipleProblem :: TargetSelector -> [AvailableTarget ()] -> RunTargetProblem -matchesMultipleProblem selector targets = CustomTargetProblem $ - TargetProblemMatchesMultiple selector targets - -multipleTargetsProblem :: TargetsMap -> TargetProblem RunProblem -multipleTargetsProblem = CustomTargetProblem . TargetProblemMultipleTargets - -componentNotExeProblem :: PackageId -> ComponentName -> TargetProblem RunProblem -componentNotExeProblem pkgid name = CustomTargetProblem $ - TargetProblemComponentNotExe pkgid name - -isSubComponentProblem - :: PackageId - -> ComponentName - -> SubComponentTarget - -> TargetProblem RunProblem -isSubComponentProblem pkgid name subcomponent = CustomTargetProblem $ - TargetProblemIsSubComponent pkgid name subcomponent - -reportTargetProblems :: Verbosity -> [RunTargetProblem] -> IO a -reportTargetProblems verbosity = - die' verbosity . unlines . map renderRunTargetProblem - -renderRunTargetProblem :: RunTargetProblem -> String -renderRunTargetProblem (TargetProblemNoTargets targetSelector) = - case targetSelectorFilter targetSelector of - Just kind | kind /= ExeKind - -> "The run command is for running executables, but the target '" - ++ showTargetSelector targetSelector ++ "' refers to " - ++ renderTargetSelector targetSelector ++ "." - - _ -> renderTargetProblemNoTargets "run" targetSelector -renderRunTargetProblem problem = - renderTargetProblem "run" renderRunProblem problem - -renderRunProblem :: RunProblem -> String -renderRunProblem (TargetProblemMatchesMultiple targetSelector targets) = - "The run command is for running a single executable at once. The target '" - ++ showTargetSelector targetSelector ++ "' refers to " - ++ renderTargetSelector targetSelector ++ " which includes " - ++ renderListCommaAnd ( ("the "++) <$> - showComponentName <$> - availableTargetComponentName <$> - foldMap - (\kind -> filterTargetsKind kind targets) - [ExeKind, TestKind, BenchKind] ) - ++ "." - -renderRunProblem (TargetProblemMultipleTargets selectorMap) = - "The run command is for running a single executable at once. The targets " - ++ renderListCommaAnd [ "'" ++ showTargetSelector ts ++ "'" - | ts <- ordNub (concatMap snd (concat (Map.elems selectorMap))) ] - ++ " refer to different executables." - -renderRunProblem (TargetProblemComponentNotExe pkgid cname) = - "The run command is for running executables, but the target '" - ++ showTargetSelector targetSelector ++ "' refers to " - ++ renderTargetSelector targetSelector ++ " from the package " - ++ prettyShow pkgid ++ "." - where - targetSelector = TargetComponent pkgid cname WholeComponent - -renderRunProblem (TargetProblemIsSubComponent pkgid cname subtarget) = - "The run command can only run an executable as a whole, " - ++ "not files or modules within them, but the target '" - ++ showTargetSelector targetSelector ++ "' refers to " - ++ renderTargetSelector targetSelector ++ "." - where - targetSelector = TargetComponent pkgid cname subtarget - -renderRunProblem (TargetProblemNoExes targetSelector) = - "Cannot run the target '" ++ showTargetSelector targetSelector - ++ "' which refers to " ++ renderTargetSelector targetSelector - ++ " because " - ++ plural (targetSelectorPluralPkgs targetSelector) "it does" "they do" - ++ " not contain any executables." +selectComponentTarget :: SubComponentTarget -> AvailableTarget k -> Either SCTP.SingleCompTargetProblem k +selectComponentTarget = SCTP.selectComponentTarget componentKindsRun diff --git a/cabal-install/Distribution/Client/Setup.hs b/cabal-install/Distribution/Client/Setup.hs index 3ed99701be1..81b2709989c 100644 --- a/cabal-install/Distribution/Client/Setup.hs +++ b/cabal-install/Distribution/Client/Setup.hs @@ -198,6 +198,7 @@ globalCommand commands = CommandUI { , "new-install" , "new-clean" , "new-sdist" + , "list-bin" -- v1 commands, stateful style , "v1-build" , "v1-configure" @@ -275,6 +276,7 @@ globalCommand commands = CommandUI { , addCmd "haddock" , addCmd "hscolour" , addCmd "exec" + , addCmd "list-bin" , par , startGroup "new-style projects (forwards-compatible aliases)" , addCmd "v2-build" diff --git a/cabal-install/Distribution/Client/SingleCompTargetProblem.hs b/cabal-install/Distribution/Client/SingleCompTargetProblem.hs new file mode 100644 index 00000000000..9e4c31d142e --- /dev/null +++ b/cabal-install/Distribution/Client/SingleCompTargetProblem.hs @@ -0,0 +1,205 @@ +module Distribution.Client.SingleCompTargetProblem ( + -- * Single component or else + singleComponentOrElse, + -- * SingleCompTargetProblem + SingleCompTargetProblem, + reportTargetProblems, + selectPackageTargets, + selectComponentTarget, + -- * Problem smart constructor + noCompsProblem, + multipleTargetsProblem, + matchesMultipleProblem, +) where + +import Distribution.Client.Compat.Prelude +import Prelude () + +import Distribution.Client.CmdErrorMessages + (componentKind, plural, renderListCommaAnd, renderTargetProblem, + renderTargetProblemNoTargets, renderTargetSelector, showTargetSelector, + targetSelectorFilter, targetSelectorPluralPkgs) +import Distribution.Client.ProjectOrchestration +import Distribution.Client.TargetProblem (TargetProblem (..)) +import Distribution.Simple.Utils (die', ordNub) +import Distribution.Types.UnitId (UnitId) +import Distribution.Types.ComponentName (showComponentName) + +import qualified Data.Set as Set +import qualified Data.Map as Map + +singleComponentOrElse + :: [ComponentKind] + -> IO (UnitId, ComponentName) + -> TargetsMap + -> IO (UnitId, ComponentName) +singleComponentOrElse kinds action tmap = + case Set.toList $ distinctTargetComponents tmap of + [(unitId, component)] + | componentKind component `elem` kinds -> return (unitId, component) + _ -> action + +selectPackageTargets + :: [ComponentKind] + -> TargetSelector + -> [AvailableTarget k] + -> Either SingleCompTargetProblem [k] +selectPackageTargets kinds targetSelector targets + -- If there is exactly one buildable executable then we select that + | [target] <- targetsExesBuildable + = Right [target] + + -- but fail if there are multiple buildable executables. + | not (null targetsExesBuildable) + = Left (matchesMultipleProblem targetSelector targetsExesBuildable') + + -- If there are executables but none are buildable then we report those + | not (null targetsExes) + = Left (TargetProblemNoneEnabled targetSelector targetsExes) + + -- If there are no executables but some other targets then we report that + | not (null targets) + = Left (noCompsProblem targetSelector) + + -- If there are no targets at all then we report that + | otherwise + = Left (TargetProblemNoTargets targetSelector) + where + -- Targets that can be executed + targetsExecutableLike = + concatMap (\kind -> filterTargetsKind kind targets) kinds + (targetsExesBuildable, + targetsExesBuildable') = selectBuildableTargets' targetsExecutableLike + + targetsExes = forgetTargetsDetail targetsExecutableLike + +-- | For a 'TargetComponent' 'TargetSelector', check if the component can be +-- selected. +-- +-- For the @run@ command we just need to check it is a executable-like +-- (an executable, a test, or a benchmark), in addition +-- to the basic checks on being buildable etc. +-- +selectComponentTarget + :: [ComponentKind] + -> SubComponentTarget + -> AvailableTarget k + -> Either SingleCompTargetProblem k +selectComponentTarget kinds subtarget@WholeComponent t + | componentKind (availableTargetComponentName t) `elem` kinds = component + | otherwise = Left (componentNotRightKindProblem pkgid cname) + where + pkgid = availableTargetPackageId t + cname = availableTargetComponentName t + component = selectComponentTargetBasic subtarget t +selectComponentTarget _ subtarget t + = Left (isSubComponentProblem (availableTargetPackageId t) + (availableTargetComponentName t) + subtarget) + +-- | The various error conditions that can occur when matching a +-- 'TargetSelector' against 'AvailableTarget's for the @run@ command. +-- +data SingleCompProblem = + -- | The 'TargetSelector' matches targets but no executables + TargetProblemNoComps TargetSelector + + -- | A single 'TargetSelector' matches multiple targets + | TargetProblemMatchesMultiple TargetSelector [AvailableTarget ()] + + -- | Multiple 'TargetSelector's match multiple targets + | TargetProblemMultipleTargets TargetsMap + + -- | The 'TargetSelector' refers to a component that is not an executable + | TargetProblemComponentNotRightKind PackageId ComponentName + + -- | Asking to run an individual file or module is not supported + | TargetProblemIsSubComponent PackageId ComponentName SubComponentTarget + deriving (Eq, Show) + +type SingleCompTargetProblem = TargetProblem SingleCompProblem + +noCompsProblem :: TargetSelector -> SingleCompTargetProblem +noCompsProblem = CustomTargetProblem . TargetProblemNoComps + +matchesMultipleProblem :: TargetSelector -> [AvailableTarget ()] -> SingleCompTargetProblem +matchesMultipleProblem selector targets = CustomTargetProblem $ + TargetProblemMatchesMultiple selector targets + +multipleTargetsProblem :: TargetsMap -> TargetProblem SingleCompProblem +multipleTargetsProblem = CustomTargetProblem . TargetProblemMultipleTargets + +componentNotRightKindProblem :: PackageId -> ComponentName -> TargetProblem SingleCompProblem +componentNotRightKindProblem pkgid name = CustomTargetProblem $ + TargetProblemComponentNotRightKind pkgid name + +isSubComponentProblem + :: PackageId + -> ComponentName + -> SubComponentTarget + -> TargetProblem SingleCompProblem +isSubComponentProblem pkgid name subcomponent = CustomTargetProblem $ + TargetProblemIsSubComponent pkgid name subcomponent + +reportTargetProblems :: Verbosity -> String -> [SingleCompTargetProblem] -> IO a +reportTargetProblems verbosity verb = + die' verbosity . unlines . map (renderSingleCompTargetProblem verb) + +renderSingleCompTargetProblem + :: String + -> SingleCompTargetProblem -> String +renderSingleCompTargetProblem verb (TargetProblemNoTargets targetSelector) = + case targetSelectorFilter targetSelector of + Just kind | kind /= ExeKind + -> "The " ++ verb ++ " is for running executables, but the target '" + ++ showTargetSelector targetSelector ++ "' refers to " + ++ renderTargetSelector targetSelector ++ "." + + _ -> renderTargetProblemNoTargets verb targetSelector +renderSingleCompTargetProblem verb problem = + renderTargetProblem verb (renderSingleCompProblem verb) problem + +renderSingleCompProblem + :: String + ->SingleCompProblem + -> String +renderSingleCompProblem cmdname (TargetProblemMatchesMultiple targetSelector targets) = + "The " ++ cmdname ++ " is for finding a single executable at once. The target '" + ++ showTargetSelector targetSelector ++ "' refers to " + ++ renderTargetSelector targetSelector ++ " which includes " + ++ renderListCommaAnd ( ("the "++) <$> + showComponentName <$> + availableTargetComponentName <$> + foldMap + (\kind -> filterTargetsKind kind targets) + [ExeKind, TestKind, BenchKind] ) + ++ "." + +renderSingleCompProblem cmdname (TargetProblemMultipleTargets selectorMap) = + "The " ++ cmdname ++ " is for finding a single executable at once. The targets " + ++ renderListCommaAnd [ "'" ++ showTargetSelector ts ++ "'" + | ts <- ordNub (concatMap snd (concat (Map.elems selectorMap))) ] + ++ " refer to different executables." + +renderSingleCompProblem cmdname (TargetProblemComponentNotRightKind pkgid cname) = + "The " ++ cmdname ++ " is for finding executables, but the target '" + ++ showTargetSelector targetSelector ++ "' refers to " + ++ renderTargetSelector targetSelector ++ " from the package " + ++ prettyShow pkgid ++ "." + where + targetSelector = TargetComponent pkgid cname WholeComponent + +renderSingleCompProblem cmdname (TargetProblemIsSubComponent pkgid cname subtarget) = + "The " ++ cmdname ++ " can only find an executable as a whole, " + ++ "not files or modules within them, but the target '" + ++ showTargetSelector targetSelector ++ "' refers to " + ++ renderTargetSelector targetSelector ++ "." + where + targetSelector = TargetComponent pkgid cname subtarget + +renderSingleCompProblem cmdname (TargetProblemNoComps targetSelector) = + "Cannot " ++ cmdname ++ " the target '" ++ showTargetSelector targetSelector + ++ "' which refers to " ++ renderTargetSelector targetSelector + ++ " because " + ++ plural (targetSelectorPluralPkgs targetSelector) "it does" "they do" + ++ " not contain any executables." diff --git a/cabal-install/cabal-install.cabal b/cabal-install/cabal-install.cabal index 28f79ca6a72..461c66a6eb3 100644 --- a/cabal-install/cabal-install.cabal +++ b/cabal-install/cabal-install.cabal @@ -163,7 +163,6 @@ executable cabal Distribution.Client.CmdBuild Distribution.Client.CmdClean Distribution.Client.CmdConfigure - Distribution.Client.CmdUpdate Distribution.Client.CmdErrorMessages Distribution.Client.CmdExec Distribution.Client.CmdFreeze @@ -171,11 +170,13 @@ executable cabal Distribution.Client.CmdInstall Distribution.Client.CmdInstall.ClientInstallFlags Distribution.Client.CmdInstall.ClientInstallTargetSelector + Distribution.Client.CmdLegacy + Distribution.Client.CmdListBin Distribution.Client.CmdRepl Distribution.Client.CmdRun - Distribution.Client.CmdTest - Distribution.Client.CmdLegacy Distribution.Client.CmdSdist + Distribution.Client.CmdTest + Distribution.Client.CmdUpdate Distribution.Client.Compat.Directory Distribution.Client.Compat.ExecutablePath Distribution.Client.Compat.FilePerms @@ -246,6 +247,7 @@ executable cabal Distribution.Client.Security.HTTP Distribution.Client.Setup Distribution.Client.SetupWrapper + Distribution.Client.SingleCompTargetProblem Distribution.Client.SolverInstallPlan Distribution.Client.SourceFiles Distribution.Client.SrcDist diff --git a/cabal-install/cabal-install.cabal.dev b/cabal-install/cabal-install.cabal.dev index a6687758a1d..c064a6025c1 100644 --- a/cabal-install/cabal-install.cabal.dev +++ b/cabal-install/cabal-install.cabal.dev @@ -155,7 +155,6 @@ library cabal-lib-client Distribution.Client.CmdBuild Distribution.Client.CmdClean Distribution.Client.CmdConfigure - Distribution.Client.CmdUpdate Distribution.Client.CmdErrorMessages Distribution.Client.CmdExec Distribution.Client.CmdFreeze @@ -163,11 +162,13 @@ library cabal-lib-client Distribution.Client.CmdInstall Distribution.Client.CmdInstall.ClientInstallFlags Distribution.Client.CmdInstall.ClientInstallTargetSelector + Distribution.Client.CmdLegacy + Distribution.Client.CmdListBin Distribution.Client.CmdRepl Distribution.Client.CmdRun - Distribution.Client.CmdTest - Distribution.Client.CmdLegacy Distribution.Client.CmdSdist + Distribution.Client.CmdTest + Distribution.Client.CmdUpdate Distribution.Client.Compat.Directory Distribution.Client.Compat.ExecutablePath Distribution.Client.Compat.FilePerms @@ -238,6 +239,7 @@ library cabal-lib-client Distribution.Client.Security.HTTP Distribution.Client.Setup Distribution.Client.SetupWrapper + Distribution.Client.SingleCompTargetProblem Distribution.Client.SolverInstallPlan Distribution.Client.SourceFiles Distribution.Client.SrcDist diff --git a/cabal-install/cabal-install.cabal.prod b/cabal-install/cabal-install.cabal.prod index 28f79ca6a72..461c66a6eb3 100644 --- a/cabal-install/cabal-install.cabal.prod +++ b/cabal-install/cabal-install.cabal.prod @@ -163,7 +163,6 @@ executable cabal Distribution.Client.CmdBuild Distribution.Client.CmdClean Distribution.Client.CmdConfigure - Distribution.Client.CmdUpdate Distribution.Client.CmdErrorMessages Distribution.Client.CmdExec Distribution.Client.CmdFreeze @@ -171,11 +170,13 @@ executable cabal Distribution.Client.CmdInstall Distribution.Client.CmdInstall.ClientInstallFlags Distribution.Client.CmdInstall.ClientInstallTargetSelector + Distribution.Client.CmdLegacy + Distribution.Client.CmdListBin Distribution.Client.CmdRepl Distribution.Client.CmdRun - Distribution.Client.CmdTest - Distribution.Client.CmdLegacy Distribution.Client.CmdSdist + Distribution.Client.CmdTest + Distribution.Client.CmdUpdate Distribution.Client.Compat.Directory Distribution.Client.Compat.ExecutablePath Distribution.Client.Compat.FilePerms @@ -246,6 +247,7 @@ executable cabal Distribution.Client.Security.HTTP Distribution.Client.Setup Distribution.Client.SetupWrapper + Distribution.Client.SingleCompTargetProblem Distribution.Client.SolverInstallPlan Distribution.Client.SourceFiles Distribution.Client.SrcDist diff --git a/cabal-install/cabal-install.cabal.zinza b/cabal-install/cabal-install.cabal.zinza index 705fbebd2ad..f600b4c929b 100644 --- a/cabal-install/cabal-install.cabal.zinza +++ b/cabal-install/cabal-install.cabal.zinza @@ -99,7 +99,6 @@ Version: 3.3.0.0 Distribution.Client.CmdBuild Distribution.Client.CmdClean Distribution.Client.CmdConfigure - Distribution.Client.CmdUpdate Distribution.Client.CmdErrorMessages Distribution.Client.CmdExec Distribution.Client.CmdFreeze @@ -107,11 +106,13 @@ Version: 3.3.0.0 Distribution.Client.CmdInstall Distribution.Client.CmdInstall.ClientInstallFlags Distribution.Client.CmdInstall.ClientInstallTargetSelector + Distribution.Client.CmdLegacy + Distribution.Client.CmdListBin Distribution.Client.CmdRepl Distribution.Client.CmdRun - Distribution.Client.CmdTest - Distribution.Client.CmdLegacy Distribution.Client.CmdSdist + Distribution.Client.CmdTest + Distribution.Client.CmdUpdate Distribution.Client.Compat.Directory Distribution.Client.Compat.ExecutablePath Distribution.Client.Compat.FilePerms @@ -182,6 +183,7 @@ Version: 3.3.0.0 Distribution.Client.Security.HTTP Distribution.Client.Setup Distribution.Client.SetupWrapper + Distribution.Client.SingleCompTargetProblem Distribution.Client.SolverInstallPlan Distribution.Client.SourceFiles Distribution.Client.SrcDist diff --git a/cabal-install/main/Main.hs b/cabal-install/main/Main.hs index 6c17dad2065..04e1658ce70 100644 --- a/cabal-install/main/Main.hs +++ b/cabal-install/main/Main.hs @@ -90,6 +90,7 @@ import qualified Distribution.Client.CmdBench as CmdBench import qualified Distribution.Client.CmdExec as CmdExec import qualified Distribution.Client.CmdClean as CmdClean import qualified Distribution.Client.CmdSdist as CmdSdist +import qualified Distribution.Client.CmdListBin as CmdListBin import Distribution.Client.CmdLegacy import Distribution.Client.Install (install) @@ -251,6 +252,7 @@ mainWorker args = do , hiddenCmd formatCommand formatAction , hiddenCmd actAsSetupCommand actAsSetupAction , hiddenCmd manpageCommand (manpageAction commandSpecs) + , regularCmd CmdListBin.listbinCommand CmdListBin.listbinAction ] ++ concat [ newCmd CmdConfigure.configureCommand CmdConfigure.configureAction diff --git a/cabal-install/tests/IntegrationTests2.hs b/cabal-install/tests/IntegrationTests2.hs index ac1878dc83b..69a44f1271a 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.SingleCompTargetProblem as SCTP import Distribution.Package import Distribution.PackageDescription @@ -846,7 +847,7 @@ testTargetProblemsRun config reportSubCase = do "targets/multiple-exes" config CmdRun.selectPackageTargets CmdRun.selectComponentTarget - [ ( flip CmdRun.matchesMultipleProblem + [ ( flip SCTP.matchesMultipleProblem [ AvailableTarget "p-0.1" (CExeName "p2") (TargetBuildable () TargetRequestedByDefault) True , AvailableTarget "p-0.1" (CExeName "p1") @@ -892,7 +893,7 @@ testTargetProblemsRun config reportSubCase = do "targets/lib-only" config CmdRun.selectPackageTargets CmdRun.selectComponentTarget - [ ( CmdRun.noExesProblem, mkTargetPackage "p-0.1" ) + [ ( SCTP.noCompsProblem, mkTargetPackage "p-0.1" ) ]