packages feed

update-nix-fetchgit-0.2.7: src/Update/Nix/Updater.hs

{-# LANGUAGE QuasiQuotes #-}
-- | This module exports a list of 'Fetcher's, programs which match on nix
-- fetching expressions and return programs to update them
module Update.Nix.Updater
  ( fetchers
  ) where

import           Data.Maybe
import           Data.Text                      ( Text
                                                , splitOn
                                                )
import           Nix                            ( NExprLoc )
import           Nix.Comments
import           Nix.Match.Typed
import qualified Update.Nix.FetchGit.Prefetch  as P
import           Update.Nix.FetchGit.Prefetch   ( Revision(..)
                                                , getGitFullName
                                                , getGitHubRevisionDate
                                                , getGitRevision
                                                , nixPrefetchGit
                                                , nixPrefetchUrl
                                                )
import           Update.Nix.FetchGit.Types
import           Update.Nix.FetchGit.Utils
import           Update.Span

type Fetcher
  = Bool -> (NExprLoc -> Maybe Comment) -> NExprLoc -> Maybe (M Updater)

fetchers
  :: Bool -> (NExprLoc -> Maybe Comment) -> [NExprLoc -> Maybe (M Updater)]
fetchers onlyCommented getComment =
  ($ getComment)
    .   ($ onlyCommented)
    <$> [ fetchgitUpdater
        , builtinsFetchGitUpdater
        , fetchTarballGithubUpdater
        , builtinsFetchTarballUpdater
        , fetchurlUpdater
        , fetchGitHubUpdater
        , hackageDirectUpdater
        ]

fetchgitUpdater :: Fetcher
fetchgitUpdater onlyCommented getComment = \case
  [matchNixLoc|
    ^fetcher {
      url = ^url;
      rev = ^rev; # rev
      sha256 = ^sha256;
      _deepClone = ^deepClone;
      _leaveDotGit = ^leaveDotGit;
      _fetchSubmodules = ^fetchSubmodules;
    }|] | extractFuncName fetcher `elem` [Just "fetchgit", Just "fetchgitPrivate"]
        , desiredRev <- commentToRequest (getComment rev)
        , onlyCommented ~> isJust desiredRev
    -> Just $ do
      url' <- fromEither $ URL <$> exprText url
      deepClone' <- fmap (fromMaybe False) . fromEither . traverse exprBool $ deepClone
      leaveDotGit' <- fmap (fromMaybe deepClone') . fromEither . traverse exprBool $ leaveDotGit
      fetchSubmodules' <- fmap (fromMaybe True) . fromEither . traverse exprBool $ fetchSubmodules
      pure $ gitUpdater url' desiredRev deepClone' leaveDotGit' fetchSubmodules' rev (Just sha256)
  _ -> Nothing

builtinsFetchGitUpdater :: Fetcher
builtinsFetchGitUpdater onlyCommented getComment = \case
  [matchNixLoc|
    ^fetcher {
      url = ^url;
      rev = ^rev; # rev
      _submodules = ^submodules;
    }|] | Just "fetchGit" <- extractFuncName fetcher
        , desiredRev <- commentToRequest (getComment rev)
        , onlyCommented ~> isJust desiredRev
    -> Just $ do
      url' <- fromEither $ URL <$> exprText url
      submodules' <- fmap (fromMaybe False) . fromEither . traverse exprBool $ submodules
      pure $ gitUpdater url' desiredRev False False submodules' rev Nothing
  _ -> Nothing

fetchTarballGithubUpdater :: Fetcher
fetchTarballGithubUpdater onlyCommented getComment = \case
  [matchNixLoc|
    ^fetcher {
      url = ^url; # rev
      sha256 = ^sha256;
    }|]
    | Just "fetchTarball" <- extractFuncName fetcher
    , Right url' <- exprText url
    , "https:" : "" : "github.com" : owner : repo : "archive" : _ <- splitOn
      "/"
      url'
    , comment <- getComment url
    , onlyCommented ~> isJust comment
    , comment /= Just "pin" -- Fall back to the regular tarball updater if we've been instructed to not change this URL
    -> Just $ do
      let rev = Revision $ fromMaybe "HEAD" comment
          repoUrl = "https://github.com/" <> owner <> "/" <> repo
      pure . Updater $ do
        revision <- getGitRevision repoUrl rev
        let newUrl = repoUrl <> "/archive/" <> revision <> ".tar.gz"
        let Updater u = tarballUpdater newUrl sha256
        date <- getGitHubRevisionDate owner repo (Revision revision)
        (_, urlUpdate) <- u
        pure (Just date, SpanUpdate (exprSpan url) (quoteString newUrl) : urlUpdate)
  _ -> Nothing

builtinsFetchTarballUpdater :: Fetcher
builtinsFetchTarballUpdater onlyCommented getComment = \case
  [matchNixLoc|
    ^fetcher {
      url = ^url; # [pin]
      sha256 = ^sha256;
    }|] | Just "fetchTarball" <- extractFuncName fetcher
        , comment <- getComment url
        , onlyCommented ~> isJust comment
    -> Just $ do
      url' <- fromEither $ exprText url
      pure $ tarballUpdater url' sha256
  _ -> Nothing

