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

-- | Copyright: (c) 2021 berberman
-- SPDX-License-Identifier: MIT
-- Maintainer: berberman <berberman@yandex.com>
-- Stability: experimental
-- Portability: portable
--
-- 'NixFetcher' is used to describe how to fetch package sources.
--
-- There are two types of fetchers overall:
--
-- 1. 'FetchGit' -- nix-prefetch-git
-- 2. 'FetchUrl' -- nix-prefetch-url
--
-- As you can see the type signature of 'prefetch':
-- a fetcher will be filled with the fetch result (hash) after the prefetch.
module NvFetcher.NixFetcher
  ( -- * Types
    NixFetcher (..),
    FetchStatus (..),
    FetchResult,

    -- * Rules
    prefetchRule,
    prefetch,

    -- * Functions
    gitHubFetcher,
    pypiFetcher,
    gitHubReleaseFetcher,
    gitFetcher,
    urlFetcher,
  )
where

import Control.Monad (void, (<=<))
import qualified Data.Aeson as A
import qualified Data.Aeson.Types as A
import Data.Coerce (coerce)
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import Development.Shake
import NeatInterpolation (trimming)
import NvFetcher.Types
import NvFetcher.Types.ShakeExtras

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

runFetcher :: NixFetcher Fresh -> Action Checksum
runFetcher :: NixFetcher 'Fresh -> Action Checksum
runFetcher = \case
  FetchGit {Bool
Text
FetchResult 'Fresh
Version
_sha256 :: forall (k :: FetchStatus). NixFetcher k -> FetchResult k
_leaveDotGit :: forall (k :: FetchStatus). NixFetcher k -> Bool
_fetchSubmodules :: forall (k :: FetchStatus). NixFetcher k -> Bool
_deepClone :: forall (k :: FetchStatus). NixFetcher k -> Bool
_rev :: forall (k :: FetchStatus). NixFetcher k -> Version
_furl :: forall (k :: FetchStatus). NixFetcher k -> Text
_sha256 :: FetchResult 'Fresh
_leaveDotGit :: Bool
_fetchSubmodules :: Bool
_deepClone :: Bool
_rev :: Version
_furl :: Text
..} -> do
    let parser :: Value -> Parser Checksum
parser = String -> (Object -> Parser Checksum) -> Value -> Parser Checksum
forall a. String -> (Object -> Parser a) -> Value -> Parser a
A.withObject String
"nix-prefetch-git" ((Object -> Parser Checksum) -> Value -> Parser Checksum)
-> (Object -> Parser Checksum) -> Value -> Parser Checksum
forall a b. (a -> b) -> a -> b
$ \Object
o -> Text -> Checksum
Checksum (Text -> Checksum) -> Parser Text -> Parser Checksum
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Object
o Object -> Text -> Parser Text
forall a. FromJSON a => Object -> Text -> Parser a
A..: Text
"sha256"
    (CmdTime Double
t, Stdout ByteString
out, CmdLine String
c) <-
      [CmdOption]
-> String
-> [String]
-> Action (CmdTime, Stdout ByteString, CmdLine)
forall r.
(Partial, CmdResult r) =>
[CmdOption] -> String -> [String] -> Action r
command [Bool -> CmdOption
EchoStderr Bool
False] String
"nix-prefetch-git" ([String] -> Action (CmdTime, Stdout ByteString, CmdLine))
-> [String] -> Action (CmdTime, Stdout ByteString, CmdLine)
forall a b. (a -> b) -> a -> b
$
        [Text -> String
T.unpack Text
_furl]
          [String] -> [String] -> [String]
forall a. Semigroup a => a -> a -> a
<> [String
"--rev", Text -> String
T.unpack (Text -> String) -> Text -> String
forall a b. (a -> b) -> a -> b
$ Version -> Text
coerce Version
_rev]
          [String] -> [String] -> [String]
forall a. Semigroup a => a -> a -> a
<> [String
"--fetch-submodules" | Bool
_fetchSubmodules]
          [String] -> [String] -> [String]
forall a. Semigroup a => a -> a -> a
<> [String
"--deepClone" | Bool
_deepClone]
          [String] -> [String] -> [String]
forall a. Semigroup a => a -> a -> a
<> [String
"--leave-dotGit" | Bool
_leaveDotGit]
    String -> Action ()
putVerbose (String -> Action ()) -> String -> Action ()
forall a b. (a -> b) -> a -> b
$ String
"Finishing running " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
c String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
", took " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Double -> String
forall a. Show a => a -> String
show Double
t String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"s"
    let result :: Maybe Checksum
