Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add unpacking of extra deps #6175

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Release notes:
matching cargo behavior and preventing logSticky spam on narrow
terminals and lots of dependencies building simultaneously

* Add unpacking of source repository packages (`extra-deps`).

**Changes since v2.11.1:**

Major changes:
Expand Down
3 changes: 3 additions & 0 deletions doc/unpack_command.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ By default:
* the package is unpacked into a directory named after the package and its
version. Pass the option `--to <directory>` to specify the destination
directory.

If PACKAGE is the name of a source repository package or a suffix of its URL
then the unpacking is direct, not via a package index.
1 change: 1 addition & 0 deletions src/Stack/Config.hs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
module Stack.Config
( loadConfig
, loadConfigYaml
, loadProjectConfig
, packagesParser
, getImplicitGlobalProjectDir
, getSnapshots
Expand Down
122 changes: 105 additions & 17 deletions src/Stack/Unpack.hs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}

Expand All @@ -7,18 +8,28 @@ module Stack.Unpack
, unpackPackages
) where

import qualified Data.List as L
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.Config ( loadProjectConfig, makeConcreteResolver )
import Stack.Prelude
import Stack.Runners ( ShouldReexec (..), withConfig )
import Stack.Types.Project ( Project (..) )
import Stack.Types.GlobalOpts ( GlobalOpts (..) )
import Stack.Types.ProjectConfig ( ProjectConfig (..) )
import Stack.Types.Runner ( Runner, globalOptsL )
import Stack.Types.StackYamlLoc ( StackYamlLoc (..) )
import Distribution.Types.PackageName ( unPackageName )

data Unpackable
= UnpackName PackageName
| UnpackIdent PackageIdentifierRevision
| UnpackRepoUrl (PackageName, RawPackageLocationImmutable)

-- | Type representing \'pretty\' exceptions thrown by functions exported by the
-- "Stack.Unpack" module.
Expand Down Expand Up @@ -54,24 +65,41 @@ unpackCmd ::
-> RIO Runner ()
unpackCmd (names, Nothing) = unpackCmd (names, Just ".")
unpackCmd (names, Just dstPath) = withConfig NoReexec $ do
mStackYaml <- view $ globalOptsL.to globalStackYaml
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
unpackPackages mStackYaml 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.
=> StackYamlLoc
-> 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
unpackPackages mStackYaml mSnapshot dest input = do
parsed <- mapM (parse mStackYaml) input
let (errs1, unpackables) = partitionEithers parsed
let (names, pirs1, raws) = splitUnpackable unpackables

repos <- catMaybes <$> mapM
(\case
(name, x@RPLIRepo{}) -> do
suffix <- parseRelDir $ unPackageName name
pure $ Just (x, dest </> suffix)
(_, RPLIHackage{}) -> pure Nothing
(_, RPLIArchive{}) -> pure Nothing)
(longestUnique raws)

forM_ repos $ \(loc, dest') -> do
unpackPackageLocationRaw dest' loc
prettyInfoL $ unpackMessage loc dest'

locs1 <- forM pirs1 $ \pir -> do
loc <- fmap cplComplete $ completePackageLocation $ RPLIHackage pir Nothing
pure (loc, packageLocationIdent loc)
Expand All @@ -93,12 +121,7 @@ unpackPackages mSnapshot dest input = do

forM_ (Map.toList locs) $ \(loc, dest') -> do
unpackPackageLocation dest' loc
prettyInfoL
[ "Unpacked"
, fromString $ T.unpack $ textDisplay loc
, "to"
, pretty dest' <> "."
]
prettyInfoL $ unpackMessage loc dest'
where
toLoc | Just snapshot <- mSnapshot = toLocSnapshot snapshot
| otherwise = toLocNoSnapshot
Expand Down Expand Up @@ -158,14 +181,79 @@ unpackPackages mSnapshot dest input = 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
-- Possible future enhancement: parse names as name + version range
parse ::
(HasPantryConfig env, HasTerm env)
=> StackYamlLoc -> String -> RIO env (Either StyleDoc Unpackable)
parse mStackYaml s = do
extra <- toLocExtraDep mStackYaml (fromString s)
pure $ case extra of
Just x -> Right $ UnpackRepoUrl x
Nothing -> case parsePackageName s of
Just x -> Right $ UnpackName x
Nothing ->
case parsePackageIdentifierRevision (T.pack s) of
Right x -> Right $ Right x
Right x -> Right $ UnpackIdent x
Left _ -> Left $ fillSep
[ flow "Could not parse as package name or identifier:"
, style Current (fromString s) <> "."
]

toLocExtraDep ::
(HasPantryConfig env, HasTerm env)
=> StackYamlLoc
-> PackageName
-> RIO env (Maybe (PackageName, RawPackageLocationImmutable))
toLocExtraDep mstackYaml name = do
pc <- loadProjectConfig mstackYaml
case pc of
PCGlobalProject -> pure Nothing
PCNoProject{} -> pure Nothing
PCProject (Project{projectDependencies}, _, _) -> do
let hits = mapMaybe (\case
RPLImmutable (RPLIRepo repo meta@RawPackageMetadata{rpmName = Just n}) -> do
if n == name then Just (name, (repo, meta)) else Nothing
RPLImmutable (RPLIRepo repo@Repo{repoUrl} meta) -> do
if T.isSuffixOf (T.pack $ unPackageName name) repoUrl then Just (name, (repo, meta)) else Nothing
RPLMutable{} -> Nothing
RPLImmutable{} -> Nothing) projectDependencies

case hits of
[] -> pure Nothing
[(n, (repo, meta))] -> pure $ Just (n, RPLIRepo repo meta)
_ -> do
prettyWarnL
[ flow "Multiple matches for"
, style Current (fromString $ packageNameString name) <> ":"
]
forM_ hits $ \case
(_, (repo, RawPackageMetadata{rpmName})) -> do
prettyWarnL
[ style Current (fromString . T.unpack $ repoUrl repo)
, style Current (fromString $ maybe "" unPackageName rpmName)
]
pure Nothing

splitUnpackable ::
[Unpackable]
-> ([PackageName], [PackageIdentifierRevision], [(PackageName, RawPackageLocationImmutable)])
splitUnpackable = foldl' go ([], [], [])
where
go (names, pirs, raws) = \case
UnpackName name -> (name : names, pirs, raws)
UnpackIdent pir -> (names, pir : pirs, raws)
UnpackRepoUrl raw -> (names, pirs, raw : raws)

longestUnique ::
[(PackageName, RawPackageLocationImmutable)]
-> [(PackageName, RawPackageLocationImmutable)]
longestUnique xs =
L.concat $ L.groupBy (\(_, p1) (_, p2) -> p1 == p2) (L.take 1 $ L.sortOn (Down . fst) xs)

unpackMessage :: Display a => a -> Path Abs Dir -> [StyleDoc]
unpackMessage loc dest =
[ "Unpacked"
, fromString $ T.unpack $ textDisplay loc
, "to"
, pretty dest <> "."
]