Как определить правило таймера в 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 это резервное копирование БД, запустить все и сгенерировать отчет. Теперь я хочу изменить отчет (потому что я его тестирую). Мне не нужна резервная копия для восстановления (и локальная база данных для обновления) (мы вечер, и я знаю, что ничего не изменилось на производстве до следующего дня)

Затем на следующий день или в следующем месяце я повторно запускаю отчет. На этот раз мне нужно, чтобы резервная копия была сделана снова, и все ее зависимости также были перезапущены.

В основном, мне нужно правило вместо

повторить отметку времени = отметка времени <старая

является

повторить отметку времени = отметка времени отметка времени + 24*36000

Но я понятия не имею, где поставить это правило.

Вопрос больше в том, куда его положить, а не как написать (это выше). Если это проще (объяснить), у меня может быть правило, которое спрашивает пользователя (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 менять.

Другие вопросы по тегам