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

-- | Copyright: (c) 2021 berberman
-- SPDX-License-Identifier: MIT
-- Maintainer: berberman <[email protected]>
-- Stability: experimental
-- Portability: portable
module NvFetcher.Core
  ( Core (..),
    coreRules,
    generateNixSourceExpr,
  )
where

import Data.Coerce (coerce)
import qualified Data.HashMap.Strict as HMap
import Data.Text (Text)
import qualified Data.Text as T
import Development.Shake
import Development.Shake.FilePath
import Development.Shake.Rule
import NeatInterpolation (trimming)
import NvFetcher.ExtractSrc
import NvFetcher.FetchRustGitDeps
import NvFetcher.NixExpr
import NvFetcher.NixFetcher
import NvFetcher.Nvchecker
import NvFetcher.Types
import NvFetcher.Types.ShakeExtras
import NvFetcher.Utils

-- | The core rule of nvchecker.
-- all rules are wired here.
coreRules :: Rules ()
coreRules :: Rules ()
coreRules = do
  Rules ()
nvcheckerRule
  Rules ()
prefetchRule
  Rules ()
extractSrcRule
  Rules ()
fetchRustGitDepsRule
  -- TODO: maybe an oracle rule is enough
  BuiltinLint (WithPackageKey Core) NixExpr
-> BuiltinIdentity (WithPackageKey Core) NixExpr
-> BuiltinRun (WithPackageKey Core) NixExpr
-> Rules ()
forall key value.
(RuleResult key ~ value, ShakeValue key, Typeable value,
 NFData value, Show value, Partial) =>
BuiltinLint key value
-> BuiltinIdentity key value -> BuiltinRun key value -> Rules ()
addBuiltinRule BuiltinLint (WithPackageKey Core) NixExpr
forall key value. BuiltinLint key value
noLint BuiltinIdentity (WithPackageKey Core) NixExpr
forall key value. BuiltinIdentity key value
noIdentity (BuiltinRun (WithPackageKey Core) NixExpr -> Rules ())
-> BuiltinRun (WithPackageKey Core) NixExpr -> Rules ()
forall a b. (a -> b) -> a -> b
$ \(WithPackageKey (Core
Core, PackageKey
pkg)) Maybe ByteString
mOld RunMode
mode -> case RunMode
mode of
    RunMode
RunDependenciesSame
      | Just ByteString
old <- Maybe ByteString
mOld,
        (NixExpr
expr, Version
_) <- ByteString -> (NixExpr, Version)
forall a. Binary a => ByteString -> a
decode' @(NixExpr, Version) ByteString
old ->
        RunResult NixExpr -> Action (RunResult NixExpr)
forall (f :: * -> *) a. Applicative f => a -> f a
pure (RunResult NixExpr -> Action (RunResult NixExpr))
-> RunResult NixExpr -> Action (RunResult NixExpr)
forall a b. (a -> b) -> a -> b
$ RunChanged -> ByteString -> NixExpr -> RunResult NixExpr
forall value. RunChanged -> ByteString -> value -> RunResult value
RunResult RunChanged
ChangedNothing ByteString
old NixExpr
expr
    RunMode
_ ->
      PackageKey -> Action (Maybe Package)
lookupPackage PackageKey
pkg Action (Maybe Package)
-> (Maybe Package -> Action (RunResult NixExpr))
-> Action (RunResult NixExpr)
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
        Maybe Package
Nothing -> String -> Action (RunResult NixExpr)
forall (m :: * -> *) a. MonadFail m => String -> m a
fail (String -> Action (RunResult NixExpr))
-> String -> Action (RunResult NixExpr)
forall a b. (a -> b) -> a -> b
$ String
"Unkown package key: " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> PackageKey -> String
forall a. Show a => a -> String
show PackageKey
pkg
        Just
          Package
            { _pversion :: Package -> NvcheckerQ
_pversion = NvcheckerQ VersionSource
versionSource NvcheckerOptions
options,
              _ppassthru :: Package -> PackagePassthru
_ppassthru = PackagePassthru HashMap NixExpr NixExpr
passthruMap,
              Maybe PackageCargoFilePath
Maybe PackageExtractSrc
NixExpr
UseStaleVersion
PackageFetcher
_ppinned :: Package -> UseStaleVersion
_pcargo :: Package -> Maybe PackageCargoFilePath
_pextract :: Package -> Maybe PackageExtractSrc
_pfetcher :: Package -> PackageFetcher
_pname :: Package -> NixExpr
_ppinned :: UseStaleVersion
_pcargo :: Maybe PackageCargoFilePath
_pextract :: Maybe PackageExtractSrc
_pfetcher :: PackageFetcher
_pname :: NixExpr
..
            } -> do
            -- it's important to always rerun
            -- since the package definition is not tracked at all
            Action ()
