stackage2nix-0.7.2: src/Distribution/Nixpkgs/Haskell/FromStack/Package.hs
module Distribution.Nixpkgs.Haskell.FromStack.Package where
import Control.Lens
import Data.Foldable as F
import Data.Function (on)
import Data.Graph (Graph, Vertex)
import Data.Maybe
import Data.Ord as O
import Distribution.Text (display, disp)
import Distribution.Package (unPackageName, packageName)
import Distribution.Nixpkgs.Haskell.BuildInfo
import Distribution.Nixpkgs.Haskell.Derivation
import Language.Nix.PrettyPrinting (onlyIf)
import Language.Nix as Nix
import Stackage.Types (SystemInfo(..))
import Text.PrettyPrint.HughesPJClass hiding ((<>))
import qualified Data.Graph as Graph
import qualified Data.Map as Map
import qualified Data.Set as Set
data Node = Node
{ _nodeDerivation :: Derivation
, _nodeTestDepends :: Set.Set String
, _nodeBenchmarkDepends :: Set.Set String
, _nodeExecutableDepends :: Set.Set String
, _nodeSetupDepends :: Set.Set String
, _nodeOtherDepends :: Set.Set String }
makeLenses ''Node
instance Eq Node where
(==) = (==) `on` nodeName
instance Ord Node where
compare = compare `on` nodeName
mkNode :: Derivation -> Node
mkNode _nodeDerivation = Node{..}
where
haskellDependencies s = Set.map (view (localName . ident))
. Set.filter isFromHackage
$ view (s . (haskell <> tool)) _nodeDerivation
_nodeTestDepends = haskellDependencies testDepends
_nodeBenchmarkDepends = haskellDependencies benchmarkDepends
_nodeExecutableDepends = haskellDependencies executableDepends
_nodeSetupDepends = haskellDependencies setupDepends
_nodeOtherDepends = haskellDependencies (executableDepends <> libraryDepends)
nodeName :: Node -> String
nodeName = unPackageName . packageName . view (nodeDerivation . pkgid)
nodeCycleDepends :: Node -> Set.Set String
nodeCycleDepends = _nodeTestDepends <> _nodeOtherDepends
nodeDepends :: Node -> Set.Set String
nodeDepends = _nodeTestDepends
<> _nodeOtherDepends
<> _nodeBenchmarkDepends
<> _nodeExecutableDepends
<> _nodeSetupDepends
findCycles :: [Node] -> [[Node]]
findCycles nodes = mapMaybe cyclic $
Graph.stronglyConnComp [(node, nodeName node, Set.toList $ nodeCycleDepends node) | node <- nodes]
where
cyclic (Graph.AcyclicSCC _) = Nothing
cyclic (Graph.CyclicSCC c) = Just c
breakCycle :: [Node] -> [String]
breakCycle [] = []
breakCycle c = nodeName breaker : concatMap breakCycle (findCycles remaining)
where
breaker = F.maximumBy (O.comparing rate) c
remaining = map updateNode c
updateNode n
| nodeName n == nodeName breaker = n & nodeTestDepends .~ mempty
| otherwise = n
names = Set.fromList $ map nodeName c
rate node = - Set.size (view nodeOtherDepends node `Set.intersection` names)
type FromVertex = Vertex -> (Node, String, [String])
type FromKey = String -> Maybe Vertex
buildNodeGraph :: [Node] -> (Graph, FromVertex, FromKey)
buildNodeGraph nodes = Graph.graphFromEdges
[(node, nodeName node, Set.toList $ nodeDepends node) | node <- nodes]
reachableDependencies :: [Node] -> [Node] -> Set.Set Node
reachableDependencies keyNodes nodes = Set.map (view _1 . fromVertex) reachableVerticesS
where
(graph, fromVertex, fromKey) = buildNodeGraph nodes
keys = mapMaybe (fromKey . nodeName) keyNodes
reachableVerticesS = F.foldr1 Set.union
$ (Set.fromList . F.toList) <$> Graph.dfs graph keys
-- pretty printing
isFromHackage :: Binding -> Bool
isFromHackage b = case view (reference . Nix.path) b of
["self",_] -> True
_ -> False
pPrintOutConfig :: SystemInfo -> [Node] -> Doc
pPrintOutConfig systemInfo nodes = vcat
[ "{ pkgs, haskellLib }:"
, ""
, "with haskellLib; self: super: {"
, ""
, " # core packages"
, nest 2 $ vcat $
Map.toList (siCorePackages systemInfo) <&> \(pkg, _version) ->
onlyIf (pkg /= "ghc")
$ hsep [doubleQuotes (text (display pkg)), equals, text "null"] <> semi
, ""
, nest 2 $ vcat $ pPrintBreakCycle <$> findCycles nodes
, ""
, "}"
]
pPrintBreakCycle :: [Node] -> Doc
pPrintBreakCycle c = vcat
[ text $ "# break cycle: " ++ unwords (map nodeName c)
, vcat $ breakCycle c <&> \breaker ->
hsep [doubleQuotes (text breaker), equals, text "dontCheck", text "super." <> text breaker] <> semi
]
pPrintOutPackages :: [Derivation] -> Doc
pPrintOutPackages drvs = vcat
[ "{ pkgs, stdenv, callPackage }:"
, ""
, "self: {"
, ""
, nest 2 $ vcat (pPrintPackageOverride <$> drvs)
, ""
, "}"
]
pPrintPackageOverride :: Derivation -> Doc
pPrintPackageOverride drv =
let
name = drv ^. pkgid . to packageName
overrides = fsep
[ disp bind <> semi
| bind <- Set.toList $ view (dependencies . each <> extraFunctionArgs) drv
, not $ isFromHackage bind ]
in
hang (hsep [doubleQuotes (text (display name)), equals, text "callPackage"]) 2
$ parens (pPrint drv) <+> (braces overrides <> semi)