{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ViewPatterns #-}

-- | Copyright: (c) 2021-2022 berberman
-- SPDX-License-Identifier: MIT
-- Maintainer: berberman <[email protected]>
-- Stability: experimental
-- Portability: portable
--
-- The main module of nvfetcher. If you want to create CLI program with it, it's enough to import only this module.
--
-- Example:
--
-- @
-- module Main where
--
-- import NvFetcher
--
-- main :: IO ()
-- main = runNvFetcher packageSet
--
-- packageSet :: PackageSet ()
-- packageSet = do
--   define $ package "feeluown-core" `fromPypi` "feeluown"
--   define $ package "qliveplayer" `fromGitHub` ("THMonster", "QLivePlayer")
-- @
--
-- You can find more examples of packages in @Main_example.hs@.
--
-- Running the created program:
--
-- * @main@ -- abbreviation of @main build@
-- * @main build@ -- build nix sources expr from given @packageSet@
-- * @main clean@ -- delete .shake dir and generated nix file
--
-- All shake options are inherited.
module NvFetcher
  ( runNvFetcher,
    runNvFetcher',
    runNvFetcherNoCLI,
    applyCliOptions,
    parseLastVersions,
    module NvFetcher.PackageSet,
    module NvFetcher.Types,
    module NvFetcher.Types.ShakeExtras,
  )
where

import Control.Monad.Extra (forM_, unless, when, whenJust, whenM)
import qualified Data.Aeson as A
import qualified Data.Aeson.Encode.Pretty as A
import qualified Data.Aeson.Types as A
import qualified Data.ByteString.Lazy.Char8 as LBS
import Data.Default
import Data.List (partition, (\\))
import qualified Data.Map.Strict as Map
import Data.Maybe (catMaybes, fromJust, fromMaybe, isJust)
import Data.Text (Text)
import qualified Data.Text as T
import Development.Shake
import Development.Shake.FilePath
import NeatInterpolation (trimming)
import NvFetcher.Config
import NvFetcher.Core
import NvFetcher.NixExpr (ToNixExpr (toNixExpr))
import NvFetcher.NixFetcher
import NvFetcher.Nvchecker
import NvFetcher.Options
import NvFetcher.PackageSet
import NvFetcher.Types
import NvFetcher.Types.ShakeExtras
import NvFetcher.Utils (aesonKey, getDataDir)
import qualified System.Directory.Extra as D
import Text.Regex.TDFA ((=~))

-- | Run nvfetcher with CLI options
--
-- This function calls 'runNvFetcherNoCLI', using 'def' 'Config' overridden by 'CLIOptions'.
-- Use this function to create your own Haskell executable program.
runNvFetcher :: PackageSet () -> IO ()
runNvFetcher :: PackageSet () -> IO ()
runNvFetcher = Config -> PackageSet () -> IO ()
runNvFetcher' Config
forall a. Default a => a
def

-- | Similar to 'runNvFetcher', but uses custom @config@ instead of 'def' overridden by 'CLIOptions'
runNvFetcher' :: Config -> PackageSet () -> IO ()
runNvFetcher' :: Config -> PackageSet () -> IO ()
runNvFetcher' Config
config PackageSet ()
packageSet =
  Parser CLIOptions -> IO CLIOptions
forall a. Parser a -> IO a
getCLIOptions Parser CLIOptions
cliOptionsParser IO CLIOptions -> (CLIOptions -> IO ()) -> IO ()
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \CLIOptions
cli ->
    Config -> CLIOptions -> IO Config
applyCliOptions Config
config CLIOptions
cli IO Config -> (Config -> IO ()) -> IO ()
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \Config
o ->
      Config -> Target -> PackageSet () -> IO ()
runNvFetcherNoCLI Config
o (CLIOptions -> Target
optTarget CLIOptions
cli) PackageSet ()
packageSet

-- | Apply 'CLIOptions' to 'Config'
applyCliOptions :: Config -> CLIOptions -> IO Config
applyCliOptions :: Config -> CLIOptions -> IO Config
applyCliOptions Config
config CLIOptions {Bool
Int
String
Maybe String
Target
optTarget :: CLIOptions -> Target
optBuildDir :: String
optCommit :: Bool
optCommitSummary :: Maybe String
optLogPath :: Maybe String
optThreads :: Int
optRetry :: Int
optTiming :: Bool
optVerbose :: Bool
optPkgNameFilter :: Maybe String
optKeyfile :: Maybe String
optKeepOldFiles :: Bool
optKeepGoing :: Bool
optTarget :: Target
optBuildDir :: CLIOptions -> String
optCommit :: CLIOptions -> Bool
optCommitSummary :: CLIOptions -> Maybe String
optLogPath :: CLIOptions -> Maybe String
optThreads :: CLIOptions -> Int
optRetry :: CLIOptions -> Int
optTiming :: CLIOptions -> Bool
optVerbose :: CLIOptions -> Bool
optPkgNameFilter :: CLIOptions -> Maybe String
optKeyfile :: CLIOptions -> Maybe String
optKeepOldFiles :: CLIOptions -> Bool
optKeepGoing :: CLIOptions -> Bool
..} = do
  Maybe String
aKeyfile <- case Maybe String
optKeyfile of
    Just String
k -> String -> Maybe String
forall a. a -> Maybe a
Just (String -> Maybe String) -> IO String -> IO (Maybe String)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String -> IO String
D.makeAbsolute String
k
    Maybe String
_ -> Maybe String -> IO (Maybe String)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Maybe String
forall a. Maybe a
Nothing
  Config -> IO Config
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Config -> IO Config) -> Config -> IO Config
forall a b. (a -> b) -> a -> b
$
    Config
