Как определить правило таймера в Shake
Я пытаюсь понять, как использовать Shake
и как строить новые правила. В качестве упражнения я решил реализовать то, что я называю backup
править.
Идея состоит в том, чтобы сгенерировать файл, если он не существует ИЛИ, если он слишком старый (более 24 часов). Мне нравится хранить длинные команды в make-файле и запускать их по требованию. Примером является резервная копия MySQL. Единственная проблема, когда резервная копия уже существует, make
ничего не делает Чтобы решить это, я могу либо
- удалите предыдущую резервную копию перед повторным созданием новой,
- сделать резервную копию цели
phony
- добавить вымышленный
force
зависимость, к которой я могу прикоснуться вручную или в cron.
Я хотел бы восстановить резервную копию, если она старше 24 часов (что я могу сделать с touch force
в хрон). Во всяком случае, это только пример, чтобы играть с Shake
, То, что я хотел бы это что-то вроде:
expirable "my_backup" 24 \out -> do
cmd "mysqldump" backup_parameter out
Я прочитал документ, но я понятия не имею, как это сделать или определить правило и что Action
является. Я понимаю, что мне нужно создать Rule
класс, но я не могу понять, что к чему.
осветление
Я не хочу, чтобы резервное копирование запускалось автоматически, а только по требованию, но не чаще одного раза в сутки.
Пример сценария: у меня есть производственная база данных на удаленной машине, локальное копирование и локальные отчеты, требующие много времени. Нормальный рабочий процесс
- скачать резервную копию производства
- обновить локальную базу данных этим
- создать несколько денормализованных таблиц в базе данных локального хранилища
- создать отчет.
Я не запускаю отчет каждый день, но только когда мне это нужно. Поэтому я не хочу запускать отчет каждые 24 часа. Это легко сделать с помощью make-файла, за исключением бита синхронизации, они очень интересны, но опять же это искусный пример, позволяющий глубоко понять, как работает Shake.
Итак, когда я впервые делаю make report
это резервное копирование БД, запустить все и сгенерировать отчет. Теперь я хочу изменить отчет (потому что я его тестирую). Мне не нужна резервная копия для восстановления (и локальная база данных для обновления) (мы вечер, и я знаю, что ничего не изменилось на производстве до следующего дня)
Затем на следующий день или в следующем месяце я повторно запускаю отчет. На этот раз мне нужно, чтобы резервная копия была сделана снова, и все ее зависимости также были перезапущены.
В основном, мне нужно правило вместо
повторить отметку времени = отметка времени <старая
является
повторить отметку времени = отметка времени
Но я понятия не имею, где поставить это правило.
Вопрос больше в том, куда его положить, а не как написать (это выше). Если это проще (объяснить), у меня может быть правило, которое спрашивает пользователя (getLine): "Вы хотите повторить эту цель (да / нет)?".
Позже мне также понадобится правило, зависящее от последнего обновления базы данных (или конкретной таблицы). Я знаю, как получить информацию из базы данных, но не знаю, как интегрировать ее в Shake.
Я могу быть смущен с тем, что Rule
является. В правилах make речь идет о том, как сделать цель (так что это скорее рецепт), или о том, что я думаю, это Action in Shake. Где, когда я говорю правило, я имею в виду правило, которое решает переделать цель или нет, а не как это сделать. В make у вас нет выбора (это временная метка), поэтому такой концепции нет.
2 ответа
В 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
(что некрасиво, но неизбежно). Затем мы позвоним maximumAge
при производстве output.txt
сказав что файл output.txt
должно быть не более 1 дня. Если это так, правило перезапускается. Простой и многоразовый.
Как работает определение Определение немного сложное, но я не ожидаю, что многие люди будут определять правила, поэтому вопрос Stackru для каждого определения правила кажется разумным:). Мы должны определить ключ и значение для правила, где ключ производит значение. Для ключа мы объявляем новый тип (как вы всегда должны указывать для ключей), в котором хранится имя файла и сколько ему разрешено быть. Для значения мы храним, сколько лет файлу. storedValue
Функция извлекает значение из ключа путем запроса файла. equalValue
функция смотрит на значение и решает, является ли значение EqualCheap
(не перестраивать) или NotEqual
(сделать перестройку). Обычно equalValue
делает old == new
в качестве основного теста, но здесь нам все равно, какое значение было в прошлый раз (мы игнорируем old
), но нам все равно, какой порог в MaxAgeQ
есть, и мы сравниваем его со значением.
maximumAge
функция просто вызывает apply1
добавить зависимость от MaxAgeQ
, а также includeMaximumAge
определяет что apply1
звонки.
Вот решение, которое частично работает:
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
менять.