result = (Value -> Parser Checksum) -> Value -> Maybe Checksum
forall a b. (a -> Parser b) -> a -> Maybe b
A.parseMaybe Value -> Parser Checksum
parser (Value -> Maybe Checksum)
-> (ByteString -> Maybe Value) -> ByteString -> Maybe Checksum
forall (m :: * -> *) b c a.
Monad m =>
(b -> m c) -> (a -> m b) -> a -> m c
<=< ByteString -> Maybe Value
forall a. FromJSON a => ByteString -> Maybe a
A.decodeStrict (ByteString -> Maybe Checksum) -> ByteString -> Maybe Checksum
forall a b. (a -> b) -> a -> b
$ ByteString
out
    case Maybe Checksum
result of
      Just Checksum
x -> Checksum -> Action Checksum
forall (f :: * -> *) a. Applicative f => a -> f a
pure Checksum
x
      Maybe Checksum
_ -> String -> Action Checksum
forall (m :: * -> *) a. MonadFail m => String -> m a
fail (String -> Action Checksum) -> String -> Action Checksum
forall a b. (a -> b) -> a -> b
$ String
"Failed to parse output from nix-prefetch-git: " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Text -> String
T.unpack (ByteString -> Text
T.decodeUtf8 ByteString
out)
  FetchUrl {Text
FetchResult 'Fresh
_sha256 :: FetchResult 'Fresh
_furl :: Text
_sha256 :: forall (k :: FetchStatus). NixFetcher k -> FetchResult k
_furl :: forall (k :: FetchStatus). NixFetcher k -> Text
..} -> do
    (CmdTime Double
t, Stdout (ByteString -> Text
T.decodeUtf8 -> Text
out), CmdLine String
c) <- [CmdOption]
-> String
-> [String]
-> Action (CmdTime, Stdout ByteString, CmdLine)
forall r.
(Partial, CmdResult r) =>
[CmdOption] -> String -> [String] -> Action r
command [Bool -> CmdOption
EchoStderr Bool
False] String
"nix-prefetch-url" [Text -> String
T.unpack Text
_furl]
    String -> Action ()
putVerbose (String -> Action ()) -> String -> Action ()
forall a b. (a -> b) -> a -> b
$ String
"Finishing running " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
c String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
", took " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Double -> String
forall a. Show a => a -> String
show Double
t String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"s"
    case (Text -> Bool) -> [Text] -> [Text]
forall a. (a -> Bool) -> [a] -> [a]
takeWhile (Bool -> Bool
not (Bool -> Bool) -> (Text -> Bool) -> Text -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Bool
T.null) ([Text] -> [Text]) -> [Text] -> [Text]
forall a b. (a -> b) -> a -> b
$ [Text] -> [Text]
forall a. [a] -> [a]
reverse ([Text] -> [Text]) -> [Text] -> [Text]
forall a b. (a -> b) -> a -> b
$ Text -> [Text]
T.lines Text
out of
      [Text
x] -> Checksum -> Action Checksum
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Checksum -> Action Checksum) -> Checksum -> Action Checksum
forall a b. (a -> b) -> a -> b
$ Text -> Checksum
coerce Text
x
      [Text]
_ -> String -> Action Checksum
forall (m :: * -> *) a. MonadFail m => String -> m a
fail (String -> Action Checksum) -> String -> Action Checksum
forall a b. (a -> b) -> a -> b
$ String
"Failed to parse output from nix-prefetch-url: " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Text -> String
T.unpack Text
out

pypiUrl :: Text -> Version -> Text
pypiUrl :: Text -> Version -> Text
pypiUrl Text
pypi (Version -> Text
coerce -> Text
ver) =
  let h :: Text
h = Char -> Text -> Text
T.cons (Text -> Char
T.head Text
pypi) Text
""
   in [trimming|https://pypi.io/packages/source/$h/$pypi/$pypi-$ver.tar.gz|]

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

-- | Rules of nix fetcher
prefetchRule :: Rules ()
prefetchRule :: Rules ()
prefetchRule = Rules (NixFetcher 'Fresh -> Action (NixFetcher 'Fetched))
-> Rules ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (Rules (NixFetcher 'Fresh -> Action (NixFetcher 'Fetched))
 -> Rules ())
-> Rules (NixFetcher 'Fresh -> Action (NixFetcher 'Fetched))
-> Rules ()
forall a b. (a -> b) -> a -> b
$
  (NixFetcher 'Fresh -> Action (NixFetcher 'Fetched))