config
      { buildDir = optBuildDir,
        actionAfterBuild = do
          whenJust optLogPath logChangesToFile
          when optCommit (commitChanges (fromMaybe "Update" optCommitSummary))
          actionAfterBuild config,
        shakeConfig =
          (shakeConfig config)
            { shakeTimings = optTiming,
              shakeVerbosity = if optVerbose then Verbose else Info,
              shakeThreads = optThreads
            },
        filterRegex = optPkgNameFilter,
        retry = optRetry,
        keyfile = aKeyfile,
        keepOldFiles = optKeepOldFiles,
        keepGoing = optKeepGoing
      }

logChangesToFile :: FilePath -> Action ()
logChangesToFile :: String -> Action ()
logChangesToFile String
fp = do
  [VersionChange]
changes <- Action [VersionChange]
getVersionChanges
  String -> String -> Action ()
forall (m :: * -> *).
(MonadIO m, Located) =>
String -> String -> m ()
writeFile' String
fp (String -> Action ()) -> String -> Action ()
forall a b. (a -> b) -> a -> b
$ [String] -> String
unlines ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ VersionChange -> String
forall a. Show a => a -> String
show (VersionChange -> String) -> [VersionChange] -> [String]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [VersionChange]
changes

commitChanges :: String -> Action ()
commitChanges :: String -> Action ()
commitChanges String
commitSummary = do
  [VersionChange]
changes <- Action [VersionChange]
getVersionChanges
  let commitMsg :: Maybe String
commitMsg = case [VersionChange]
changes of
        [VersionChange
x] -> String -> Maybe String
forall a. a -> Maybe a
Just (String -> Maybe String) -> String -> Maybe String
forall a b. (a -> b) -> a -> b
$ VersionChange -> String
forall a. Show a => a -> String
show VersionChange
x
        xs :: [VersionChange]
xs@(VersionChange
_ : [VersionChange]
_) -> String -> Maybe String
forall a. a -> Maybe a
Just (String -> Maybe String) -> String -> Maybe String
forall a b. (a -> b) -> a -> b
$ String
commitSummary String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"\n" String -> String -> String
forall a. Semigroup a => a -> a -> a
<> [String] -> String
unlines (VersionChange -> String
forall a. Show a => a -> String
show (VersionChange -> String) -> [VersionChange] -> [String]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [VersionChange]
xs)
        [] -> Maybe String
forall a. Maybe a
Nothing
  Maybe String -> (String -> Action ()) -> Action ()
forall (m :: * -> *) a.
Applicative m =>
Maybe a -> (a -> m ()) -> m ()
whenJust Maybe String
commitMsg ((String -> Action ()) -> Action ())
-> (String -> Action ()) -> Action ()
forall a b. (a -> b) -> a -> b
$ \String
msg -> do
    String -> Action ()
putInfo String
"Commiting changes"
    Action String
