我正在尝试了解如何使用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中,你没有选择(它的时间戳)所以没有这样的概念。
答案 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
调用的内容。