module Runner ( run ) where
import Control.Lens
import Data.Foldable as F
import Data.List as L
import Data.Maybe (fromMaybe)
import Data.Semigroup ((<>))
import Distribution.Nixpkgs.Haskell.FromStack
import Distribution.Nixpkgs.Haskell.FromStack.Package
import Distribution.Nixpkgs.Haskell.Hackage as DB
import Distribution.Nixpkgs.Haskell.PackageSourceSpec (loadHackageDB)
import Distribution.Nixpkgs.Haskell.Stack
import Distribution.Nixpkgs.Haskell.Stack.PrettyPrinting as PP
import Distribution.Version (Version)
import Distribution.Compiler (AbiTag(..), unknownCompilerInfo)
import Distribution.Package (PackageName, mkPackageName, pkgName)
import Distribution.Text as Text (display)
import Language.Nix as Nix
import Options.Applicative
import Paths_stackage2nix ( version )
import Runner.Cli
import Stack.Config
import Stack.Types
import Stackage.Types
import System.IO (withFile, IOMode(..), hPutStrLn)
import Text.PrettyPrint.HughesPJClass (Doc, render)
import qualified Data.Set as Set
import qualified Data.Map as Map
import qualified LtsHaskell as LH
run :: IO ()
run = do
opts <- execParser pinfo
hackageDb <- loadHackageDB (opts ^. optHackageDb) Nothing
let
-- With optparse-applicative it seems there's no way to make
-- 'StackageOptions' optional for Stackage override and required in
-- other cases
missingErr = error . ("Missing required parameter: " <>)
sopts = StackageOptions
(fromMaybe (missingErr "--all-cabal-hashes") (opts ^. optAllCabalHashesRepo) )
(fromMaybe (missingErr "--lts-haskell") (opts ^. optLtsHaskellRepo))
case opts ^. optConfigOrigin of
-- Generate Stackage packages from stack.yaml resolver
OriginStackYaml stackYaml buildTarget -> do
stackConf <- either fail pure =<< readStackConfig stackYaml
let
stackResolver = stackConf ^. scResolver
stackPackagesConfig = mkStackPackagesConfig opts
stackConfPackages <- traverse (fmap mkNode . packageDerivation stackPackagesConfig hackageDb)
$ stackConf ^. scPackages
case buildTarget of
TargetStackageClosure -> do
(buildPlanFile, buildPlan, packageSetConfig) <- loadStackageBuildPlan hackageDb stackResolver opts sopts
let
overrideConfig = mkOverrideConfig opts (siGhcVersion $ bpSystemInfo buildPlan)
stackageSetConfig = if opts ^. optTweaks . tExtraDepsRevisionLatest
then extraDepsLatestConfig stackConfPackages packageSetConfig
else packageSetConfig
stackagePackages <- buildStackagePackages stackageSetConfig buildPlan
let
reachable = reachableDeps stackConfPackages
-- Nixpkgs generic-builder puts hscolour on path for all libraries
withHscolour pkgs =
let hscolour = F.find ((== "hscolour") . nodeName) stackagePackages
in maybe pkgs (`Set.insert` pkgs) hscolour
-- Find all reachable dependencies in stackage set to stick into
-- stackage packages file. This is performed on the full stackage
-- set rather than pruning stackage packages beforehand because
-- stackage does not concern itself with build tools while cabal2nix
-- does: pruning only after generating full set of packages allows
-- us to make sure all those extra dependencies are explicitly
-- listed as well.
nodes = Set.toAscList
$ withHscolour
$ flip reachableDependencies stackagePackages
-- Originally reachable nodes are root nodes
$ L.filter (\n -> mkPackageName (nodeName n) `Set.member` reachable) stackagePackages
writeOutFile buildPlanFile (opts ^. optOutStackagePackages)
$ pPrintOutPackages (view nodeDerivation <$> nodes)
writeOutFile buildPlanFile (opts ^. optOutStackageConfig)
$ pPrintOutConfig (bpSystemInfo buildPlan) nodes
writeOutFile (stackYaml ^. syFilePath) (opts ^. optOutDerivation)
$ PP.overrideHaskellPackages overrideConfig (view nodeDerivation <$> stackConfPackages)
TargetStackagePackages -> do
(buildPlanFile, buildPlan, packageSetConfig) <- loadStackageBuildPlan hackageDb stackResolver opts sopts
let
overrideConfig = mkOverrideConfig opts (siGhcVersion $ bpSystemInfo buildPlan)
stackageSetConfig = if opts ^. optTweaks . tExtraDepsRevisionLatest
then extraDepsLatestConfig stackConfPackages packageSetConfig
else packageSetConfig
-- stackageLoader mHash pkgId =
-- if pkgName pkgId `Set.member` reachable
-- then packageLoader packageSetConfig Nothing pkgId
-- else packageLoader packageSetConfig mHash pkgId
-- stackageSetConfig = packageSetConfig { packageLoader = stackageLoader }
nodes <- buildStackagePackages stackageSetConfig buildPlan
writeOutFile buildPlanFile (opts ^. optOutStackagePackages)
$ pPrintOutPackages (view nodeDerivation <$> nodes)
writeOutFile buildPlanFile (opts ^. optOutStackageConfig)
$ pPrintOutConfig (bpSystemInfo buildPlan) nodes
writeOutFile (stackYaml ^. syFilePath) (opts ^. optOutDerivation)
$ PP.overrideHaskellPackages overrideConfig (view nodeDerivation <$> stackConfPackages)
TargetStackageOverride ->
writeOutFile (stackYaml ^. syFilePath) (opts ^. optOutDerivation) $
PP.overrideStackage
(stackConf ^. scResolver)
(opts ^. optNixpkgsRepository)
(view nodeDerivation <$> stackConfPackages)
-- Generate Stackage packages from resolver
OriginResolver stackResolver -> do
(buildPlanFile, buildPlan, packageSetConfig) <- loadStackageBuildPlan hackageDb stackResolver opts sopts
nodes <- buildStackagePackages packageSetConfig buildPlan
let overrideConfig = mkOverrideConfig opts (siGhcVersion $ bpSystemInfo buildPlan)
writeOutFile buildPlanFile (opts ^. optOutStackagePackages)
$ pPrintOutPackages (view nodeDerivation <$> nodes)
writeOutFile buildPlanFile (opts ^. optOutStackageConfig)
$ pPrintOutConfig (bpSystemInfo buildPlan) nodes
writeOutFile buildPlanFile (opts ^. optOutDerivation)
$ PP.pPrintHaskellPackages overrideConfig
loadStackageBuildPlan :: DB.HackageDB -> StackResolver -> Options -> StackageOptions -> IO (FilePath, BuildPlan, PackageSetConfig)
loadStackageBuildPlan hackageDb stackResolver opts sopts = do
let buildPlanFile = LH.buildPlanFilePath (sopts ^. soptLtsHaskellRepo) stackResolver
buildPlan <- LH.loadBuildPlan buildPlanFile
packageSetConfig <- LH.buildPackageSetConfig
hackageDb
(sopts ^. soptAllCabalHashesRepo)
(opts ^. optNixpkgsRepository)
buildPlan
return (buildPlanFile, buildPlan, packageSetConfig)
buildStackagePackages :: PackageSetConfig -> BuildPlan -> IO [Node]
buildStackagePackages packageSetConfig buildPlan = do
let
stackagePackageConfig = PackageConfig
{ enableCheck = True
, enableHaddock = True }
traverse (uncurry (buildNode packageSetConfig stackagePackageConfig))
$ Map.toAscList (bpPackages buildPlan)
reachableDeps :: Traversable t => t Node -> Set.Set PackageName
reachableDeps = Set.map mkPackageName
. F.foldr1 Set.union
. fmap nodeDepends
-- Return 'PackageSetConfig' with package loader that loads latest Hackage
-- revison for direct (reachable) dependencies of input packages
extraDepsLatestConfig :: Traversable t => t Node -> PackageSetConfig -> PackageSetConfig
extraDepsLatestConfig packages packageSetConfig =
let
reachable = reachableDeps packages
stackageLoader mHash pkgId =
if pkgName pkgId `Set.member` reachable
then packageLoader packageSetConfig Nothing pkgId
else packageLoader packageSetConfig mHash pkgId
in packageSetConfig { packageLoader = stackageLoader }
writeOutFile :: Show source => source -> FilePath -> Doc -> IO ()
writeOutFile source filePath contents =
withFile filePath WriteMode $ \h -> do
hPutStrLn h ("# Generated by stackage2nix " ++ Text.display version ++ " from " ++ show source)
hPutStrLn h $ render contents
mkOverrideConfig :: Options -> Version -> OverrideConfig
mkOverrideConfig opts ghcVersion = OverrideConfig
{ _ocGhc = ghcVersion
, _ocStackagePackages = opts ^. optOutStackagePackages
, _ocStackageConfig = opts ^. optOutStackageConfig
, _ocNixpkgs = opts ^. optNixpkgsRepository }
mkStackPackagesConfig :: Options -> StackPackagesConfig
mkStackPackagesConfig opts = StackPackagesConfig
{ _spcHaskellResolver = const True
, _spcNixpkgsResolver = \i -> Just (Nix.binding # (i, Nix.path # [i]))
, _spcTargetPlatform = opts ^. optPlatform
, _spcTargetCompiler = unknownCompilerInfo (opts ^. optCompilerId) NoAbiTag
, _spcFlagAssignment = []
, _spcDoCheckPackages = opts ^. optDoCheckPackages
, _spcDoHaddockPackages = opts ^. optDoHaddockPackages }