|
| 1 | +{-# LANGUAGE NamedFieldPuns #-} |
| 2 | +{-# LANGUAGE ViewPatterns #-} |
| 3 | + |
| 4 | +-- | cabal-install CLI command: build |
| 5 | +-- |
| 6 | +module Distribution.Client.CmdInstall ( |
| 7 | + -- * The @build@ CLI and action |
| 8 | + installCommand, |
| 9 | + installAction, |
| 10 | + |
| 11 | + -- * Internals exposed for testing |
| 12 | + TargetProblem(..), |
| 13 | + selectPackageTargets, |
| 14 | + selectComponentTarget |
| 15 | + ) where |
| 16 | + |
| 17 | +import Prelude () |
| 18 | +import Distribution.Client.Compat.Prelude |
| 19 | + |
| 20 | +import Distribution.Client.ProjectOrchestration |
| 21 | +import Distribution.Client.CmdErrorMessages |
| 22 | + |
| 23 | +import Distribution.Client.Setup |
| 24 | + ( GlobalFlags, ConfigFlags(..), ConfigExFlags, InstallFlags |
| 25 | + , applyFlagDefaults ) |
| 26 | +import qualified Distribution.Client.Setup as Client |
| 27 | +import Distribution.Client.Types |
| 28 | + ( PackageSpecifier(NamedPackage), UnresolvedSourcePackage ) |
| 29 | +import Distribution.Client.ProjectPlanning.Types |
| 30 | + ( pkgConfigCompiler ) |
| 31 | +import Distribution.Client.ProjectConfig.Types |
| 32 | + ( ProjectConfig, ProjectConfigBuildOnly(..) |
| 33 | + , projectConfigLogsDir, projectConfigStoreDir, projectConfigShared |
| 34 | + , projectConfigBuildOnly, projectConfigDistDir |
| 35 | + , projectConfigConfigFile ) |
| 36 | +import Distribution.Client.Config |
| 37 | + ( defaultCabalDir ) |
| 38 | +import Distribution.Client.ProjectConfig |
| 39 | + ( readGlobalConfig, resolveBuildTimeSettings ) |
| 40 | +import Distribution.Client.DistDirLayout |
| 41 | + ( defaultDistDirLayout, distDirectory, mkCabalDirLayout |
| 42 | + , ProjectRoot(ProjectRootImplicit), distProjectCacheDirectory |
| 43 | + , storePackageDirectory, cabalStoreDirLayout ) |
| 44 | +import Distribution.Client.RebuildMonad |
| 45 | + ( runRebuild ) |
| 46 | +import Distribution.Client.InstallSymlink |
| 47 | + ( symlinkBinary ) |
| 48 | +import Distribution.Simple.Setup |
| 49 | + ( HaddockFlags, fromFlagOrDefault, flagToMaybe ) |
| 50 | +import Distribution.Simple.Command |
| 51 | + ( CommandUI(..), usageAlternatives ) |
| 52 | +import Distribution.Simple.Compiler |
| 53 | + ( compilerId ) |
| 54 | +import Distribution.Types.PackageName |
| 55 | + ( mkPackageName ) |
| 56 | +import Distribution.Types.UnitId |
| 57 | + ( UnitId ) |
| 58 | +import Distribution.Types.UnqualComponentName |
| 59 | + ( UnqualComponentName, unUnqualComponentName ) |
| 60 | +import Distribution.Verbosity |
| 61 | + ( Verbosity, normal ) |
| 62 | +import Distribution.Simple.Utils |
| 63 | + ( wrapText, die', withTempDirectory, createDirectoryIfMissingVerbose ) |
| 64 | + |
| 65 | +import qualified Data.Map as Map |
| 66 | +import System.Directory ( getTemporaryDirectory, makeAbsolute ) |
| 67 | +import System.FilePath ( (</>) ) |
| 68 | + |
| 69 | +import qualified Distribution.Client.CmdBuild as CmdBuild |
| 70 | + |
| 71 | +installCommand :: CommandUI (ConfigFlags, ConfigExFlags, InstallFlags, HaddockFlags) |
| 72 | +installCommand = CommandUI |
| 73 | + { commandName = "new-install" |
| 74 | + , commandSynopsis = "Install packages." |
| 75 | + , commandUsage = usageAlternatives "new-install" [ "[TARGETS] [FLAGS]" ] |
| 76 | + , commandDescription = Just $ \_ -> wrapText $ |
| 77 | + "Installs one or more packages. This is done by installing them " |
| 78 | + ++ "in the store and symlinking the executables in the directory " |
| 79 | + ++ "specified by the --symlink-bindir flag (`~/.cabal/bin/` by default). " |
| 80 | + ++ "If you want the installed executables to be available globally, " |
| 81 | + ++ "make sure that the PATH environment variable contains that directory. " |
| 82 | + ++ "\n\n" |
| 83 | + ++ "If TARGET is a library, it will be added to the global environment. " |
| 84 | + ++ "When doing this, cabal will try to build a plan that includes all " |
| 85 | + ++ "the previously installed libraries. This is currently not implemented." |
| 86 | + , commandNotes = Just $ \pname -> |
| 87 | + "Examples:\n" |
| 88 | + ++ " " ++ pname ++ " new-install\n" |
| 89 | + ++ " Install the package in the current directory\n" |
| 90 | + ++ " " ++ pname ++ " new-install pkgname\n" |
| 91 | + ++ " Install the package named pkgname (fetching it from hackage if necessary)\n" |
| 92 | + ++ " " ++ pname ++ " new-install ./pkgfoo\n" |
| 93 | + ++ " Install the package in the ./pkgfoo directory\n" |
| 94 | + |
| 95 | + ++ cmdCommonHelpTextNewBuildBeta |
| 96 | + , commandOptions = commandOptions CmdBuild.buildCommand |
| 97 | + , commandDefaultFlags = commandDefaultFlags CmdBuild.buildCommand |
| 98 | + } |
| 99 | + |
| 100 | + |
| 101 | +-- | The @install@ command actually serves four different needs. It installs: |
| 102 | +-- * Nonlocal exes: |
| 103 | +-- For example a program from hackage. The behavior is similar to the old |
| 104 | +-- install command, except that now conflicts between separate runs of the |
| 105 | +-- command are impossible thanks to the store. |
| 106 | +-- Exes are installed in the store like a normal dependency, then they are |
| 107 | +-- symlinked uin the directory specified by --symlink-bindir. |
| 108 | +-- To do this we need a dummy projectBaseContext containing the targets as |
| 109 | +-- estra packages and using a temporary dist directory. |
| 110 | +-- * Nonlocal libraries (TODO see #4558) |
| 111 | +-- * Local exes (TODO see #4558) |
| 112 | +-- * Local libraries (TODO see #4558) |
| 113 | +-- |
| 114 | +-- For more details on how this works, see the module |
| 115 | +-- "Distribution.Client.ProjectOrchestration" |
| 116 | +-- |
| 117 | +installAction :: (ConfigFlags, ConfigExFlags, InstallFlags, HaddockFlags) |
| 118 | + -> [String] -> GlobalFlags -> IO () |
| 119 | +installAction (applyFlagDefaults -> (configFlags, configExFlags, installFlags, haddockFlags)) |
| 120 | + targetStrings globalFlags = do |
| 121 | + -- We need a place to put a temporary dist directory |
| 122 | + globalTmp <- getTemporaryDirectory |
| 123 | + withTempDirectory |
| 124 | + verbosity |
| 125 | + globalTmp |
| 126 | + "cabal-install." |
| 127 | + $ \tmpDir -> do |
| 128 | + |
| 129 | + let packageNames = mkPackageName <$> targetStrings |
| 130 | + packageSpecifiers = |
| 131 | + (\pname -> NamedPackage pname []) <$> packageNames |
| 132 | + |
| 133 | + baseCtx <- establishDummyProjectBaseContext |
| 134 | + verbosity |
| 135 | + cliConfig |
| 136 | + tmpDir |
| 137 | + packageSpecifiers |
| 138 | + |
| 139 | + let targetSelectors = TargetPackageName <$> packageNames |
| 140 | + |
| 141 | + buildCtx <- |
| 142 | + runProjectPreBuildPhase verbosity baseCtx $ \elaboratedPlan -> do |
| 143 | + |
| 144 | + -- Interpret the targets on the command line as build targets |
| 145 | + targets <- either (reportTargetProblems verbosity) return |
| 146 | + $ resolveTargets |
| 147 | + selectPackageTargets |
| 148 | + selectComponentTarget |
| 149 | + TargetProblemCommon |
| 150 | + elaboratedPlan |
| 151 | + targetSelectors |
| 152 | + |
| 153 | + let elaboratedPlan' = pruneInstallPlanToTargets |
| 154 | + TargetActionBuild |
| 155 | + targets |
| 156 | + elaboratedPlan |
| 157 | + elaboratedPlan'' <- |
| 158 | + if buildSettingOnlyDeps (buildSettings baseCtx) |
| 159 | + then either (reportCannotPruneDependencies verbosity) return $ |
| 160 | + pruneInstallPlanToDependencies (Map.keysSet targets) |
| 161 | + elaboratedPlan' |
| 162 | + else return elaboratedPlan' |
| 163 | + |
| 164 | + return (elaboratedPlan'', targets) |
| 165 | + |
| 166 | + printPlan verbosity baseCtx buildCtx |
| 167 | + |
| 168 | + buildOutcomes <- runProjectBuildPhase verbosity baseCtx buildCtx |
| 169 | + |
| 170 | + let compiler = pkgConfigCompiler $ elaboratedShared buildCtx |
| 171 | + let mkPkgBinDir = (</> "bin") . |
| 172 | + storePackageDirectory |
| 173 | + (cabalStoreDirLayout $ cabalDirLayout baseCtx) |
| 174 | + (compilerId compiler) |
| 175 | + |
| 176 | + -- If there are exes, symlink them |
| 177 | + let defaultSymlinkBindir = error "TODO: how do I get the default ~/.cabal (or ~/.local) directory? (use --symlink-bindir explicitly for now)" </> "bin" |
| 178 | + symlinkBindir <- makeAbsolute $ fromFlagOrDefault defaultSymlinkBindir (Client.installSymlinkBinDir installFlags) |
| 179 | + traverse_ (symlinkBuiltPackage mkPkgBinDir symlinkBindir) |
| 180 | + $ Map.toList $ targetsMap buildCtx |
| 181 | + runProjectPostBuildPhase verbosity baseCtx buildCtx buildOutcomes |
| 182 | + where |
| 183 | + verbosity = fromFlagOrDefault normal (configVerbosity configFlags) |
| 184 | + cliConfig = commandLineFlagsToProjectConfig |
| 185 | + globalFlags configFlags configExFlags |
| 186 | + installFlags haddockFlags |
| 187 | + |
| 188 | + |
| 189 | +-- | Symlink every exe from a package from the store to a given location |
| 190 | +symlinkBuiltPackage :: (UnitId -> FilePath) -- ^ A function to get an UnitId's |
| 191 | + -- store directory |
| 192 | + -> FilePath -- ^ Where to put the symlink |
| 193 | + -> ( UnitId |
| 194 | + , [(ComponentTarget, [TargetSelector PackageId])] ) |
| 195 | + -> IO () |
| 196 | +symlinkBuiltPackage mkSourceBinDir destDir (pkg, components) = |
| 197 | + traverse_ (symlinkBuiltExe (mkSourceBinDir pkg) destDir) exes |
| 198 | + where |
| 199 | + exes = catMaybes $ (exeMaybe . fst) <$> components |
| 200 | + exeMaybe (ComponentTarget (CExeName exe) _) = Just exe |
| 201 | + exeMaybe _ = Nothing |
| 202 | + |
| 203 | +-- | Symlink a specific exe. |
| 204 | +symlinkBuiltExe :: FilePath -> FilePath -> UnqualComponentName -> IO Bool |
| 205 | +symlinkBuiltExe sourceDir destDir exe = |
| 206 | + symlinkBinary |
| 207 | + destDir |
| 208 | + sourceDir |
| 209 | + exe |
| 210 | + $ unUnqualComponentName exe |
| 211 | + |
| 212 | +-- | Create a dummy project context, without a .cabal or a .cabal.project file |
| 213 | +-- (a place where to put a temporary dist directory is still needed) |
| 214 | +establishDummyProjectBaseContext :: Verbosity |
| 215 | + -> ProjectConfig |
| 216 | + -> FilePath -- ^ Where to put the dist directory |
| 217 | + -> [PackageSpecifier UnresolvedSourcePackage] -- ^ The packages to be included in the project |
| 218 | + -> IO ProjectBaseContext |
| 219 | +establishDummyProjectBaseContext verbosity cliConfig tmpDir localPackages = do |
| 220 | + |
| 221 | + cabalDir <- defaultCabalDir |
| 222 | + |
| 223 | + -- Create the dist directories |
| 224 | + createDirectoryIfMissingVerbose verbosity True $ distDirectory distDirLayout |
| 225 | + createDirectoryIfMissingVerbose verbosity True $ distProjectCacheDirectory distDirLayout |
| 226 | + |
| 227 | + globalConfig <- runRebuild "" |
| 228 | + $ readGlobalConfig verbosity |
| 229 | + $ projectConfigConfigFile |
| 230 | + $ projectConfigShared cliConfig |
| 231 | + let projectConfig = globalConfig <> cliConfig |
| 232 | + |
| 233 | + let ProjectConfigBuildOnly { |
| 234 | + projectConfigLogsDir, |
| 235 | + projectConfigStoreDir |
| 236 | + } = projectConfigBuildOnly projectConfig |
| 237 | + |
| 238 | + mlogsDir = flagToMaybe projectConfigLogsDir |
| 239 | + mstoreDir = flagToMaybe projectConfigStoreDir |
| 240 | + cabalDirLayout = mkCabalDirLayout cabalDir mstoreDir mlogsDir |
| 241 | + |
| 242 | + buildSettings = resolveBuildTimeSettings |
| 243 | + verbosity cabalDirLayout |
| 244 | + projectConfig |
| 245 | + |
| 246 | + return ProjectBaseContext { |
| 247 | + distDirLayout, |
| 248 | + cabalDirLayout, |
| 249 | + projectConfig, |
| 250 | + localPackages, |
| 251 | + buildSettings |
| 252 | + } |
| 253 | + where |
| 254 | + mdistDirectory = flagToMaybe |
| 255 | + $ projectConfigDistDir |
| 256 | + $ projectConfigShared cliConfig |
| 257 | + projectRoot = ProjectRootImplicit tmpDir |
| 258 | + distDirLayout = defaultDistDirLayout projectRoot |
| 259 | + mdistDirectory |
| 260 | + |
| 261 | +-- | This defines what a 'TargetSelector' means for the @bench@ command. |
| 262 | +-- It selects the 'AvailableTarget's that the 'TargetSelector' refers to, |
| 263 | +-- or otherwise classifies the problem. |
| 264 | +-- |
| 265 | +-- For the @build@ command select all components except non-buildable and disabled |
| 266 | +-- tests\/benchmarks, fail if there are no such components |
| 267 | +-- |
| 268 | +selectPackageTargets :: TargetSelector PackageId |
| 269 | + -> [AvailableTarget k] -> Either TargetProblem [k] |
| 270 | +selectPackageTargets targetSelector targets |
| 271 | + |
| 272 | + -- If there are any buildable targets then we select those |
| 273 | + | not (null targetsBuildable) |
| 274 | + = Right targetsBuildable |
| 275 | + |
| 276 | + -- If there are targets but none are buildable then we report those |
| 277 | + | not (null targets) |
| 278 | + = Left (TargetProblemNoneEnabled targetSelector targets') |
| 279 | + |
| 280 | + -- If there are no targets at all then we report that |
| 281 | + | otherwise |
| 282 | + = Left (TargetProblemNoTargets targetSelector) |
| 283 | + where |
| 284 | + targets' = forgetTargetsDetail targets |
| 285 | + targetsBuildable = selectBuildableTargetsWith |
| 286 | + (buildable targetSelector) |
| 287 | + targets |
| 288 | + |
| 289 | + -- When there's a target filter like "pkg:tests" then we do select tests, |
| 290 | + -- but if it's just a target like "pkg" then we don't build tests unless |
| 291 | + -- they are requested by default (i.e. by using --enable-tests) |
| 292 | + buildable (TargetPackage _ _ Nothing) TargetNotRequestedByDefault = False |
| 293 | + buildable (TargetAllPackages Nothing) TargetNotRequestedByDefault = False |
| 294 | + buildable _ _ = True |
| 295 | + |
| 296 | +-- | For a 'TargetComponent' 'TargetSelector', check if the component can be |
| 297 | +-- selected. |
| 298 | +-- |
| 299 | +-- For the @build@ command we just need the basic checks on being buildable etc. |
| 300 | +-- |
| 301 | +selectComponentTarget :: PackageId -> ComponentName -> SubComponentTarget |
| 302 | + -> AvailableTarget k -> Either TargetProblem k |
| 303 | +selectComponentTarget pkgid cname subtarget = |
| 304 | + either (Left . TargetProblemCommon) Right |
| 305 | + . selectComponentTargetBasic pkgid cname subtarget |
| 306 | + |
| 307 | + |
| 308 | +-- | The various error conditions that can occur when matching a |
| 309 | +-- 'TargetSelector' against 'AvailableTarget's for the @build@ command. |
| 310 | +-- |
| 311 | +data TargetProblem = |
| 312 | + TargetProblemCommon TargetProblemCommon |
| 313 | + |
| 314 | + -- | The 'TargetSelector' matches targets but none are buildable |
| 315 | + | TargetProblemNoneEnabled (TargetSelector PackageId) [AvailableTarget ()] |
| 316 | + |
| 317 | + -- | There are no targets at all |
| 318 | + | TargetProblemNoTargets (TargetSelector PackageId) |
| 319 | + deriving (Eq, Show) |
| 320 | + |
| 321 | +reportTargetProblems :: Verbosity -> [TargetProblem] -> IO a |
| 322 | +reportTargetProblems verbosity = |
| 323 | + die' verbosity . unlines . map renderTargetProblem |
| 324 | + |
| 325 | +renderTargetProblem :: TargetProblem -> String |
| 326 | +renderTargetProblem (TargetProblemCommon problem) = |
| 327 | + renderTargetProblemCommon "build" problem |
| 328 | +renderTargetProblem (TargetProblemNoneEnabled targetSelector targets) = |
| 329 | + renderTargetProblemNoneEnabled "build" targetSelector targets |
| 330 | +renderTargetProblem(TargetProblemNoTargets targetSelector) = |
| 331 | + renderTargetProblemNoTargets "build" targetSelector |
| 332 | + |
| 333 | +reportCannotPruneDependencies :: Verbosity -> CannotPruneDependencies -> IO a |
| 334 | +reportCannotPruneDependencies verbosity = |
| 335 | + die' verbosity . renderCannotPruneDependencies |
| 336 | + |
0 commit comments