nvfetcher-0.6.0.0: src/NvFetcher/Nvchecker.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
-- | Copyright: (c) 2021-2022 berberman
-- SPDX-License-Identifier: MIT
-- Maintainer: berberman <berberman@yandex.com>
-- Stability: experimental
-- Portability: portable
--
-- [nvchecker](https://github.com/lilydjwg/nvchecker) is a program checking new versions of packages.
-- We encode the checking process into shake build system, generating configuration of nvchecker and calling it externally.
-- Now we call nvchecker for each 'VersionSource', which seems not to be efficient, but it's tolerable when running in parallel.
--
-- Meanwhile, we lose the capabilities of tracking version updates, i.e. normally nvchecker will help us maintain a list of old versions,
-- so that we are able to know which package's version is updated in this run. Fortunately, we can reimplement this in shake,
-- see 'nvcheckerRule' for details.
module NvFetcher.Nvchecker
( -- * Types
VersionSortMethod (..),
ListOptions (..),
CheckVersion (..),
NvcheckerOptions (..),
VersionSource (..),
NvcheckerResult (..),
-- * Rules
nvcheckerRule,
-- * Functions
checkVersion,
checkVersion',
)
where
import Control.Monad (void)
import Control.Monad.Extra (fromMaybeM)
import qualified Data.Aeson as A
import qualified Data.ByteString.Char8 as BS
import Data.Coerce (coerce)
import Data.Maybe (fromJust)
import Data.String (fromString)
import qualified Data.Text as T
import Development.Shake
import Development.Shake.Rule
import NvFetcher.Types
import NvFetcher.Types.ShakeExtras
import NvFetcher.Utils
import Prettyprinter (pretty, (<+>))
import Toml (Value (Bool, Text))
import qualified Toml
import Toml.Type.Edsl
-- | Rules of nvchecker
nvcheckerRule :: Rules ()
nvcheckerRule = do
persistedRule
oneShotRule
-- | Nvchecker rule for packages, which is aware of version changes and supports using stale version.
-- nvchecker will be called at most one time given a package key. Follow-up using of this rule will return cached result.
-- 'PackageKey' is required for caching.
-- Run this rule by calling 'checkVersion'
persistedRule :: Rules ()
persistedRule = addBuiltinRule noLint noIdentity $ \(WithPackageKey (key@(CheckVersion versionSource options), pkg)) _old _mode -> do
putInfo . show $ "#" <+> pretty key
oldVer <- getRecentLastVersion pkg
useStaleVersion <- _ppinned . fromJust <$> lookupPackage pkg
let useStale = case useStaleVersion of
PermanentStale -> True
TemporaryStale -> True
_ -> False
case useStale of
True
| Just oldVer' <- oldVer -> do
-- use the stale version if we have
putInfo $ T.unpack $ "Skip running nvchecker, use stale version " <> coerce oldVer' <> " for " <> coerce pkg
let result = NvcheckerResult {nvNow = oldVer', nvOld = oldVer, nvStale = True}
pure $ RunResult ChangedRecomputeSame (encode' result) result
-- run nvchecker
_ -> do
-- if we already run this rule for a package, we can recover the last result from getLastVersionUpdated
-- (when cacheNvchecker is enabled)
useCache <- nvcheckerCacheEnabled
now <- fromMaybeM (coerce <$> runNvchecker pkg options versionSource) (if useCache then getLastVersionUpdated pkg else pure Nothing)
let runChanged = case oldVer of
Just oldVer'
| oldVer' == now -> ChangedRecomputeSame
_ -> ChangedRecomputeDiff
result = NvcheckerResult {nvNow = now, nvOld = oldVer, nvStale = False}
-- always update
updateLastVersion pkg now
pure $ RunResult runChanged mempty result
-- | Nvchecker rule without cache
-- Rule this rule by calling 'checkVersion''
oneShotRule :: Rules ()
oneShotRule = void $
addOracle $ \key@(CheckVersion versionSource options) -> do
putInfo . show $ pretty key
now <- runNvchecker (PackageKey "pkg") options versionSource
pure $ NvcheckerResult now Nothing False
runNvchecker :: PackageKey -> NvcheckerOptions -> VersionSource -> Action Version
runNvchecker pkg options versionSource = withTempFile $ \config -> withRetry $ do
mKeyfile <- getKeyfilePath
let nvcheckerConfig = T.unpack $ Toml.pretty $ mkToml $ genNvConfig pkg options mKeyfile versionSource
putVerbose $ "Generated nvchecker config for " <> show pkg <> ":" <> nvcheckerConfig
writeFile' config nvcheckerConfig
(CmdTime t, Stdout out, CmdLine c) <- quietly . cmd $ "nvchecker --logger json -c " <> config
putVerbose $ "Finishing running " <> c <> ", took " <> show t <> "s"
case reverse . lines $ out of
(o : _) | Just raw <- A.decodeStrict' $ BS.pack o -> case raw of
NvcheckerSuccess x -> pure x
NvcheckerError err -> fail $ "Failed to run nvchecker: " <> T.unpack err
_ -> fail $ "Failed to parse output from nvchecker: " <> out
genNvConfig :: PackageKey -> NvcheckerOptions -> Maybe FilePath -> VersionSource -> TDSL
genNvConfig pkg options mKeyfile versionSource =
case mKeyfile of
Just keyfile -> do
table "__config__" $
"keyfile" =: Text (T.pack keyfile)
_ -> pure ()
>> table
(fromString $ T.unpack $ coerce pkg)
( do
genVersionSource versionSource
genOptions options
)
where
key =:? (Just x) = key =: Text x
_ =:? _ = pure ()
genVersionSource = \case
GitHubRelease {..} -> do
"source" =: "github"
"github" =: Text (_owner <> "/" <> _repo)
"use_latest_release" =: Bool True
GitHubTag {..} -> do
"source" =: "github"
"github" =: Text (_owner <> "/" <> _repo)
"use_max_tag" =: Bool True
genListOptions _listOptions
Git {..} -> do
"source" =: "git"
"git" =: Text _vurl
"branch" =:? coerce _vbranch
"use_commit" =: Bool True
Aur {..} -> do
"source" =: "aur"
"aur" =: Text _aur
"strip_release" =: Bool True
ArchLinux {..} -> do
"source" =: "archpkg"
"archpkg" =: Text _archpkg
"strip_release" =: Bool True
Pypi {..} -> do
"source" =: "pypi"
"pypi" =: Text _pypi
Manual {..} -> do
"source" =: "manual"
"manual" =: Text _manual
Repology {..} -> do
"source" =: "repology"
"repology" =: Text _repology
"repo" =: Text _repo
Webpage {..} -> do
"source" =: "regex"
"url" =: Text _vurl
"regex" =: Text _regex
genListOptions _listOptions
HttpHeader {..} -> do
"source" =: "httpheader"
"url" =: Text _vurl
"regex" =: Text _regex
genListOptions _listOptions
OpenVsx {..} -> do
"source" =: "openvsx"
"openvsx" =: Text (_ovPublisher <> "." <> _ovExtName)
VscodeMarketplace {..} -> do
"source" =: "vsmarketplace"
"vsmarketplace" =: Text (_vsmPublisher <> "." <> _vsmExtName)
Cmd {..} -> do
"source" =: "cmd"
"cmd" =: Text _vcmd
Container {..} -> do
"source" =: "container"
"container" =: Text _vcontainer
genListOptions _listOptions
genListOptions ListOptions {..} = do
"include_regex" =:? _includeRegex
"exclude_regex" =:? _excludeRegex
"sort_version_key" =:? fmap (T.pack . show) _sortVersionKey
"ignored" =:? _ignored
genOptions NvcheckerOptions {..} = do
"prefix" =:? _stripPrefix
"from_pattern" =:? _fromPattern
"to_pattern" =:? _toPattern
-- | Run nvchecker given 'PackageKey'
-- Recording version changes and using stale version are available.
checkVersion :: VersionSource -> NvcheckerOptions -> PackageKey -> Action NvcheckerResult
checkVersion v o k = apply1 $ WithPackageKey (CheckVersion v o, k)
-- | Run nvchecker without cache
checkVersion' :: VersionSource -> NvcheckerOptions -> Action NvcheckerResult
checkVersion' v o = askOracle $ CheckVersion v o