getBuildDir Action String -> (String -> Action ()) -> Action ()
forall a b. Action a -> (a -> Action b) -> Action b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \String
dir -> Located => [CmdOption] -> String -> [String] -> Action ()
[CmdOption] -> String -> [String] -> Action ()
command_ [] String
"git" [String
"add", String
dir]
    Located => [CmdOption] -> String -> [String] -> Action ()
[CmdOption] -> String -> [String] -> Action ()
command_ [] String
"git" [String
"commit", String
"-m", String
msg]

-- | @Parse generated.nix@
parseLastVersions :: FilePath -> IO (Maybe (Map.Map PackageKey Version))
parseLastVersions :: String -> IO (Maybe (Map PackageKey Version))
parseLastVersions String
jsonFile =
  String -> IO Bool
D.doesFileExist String
jsonFile IO Bool
-> (Bool -> IO (Maybe (Map PackageKey Version)))
-> IO (Maybe (Map PackageKey Version))
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
    Bool
True -> do
      Maybe (Map PackageName Object)
objs <- String -> IO (Maybe (Map PackageName Object))
forall a. FromJSON a => String -> IO (Maybe a)
A.decodeFileStrict' String
jsonFile
      Maybe (Map PackageKey Version)
-> IO (Maybe (Map PackageKey Version))
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Maybe (Map PackageKey Version)
 -> IO (Maybe (Map PackageKey Version)))
-> Maybe (Map PackageKey Version)
-> IO (Maybe (Map PackageKey Version))
forall a b. (a -> b) -> a -> b
$
        ((Map PackageName Object -> Map PackageKey Version)
 -> Maybe (Map PackageName Object)
 -> Maybe (Map PackageKey Version))
-> Maybe (Map PackageName Object)
-> (Map PackageName Object -> Map PackageKey Version)
-> Maybe (Map PackageKey Version)
forall a b c. (a -> b -> c) -> b -> a -> c
flip (Map PackageName Object -> Map PackageKey Version)
-> Maybe (Map PackageName Object) -> Maybe (Map PackageKey Version)
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Maybe (Map PackageName Object)
objs ((Map PackageName Object -> Map PackageKey Version)
 -> Maybe (Map PackageKey Version))
-> (Map PackageName Object -> Map PackageKey Version)
-> Maybe (Map PackageKey Version)
forall a b. (a -> b) -> a -> b
$
          ( \[(PackageName, Object)]
xs ->
              [(PackageKey, Version)] -> Map PackageKey Version
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList
                ([(PackageKey, Version)] -> Map PackageKey Version)
-> ([Maybe (PackageKey, Version)] -> [(PackageKey, Version)])
-> [Maybe (PackageKey, Version)]
-> Map PackageKey Version
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Maybe (PackageKey, Version)] -> [(PackageKey, Version)]
forall a. [Maybe a] -> [a]
catMaybes
                ([Maybe (PackageKey, Version)] -> Map PackageKey Version)
-> [Maybe (PackageKey, Version)] -> Map PackageKey Version
forall a b. (a -> b) -> a -> b
$ [(PackageName -> PackageKey
PackageKey PackageName
k,) (Version -> (PackageKey, Version))
-> Maybe Version -> Maybe (PackageKey, Version)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (Object -> Parser Version) -> Object -> Maybe Version
forall a b. (a -> Parser b) -> a -> Maybe b
A.parseMaybe (Object -> Key -> Parser Version
forall a. FromJSON a => Object -> Key -> Parser a
A..: Key
"version") Object
obj | (PackageName
k, Object
obj) <- [(PackageName, Object)]
xs]
          )
            ([(PackageName, Object)] -> Map PackageKey Version)
-> (Map PackageName Object -> [(PackageName, Object)])
-> Map PackageName Object
-> Map PackageKey Version
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Map PackageName Object -> [(PackageName, Object)]
forall k a. Map k a -> [(k, a)]
Map.toList
    Bool
_ -> Maybe (Map PackageKey Version)
-> IO (Maybe (Map PackageKey Version))
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Maybe (Map PackageKey Version)
forall a. Monoid a => a
mempty