alwaysRerun
            (NvcheckerA Version
version Maybe Version
mOldV Bool
_isStale) <- VersionSource
-> NvcheckerOptions -> PackageKey -> Action NvcheckerA
checkVersion VersionSource
versionSource NvcheckerOptions
options PackageKey
pkg
            NixFetcher 'Fetched
prefetched <- NixFetcher 'Fresh -> Action (NixFetcher 'Fetched)
prefetch (NixFetcher 'Fresh -> Action (NixFetcher 'Fetched))
-> NixFetcher 'Fresh -> Action (NixFetcher 'Fetched)
forall a b. (a -> b) -> a -> b
$ PackageFetcher
_pfetcher Version
version
            String
shakeDir <- Action String
getShakeDir
            -- extract src
            NixExpr
appending1 <-
              case Maybe PackageExtractSrc
_pextract of
                Just (PackageExtractSrc NonEmpty String
extract) -> do
                  [(String, NixExpr)]
result <- HashMap String NixExpr -> [(String, NixExpr)]
forall k v. HashMap k v -> [(k, v)]
HMap.toList (HashMap String NixExpr -> [(String, NixExpr)])
-> Action (HashMap String NixExpr) -> Action [(String, NixExpr)]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> NixFetcher 'Fetched
-> NonEmpty String -> Action (HashMap String NixExpr)
extractSrcs NixFetcher 'Fetched
prefetched NonEmpty String
extract
                  [NixExpr] -> NixExpr
T.unlines
                    ([NixExpr] -> NixExpr) -> Action [NixExpr] -> Action NixExpr
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [Action NixExpr] -> Action [NixExpr]
forall (t :: * -> *) (m :: * -> *) a.
(Traversable t, Monad m) =>
t (m a) -> m (t a)
sequence
                      [ do
                          -- write extracted files to shake dir
                          -- and read them in nix using 'builtins.readFile'
                          String -> String -> Action ()
forall (m :: * -> *).
(MonadIO m, Partial) =>
String -> String -> m ()
writeFile' (String
shakeDir String -> String -> String
</> String
path) (NixExpr -> String
T.unpack NixExpr
v)
                          NixExpr -> Action NixExpr
forall (f :: * -> *) a. Applicative f => a -> f a
pure (NixExpr -> Action NixExpr) -> NixExpr -> Action NixExpr
forall a b. (a -> b) -> a -> b
$ String -> NixExpr
forall a. ToNixExpr a => a -> NixExpr
toNixExpr String
k NixExpr -> NixExpr -> NixExpr
forall a. Semigroup a => a -> a -> a
<> NixExpr
" = builtins.readFile ./" NixExpr -> NixExpr -> NixExpr
forall a. Semigroup a => a -> a -> a
<> String -> NixExpr
T.pack String
path NixExpr -> NixExpr -> NixExpr
forall a. Semigroup a => a -> a -> a
<> NixExpr
";"
                        | (String
k, NixExpr
v) <- [(String, NixExpr)]
result,
                          let path :: String
path =
                                NixExpr -> String
T.unpack NixExpr
_pname
                                  String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"-"
                                  String -> String -> String
forall a. Semigroup a => a -> a -> a
<> NixExpr -> String
T.unpack (Version -> NixExpr
coerce Version
version)
                                  String -> String -> String
</> String
k
                      ]
                Maybe PackageExtractSrc
_ -> NixExpr -> Action NixExpr
forall (f :: * -> *) a. Applicative f => a -> f a
pure NixExpr
""
            -- cargo lock
            NixExpr
appending2 <-
              case Maybe PackageCargoFilePath
