module Stackage.CompleteBuild
( BuildType (..)
, BumpType (..)
, BuildFlags (..)
, completeBuild
, justCheck
, getStackageAuthToken
) where
import Control.Concurrent (threadDelay, getNumCapabilities)
import Control.Concurrent.Async (withAsync)
import Data.Default.Class (def)
import Data.Semigroup (Max (..), Option (..))
import Data.Text.Read (decimal)
import Data.Time
import Data.Yaml (decodeFileEither, encodeFile)
import Network.HTTP.Client
import Network.HTTP.Client.TLS (tlsManagerSettings)
import Stackage.BuildConstraints
import Stackage.BuildPlan
import Stackage.CheckBuildPlan
import Stackage.PerformBuild
import Stackage.Prelude
import Stackage.ServerBundle
import Stackage.UpdateBuildPlan
import Stackage.Upload
import System.Environment (lookupEnv)
import System.IO (BufferMode (LineBuffering), hSetBuffering)
data BuildFlags = BuildFlags
{ bfEnableTests :: !Bool
, bfEnableHaddock :: !Bool
, bfDoUpload :: !Bool
, bfEnableLibProfile :: !Bool
, bfEnableExecDyn :: !Bool
, bfVerbose :: !Bool
, bfSkipCheck :: !Bool
, bfServer :: !StackageServer
, bfBuildHoogle :: !Bool
, bfBundleDest :: !(Maybe FilePath)
, bfGitPush :: !Bool
} deriving (Show)
data BuildType = Nightly | LTS BumpType Text
deriving (Show, Read, Eq, Ord)
data BumpType = Major | Minor
deriving (Show, Read, Eq, Ord)
data Settings = Settings
{ plan :: BuildPlan
, planFile :: FilePath
, buildDir :: FilePath
, logDir :: FilePath
, title :: Text -> Text
, slug :: Text
, setArgs :: Text -> UploadBundle -> UploadBundle
, postBuild :: IO ()
, distroName :: Text
, snapshotType :: SnapshotType
, bundleDest :: FilePath
}
nightlyPlanFile :: Text
-> FilePath
nightlyPlanFile day = fpFromText ("nightly-" ++ day) <.> "yaml"
nightlySettings :: Text
-> BuildFlags
-> BuildPlan
-> Settings
nightlySettings day bf plan' = Settings
{ planFile = nightlyPlanFile day
, buildDir = fpFromText $ "builds/nightly"
, logDir = fpFromText $ "logs/stackage-nightly-" ++ day
, title = \ghcVer -> concat
[ "Stackage Nightly "
, day
, ", GHC "
, ghcVer
]
, slug = slug'
, setArgs = \ghcVer ub -> ub { ubNightly = Just ghcVer }
, plan = plan'
, postBuild = return ()
, distroName = "Stackage"
, snapshotType = STNightly
, bundleDest = fromMaybe
(fpFromText $ "stackage-nightly-" ++ day ++ ".bundle")
(bfBundleDest bf)
}
where
slug' = "nightly-" ++ day
parseGoal :: MonadThrow m
=> BumpType
-> Text
-> m (LTSVer -> Bool)
parseGoal _ "" = return $ const True
parseGoal bumpType t =
case decimal t of
Right (major, "") -> return $ \(LTSVer major' _) ->
case bumpType of
Major -> major' < major
Minor -> major' <= major
_ ->
case parseLTSRaw t of
Nothing -> throwM $ ParseGoalFailure t
Just x -> return (< x)
data ParseGoalFailure = ParseGoalFailure Text
deriving (Show, Typeable)
instance Exception ParseGoalFailure
getSettings :: Manager -> BuildFlags -> BuildType -> IO Settings
getSettings man bf Nightly = do
day <- tshow . utctDay <$> getCurrentTime
bc <- defaultBuildConstraints man
pkgs <- getLatestAllowedPlans bc
plan' <- newBuildPlan pkgs bc
return $ nightlySettings day bf plan'
getSettings man bf (LTS bumpType goal) = do
matchesGoal <- parseGoal bumpType goal
Option mlts <- fmap (fmap getMax) $ runResourceT
$ sourceDirectory "."
$= concatMapC (parseLTSVer . filename)
$= filterC matchesGoal
$$ foldMapC (Option . Just . Max)
(new, plan') <- case bumpType of
Major -> do
let new =
case mlts of
Nothing -> LTSVer 0 0
Just (LTSVer x _) -> LTSVer (x + 1) 0
bc <- defaultBuildConstraints man
pkgs <- getLatestAllowedPlans bc
plan' <- newBuildPlan pkgs bc
return (new, plan')
Minor -> do
old <- maybe (error "No LTS plans found in current directory") return mlts
oldplan <- decodeFileEither (fpToString $ renderLTSVer old)
>>= either throwM return
let new = incrLTSVer old
let bc = updateBuildConstraints oldplan
pkgs <- getLatestAllowedPlans bc
plan' <- newBuildPlan pkgs bc
return (new, plan')
let newfile = renderLTSVer new
return Settings
{ planFile = newfile
, buildDir = fpFromText $ "builds/lts"
, logDir = fpFromText $ "logs/stackage-lts-" ++ tshow new
, title = \ghcVer -> concat
[ "LTS Haskell "
, tshow new
, ", GHC "
, ghcVer
]
, slug = "lts-" ++ tshow new
, setArgs = \_ ub -> ub { ubLTS = Just $ tshow new }
, plan = plan'
, postBuild = do
let git args = withCheckedProcess
(proc "git" args) $ \ClosedStream Inherited Inherited ->
return ()
putStrLn "Committing new LTS file to Git"
git ["add", fpToString newfile]
git ["commit", "-m", "Added new LTS release: " ++ show new]
when (bfGitPush bf) $ do
putStrLn "Pushing to Git repository"
git ["push"]
, distroName = "LTSHaskell"
, snapshotType =
case new of
LTSVer x y -> STLTS x y
, bundleDest = fromMaybe
(fpFromText $ "stackage-lts-" ++ tshow new ++ ".bundle")
(bfBundleDest bf)
}
data LTSVer = LTSVer !Int !Int
deriving (Eq, Ord)
instance Show LTSVer where
show (LTSVer x y) = concat [show x, ".", show y]
incrLTSVer :: LTSVer -> LTSVer
incrLTSVer (LTSVer x y) = LTSVer x (y + 1)
parseLTSVer :: FilePath -> Maybe LTSVer
parseLTSVer fp = do
w <- stripPrefix "lts-" $ fpToText fp
x <- stripSuffix ".yaml" w
parseLTSRaw x
parseLTSRaw :: Text -> Maybe LTSVer
parseLTSRaw x = do
Right (major, y) <- Just $ decimal x
z <- stripPrefix "." y
Right (minor, "") <- Just $ decimal z
return $ LTSVer major minor
renderLTSVer :: LTSVer -> FilePath
renderLTSVer lts = fpFromText $ concat
[ "lts-"
, tshow lts
, ".yaml"
]
stillAlive :: IO () -> IO ()
stillAlive inner =
withAsync (printer 1) $ const inner
where
printer i = forever $ do
threadDelay 60000000
putStrLn $ "Still alive: " ++ tshow i
printer $! i + 1
justCheck :: IO ()
justCheck = stillAlive $ withManager tlsManagerSettings $ \man -> do
putStrLn "Loading build constraints"
bc <- defaultBuildConstraints man
putStrLn "Creating build plan"
plans <- getLatestAllowedPlans bc
plan <- newBuildPlan plans bc
putStrLn $ "Writing build plan to check-plan.yaml"
encodeFile "check-plan.yaml" plan
putStrLn "Checking plan"
checkBuildPlan plan
putStrLn "Plan seems valid!"
getPerformBuild :: BuildFlags -> Settings -> IO PerformBuild
getPerformBuild buildFlags Settings {..} = do
jobs <- getNumCapabilities
return PerformBuild
{ pbPlan = plan
, pbInstallDest = buildDir
, pbLogDir = logDir
, pbLog = hPut stdout
, pbJobs = jobs
, pbGlobalInstall = False
, pbEnableTests = bfEnableTests buildFlags
, pbEnableHaddock = bfEnableHaddock buildFlags
, pbEnableLibProfiling = bfEnableLibProfile buildFlags
, pbEnableExecDyn = bfEnableExecDyn buildFlags
, pbVerbose = bfVerbose buildFlags
, pbAllowNewer = bfSkipCheck buildFlags
, pbBuildHoogle = bfBuildHoogle buildFlags
}
completeBuild :: BuildType -> BuildFlags -> IO ()
completeBuild buildType buildFlags = withManager tlsManagerSettings $ \man -> do
hSetBuffering stdout LineBuffering
putStrLn $ "Loading settings for: " ++ tshow buildType
settings@Settings {..} <- getSettings man buildFlags buildType
putStrLn $ "Writing build plan to: " ++ fpToText planFile
encodeFile (fpToString planFile) plan
if bfSkipCheck buildFlags
then putStrLn "Skipping build plan check"
else do
putStrLn "Checking build plan"
checkBuildPlan plan
putStrLn "Performing build"
pb <- getPerformBuild buildFlags settings
performBuild pb >>= mapM_ putStrLn
putStrLn $ "Creating bundle (v2) at: " ++ fpToText bundleDest
createBundleV2 CreateBundleV2
{ cb2Plan = plan
, cb2Type = snapshotType
, cb2DocsDir = pbDocDir pb
, cb2Dest = bundleDest
}
postBuild `catchAny` print
when (bfDoUpload buildFlags) $
finallyUpload
buildFlags
settings
man
getStackageAuthToken :: IO Text
getStackageAuthToken = do
mtoken <- lookupEnv "STACKAGE_AUTH_TOKEN"
case mtoken of
Nothing -> decodeUtf8 <$> readFile "/auth-token"
Just token -> return $ pack token
finallyUpload :: BuildFlags
-> Settings -> Manager -> IO ()
finallyUpload buildFlags settings@Settings{..} man = do
let server = bfServer buildFlags
pb <- getPerformBuild buildFlags settings
putStrLn "Uploading bundle to Stackage Server"
token <- getStackageAuthToken
res <- flip uploadBundleV2 man UploadBundleV2
{ ub2Server = server
, ub2AuthToken = token
, ub2Bundle = bundleDest
}
putStrLn $ "New snapshot available at: " ++ res
ecreds <- tryIO $ readFile "/hackage-creds"
case map encodeUtf8 $ words $ decodeUtf8 $ either (const "") id ecreds of
[username, password] -> do
putStrLn "Uploading as Hackage distro"
res2 <- uploadHackageDistroNamed distroName plan username password man
putStrLn $ "Distro upload response: " ++ tshow res2
_ -> putStrLn "No creds found, skipping Hackage distro upload"