-- | Entry point of nvfetcher
runNvFetcherNoCLI :: Config -> Target -> PackageSet () -> IO ()
runNvFetcherNoCLI :: Config -> Target -> PackageSet () -> IO ()
runNvFetcherNoCLI config :: Config
config@Config {Bool
Int
String
Maybe String
ShakeOptions
Action ()
Rules ()
buildDir :: Config -> String
actionAfterBuild :: Config -> Action ()
shakeConfig :: Config -> ShakeOptions
filterRegex :: Config -> Maybe String
retry :: Config -> Int
keyfile :: Config -> Maybe String
keepOldFiles :: Config -> Bool
keepGoing :: Config -> Bool
shakeConfig :: ShakeOptions
buildDir :: String
customRules :: Rules ()
actionAfterBuild :: Action ()
actionAfterClean :: Action ()
retry :: Int
filterRegex :: Maybe String
cacheNvchecker :: Bool
keepOldFiles :: Bool
keyfile :: Maybe String
keepGoing :: Bool
customRules :: Config -> Rules ()
actionAfterClean :: Config -> Action ()
cacheNvchecker :: Config -> Bool
..} Target
target PackageSet ()
packageSet = do
  Map PackageKey Package
pkgs <- (Package -> Package)
-> Map PackageKey Package -> Map PackageKey Package
forall a b k. (a -> b) -> Map k a -> Map k b
Map.map Package -> Package
pinIfUnmatch (Map PackageKey Package -> Map PackageKey Package)
-> IO (Map PackageKey Package) -> IO (Map PackageKey Package)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> PackageSet () -> IO (Map PackageKey Package)
runPackageSet PackageSet ()
packageSet
  Maybe (Map PackageKey Version)
lastVersions <- String -> IO (Maybe (Map PackageKey Version))
parseLastVersions (String -> IO (Maybe (Map PackageKey Version)))
-> String -> IO (Maybe (Map PackageKey Version))
forall a b. (a -> b) -> a -> b
$ String
buildDir String -> String -> String
</> String
generatedJsonFileName
  String
shakeDir <- IO String
getDataDir
  -- Set shakeFiles and shakeVersion
  let shakeOptions1 :: ShakeOptions
shakeOptions1 = ShakeOptions
shakeConfig {shakeFiles = shakeDir, shakeVersion = "2"}
  -- shakeConfig in Config will be shakeOptions1 (not including shake extra)
  ShakeExtras
shakeExtras <- Config
-> Map PackageKey Package
-> Map PackageKey Version
-> IO ShakeExtras
initShakeExtras (Config
config {shakeConfig = shakeOptions1}) Map PackageKey Package
pkgs (Map PackageKey Version -> IO ShakeExtras)
-> Map PackageKey Version -> IO ShakeExtras
forall a b. (a -> b) -> a -> b
$ Map PackageKey Version
-> Maybe (Map PackageKey Version) -> Map PackageKey Version
forall a. a -> Maybe a -> a
fromMaybe Map PackageKey Version
forall a. Monoid a => a
mempty Maybe (Map PackageKey Version)
lastVersions
  -- Set shakeExtra
  let shakeOptions2 :: ShakeOptions
shakeOptions2 = ShakeOptions
shakeOptions1 {shakeExtra = addShakeExtra shakeExtras (shakeExtra shakeConfig)}
      rules :: Rules ()
rules = Config -> Rules ()
mainRules Config
config
  ShakeOptions -> Rules () -> IO ()
shake ShakeOptions
shakeOptions2 (Rules () -> IO ()) -> Rules () -> IO ()
forall a b. (a -> b) -> a -> b
$ Located => [String] -> Rules ()
[String] -> Rules ()
want [Target -> String
forall a. Show a => a -> String
show Target
target] Rules () -> Rules () -> Rules ()
forall a b. Rules a -> Rules b -> Rules b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> Rules ()
rules
  where
    -- Don't touch already pinned packages
    pinIfUnmatch :: Package -> Package
pinIfUnmatch x :: Package
x@Package {Maybe PackageCargoLockFiles
Maybe PackageExtractSrc
PackageName
UseStaleVersion
PackagePassthru
DateFormat
ForceFetch
CheckVersion
PackageFetcher
_pname :: PackageName
_pversion :: CheckVersion
_pfetcher :: PackageFetcher
_pextract :: Maybe PackageExtractSrc
_pcargo :: Maybe PackageCargoLockFiles
_ppassthru :: PackagePassthru
_ppinned :: UseStaleVersion
_pgitdateformat :: DateFormat
_pforcefetch :: ForceFetch
_pname :: Package -> PackageName
_pversion :: Package -> CheckVersion
_pfetcher :: Package -> PackageFetcher
_pextract :: Package -> Maybe PackageExtractSrc
_pcargo :: Package -> Maybe PackageCargoLockFiles
_ppassthru :: Package -> PackagePassthru
_ppinned :: Package -> UseStaleVersion
_pgitdateformat :: Package -> DateFormat
_pforcefetch :: Package -> ForceFetch
..}
      | Just String