_pcargo of
                Just (PackageCargoFilePath String
lockPath) -> do
                  (String
_, NixExpr
lockData) <- [(String, NixExpr)] -> (String, NixExpr)
forall a. [a] -> a
head ([(String, NixExpr)] -> (String, NixExpr))
-> (HashMap String NixExpr -> [(String, NixExpr)])
-> HashMap String NixExpr
-> (String, NixExpr)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. HashMap String NixExpr -> [(String, NixExpr)]
forall k v. HashMap k v -> [(k, v)]
HMap.toList (HashMap String NixExpr -> (String, NixExpr))
-> Action (HashMap String NixExpr) -> Action (String, NixExpr)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> NixFetcher 'Fetched -> String -> Action (HashMap String NixExpr)
extractSrc NixFetcher 'Fetched
prefetched String
lockPath
                  HashMap NixExpr Checksum
result <- NixFetcher 'Fetched -> String -> Action (HashMap NixExpr Checksum)
fetchRustGitDeps NixFetcher 'Fetched
prefetched String
lockPath
                  let body :: NixExpr
body = [NixExpr] -> NixExpr
T.unlines [NixExpr -> NixExpr
asString NixExpr
k NixExpr -> NixExpr -> NixExpr
forall a. Semigroup a => a -> a -> a
<> NixExpr
" = " NixExpr -> NixExpr -> NixExpr
forall a. Semigroup a => a -> a -> a
<> NixExpr -> NixExpr
coerce (NixExpr -> NixExpr
asString (NixExpr -> NixExpr) -> NixExpr -> NixExpr
forall a b. (a -> b) -> a -> b
$ Checksum -> NixExpr
coerce Checksum
v) NixExpr -> NixExpr -> NixExpr
forall a. Semigroup a => a -> a -> a
<> NixExpr
";" | (NixExpr
k, Checksum
v) <- HashMap NixExpr Checksum -> [(NixExpr, Checksum)]
forall k v. HashMap k v -> [(k, v)]
HMap.toList HashMap NixExpr Checksum
result]
                      lockPath' :: String
lockPath' =
                        NixExpr -> String
T.unpack NixExpr
_pname
                          String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"-"
                          String -> String -> String
forall a. Semigroup a => a -> a -> a
<> NixExpr -> String
T.unpack (Version -> NixExpr
coerce Version
version)
                          String -> String -> String
</> String
lockPath
                      lockPathNix :: NixExpr
lockPathNix = NixExpr
"./" NixExpr -> NixExpr -> NixExpr
forall a. Semigroup a => a -> a -> a
<> String -> NixExpr
T.pack String
lockPath'
                  -- similar to extract src, write lock file to shake dir
                  String -> String -> Action ()
forall (m :: * -> *).
(MonadIO m, Partial) =>
String -> String -> m ()
writeFile' (String
shakeDir String -> String -> String
</> String
lockPath') (String -> Action ()) -> String -> Action ()
forall a b. (a -> b) -> a -> b
$ NixExpr -> String
T.unpack NixExpr
lockData
                  NixExpr -> Action NixExpr
forall (f :: * -> *) a. Applicative f => a -> f a
pure
                    [trimming|
                  cargoLock = {
                    lockFile = $lockPathNix;
                    outputHashes = {
                      $body
                    };
                  };
                |]
                Maybe PackageCargoFilePath
_ -> NixExpr -> Action NixExpr
forall (f :: * -> *) a. Applicative f => a -> f a
pure NixExpr
""
            -- passthru
            let appending3 :: NixExpr
appending3 = [NixExpr] -> NixExpr
T.unlines [NixExpr
k NixExpr -> NixExpr -> NixExpr
forall a. Semigroup a => a -> a -> a
<> NixExpr
" = " NixExpr -> NixExpr -> NixExpr
forall a. Semigroup a => a -> a -> a
<> NixExpr
v NixExpr -> NixExpr -> NixExpr
forall a. Semigroup a => a -> a -> a
<> NixExpr
";" | (NixExpr
k, NixExpr -> NixExpr
asString -> NixExpr
v) <- HashMap NixExpr NixExpr -> [(NixExpr, NixExpr)]
forall k v. HashMap k v -> [(k, v)]
HMap.toList HashMap NixExpr NixExpr
passthruMap]

            -- update changelog
            case Maybe Version
mOldV of
              Maybe Version
Nothing ->
                NixExpr -> Maybe Version -> Version -> Action ()
recordVersionChange NixExpr
_pname Maybe Version
forall a. Maybe a
Nothing Version
version
              Just Version