fetchurlUpdater :: Fetcher
fetchurlUpdater onlyCommented getComment = \case
  [matchNixLoc|
    ^fetcher {
      url = ^url; # [pin]
      sha256 = ^sha256;
    }|] | Just "fetchurl" <- extractFuncName fetcher
        , comment <- getComment url
        , onlyCommented ~> isJust comment
    -> Just $ do
      url' <- fromEither $ exprText url
      pure $ urlUpdater url' sha256
  _ -> Nothing

fetchGitHubUpdater :: Fetcher
fetchGitHubUpdater onlyCommented getComment = \case
  [matchNixLoc|
    ^fetcher {
      owner = ^owner;
      repo = ^repo;
      rev = ^rev;
      sha256 = ^sha256;
      _fetchSubmodules = ^fetchSubmodules;
    }|] | Just fun <- extractFuncName fetcher >>= \case
                        "fetchFromGitHub" -> Just GitHub
                        "fetchFromGitLab" -> Just GitLab
                        _ -> Nothing
        , desiredRev <- commentToRequest (getComment rev)
        , onlyCommented ~> isJust desiredRev
    -> Just $ do
      owner' <- fromEither $ exprText owner
      repo' <- fromEither $ exprText repo
      fetchSubmodules' <- fmap (fromMaybe False) . fromEither . traverse exprBool $ fetchSubmodules
      pure $ gitUpdater (fun owner' repo') desiredRev False False fetchSubmodules' rev (Just sha256)
  _ -> Nothing

-- |
-- @
-- callHackageDirect = {pkg, ver, sha256}:
--   let pkgver = "${pkg}-${ver}";
--   in self.callCabal2nix pkg (pkgs.fetchzip {
--        url = "mirror://hackage/${pkgver}/${pkgver}.tar.gz";
--        inherit sha256;
--      });
-- @
hackageDirectUpdater :: Fetcher
hackageDirectUpdater onlyCommented _ = \case
  [matchNixLoc|
    ^fetcher {
      pkg = ^pkg;
      ver = ^ver;
      sha256 = ^sha256;
    }
  |] | Just "callHackageDirect" <- extractFuncName fetcher
     , not onlyCommented -- no comments on this one
     -> Just $ do
      pkg' <- fromEither $ exprText pkg
      ver' <- fromEither $ exprText ver
      let pkgver = pkg' <> "-" <> ver'
          url = "mirror://hackage/" <> pkgver <> "/" <> pkgver <> ".tar.gz"
      pure $ tarballUpdater url sha256
  _ -> Nothing

----------------------------------------------------------------
-- Helpers
----------------------------------------------------------------

data RevisionRequest
  = Pin
  | DoNotPin Revision

commentToRequest :: Maybe Text -> Maybe RevisionRequest
commentToRequest = \case
  Nothing    -> Nothing
  Just "pin" -> Just Pin
  Just r     -> Just (DoNotPin (Revision r))

gitUpdater
  :: RepoLocation
  -- ^ Repo URL
  -> Maybe RevisionRequest
  -- ^ Desired revision
  -> Bool
  -- ^ Deep Clone
  -> Bool
  -- ^ Leave .git
  -> Bool
  -- ^ Fetch submodules
  -> NExprLoc
  -- ^ rev
  -> Maybe NExprLoc
  -- ^ sha256, not present for some fetchers
  -> Updater
gitUpdater repoLocation revisionRequest deepClone leaveDotGit fetchSubmodules revExpr sha256Expr
  = Updater $ do
    let repoUrl = extractUrlString repoLocation
    logVerbose $ "Updating " <> prettyRepoLocation repoLocation
    revArgs <- case revisionRequest of
      Nothing  -> pure []
      Just req -> do
        rev <- case req of
          Pin        -> fromEither (exprText revExpr)
          DoNotPin r -> getGitFullName repoUrl r
        pure ["--rev", rev]
    let args =
          revArgs
            <> [ "--deepClone" | deepClone ]
            <> [ "--leave-dotGit" | leaveDotGit ]
            <> [ "--fetch-submodules" | fetchSubmodules ]
    o <- nixPrefetchGit args repoUrl
    d <- fromEither $ parseISO8601DateToDay (P.date o)
    pure
      ( Just d
      , [ SpanUpdate (exprSpan e) (quoteString (P.sha256 o))
        | Just e <- pure sha256Expr
        ]
        <> [SpanUpdate (exprSpan revExpr) (quoteString $ P.rev o)]
      )

tarballUpdater
  :: Text
  -- ^ URL
  -> NExprLoc
  -- ^ sha256
  -> Updater
tarballUpdater url sha256Expr = Updater $ do
  logVerbose $ "Updating " <> url
  sha256 <- nixPrefetchUrl ["--unpack"] url
  pure (Nothing, [SpanUpdate (exprSpan sha256Expr) (quoteString sha256)])

urlUpdater
  :: Text
  -- ^ URL
  -> NExprLoc
  -- ^ sha256
  -> Updater
urlUpdater url sha256Expr = Updater $ do
  logVerbose $ "Updating " <> url
  sha256 <- nixPrefetchUrl [] url
  pure (Nothing, [SpanUpdate (exprSpan sha256Expr) (quoteString sha256)])

(~>) :: Bool -> Bool -> Bool
x ~> y = not x || y