regex <- Maybe String
filterRegex =
          Package
x
            { _ppinned = case _ppinned of
                UseStaleVersion
PermanentStale -> UseStaleVersion
PermanentStale
                UseStaleVersion
_ ->
                  if PackageName
_pname PackageName -> String -> Bool
forall source source1 target.
(RegexMaker Regex CompOption ExecOption source,
 RegexContext Regex source1 target) =>
source1 -> source -> target
=~ String
regex
                    then UseStaleVersion
NoStale
                    else UseStaleVersion
TemporaryStale
            }
      | Bool
otherwise = Package
x

--------------------------------------------------------------------------------

mainRules :: Config -> Rules ()
mainRules :: Config -> Rules ()
mainRules Config {Bool
Int
String
Maybe String
ShakeOptions
Action ()
Rules ()
buildDir :: Config -> String
actionAfterBuild :: Config -> Action ()
shakeConfig :: Config -> ShakeOptions
filterRegex :: Config -> Maybe String
retry :: Config -> Int
keyfile :: Config -> Maybe String
keepOldFiles :: Config -> Bool
keepGoing :: Config -> Bool
customRules :: Config -> Rules ()
actionAfterClean :: Config -> Action ()
cacheNvchecker :: Config -> Bool
shakeConfig :: ShakeOptions
buildDir :: String
customRules :: Rules ()
actionAfterBuild :: Action ()
actionAfterClean :: Action ()
retry :: Int
filterRegex :: Maybe String
cacheNvchecker :: Bool
keepOldFiles :: Bool
keyfile :: Maybe String
keepGoing :: Bool
..} = do
  String
"clean" Located => String -> Action () -> Rules ()
String -> Action () -> Rules ()
~> do
    Action String
getBuildDir Action String -> (String -> Action ()) -> Action ()
forall a b. Action a -> (a -> Action b) -> Action b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (String -> [String] -> Action ())
-> [String] -> String -> Action ()
forall a b c. (a -> b -> c) -> b -> a -> c
flip String -> [String] -> Action ()
removeFilesAfter [String
"//*"]
    Action ()
actionAfterClean

  String
"purge" Located => String -> Action () -> Rules ()
String -> Action () -> Rules ()
~> do
    String
shakeDir <- ShakeOptions -> String
shakeFiles (ShakeOptions -> String) -> Action ShakeOptions -> Action String
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Action ShakeOptions
getShakeOptions
    String -> [String] -> Action ()
removeFilesAfter String
shakeDir [String
"//*"]

  String
"build" Located => String -> Action () -> Rules ()
String -> Action () -> Rules ()
~> do
    -- remove all files in build dir except generated nix and json
    -- since core rule has always rerun, any file not generated in this run will be removed
    Bool -> Action () -> Action ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless Bool
keepOldFiles (Action () -> Action ()) -> Action () -> Action ()
forall a b. (a -> b) -> a -> b
$
      Action Bool -> Action () -> Action ()
forall (m :: * -> *). Monad m => m Bool -> m () -> m ()
whenM (IO Bool -> Action Bool
forall a. IO a -> Action a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO Bool -> Action Bool) -> IO Bool -> Action Bool
forall a b. (a -> b) -> a -> b
$ String -> IO Bool
D.doesDirectoryExist String
buildDir) (Action () -> Action ()) -> Action () -> Action ()
forall a b. (a -> b) -> a -> b
$ do
        [String]
oldFiles <- ([String] -> [String] -> [String]
forall a. Eq a => [a] -> [a] -> [a]
\\ [String
generatedJsonFileName, String
generatedNixFileName]) ([String] -> [String]) -> Action [String] -> Action [String]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO [String] -> Action [String]
forall a. IO a -> Action a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (String -> IO [String]
D.listDirectory String
buildDir)
        String -> Action ()
putVerbose (String -> Action ()) -> String -> Action ()
forall a b. (a -> b) -> a -> b
$ String
"Removing old files: " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> [String] -> String
forall a. Show a => a -> String
show [String]
oldFiles
        IO () -> Action ()