-> Rules (NixFetcher 'Fresh -> Action (NixFetcher 'Fetched))
forall q a.
(RuleResult q ~ a, ShakeValue q, ShakeValue a, Partial) =>
(q -> Action a) -> Rules (q -> Action a)
addOracleCache ((NixFetcher 'Fresh -> Action (NixFetcher 'Fetched))
 -> Rules (NixFetcher 'Fresh -> Action (NixFetcher 'Fetched)))
-> (NixFetcher 'Fresh -> Action (NixFetcher 'Fetched))
-> Rules (NixFetcher 'Fresh -> Action (NixFetcher 'Fetched))
forall a b. (a -> b) -> a -> b
$ \(NixFetcher 'Fresh
f :: NixFetcher Fresh) -> do
    Checksum
sha256 <- Action Checksum -> Action Checksum
forall a. Action a -> Action a
withRetries (Action Checksum -> Action Checksum)
-> Action Checksum -> Action Checksum
forall a b. (a -> b) -> a -> b
$ NixFetcher 'Fresh -> Action Checksum
runFetcher NixFetcher 'Fresh
f
    NixFetcher 'Fetched -> Action (NixFetcher 'Fetched)
forall (f :: * -> *) a. Applicative f => a -> f a
pure (NixFetcher 'Fetched -> Action (NixFetcher 'Fetched))
-> NixFetcher 'Fetched -> Action (NixFetcher 'Fetched)
forall a b. (a -> b) -> a -> b
$ NixFetcher 'Fresh
f {_sha256 :: FetchResult 'Fetched
_sha256 = FetchResult 'Fetched
Checksum
sha256}

-- | Run nix fetcher
prefetch :: NixFetcher Fresh -> Action (NixFetcher Fetched)
prefetch :: NixFetcher 'Fresh -> Action (NixFetcher 'Fetched)
prefetch = NixFetcher 'Fresh -> Action (NixFetcher 'Fetched)
forall q a.
(RuleResult q ~ a, ShakeValue q, ShakeValue a) =>
q -> Action a
askOracle

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

-- | Create a fetcher from git url
gitFetcher :: Text -> PackageFetcher
gitFetcher :: Text -> PackageFetcher
gitFetcher Text
furl Version
rev = Text
-> Version
-> Bool
-> Bool
-> Bool
-> FetchResult 'Fresh
-> NixFetcher 'Fresh
forall (k :: FetchStatus).
Text
-> Version -> Bool -> Bool -> Bool -> FetchResult k -> NixFetcher k
FetchGit Text
furl Version
rev Bool
False Bool
False Bool
False ()

-- | Create a fetcher from github repo
gitHubFetcher ::
  -- | owner and repo
  (Text, Text) ->
  PackageFetcher
gitHubFetcher :: (Text, Text) -> PackageFetcher
gitHubFetcher (Text
owner, Text
repo) = Text -> PackageFetcher
gitFetcher [trimming|https://github.com/$owner/$repo|]

-- | Create a fetcher from pypi
pypiFetcher :: Text -> PackageFetcher
pypiFetcher :: Text -> PackageFetcher
pypiFetcher Text
p Version
v = Text -> NixFetcher 'Fresh
urlFetcher (Text -> NixFetcher 'Fresh) -> Text -> NixFetcher 'Fresh
forall a b. (a -> b) -> a -> b
$ Text -> Version -> Text
pypiUrl Text
p Version
v

-- | Create a fetcher from github release
gitHubReleaseFetcher ::
  -- | owner and repo
  (Text, Text) ->
  -- | file name
  Text ->
  PackageFetcher
gitHubReleaseFetcher :: (Text, Text) -> Text -> PackageFetcher
gitHubReleaseFetcher (Text
owner, Text
repo) Text
fp (Version -> Text
coerce -> Text
ver) =
  Text -> NixFetcher 'Fresh
urlFetcher
    [trimming|https://github.com/$owner/$repo/releases/download/$ver/$fp|]

-- | Create a fetcher from url
urlFetcher :: Text -> NixFetcher Fresh
urlFetcher :: Text -> NixFetcher 'Fresh
urlFetcher = (Text -> () -> NixFetcher 'Fresh)
-> () -> Text -> NixFetcher 'Fresh
forall a b c. (a -> b -> c) -> b -> a -> c
flip Text -> () -> NixFetcher 'Fresh
forall (k :: FetchStatus). Text -> FetchResult k -> NixFetcher k
FetchUrl ()