{-# LANGUAGE DataKinds #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} module Stack2nix.External.Stack ( PackageRef(..), runPlan ) where import Control.Lens ((%~)) import Control.Monad (when) import Data.List (concat) import qualified Data.Map.Strict as M import Data.Maybe (fromJust) import qualified Data.Set as Set (fromList, union) import Data.Text (pack, unpack) import Distribution.Nixpkgs.Haskell.Derivation (Derivation, configureFlags) import qualified Distribution.Nixpkgs.Haskell.Hackage as DB import Options.Applicative import Path (parseAbsFile) import Stack.Build.Source (getGhcOptions, loadSourceMapFull) import Stack.Build.Target (NeedTargets (..)) import Stack.Config import Stack.Options.BuildParser import Stack.Options.GlobalParser import Stack.Options.Utils (GlobalOptsContext (..)) import Stack.Prelude hiding (logDebug) import Stack.Runners (loadCompilerVersion, withBuildConfig) import Stack.Types.BuildPlan (PackageLocation (..), Repo (..)) import Stack.Types.Compiler (getGhcVersion) import Stack.Types.Config import Stack.Types.Config.Build (BuildCommand (..)) import Stack.Types.FlagName (toCabalFlagName) import Stack.Types.Nix import Stack.Types.Package (PackageSource (..), lpLocation, lpPackage, packageFlags, packageName, packageVersion) import Stack.Types.PackageIdentifier (PackageIdentifier (..), PackageIdentifierRevision (..), packageIdentifierString) import Stack.Types.PackageName (PackageName, parsePackageName) import Stack.Types.Runner import Stack.Types.Version (Version) import Stack2nix.External.Cabal2nix (cabal2nix) import Stack2nix.Hackage (loadHackageDB) import Stack2nix.Render (render) import Stack2nix.Types (Args (..), Flags) import Stack2nix.Util (ensureExecutable, logDebug, mapPool) import System.Directory (canonicalizePath, createDirectoryIfMissing, getCurrentDirectory, makeRelativeToCurrentDirectory) import System.FilePath (makeRelative, ()) import Text.PrettyPrint.HughesPJClass (Doc) data PackageRef = HackagePackage Flags PackageIdentifierRevision | NonHackagePackage Flags PackageIdentifier (PackageLocation FilePath) deriving (Eq, Show) genNixFile :: Args -> Version -> FilePath -> Maybe String -> Maybe String -> DB.HackageDB -> PackageRef -> IO (Either Doc Derivation) genNixFile args ghcVersion baseDir uri argRev hackageDB pkgRef = do cwd <- getCurrentDirectory case pkgRef of NonHackagePackage _flags _ident PLArchive {} -> error "genNixFile: No support for archive package locations" HackagePackage flags (PackageIdentifierRevision pkg _) -> cabal2nix args ghcVersion ("cabal://" <> packageIdentifierString pkg) Nothing Nothing flags hackageDB NonHackagePackage flags _ident (PLRepo repo) -> cabal2nix args ghcVersion (unpack $ repoUrl repo) (Just $ repoCommit repo) (Just (repoSubdirs repo)) flags hackageDB NonHackagePackage flags _ident (PLFilePath path) -> do relPath <- makeRelativeToCurrentDirectory path projRoot <- canonicalizePath $ cwd baseDir let defDir = baseDir makeRelative projRoot path cabal2nix args ghcVersion (fromMaybe defDir uri) (pack <$> argRev) (const relPath <$> uri) flags hackageDB -- TODO: remove once we use flags, options sourceMapToPackages :: Map PackageName PackageSource -> [PackageRef] sourceMapToPackages = map sourceToPackage . M.elems where sourceToPackage :: PackageSource -> PackageRef sourceToPackage (PSIndex _ flags _options pir) = HackagePackage (toCabalFlags flags) pir sourceToPackage (PSFiles lp _) = let pkg = lpPackage lp ident = PackageIdentifier (packageName pkg) (packageVersion pkg) in NonHackagePackage (toCabalFlags $ packageFlags pkg) ident (lpLocation lp) toCabalFlags fs = [ (toCabalFlagName f0, enabled) | (f0, enabled) <- M.toList fs ] planAndGenerate :: HasEnvConfig env => BuildOptsCLI -> FilePath -> Maybe String -> Args -> Version -> RIO env () planAndGenerate boptsCli baseDir remoteUri args@Args {..} ghcVersion = do (_targets, _mbp, _locals, _extraToBuild, sourceMap) <- loadSourceMapFull NeedTargets boptsCli -- Stackage lists bin-package-db but it's in GHC 7.10's boot libraries binPackageDb <- parsePackageName "bin-package-db" let pkgs = sourceMapToPackages (M.delete binPackageDb sourceMap) liftIO $ logDebug args $ "plan:\n" ++ show pkgs hackageDB <- liftIO $ loadHackageDB Nothing argHackageSnapshot buildConf <- envConfigBuildConfig <$> view envConfigL drvs <- liftIO $ mapPool argThreads (\p -> fmap (addGhcOptions buildConf p) <$> genNixFile args ghcVersion baseDir remoteUri argRev hackageDB p ) pkgs let locals = map (\l -> show (packageName (lpPackage l))) _locals liftIO . render drvs args locals $ nixVersion ghcVersion -- | Add ghc-options declared in stack.yaml to the nix derivation for a package -- by adding to the configureFlags attribute of the derivation addGhcOptions :: BuildConfig -> PackageRef -> Derivation -> Derivation addGhcOptions buildConf pkgRef drv = drv & configureFlags %~ (Set.union stackGhcOptions) where stackGhcOptions :: Set String stackGhcOptions = Set.fromList . map (unpack . ("--ghc-option=" <>)) $ getGhcOptions buildConf buildOpts pkgName False False pkgName :: PackageName pkgName = case pkgRef of HackagePackage _ (PackageIdentifierRevision (PackageIdentifier n _) _) -> n NonHackagePackage _ (PackageIdentifier n _) _ -> n runPlan :: FilePath -> Maybe String -> Args -> IO () runPlan baseDir remoteUri args@Args{..} = do let stackRoot = "/tmp/s2n" createDirectoryIfMissing True stackRoot let globals = globalOpts baseDir stackRoot args let stackFile = baseDir argStackYaml ghcVersion <- getGhcVersionIO globals stackFile when argEnsureExecutables $ ensureExecutable ("haskell.compiler.ghc" ++ nixVersion ghcVersion) withBuildConfig globals $ planAndGenerate buildOpts baseDir remoteUri args ghcVersion nixVersion :: Version -> String nixVersion = filter (/= '.') . show getGhcVersionIO :: GlobalOpts -> FilePath -> IO Version getGhcVersionIO go stackFile = do cp <- canonicalizePath stackFile fp <- parseAbsFile cp lc <- withRunner LevelError True False ColorAuto Nothing False $ \runner -> -- https://www.fpcomplete.com/blog/2017/07/the-rio-monad runRIO runner $ loadConfig mempty Nothing (SYLOverride fp) getGhcVersion <$> loadCompilerVersion go lc globalOpts :: FilePath -> FilePath -> Args -> GlobalOpts globalOpts currentDir stackRoot Args{..} = go { globalReExecVersion = Just "1.5.1" -- TODO: obtain from stack lib if exposed , globalConfigMonoid = (globalConfigMonoid go) { configMonoidNixOpts = mempty { nixMonoidEnable = First (Just True) } } , globalStackYaml = SYLOverride (currentDir argStackYaml) , globalLogLevel = if argVerbose then LevelDebug else LevelInfo } where pinfo = info (globalOptsParser currentDir OuterGlobalOpts (Just LevelError)) briefDesc args = concat [ ["--stack-root", stackRoot] , ["--jobs", show argThreads] , ["--test" | argTest] , ["--bench" | argBench] , ["--haddock" | argHaddock] , ["--no-install-ghc"] ] go = globalOptsFromMonoid False ColorNever . fromJust . getParseResult $ execParserPure defaultPrefs pinfo args buildOpts :: BuildOptsCLI buildOpts = fromJust . getParseResult $ execParserPure defaultPrefs (info (buildOptsParser Build) briefDesc) ["--dry-run"]