forall a. IO a -> Action a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> Action ()) -> IO () -> Action ()
forall a b. (a -> b) -> a -> b
$ String -> [String] -> IO ()
removeFiles String
buildDir [String]
oldFiles
    [PackageKey]
allKeys <- Action [PackageKey]
getAllPackageKeys
    [(PackageKey, Maybe PackageResult)]
results <- ([Maybe PackageResult] -> [(PackageKey, Maybe PackageResult)])
-> Action [Maybe PackageResult]
-> Action [(PackageKey, Maybe PackageResult)]
forall a b. (a -> b) -> Action a -> Action b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ([PackageKey]
-> [Maybe PackageResult] -> [(PackageKey, Maybe PackageResult)]
forall a b. [a] -> [b] -> [(a, b)]
zip [PackageKey]
allKeys) (Action [Maybe PackageResult]
 -> Action [(PackageKey, Maybe PackageResult)])
-> Action [Maybe PackageResult]
-> Action [(PackageKey, Maybe PackageResult)]
forall a b. (a -> b) -> a -> b
$ [Action (Maybe PackageResult)] -> Action [Maybe PackageResult]
forall a. [Action a] -> Action [a]
parallel ([Action (Maybe PackageResult)] -> Action [Maybe PackageResult])
-> [Action (Maybe PackageResult)] -> Action [Maybe PackageResult]
forall a b. (a -> b) -> a -> b
$ PackageKey -> Action (Maybe PackageResult)
runPackage (PackageKey -> Action (Maybe PackageResult))
-> [PackageKey] -> [Action (Maybe PackageResult)]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [PackageKey]
allKeys
    let (((PackageKey, Maybe PackageResult) -> PackageResult)
-> [(PackageKey, Maybe PackageResult)] -> [PackageResult]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Maybe PackageResult -> PackageResult
forall a. Located => Maybe a -> a
fromJust (Maybe PackageResult -> PackageResult)
-> ((PackageKey, Maybe PackageResult) -> Maybe PackageResult)
-> (PackageKey, Maybe PackageResult)
-> PackageResult
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (PackageKey, Maybe PackageResult) -> Maybe PackageResult
forall a b. (a, b) -> b
snd) -> [PackageResult]
successResults, ((PackageKey, Maybe PackageResult) -> PackageKey)
-> [(PackageKey, Maybe PackageResult)] -> [PackageKey]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (PackageKey, Maybe PackageResult) -> PackageKey
forall a b. (a, b) -> a
fst -> [PackageKey]
failureKeys) = ((PackageKey, Maybe PackageResult) -> Bool)
-> [(PackageKey, Maybe PackageResult)]
-> ([(PackageKey, Maybe PackageResult)],
    [(PackageKey, Maybe PackageResult)])
forall a. (a -> Bool) -> [a] -> ([a], [a])
partition (Maybe PackageResult -> Bool
forall a. Maybe a -> Bool
isJust (Maybe PackageResult -> Bool)
-> ((PackageKey, Maybe PackageResult) -> Maybe PackageResult)
-> (PackageKey, Maybe PackageResult)
-> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (PackageKey, Maybe PackageResult) -> Maybe PackageResult
forall a b. (a, b) -> b
snd) [(PackageKey, Maybe PackageResult)]
results
    -- Record removed packages to version changes
    -- Failure keys are also considered as removed in this run
    Action (Map PackageKey Version)
getAllOnDiskVersions
      Action (Map PackageKey Version)
-> (Map PackageKey Version -> Action ()) -> Action ()
forall a b. Action a -> (a -> Action b) -> Action b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \Map PackageKey Version
oldPkgs -> [PackageKey] -> (PackageKey -> Action ()) -> Action ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ (Map PackageKey Version -> [PackageKey]
forall k a. Map k a -> [k]
Map.keys Map PackageKey Version
oldPkgs [PackageKey] -> [PackageKey] -> [PackageKey]
forall a. Eq a => [a] -> [a] -> [a]
\\ ([PackageKey]
allKeys [PackageKey] -> [PackageKey] -> [PackageKey]
forall a. Eq a => [a] -> [a] -> [a]
\\ [PackageKey]
failureKeys)) ((PackageKey -> Action ()) -> Action ())
-> (PackageKey -> Action ()) -> Action ()
forall a b. (a -> b) -> a -> b
$
        \PackageKey
