stack-2.11.1: src/Stack/Unpack.hs
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
-- | Functions related to Stack's @unpack@ command.
module Stack.Unpack
( unpackCmd
, unpackPackages
) where
import Path ( (</>), parseRelDir )
import Path.IO ( doesDirExist, resolveDir' )
import Pantry ( loadSnapshot )
import qualified RIO.Map as Map
import RIO.Process ( HasProcessContext )
import qualified RIO.Set as Set
import qualified RIO.Text as T
import Stack.Config ( makeConcreteResolver )
import Stack.Prelude
import Stack.Runners ( ShouldReexec (..), withConfig )
import Stack.Types.GlobalOpts ( GlobalOpts (..) )
import Stack.Types.Runner ( Runner, globalOptsL )
-- | Type representing \'pretty\' exceptions thrown by functions exported by the
-- "Stack.Unpack" module.
data UnpackPrettyException
= UnpackDirectoryAlreadyExists (Set (Path Abs Dir))
| CouldNotParsePackageSelectors [StyleDoc]
deriving (Show, Typeable)
instance Pretty UnpackPrettyException where
pretty (UnpackDirectoryAlreadyExists dirs) =
"[S-3515]"
<> line
<> flow "Stack was unable to unpack due to directories already being \
\present:"
<> line
<> bulletedList (map pretty $ Set.toList dirs)
pretty (CouldNotParsePackageSelectors errs) =
"[S-2628]"
<> line
<> flow "The following package selectors are not valid package names or \
\identifiers:"
<> line
<> bulletedList errs
instance Exception UnpackPrettyException
-- | Function underlying the @stack unpack@ command. Unpack packages to the
-- filesystem.
unpackCmd ::
([String], Maybe Text)
-- ^ A pair of a list of names or identifiers and an optional destination
-- path.
-> RIO Runner ()
unpackCmd (names, Nothing) = unpackCmd (names, Just ".")
unpackCmd (names, Just dstPath) = withConfig NoReexec $ do
mresolver <- view $ globalOptsL.to globalResolver
mSnapshot <- forM mresolver $ \resolver -> do
concrete <- makeConcreteResolver resolver
loc <- completeSnapshotLocation concrete
loadSnapshot loc
dstPath' <- resolveDir' $ T.unpack dstPath
unpackPackages mSnapshot dstPath' names
-- | Intended to work for the command line command.
unpackPackages ::
forall env. (HasPantryConfig env, HasProcessContext env, HasTerm env)
=> Maybe RawSnapshot -- ^ When looking up by name, take from this build plan.
-> Path Abs Dir -- ^ Destination.
-> [String] -- ^ Names or identifiers.
-> RIO env ()
unpackPackages mSnapshot dest input = do
let (errs1, (names, pirs1)) =
fmap partitionEithers $ partitionEithers $ map parse input
locs1 <- forM pirs1 $ \pir -> do
loc <- fmap cplComplete $ completePackageLocation $ RPLIHackage pir Nothing
pure (loc, packageLocationIdent loc)
(errs2, locs2) <- partitionEithers <$> traverse toLoc names
case errs1 ++ errs2 of
[] -> pure ()
errs -> prettyThrowM $ CouldNotParsePackageSelectors errs
locs <- Map.fromList <$> mapM
(\(pir, ident) -> do
suffix <- parseRelDir $ packageIdentifierString ident
pure (pir, dest </> suffix)
)
(locs1 ++ locs2)
alreadyUnpacked <- filterM doesDirExist $ Map.elems locs
unless (null alreadyUnpacked) $
prettyThrowM $ UnpackDirectoryAlreadyExists $ Set.fromList alreadyUnpacked
forM_ (Map.toList locs) $ \(loc, dest') -> do
unpackPackageLocation dest' loc
prettyInfoL
[ "Unpacked"
, fromString $ T.unpack $ textDisplay loc
, "to"
, pretty dest' <> "."
]
where
toLoc | Just snapshot <- mSnapshot = toLocSnapshot snapshot
| otherwise = toLocNoSnapshot
toLocNoSnapshot ::
PackageName
-> RIO env (Either StyleDoc (PackageLocationImmutable, PackageIdentifier))
toLocNoSnapshot name = do
mloc1 <- getLatestHackageLocation
YesRequireHackageIndex
name
UsePreferredVersions
mloc <-
case mloc1 of
Just _ -> pure mloc1
Nothing -> do
updated <- updateHackageIndex
$ Just
$ "Could not find package "
<> fromString (packageNameString name)
<> ", updating"
case updated of
UpdateOccurred ->
getLatestHackageLocation
YesRequireHackageIndex
name
UsePreferredVersions
NoUpdateOccurred -> pure Nothing
case mloc of
Nothing -> do
candidates <- getHackageTypoCorrections name
pure $ Left $ fillSep
[ flow "Could not find package"
, style Current (fromString $ packageNameString name)
, flow "on Hackage."
, if null candidates
then mempty
else fillSep $
flow "Perhaps you meant one of:"
: mkNarrativeList (Just Good) False
(map (fromString . packageNameString) candidates :: [StyleDoc])
]
Just loc -> pure $ Right (loc, packageLocationIdent loc)
toLocSnapshot ::
RawSnapshot
-> PackageName
-> RIO env (Either StyleDoc (PackageLocationImmutable, PackageIdentifier))
toLocSnapshot snapshot name =
case Map.lookup name (rsPackages snapshot) of
Nothing ->
pure $ Left $ fillSep
[ flow "Package does not appear in snapshot:"
, style Current (fromString $ packageNameString name) <> "."
]
Just sp -> do
loc <- cplComplete <$> completePackageLocation (rspLocation sp)
pure $ Right (loc, packageLocationIdent loc)
-- Possible future enhancement: parse names as name + version range
parse s =
case parsePackageName s of
Just x -> Right $ Left x
Nothing ->
case parsePackageIdentifierRevision (T.pack s) of
Right x -> Right $ Right x
Left _ -> Left $ fillSep
[ flow "Could not parse as package name or identifier:"
, style Current (fromString s) <> "."
]