haskell - 如何在 Shake 中定义定时器规则

标签 haskell shake-build-system

我正在尝试了解如何使用 Shake 以及如何构建新规则。作为练习,我决定实现我称之为 backup 的规则。

我们的想法是在文件不存在或文件太旧(让我们超过 24 小时)时生成一个文件。我喜欢将长命令存储在 makefile 中并按需运行它们。一个例子是 mysql 备份。唯一的问题是当备份已经存在时,make 不会做任何事情。为了解决这个问题,我可以

  • 在重做新备份之前删除之前的备份,
  • 制作备份目标
  • 添加一个虚构的 force 依赖项,我可以手动或在 cron 中触摸它。

我想要的是,如果超过 24 小时就重做备份(我可以在 cron 中使用 touch force 来做到这一点)。无论如何,这只是一个使用 Shake 的例子。我想要的是这样的:

expirable "my_backup" 24 \out -> do
    cmd "mysqldump" backup_parameter out

我阅读了文档,但我不知道如何执行此操作或定义规则以及什么是 Action。 我知道我需要实例化一个 Rule 类,但我不知道什么是什么。

澄清

我不希望备份自动运行,而是仅按需运行,但最多每 24 小时运行一次。

一个示例场景是 我在远程机器上有一个生产数据库,本地副本并在本地运行一些耗时的报告。正常的工作流程是

  • 下载生产备份
  • 用它刷新本地数据库
  • 在本地仓库数据库上创建一些非规范化表
  • 生成一些报告。

我不是每天都运行报告,而是仅在需要时才运行。所以我不想每 24 小时运行一次报告。除了计时位之外,使用 makefile 很容易做到,它们很麻烦,但它又是一个人为的例子,可以深入了解 Shake 的工作原理。

因此,当我第一次执行 make report 时,它会备份数据库运行所有内容并生成报告。 现在,我想修改报告(因为我正在测试它)。我不需要重新生成备份(也不需要刷新本地数据库)(我们是晚上,我知道直到第二天生产环境才发生任何变化)

然后第二天或下个月,我重新运行报告。这次我需要再次完成备份,并且它的所有依赖项也要重新运行。

基本上我需要的规则是代替

重做时间戳=时间戳<旧

重做时间戳=时间戳<旧||现在 > 时间戳 + 24*36000

但我不知道该把这条规则放在哪里。

问题更多的是把它放在哪里,而不是如何写它(在上面)。 如果它更容易(解释)我可以有一个规则来询问用户(getLine)“你想重做这个目标(是/否)吗?”。

稍后我还需要一个规则,该规则取决于数据库(或特定表)的最后更新。我知道如何从数据库中获取信息,但不知道如何将其集成到 Shake 中。

我可能对什么是 Rule 感到困惑。在 make 中,规则是关于如何制定目标(因此它更像是一个食谱)或者我认为是 Shake 中的 Action 。在哪里,当我说规则时,我的意思是决定是否重新制作目标的规则,而不是如何去做。在 make 中,你没有选择权(它是时间戳),所以没有这样的概念。

最佳答案

Shake 中的“编写规则”有两种含义:1) 使用*> 或类似的方式来定义特定于您的构建系统的规则; 2)定义新类型的规则,例如自己定义运算符,例如 *>。大多数 Shake 用户经常做 1,而从不做 2。你的问题似乎完全与 2 有关,这当然是可能的(所有规则都写在 Shake 的核心之外)但很少见。

要定义在检查构建时运行的东西,您需要使用 Development.Shake.Rule 模块,并定义类型类 Rule 的实例。您通常希望对 apply1 函数进行修饰,以便人们可以以类型安全的方式使用您的规则。如果您正在编写一个简单的规则(例如查找修改日期,看看它是否已更改)那么它并不太难。如果您正在执行更复杂的规则(例如,检查一个文件是否存在不超过 1 天),它会有点诡计,但仍然有可能 - 它需要更加小心地考虑将什么存储在何处。以您的“如果文件早于一定秒数则重建”为例,我们可以定义:

module MaximumAgeRule(maximumAge, includeMaximumAge) where

import Data.Maybe
import Development.Shake.Rule
import Development.Shake.Classes
import Development.Shake
import System.Directory as IO
import Data.Time

newtype MaxAgeQ = MaxAgeQ (FilePath, Double)
    deriving (Show,Binary,NFData,Hashable,Typeable,Eq)

instance Rule MaxAgeQ Double where
    storedValue _ (MaxAgeQ (file, secs)) = do
        exists <- IO.doesFileExist file
        if not exists then return Nothing else do
            mtime <- getModificationTime file
            now <- getCurrentTime
            return $ Just $ fromRational (toRational $ diffUTCTime now mtime)
    equalValue _ (MaxAgeQ (_, t)) old new = if new < t then EqualCheap else NotEqual

-- | Define that the file must be no more than N seconds old
maximumAge :: FilePath -> Double -> Action ()
maximumAge file secs = do
    apply1 $ MaxAgeQ (file, secs) :: Action Double
    return ()

includeMaximumAge :: Rules ()
includeMaximumAge = do
    rule $ \q@(MaxAgeQ (_, secs)) -> Just $ do
        opts <- getShakeOptions
        liftIO $ fmap (fromMaybe $ secs + 1) $ storedValue opts q

然后我们可以将规则用于:

import Development.Shake
import MaximumAgeRule

main = shakeArgs shakeOptions $ do
    includeMaximumAge
    want ["output.txt"]
    "output.txt" *> \out -> do
        maximumAge out (24*60*60)
        liftIO $ putStrLn "rerunning"
        copyFile' "input.txt" "output.txt"

现在文件 input.txt 每次更改时都会被复制到 output.txt。此外,如果 output.txt 超过一天,它将被重新复制。

如何使用 由于我们使用的是自定义规则,因此我们必须使用 includeMaximumAge 进行声明(这很丑陋,但不可避免)。然后我们在生成 output.txt 时调用 maximumAge,表示文件 output.txt 必须不超过 1 天。如果是,规则将重新运行。简单且可重复使用。

定义的工作原理 定义有点复杂,但我不希望很多人定义规则,因此每个规则定义的 StackOverflow 问题似乎是合理的:)。我们必须为规则定义一个键和一个值,其中键产生值。对于 key ,我们声明了一个新类型(对于 key ,您总是应该这样做),它存储文件名和允许的时间。对于该值,我们存储文件的年龄。 storedValue 函数通过查询文件从键中检索值。 equalValue 函数查看该值并决定该值是 EqualCheap(不重建)还是 NotEqual(重建)。通常 equalValueold == new 作为其主要测试,但这里我们不关心上次的值是多少(我们忽略 old ),但我们确实关心 MaxAgeQ 中的阈值是多少,并将其与值进行比较。

maximumAge 函数只是调用 apply1 添加对 MaxAgeQ 的依赖,而 includeMaximumAge 定义了 apply1 调用。

关于haskell - 如何在 Shake 中定义定时器规则,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24897133/

相关文章:

haskell - 为什么不能根据 foldl 重新定义(实现)foldr

haskell - 摇一摇: how to proceed when file name is not known a-priori

haskell - 从(操作 a)的内容动态生成规则

haskell - 如何检查运行抖动是否会重建目标(而不实际尝试构建它)?

haskell - 可能阻塞的流处理器的 ArrowCircuit 实例

algorithm - Haskell中的树合并算法

oop - OOP 接口(interface)和 FP 类型类的区别

algorithm - 带有 K 钉的汉诺塔

haskell - 带 Shake 的多输入、多输出编译器