pkg -> PackageName -> Maybe Version -> Version -> Action ()
recordVersionChange (PackageKey -> PackageName
forall a b. Coercible a b => a -> b
coerce PackageKey
pkg) (Map PackageKey Version
oldPkgs Map PackageKey Version -> PackageKey -> Maybe Version
forall k a. Ord k => Map k a -> k -> Maybe a
Map.!? PackageKey
pkg) Version
"∅"
    Action [VersionChange]
getVersionChanges Action [VersionChange]
-> ([VersionChange] -> Action ()) -> Action ()
forall a b. Action a -> (a -> Action b) -> Action b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \[VersionChange]
changes ->
      if [VersionChange] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [VersionChange]
changes
        then String -> Action ()
putInfo String
"Up to date"
        else do
          String -> Action ()
putInfo String
"Changes:"
          String -> Action ()
putInfo (String -> Action ()) -> String -> Action ()
forall a b. (a -> b) -> a -> b
$ [String] -> String
unlines ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ VersionChange -> String
forall a. Show a => a -> String
show (VersionChange -> String) -> [VersionChange] -> [String]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [VersionChange]
changes
    String
buildDir <- Action String
getBuildDir
    let generatedNixPath :: String
generatedNixPath = String
buildDir String -> String -> String
</> String
generatedNixFileName
        generatedJSONPath :: String
generatedJSONPath = String
buildDir String -> String -> String
</> String
generatedJsonFileName
    String -> Action ()
putVerbose (String -> Action ()) -> String -> Action ()
forall a b. (a -> b) -> a -> b
$ String
"Generating " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
generatedNixPath
    String -> String -> Action ()
forall (m :: * -> *).
(MonadIO m, Located) =>
String -> String -> m ()
writeFileChanged String
generatedNixPath (String -> Action ()) -> String -> Action ()
forall a b. (a -> b) -> a -> b
$ PackageName -> String
T.unpack (PackageName -> String) -> PackageName -> String
forall a b. (a -> b) -> a -> b
$ PackageName -> PackageName
srouces ([PackageName] -> PackageName
T.unlines ([PackageName] -> PackageName) -> [PackageName] -> PackageName
forall a b. (a -> b) -> a -> b
$ PackageResult -> PackageName
forall a. ToNixExpr a => a -> PackageName
toNixExpr (PackageResult -> PackageName) -> [PackageResult] -> [PackageName]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [PackageResult]
successResults) PackageName -> PackageName -> PackageName
forall a. Semigroup a => a -> a -> a
<> PackageName
"\n"
    String -> Action ()
putVerbose (String -> Action ()) -> String -> Action ()
forall a b. (a -> b) -> a -> b
$ String
"Generating " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
generatedJSONPath
    String -> String -> Action ()
forall (m :: * -> *).
(MonadIO m, Located) =>
String -> String -> m ()
writeFileChanged String
generatedJSONPath (String -> Action ()) -> String -> Action ()
forall a b. (a -> b) -> a -> b
$ ByteString -> String
LBS.unpack (ByteString -> String) -> ByteString -> String
forall a b. (a -> b) -> a -> b
$ Value -> ByteString
forall a. ToJSON a => a -> ByteString
A.encodePretty (Value -> ByteString) -> Value -> ByteString
forall a b. (a -> b) -> a -> b
$ [Pair] -> Value
A.object [PackageName -> Key
aesonKey (PackageResult -> PackageName
_prname PackageResult
r) Key -> PackageResult -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
forall v. ToJSON v => Key -> v -> Pair
A..= PackageResult
r | PackageResult
r <- [PackageResult]
successResults]
    Action ()
actionAfterBuild

  Rules ()
customRules
  Rules ()
coreRules

srouces :: Text -> Text
srouces :: PackageName -> PackageName
srouces PackageName
body =
  [trimming|
    # This file was generated by nvfetcher, please do not modify it manually.
    { fetchgit, fetchurl, fetchFromGitHub, dockerTools }:
    {
      $body
    }
  |]

generatedNixFileName :: String
generatedNixFileName :: String
generatedNixFileName = String
"generated.nix"

generatedJsonFileName :: String
generatedJsonFileName :: String
generatedJsonFileName = String
"generated.json"