{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeApplications #-}

-- | Copyright: (c) 2021-2022 berberman
-- SPDX-License-Identifier: MIT
-- Maintainer: berberman <berberman@yandex.com>
-- Stability: experimental
-- Portability: portable
--
-- This module is about global information we use in rules.
module NvFetcher.Types.ShakeExtras
  ( -- * Types
    ShakeExtras (..),
    initShakeExtras,
    getShakeExtras,

    -- * Packages
    lookupPackage,
    getAllPackageKeys,
    isPackageKeyTarget,

    -- * Version changes
    recordVersionChange,
    getVersionChanges,

    -- * Retry
    withRetry,

    -- * Build dir
    getBuildDir,

    -- * Keyfile
    getKeyfilePath,

    -- * Last versions
    getLastVersionOnDisk,
    getRecentLastVersion,
    updateLastVersion,
    getAllOnDiskVersions,
    getLastVersionUpdated,

    -- * Cache nvchecker
    nvcheckerCacheEnabled,
  )
where

import Control.Concurrent.Extra
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Development.Shake
import NvFetcher.Config
import NvFetcher.Types

data LastVersion
  = OnDisk Version
  | Updated
      (Maybe Version)
      -- ^ on disk if has
      Version

-- | Values we use during the build. It's stored in 'shakeExtra'
data ShakeExtras = ShakeExtras
  { ShakeExtras -> Config
config :: Config,
    ShakeExtras -> Var [VersionChange]
versionChanges :: Var [VersionChange],
    ShakeExtras -> Map PackageKey Package
targetPackages :: Map PackageKey Package,
    ShakeExtras -> Var (Map PackageKey LastVersion)
lastVersions :: Var (Map PackageKey LastVersion)
  }

-- | Get our values from shake
getShakeExtras :: Action ShakeExtras
getShakeExtras :: Action ShakeExtras
getShakeExtras =
  forall a. Typeable a => Action (Maybe a)
getShakeExtra @ShakeExtras forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
    Just ShakeExtras
x -> forall (f :: * -> *) a. Applicative f => a -> f a
pure ShakeExtras
x
    Maybe ShakeExtras
_ -> forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
"ShakeExtras is missing!"

-- | Create an empty 'ShakeExtras' from packages to build, times to retry for each rule,
-- build dir, and on disk versions
initShakeExtras :: Config -> Map PackageKey Package -> Map PackageKey Version -> IO ShakeExtras
initShakeExtras :: Config
-> Map PackageKey Package
-> Map PackageKey Version
-> IO ShakeExtras
initShakeExtras Config
config Map PackageKey Package
targetPackages Map PackageKey Version
lv = do
  Var [VersionChange]
versionChanges <- forall a. a -> IO (Var a)
newVar forall a. Monoid a => a
mempty
  Var (Map PackageKey LastVersion)
lastVersions <- forall a. a -> IO (Var a)
newVar forall a b. (a -> b) -> a -> b
$ forall a b k. (a -> b) -> Map k a -> Map k b
Map.map Version -> LastVersion
OnDisk Map PackageKey Version
lv
  forall (f :: * -> *) a. Applicative f => a -> f a
pure ShakeExtras {Map PackageKey Package
Var [VersionChange]
Var (Map PackageKey LastVersion)
Config
lastVersions :: Var (Map PackageKey LastVersion)
versionChanges :: Var [VersionChange]
targetPackages :: Map PackageKey Package
config :: Config
lastVersions :: Var (Map PackageKey LastVersion)
targetPackages :: Map PackageKey Package
versionChanges :: Var [VersionChange]
config :: Config
..}

-- | Get keys of all packages to build
getAllPackageKeys :: Action [PackageKey]
getAllPackageKeys :: Action [PackageKey]
getAllPackageKeys = do
  ShakeExtras {Map PackageKey Package
Var [VersionChange]
Var (Map PackageKey LastVersion)
Config
lastVersions :: Var (Map PackageKey LastVersion)
targetPackages :: Map PackageKey Package
versionChanges :: Var [VersionChange]
config :: Config
lastVersions :: ShakeExtras -> Var (Map PackageKey LastVersion)
targetPackages :: ShakeExtras -> Map PackageKey Package
versionChanges :: ShakeExtras -> Var [VersionChange]
config :: ShakeExtras -> Config
..} <- Action ShakeExtras
getShakeExtras
  forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ forall k a. Map k a -> [k]
Map.keys Map PackageKey Package
targetPackages

-- | Find a package given its key
lookupPackage :: PackageKey -> Action (Maybe Package)
lookupPackage :: PackageKey -> Action (Maybe Package)
lookupPackage PackageKey
key = do
  ShakeExtras {Map PackageKey Package
Var [VersionChange]
Var (Map PackageKey LastVersion)
Config
lastVersions :: Var (Map PackageKey LastVersion)
targetPackages :: Map PackageKey Package
versionChanges :: Var [VersionChange]
config :: Config
lastVersions :: ShakeExtras -> Var (Map PackageKey LastVersion)
targetPackages :: ShakeExtras -> Map PackageKey Package
versionChanges :: ShakeExtras -> Var [VersionChange]
config :: ShakeExtras -> Config
..} <- Action ShakeExtras
getShakeExtras
  forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup PackageKey
key Map PackageKey Package
targetPackages

-- | Check if we need build this package
isPackageKeyTarget :: PackageKey -> Action Bool
isPackageKeyTarget :: PackageKey -> Action Bool
isPackageKeyTarget PackageKey
k = forall k a. Ord k => k -> Map k a -> Bool
Map.member PackageKey
k forall b c a. (b -> c) -> (a -> b) -> a -> c
. ShakeExtras -> Map PackageKey Package
targetPackages forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Action ShakeExtras
getShakeExtras

-- | Record version change of a package
recordVersionChange :: PackageName -> Maybe Version -> Version -> Action ()
recordVersionChange :: PackageName -> Maybe Version -> Version -> Action ()
recordVersionChange PackageName
vcName Maybe Version
vcOld Version
vcNew = do
  ShakeExtras {Map PackageKey Package
Var [VersionChange]
Var (Map PackageKey LastVersion)
Config
lastVersions :: Var (Map PackageKey LastVersion)
targetPackages :: Map PackageKey Package
versionChanges :: Var [VersionChange]
config :: Config
lastVersions :: ShakeExtras -> Var (Map PackageKey LastVersion)
targetPackages :: ShakeExtras -> Map PackageKey Package
versionChanges :: ShakeExtras -> Var [VersionChange]
config :: ShakeExtras -> Config
..} <- Action ShakeExtras
getShakeExtras
  forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ forall a. Var a -> (a -> IO a) -> IO ()
modifyVar_ Var [VersionChange]
versionChanges (forall (f :: * -> *) a. Applicative f => a -> f a
pure forall b c a. (b -> c) -> (a -> b) -> a -> c
. (forall a. [a] -> [a] -> [a]
++ [VersionChange {Maybe Version
PackageName
Version
vcNew :: Version
vcOld :: Maybe Version
vcName :: PackageName
vcNew :: Version
vcOld :: Maybe Version
vcName :: PackageName
..}]))

-- | Get version changes since the last run
getVersionChanges :: Action [VersionChange]
getVersionChanges :: Action [VersionChange]
getVersionChanges = do
  ShakeExtras {Map PackageKey Package
Var [VersionChange]
Var (Map PackageKey LastVersion)
Config
lastVersions :: Var (Map PackageKey LastVersion)
targetPackages :: Map PackageKey Package
versionChanges :: Var [VersionChange]
config :: Config
lastVersions :: ShakeExtras -> Var (Map PackageKey LastVersion)
targetPackages :: ShakeExtras -> Map PackageKey Package
versionChanges :: ShakeExtras -> Var [VersionChange]
config :: ShakeExtras -> Config
..} <- Action ShakeExtras
getShakeExtras
  forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ forall a. Var a -> IO a
readVar Var [VersionChange]
versionChanges

-- | Run an action, retry at most 'retry' times (defined in config) if it throws an exception
withRetry :: Action a -> Action a
withRetry :: forall a. Action a -> Action a
withRetry Action a
a = Action ShakeExtras
getShakeExtras forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \ShakeExtras {Map PackageKey Package
Var [VersionChange]
Var (Map PackageKey LastVersion)
Config
lastVersions :: Var (Map PackageKey LastVersion)
targetPackages :: Map PackageKey Package
versionChanges :: Var [VersionChange]
config :: Config
lastVersions :: ShakeExtras -> Var (Map PackageKey LastVersion)
targetPackages :: ShakeExtras -> Map PackageKey Package
versionChanges :: ShakeExtras -> Var [VersionChange]
config :: ShakeExtras -> Config
..} -> forall a. Int -> Action a -> Action a
actionRetry (Config -> Int
retry Config
config) Action a
a

-- | Get build dir
getBuildDir :: Action FilePath
getBuildDir :: Action String
getBuildDir = Config -> String
buildDir forall b c a. (b -> c) -> (a -> b) -> a -> c
. ShakeExtras -> Config
config forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Action ShakeExtras
getShakeExtras

-- | Get keyfile path
getKeyfilePath :: Action (Maybe FilePath)
getKeyfilePath :: Action (Maybe String)
getKeyfilePath = Config -> Maybe String
keyfile forall b c a. (b -> c) -> (a -> b) -> a -> c
. ShakeExtras -> Config
config forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Action ShakeExtras
getShakeExtras

-- | Get initial version of a package
getLastVersionOnDisk :: PackageKey -> Action (Maybe Version)
getLastVersionOnDisk :: PackageKey -> Action (Maybe Version)
getLastVersionOnDisk PackageKey
k = do
  ShakeExtras {Map PackageKey Package
Var [VersionChange]
Var (Map PackageKey LastVersion)
Config
lastVersions :: Var (Map PackageKey LastVersion)
targetPackages :: Map PackageKey Package
versionChanges :: Var [VersionChange]
config :: Config
lastVersions :: ShakeExtras -> Var (Map PackageKey LastVersion)
targetPackages :: ShakeExtras -> Map PackageKey Package
versionChanges :: ShakeExtras -> Var [VersionChange]
config :: ShakeExtras -> Config
..} <- Action ShakeExtras
getShakeExtras
  Map PackageKey LastVersion
versions <- forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ forall a. Var a -> IO a
readVar Var (Map PackageKey LastVersion)
lastVersions
  forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ case Map PackageKey LastVersion
versions forall k a. Ord k => Map k a -> k -> Maybe a
Map.!? PackageKey
k of
    Just (Updated Maybe Version
v Version
_) -> Maybe Version
v
    Just (OnDisk Version
v) -> forall a. a -> Maybe a
Just Version
v
    Maybe LastVersion
_ -> forall a. Maybe a
Nothing

-- | Get version of a package, no matter it was initial one or rule result
getRecentLastVersion :: PackageKey -> Action (Maybe Version)
getRecentLastVersion :: PackageKey -> Action (Maybe Version)
getRecentLastVersion PackageKey
k = do
  ShakeExtras {Map PackageKey Package
Var [VersionChange]
Var (Map PackageKey LastVersion)
Config
lastVersions :: Var (Map PackageKey LastVersion)
targetPackages :: Map PackageKey Package
versionChanges :: Var [VersionChange]
config :: Config
lastVersions :: ShakeExtras -> Var (Map PackageKey LastVersion)
targetPackages :: ShakeExtras -> Map PackageKey Package
versionChanges :: ShakeExtras -> Var [VersionChange]
config :: ShakeExtras -> Config
..} <- Action ShakeExtras
getShakeExtras
  Map PackageKey LastVersion
versions <- forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ forall a. Var a -> IO a
readVar Var (Map PackageKey LastVersion)
lastVersions
  forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ case Map PackageKey LastVersion
versions forall k a. Ord k => Map k a -> k -> Maybe a
Map.!? PackageKey
k of
    Just (Updated Maybe Version
_ Version
v) -> forall a. a -> Maybe a
Just Version
v
    Just (OnDisk Version
v) -> forall a. a -> Maybe a
Just Version
v
    Maybe LastVersion
_ -> forall a. Maybe a
Nothing

-- | Get updated version of a package
getLastVersionUpdated :: PackageKey -> Action (Maybe Version)
getLastVersionUpdated :: PackageKey -> Action (Maybe Version)
getLastVersionUpdated PackageKey
k = do
  ShakeExtras {Map PackageKey Package
Var [VersionChange]
Var (Map PackageKey LastVersion)
Config
lastVersions :: Var (Map PackageKey LastVersion)
targetPackages :: Map PackageKey Package
versionChanges :: Var [VersionChange]
config :: Config
lastVersions :: ShakeExtras -> Var (Map PackageKey LastVersion)
targetPackages :: ShakeExtras -> Map PackageKey Package
versionChanges :: ShakeExtras -> Var [VersionChange]
config :: ShakeExtras -> Config
..} <- Action ShakeExtras
getShakeExtras
  Map PackageKey LastVersion
versions <- forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ forall a. Var a -> IO a
readVar Var (Map PackageKey LastVersion)
lastVersions
  forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ case Map PackageKey LastVersion
versions forall k a. Ord k => Map k a -> k -> Maybe a
Map.!? PackageKey
k of
    Just (Updated Maybe Version
_ Version
v) -> forall a. a -> Maybe a
Just Version
v
    Maybe LastVersion
_ -> forall a. Maybe a
Nothing

-- | Add nvchecker result of a package
updateLastVersion :: PackageKey -> Version -> Action ()
updateLastVersion :: PackageKey -> Version -> Action ()
updateLastVersion PackageKey
k Version
v = do
  ShakeExtras {Map PackageKey Package
Var [VersionChange]
Var (Map PackageKey LastVersion)
Config
lastVersions :: Var (Map PackageKey LastVersion)
targetPackages :: Map PackageKey Package
versionChanges :: Var [VersionChange]
config :: Config
lastVersions :: ShakeExtras -> Var (Map PackageKey LastVersion)
targetPackages :: ShakeExtras -> Map PackageKey Package
versionChanges :: ShakeExtras -> Var [VersionChange]
config :: ShakeExtras -> Config
..} <- Action ShakeExtras
getShakeExtras
  forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$
    forall a. Var a -> (a -> IO a) -> IO ()
modifyVar_ Var (Map PackageKey LastVersion)
lastVersions forall a b. (a -> b) -> a -> b
$ \Map PackageKey LastVersion
versions -> forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ case Map PackageKey LastVersion
versions forall k a. Ord k => Map k a -> k -> Maybe a
Map.!? PackageKey
k of
      Just (Updated Maybe Version
o Version
_) -> forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert PackageKey
k (Maybe Version -> Version -> LastVersion
Updated Maybe Version
o Version
v) Map PackageKey LastVersion
versions
      Just (OnDisk Version
lv) -> forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert PackageKey
k (Maybe Version -> Version -> LastVersion
Updated (forall a. a -> Maybe a
Just Version
lv) Version
v) Map PackageKey LastVersion
versions
      Maybe LastVersion
_ -> forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert PackageKey
k (Maybe Version -> Version -> LastVersion
Updated forall a. Maybe a
Nothing Version
v) Map PackageKey LastVersion
versions

-- | Get all initial versions
getAllOnDiskVersions :: Action (Map PackageKey Version)
getAllOnDiskVersions :: Action (Map PackageKey Version)
getAllOnDiskVersions = do
  ShakeExtras {Map PackageKey Package
Var [VersionChange]
Var (Map PackageKey LastVersion)
Config
lastVersions :: Var (Map PackageKey LastVersion)
targetPackages :: Map PackageKey Package
versionChanges :: Var [VersionChange]
config :: Config
lastVersions :: ShakeExtras -> Var (Map PackageKey LastVersion)
targetPackages :: ShakeExtras -> Map PackageKey Package
versionChanges :: ShakeExtras -> Var [VersionChange]
config :: ShakeExtras -> Config
..} <- Action ShakeExtras
getShakeExtras
  Map PackageKey LastVersion
versions <- forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ forall a. Var a -> IO a
readVar Var (Map PackageKey LastVersion)
lastVersions
  let xs :: [(PackageKey, Maybe Version)]
xs = forall k a. Map k a -> [(k, a)]
Map.toList forall a b. (a -> b) -> a -> b
$
        forall a b c. (a -> b -> c) -> b -> a -> c
flip forall a b k. (a -> b) -> Map k a -> Map k b
Map.map Map PackageKey LastVersion
versions forall a b. (a -> b) -> a -> b
$ \case
          OnDisk Version
v -> forall a. a -> Maybe a
Just Version
v
          Updated Maybe Version
v Version
_ -> Maybe Version
v
  forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList [(PackageKey
k, Version
v) | (PackageKey
k, Just Version
v) <- [(PackageKey, Maybe Version)]
xs]

-- | Get if 'cacheNvchecker' is enabled
nvcheckerCacheEnabled :: Action Bool
nvcheckerCacheEnabled :: Action Bool
nvcheckerCacheEnabled = Config -> Bool
cacheNvchecker forall b c a. (b -> c) -> (a -> b) -> a -> c
. ShakeExtras -> Config
config forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Action ShakeExtras
getShakeExtras