如何在Shake中定义计时器规则

时间:2014-07-22 20:24:23

标签: haskell shake-build-system

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

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

  • 删除之前的备份,然后重做新的备份,
  • 制作备用目标phony
  • 添加一个虚构的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的内容感到困惑。在制定规则是关于如何制作目标(所以它更像是一个配方)或者我认为是Shake中的Action。在哪里,当我说规则时,我指的是决定重建目标的规则,而不是如何去做。在make中,你没有选择(它的时间戳)所以没有这样的概念。

2 个答案:

答案 0 :(得分:1)

这是一个部分有效的解决方案:

import Development.Shake
import Control.Monad
import System.Directory as IO
import Data.Time

buildBackupAt :: FilePath -> Action ()
buildBackupAt out = cmd "mysqldump" "-backup" out {- Or whatever -}

-- Argument order chosen for partial application
buildEvery :: NominalDiffTime -> (FilePath -> Action ()) -> FilePath -> Action ()
buildEvery secs act file = do
    alwaysRerun
    exists <- liftIO $ IO.doesFileExist file
    rebuild <- if not exists then return True else do
        mtime <- liftIO $ getModificationTime file
        now <- liftIO $ getCurrentTime
        return $ diffUTCTime now mtime > secs
    when rebuild $ act file

myRules :: Rules ()
myRules = "my_backup" *> buildEvery (24*60*60) buildBackupAt
-- File name is a FilePattern that shake turns into a FilePath; no wildcard here,
-- so it's simple, but you can wildcard, too as long as you action pays attention
-- to the FilePath passed in.

这将每天重建备份,但如果在buildBackupAt中声明的依赖项发生更改,则不会重建。

答案 1 :(得分:1)

Shake中有两种“编写规则”的含义:1)使用*>或类似来定义特定于构建系统的规则; 2)定义新类型的规则,例如自己定义*>等运算符。 Shake的大多数用户做了很多,从来没做过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声明(这很丑陋,但不可避免)。然后,我们在生成maximumAge时致电output.txt,说文件output.txt必须不超过1天。如果是,则规则重新运行。简单且可重复使用。

定义如何工作定义有点复杂,但我不希望很多人定义规则,因此每个规则定义的StackOverflow问题似乎是合理的:)。我们必须为规则定义一个键和一个值,其中键生成值。对于密钥,我们声明一个新的类型(对于密钥总是应该这样),它存储文件名以及允许的文件大小。对于该值,我们存储文件的大小。 storedValue函数通过查询文件从密钥中检索值。 equalValue函数查看该值并确定值是EqualCheap(不重建)还是NotEqual(重建)。通常equalValue执行old == new作为主要测试,但在这里我们不关心上次的值是什么(我们忽略old),但是我们关心MaxAgeQ中的阈值1}}是,我们将它与值进行比较。

maximumAge函数只调用apply1添加对MaxAgeQ的依赖关系,includeMaximumAge定义apply1调用的内容。

相关问题