old
                | Version
old Version -> Version -> Bool
forall a. Eq a => a -> a -> Bool
/= Version
version ->
                  NixExpr -> Maybe Version -> Version -> Action ()
recordVersionChange NixExpr
_pname (Version -> Maybe Version
forall a. a -> Maybe a
Just Version
old) Version
version
              Maybe Version
_ -> () -> Action ()
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()

            let result :: NixExpr
result = NixExpr -> Version -> NixFetcher 'Fetched -> NixExpr -> NixExpr
gen NixExpr
_pname Version
version NixFetcher 'Fetched
prefetched (NixExpr -> NixExpr) -> NixExpr -> NixExpr
forall a b. (a -> b) -> a -> b
$ NixExpr
appending1 NixExpr -> NixExpr -> NixExpr
forall a. Semigroup a => a -> a -> a
<> NixExpr
appending2 NixExpr -> NixExpr -> NixExpr
forall a. Semigroup a => a -> a -> a
<> NixExpr
appending3
            RunResult NixExpr -> Action (RunResult NixExpr)
forall (f :: * -> *) a. Applicative f => a -> f a
pure (RunResult NixExpr -> Action (RunResult NixExpr))
-> RunResult NixExpr -> Action (RunResult NixExpr)
forall a b. (a -> b) -> a -> b
$ RunChanged -> ByteString -> NixExpr -> RunResult NixExpr
forall value. RunChanged -> ByteString -> value -> RunResult value
RunResult RunChanged
ChangedRecomputeDiff ((NixExpr, Version) -> ByteString
forall a. Binary a => a -> ByteString
encode' (NixExpr
result, Version
version)) NixExpr
result

-- | Run the core rule.
-- Given a 'PackageKey', run "NvFetcher.Nvchecker", "NvFetcher.NixFetcher"
-- (may also run "NvFetcher.ExtractSrc" or "NvFetrcher.FetchRustGitDeps")
--
-- @
-- Package
-- { _pname = "feeluown-core",
--   _pversion = NvcheckerQ (Pypi "feeluown") def,
--   _pfetcher = pypiFetcher "feeluown",
--   _pextract = Nothing,
--   _pcargo = Nothing,
--   _ppassthru = PackagePassthru (HashMap.fromList [("a", "B")])
-- }
-- @
--
-- resulting a nix exprs snippet like:
--
-- @
-- feeluown-core = {
--     pname = "feeluown-core";
--     version = "3.7.7";
--     src = fetchurl {
--       sha256 = "06d3j39ff9znqxkhp9ly81lcgajkhg30hyqxy2809yn23xixg3x2";
--       url = "https://pypi.io/packages/source/f/feeluown/feeluown-3.7.7.tar.gz";
--     };
--     a = "B";
--   };
-- @
generateNixSourceExpr :: PackageKey -> Action NixExpr
generateNixSourceExpr :: PackageKey -> Action NixExpr
generateNixSourceExpr PackageKey
k = WithPackageKey Core -> Action NixExpr
forall key value.
(Partial, RuleResult key ~ value, ShakeValue key,
 Typeable value) =>
key -> Action value
apply1 (WithPackageKey Core -> Action NixExpr)
-> WithPackageKey Core -> Action NixExpr
forall a b. (a -> b) -> a -> b
$ (Core, PackageKey) -> WithPackageKey Core
forall k. (k, PackageKey) -> WithPackageKey k
WithPackageKey (Core
Core, PackageKey
k)

gen :: PackageName -> Version -> NixFetcher Fetched -> Text -> Text
gen :: NixExpr -> Version -> NixFetcher 'Fetched -> NixExpr -> NixExpr
gen NixExpr
name (Version -> NixExpr
coerce -> NixExpr
ver) (NixFetcher 'Fetched -> NixExpr
forall a. ToNixExpr a => a -> NixExpr
toNixExpr -> NixExpr
srcP) NixExpr
appending =
  [trimming|
  $name = {
    pname = "$name";
    version = "$ver";
    src = $srcP;$appending'
  };
|]
  where
    appending' :: NixExpr
appending' = if NixExpr -> Bool
T.null NixExpr
appending then NixExpr
"" else NixExpr
"\n" NixExpr -> NixExpr -> NixExpr
forall a. Semigroup a => a -> a -> a
<> NixExpr
appending