packages feed

dyre (empty) → 0.4

raw patch · 10 files changed

+626/−0 lines, 10 filesdep +Win32dep +basedep +directorysetup-changed

Dependencies added: Win32, base, directory, executable-path, filepath, ghc-paths, io-storage, old-time, process, unix, xdg-basedir

Files

+ Config/Dyre.hs view
@@ -0,0 +1,149 @@+{- |+Dyre is a library for configuring your Haskell programs. Like Xmonad,+programs configured with Dyre will look for a configuration file written+in Haskell, which essentially defines a custom program configured exactly+as the user wishes it to be. And since the configuration is written in+Haskell, the user is free to do anything they might wish in the context+of configuring the program.++Dyre places emphasis on elegance of operation and ease of integration+with existing applications. The 'wrapMain' function is the sole entry+point for Dyre. When partially applied with a parameter structure, it+wraps around the 'realMain' value from that structure, yielding an almost+identical function which has been augmented with dynamic recompilation+functionality.++The 'Config.Dyre.Relaunch' module provides the ability to restart the+program (recompiling if applicable), and persist state across restarts,+but it has no impact whatsoever on the rest of the library whether it+is used or not.++A full example of using Dyre's recompilation and relaunching features+is as follows:++>-- DyreExample.hs --+>module DyreExample where+>+>import qualified Config.Dyre as Dyre+>+>import Config.Dyre.Relaunch+>import System.IO+>+>data Config = Config { message :: String, errorMsg :: Maybe String }+>data State  = State { bufferLines :: [String] } deriving (Read, Show)+>+>defaultConfig :: Config+>defaultConfig = Config "Dyre Example v0.1" Nothing+>+>showError :: Config -> String -> Config+>showError cfg msg = cfg { errorMsg = Just msg }+>+>realMain Config{message = message, errorMsg = errorMsg } = do+>    (State buffer) <- restoreState $ State []+>    case errorMsg of+>         Nothing -> return ()+>         Just em -> putStrLn $ "Error: " ++ em+>    putStrLn message+>    mapM putStrLn . reverse $ buffer+>    putStr "> " >> hFlush stdout+>    input <- getLine+>    case input of+>         "exit" -> return ()+>         "quit" -> return ()+>         other  -> relaunchWithState (State $ other:buffer) Nothing+>+>dyreExample = Dyre.wrapMain $ Dyre.defaultParams+>    { Dyre.projectName = "dyreExample"+>    , Dyre.realMain    = realMain+>    , Dyre.showError   = showError+>    }++Notice that all of the program logic is contained in the 'DyreExample'+module. The main module of the program is absolutely trivial.++>-- Main.hs --+>module Main where+>import DyreExample+>main = dyreExample defaultConfig++When reading the above program, bear in mind exactly how much of the+code is simply *program logic*. Dyre is designed to intelligently+handle recompilation with a bare minimum of program modification.++Some mention should be made of Dyre's defaults. The 'defaultParams'+structure used in the example defines reasonable default values for+several configuration items. In the absence of any other definitions,+Dyre will default to outputting status messages to stderr, not hiding+any packages during compilation, and passing no special options to GHC.++Also, Dyre will expect configuration files to be placed at the path+'$XDG_CONFIG_HOME/<app>/<app>.hs', and it will store cache files in+the '$XDG_CACHE_HOME/<app>/' directory. The 'System.Environment.XDG'+module will be used to determine these paths, so refer to it for+behaviour on Windows platforms.+-}+module Config.Dyre ( wrapMain, Params(..), defaultParams ) where++import System.IO           ( hPutStrLn, stderr )+import System.Directory    ( doesFileExist )++import Config.Dyre.Params  ( Params(..) )+import Config.Dyre.Compile ( customCompile )+import Config.Dyre.Compat  ( customExec )+import Config.Dyre.Options ( getReconf, getDebug, withDyreOptions )+import Config.Dyre.Paths   ( getPaths, maybeModTime )++-- | A set of reasonable defaults for configuring Dyre. If the minimal set of+--   fields are modified, the program will use the XDG-defined locations for+--   configuration and cache files (see 'System.Environment.XDG.BaseDir' for+--   details), pass no special options to GHC, and will output status messages+--   to stderr.+--+--   The fields that will have to be filled are 'projectName', 'realMain', and+--   'showError'+defaultParams :: Params cfgType+defaultParams = Params+    { projectName  = undefined+    , configDir    = Nothing+    , cacheDir     = Nothing+    , realMain     = undefined+    , showError    = undefined+    , hidePackages = []+    , ghcOpts      = []+    , statusOut    = hPutStrLn stderr+    }++-- | 'wrapMain' is how Dyre recieves control of the program. It is expected+--   that it will be partially applied with its parameters to yield a "main"+--   entry point, which will then be called by the 'main' function, as well+--   as by any custom configurations.+wrapMain :: Params cfgType -> cfgType -> IO ()+wrapMain params@Params{projectName = pName} cfg = withDyreOptions $ do+    -- Get the important paths+    (thisBinary, tempBinary, configFile, cacheDir) <- getPaths params++    -- Check their modification times+    thisTime <- maybeModTime thisBinary+    tempTime <- maybeModTime tempBinary+    confTime <- maybeModTime configFile++    -- If there's a config file, and the temp binary is older than something+    -- else, or we were specially told to recompile, then we should recompile.+    let confExists = confTime /= Nothing+    forceReconf <- getReconf+    errors <- if confExists &&+                 (tempTime < confTime || tempTime < thisTime || forceReconf)+                 then customCompile params+                 else return Nothing++    -- If there's a custom binary and we're not it, run it. Otherwise+    -- just launch the main function, reporting errors if appropriate.+    -- Also we don't want to use a custom binary if the conf file is+    -- gone.+    customExists <- doesFileExist tempBinary+    if confExists && customExists && (thisBinary /= tempBinary)+       then do statusOut params $ "Launching custom binary '" ++ tempBinary ++ "'\n"+               customExec tempBinary Nothing+       else realMain params $ case errors of+                                   Nothing -> cfg+                                   Just er -> (showError params) cfg er
+ Config/Dyre/Compat.hs view
@@ -0,0 +1,71 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE ForeignFunctionInterface #-}++{- |+Compatibility code for things that need to be done differently+on different systems.+-}+module Config.Dyre.Compat ( customExec, getPIDString ) where++import Config.Dyre.Options ( customOptions )++#if defined(mingw32_HOST_OS) || defined(__MINGW32__)++import System.Win32+import System.Process+import System.Exit+import System.Mem++-- This can be removed as soon as a 'getProcessID' function+-- gets added to 'System.Win32'+foreign import stdcall unsafe "winbase.h GetCurrentProcessId"+    c_GetCurrentProcessID :: IO DWORD+getPIDString = fmap show c_GetCurrentProcessID++customExec binary mArgs = do+    args <- customOptions mArgs+    -- This whole thing is a terrible, ugly hack. Since Windows+    -- is too braindead to provide an exec() system call for us+    -- to use, we simply create a new process that inherits+    -- the stdio handles.+    (_,_,_,child) <- createProcess $ CreateProcess+        { cmdspec   = RawCommand binary args+        , cwd       = Nothing+        , env       = Nothing+        , std_in    = Inherit+        , std_out   = Inherit+        , std_err   = Inherit+        , close_fds = True+        }+    -- Do some garbage collection in an optimistic attempt to+    -- offset some of the memory we waste here.+    performGC+    -- And to prevent terminal apps from losing IO, we have to+    -- sit around waiting for the child to exit.+    exitCode <- waitForProcess child+    case exitCode of+         ExitSuccess -> c_ExitProcess 0+         ExitFailure c -> c_ExitProcess (fromIntegral c)++foreign import stdcall unsafe "winbase.h ExitProcess"+    c_ExitProcess :: UINT -> IO ()++#else++import System.Posix.Process ( executeFile, getProcessID )++getPIDString = fmap show getProcessID++customExec binary mArgs = do+    args <- customOptions mArgs+    executeFile binary False args Nothing++#endif++-- | Called whenever execution needs to be transferred over to+--   a different binary.+customExec :: FilePath -> Maybe [String] -> IO ()++-- | What it says on the tin. Gets the current PID as a string.+--   Used to determine the name for the state file during restarts.+getPIDString :: IO String
+ Config/Dyre/Compile.hs view
@@ -0,0 +1,58 @@+{- |+Compiling the custom executable. The majority of the code actually+deals with error handling, and not the compilation itself /per se/.+-}+module Config.Dyre.Compile ( customCompile ) where++import System.IO         ( openFile, hClose, IOMode(..) )+import System.Exit       ( ExitCode(..) )+import System.Process    ( runProcess, waitForProcess )+import System.FilePath   ( (</>) )+import System.Directory  ( getCurrentDirectory, doesFileExist+                         , createDirectoryIfMissing )+import Control.Exception ( bracket )+import GHC.Paths         ( ghc )++import Config.Dyre.Paths  ( getPaths )+import Config.Dyre.Params ( Params(..) )++-- | Attempts to compile the configuration file. Will return a string+--   containing any compiler output.+customCompile :: Params cfgType -> IO (Maybe String)+customCompile params@Params{statusOut = output} = do+    (thisBinary, tempBinary, configFile, cacheDir) <- getPaths params+    output $ "Configuration '" ++ configFile ++  "' changed. Recompiling."+    createDirectoryIfMissing True cacheDir++    -- Compile occurs in here+    let errFile = cacheDir </> "errors.log"+    result <- bracket (openFile errFile WriteMode) hClose $ \errHandle -> do+        ghcOpts <- makeFlags params configFile tempBinary+        ghcProc <- runProcess ghc ghcOpts (Just cacheDir) Nothing+                              Nothing Nothing (Just errHandle)+        waitForProcess ghcProc++    -- Display a helpful little status message+    if result /= ExitSuccess+       then output $ "Error occurred while loading configuration file."+       else output $ "Program reconfiguration successful."++    -- If the error file exists and actually has some contents, return+    -- 'Just' the error string. Otherwise return 'Nothing'.+    errorsExist <- doesFileExist errFile+    if not errorsExist+       then return Nothing+       else do errors <- readFile errFile+               if errors == ""+                  then return Nothing+                  else return . Just $ errors++-- | Assemble the arguments to GHC so everything compiles right.+makeFlags :: Params cfgType -> FilePath -> FilePath -> IO [String]+makeFlags Params{ghcOpts = flags, hidePackages = hides} cfgFile tmpFile = do+    currentDir <- getCurrentDirectory+    return . concat $ [ ["-v0", "-fforce-recomp", "-i" ++ currentDir]+                      , prefix "-hide-package" hides, flags+                      , ["--make", cfgFile, "-o", tmpFile]+                      ]+  where prefix y xs = concat . map (\x -> [y,x]) $ xs
+ Config/Dyre/Options.hs view
@@ -0,0 +1,115 @@+{-+Handling for the command-line options that can be used to configure+Dyre. As of the last count, there are four of them, and more are+unlikely to be needed. The only one that a user should ever need to+use is the '--force-reconf' option, so the others all begin with+'--dyre-<option-name>'.++At the start of the program, before anything else occurs, the+'withDyreOptions' function is used to hide Dyre's command-line+options. They are loaded into the IO monad using the module+'System.IO.Storage'. This keeps them safely out of the way of+the user code and our own.++Later, when Dyre needs to access the options, it does so through+the accessor functions defined here. When it comes time to pass+control over to a new binary, it gets an argument list which+preserves the important flags with a call to 'customOptions'.+-}+module Config.Dyre.Options+  ( withDyreOptions+  , customOptions+  , getReconf+  , getDebug+  , getMasterBinary+  , getStatePersist+  ) where++import Data.List+import Data.Maybe+import System.IO.Storage+import System.Environment+import System.Environment.Executable++-- | Store Dyre's command-line options to the IO-Store "dyre",+--   and then execute the provided IO action with all Dyre's+--   options removed from the command-line arguments.+withDyreOptions :: IO a -> IO a+withDyreOptions action = withStore "dyre" $ do+    -- Pretty important+    args <- getArgs++    -- If the flag exists, it overrides the current file. Likewise,+    --   if it doesn't exist, we end up with the path to our current+    --   file. This seems like a sensible way to do it.+    this <- getExecutablePath+    putValue "dyre" "masterBinary" this+    storeFlag args "--dyre-master-binary=" "masterBinary"++    -- Load the other important arguments into IO storage.+    storeFlag args "--dyre-state-persist=" "persistState"+    putValue "dyre" "forceReconf"  $ "--force-reconf" `elem` args+    putValue "dyre" "debugMode"    $ "--dyre-debug"   `elem` args++    -- We filter the arguments, so now Dyre's arguments 'vanish'+    withArgs (filterArgs args) action+  where filterArgs = filter $ not . prefixElem dyreArgs+        prefixElem xs = or . zipWith ($) (map isPrefixOf xs) . repeat++-- | Get the value of the '--force-reconf' flag, which is used+--   to force a recompile of the custom configuration.+getReconf :: IO Bool+getReconf = getDefaultValue "dyre" "forceReconf" False++-- | Get the value of the '--dyre-debug' flag, which is used+--   to debug a program without installation. Specifically,+--   it forces the application to use './cache/' as the cache+--   directory, and './' as the configuration directory.+getDebug  :: IO Bool+getDebug = getDefaultValue "dyre" "debugMode" False++-- | Get the path to the master binary. This is set to the path of+--   the *current* binary unless the '--dyre-master-binary=' flag+--   is set. Obviously, we pass the '--dyre-master-binary=' flag to+--   the custom configured application from the master binary.+getMasterBinary :: IO (Maybe String)+getMasterBinary = getValue "dyre" "masterBinary"++-- | Get the path to a persistent state file. This is set only when+--   the '--dyre-state-persist=' flag is passed to the program. It+--   is used internally by 'Config.Dyre.Relaunch' to save and restore+--   state when relaunching the program.+getStatePersist :: IO (Maybe String)+getStatePersist = getValue "dyre" "persistState"++-- | Return the set of options which will be passed to another instance+--   of Dyre. Preserves the master binary, state file, and debug mode+--   flags, but doesn't pass along the forced-recompile flag. Can be+--   passed a set of other arguments to use, or it defaults to using+--   the current arguments when passed 'Nothing'.+customOptions :: Maybe [String] -> IO [String]+customOptions otherArgs = do+    masterPath <- getMasterBinary+    stateFile  <- getStatePersist+    debugMode  <- getDebug+    return . filter (not . null) $ fromMaybe [] otherArgs +++        [ if debugMode then "--dyre-debug" else ""+        , case stateFile of+               Nothing -> ""+               Just sf -> "--dyre-state-persist=" ++ sf+        , "--dyre-master-binary=" ++ (fromJust masterPath)+        ]++-- | Look for the given flag in the argument array, and store+--   its value under the given name if it exists.+storeFlag :: [String] -> String -> String -> IO ()+storeFlag args flag name+    | null match  = return ()+    | otherwise   = putValue "dyre" name $ drop (length flag) (head match)+  where match = filter (isPrefixOf flag) args++-- | The array of all arguments that Dyre recognizes. Used to+--   make sure none of them are visible past 'withDyreOptions'+dyreArgs :: [String]+dyreArgs = [ "--force-reconf", "--dyre-state-persist"+           , "--dyre-debug", "--dyre-master-binary" ]
+ Config/Dyre/Params.hs view
@@ -0,0 +1,40 @@+{- |+Defines the 'Params' datatype which Dyre uses to define all+program-specific configuration data. Shouldn't be imported+directly, as 'Config.Dyre' re-exports it.+-}+module Config.Dyre.Params ( Params(..) ) where++-- | This structure is how all kinds of useful data is fed into Dyre. Of+--   course, only the 'projectName', 'realMain', and 'showError' fields+--   are really necessary. By using the set of default values provided+--   as 'Config.Dyre.defaultParams', you can get all the benefits of+--   using Dyre to configure your program in only five or six lines of+--   code.+data Params cfgType = Params+    { projectName  :: String+    -- ^ The name of the project. This needs to also be the name of+    --   the executable, and the name of the configuration file.+    , configDir    :: Maybe (IO FilePath)+    -- ^ The directory to look for a configuration file in.+    , cacheDir     :: Maybe (IO FilePath)+    -- ^ The directory to store build files in, including the final+    --   generated executable.+    , realMain     :: cfgType -> IO ()+    -- ^ The main function of the program. When Dyre has completed+    --   all of its recompilation, it passes the configuration data+    --   to this function and gets out of the way.+    , showError    :: cfgType -> String -> cfgType+    -- ^ This function is used to display error messages that occur+    --   during recompilation, by allowing the program to modify its+    --   initial configuration.+    , hidePackages :: [String]+    -- ^ Packages that need to be hidden during compilation+    , ghcOpts      :: [String]+    -- ^ Miscellaneous GHC compilation settings go here+    , statusOut    :: String -> IO ()+    -- ^ A status output function. Will be called with messages+    --   when Dyre recompiles or launches anything. A good value+    --   is 'hPutStrLn stderr', assuming there is no pressing+    --   reason to not put messages on stderr.+    }
+ Config/Dyre/Paths.hs view
@@ -0,0 +1,39 @@+module Config.Dyre.Paths where++import System.Info+import System.Time+import System.FilePath+import System.Directory+import System.Environment.Executable+import System.Environment.XDG.BaseDir++import Config.Dyre.Params+import Config.Dyre.Options++-- | Return the paths to, respectively, the current binary, the custom+--   binary, the config file, and the cache directory.+getPaths :: Params c -> IO (FilePath, FilePath, FilePath, FilePath)+getPaths params@Params{projectName = pName} = do+    thisBinary <- getExecutablePath+    debugMode  <- getDebug+    cwd <- getCurrentDirectory+    cacheDir  <- case (debugMode, cacheDir params) of+                      (True,  _      ) -> return $ cwd </> "cache"+                      (False, Nothing) -> getUserCacheDir pName+                      (False, Just cd) -> cd+    configDir <- case (debugMode, configDir params) of+                      (True,  _      ) -> return $ cwd+                      (False, Nothing) -> getUserConfigDir pName+                      (False, Just cd) -> cd+    let tempBinary = cacheDir </> pName ++ "-" ++ os ++ "-" ++ arch+    let configFile = configDir </> pName ++ ".hs"+    return $ (thisBinary, tempBinary, configFile, cacheDir)++-- | Check if a file exists. If it exists, return Just the modification+--   time. If it doesn't exist, return Nothing.+maybeModTime :: FilePath -> IO (Maybe ClockTime)+maybeModTime path = do+    fileExists <- doesFileExist path+    if fileExists+       then fmap Just $ getModificationTime path+       else return Nothing
+ Config/Dyre/Relaunch.hs view
@@ -0,0 +1,76 @@+{-+This is the only other module aside from 'Config.Dyre' which needs+to be imported specially. It contains functions for restarting the+program (which, usefully, will cause a recompile if the config has+been changed), as well as saving and restoring state across said+restarts.++The impossibly simple function arguments are a consequence of a+little cheating we do using the 'System.IO.Storage' library. Of+course, we can't use the stored data unless something else put+it there, so this module will probably explode horribly if used+outside of a program whose recompilation is managed by Dyre.+-}+module Config.Dyre.Relaunch+  ( relaunchWithState+  , restoreState+  , relaunchMaster+  , maybeRestoreState+  ) where++import Data.Maybe           ( fromMaybe, fromJust )+import System.IO            ( writeFile, readFile )+import System.IO.Error      ( try )+import System.FilePath      ( (</>) )+import System.Directory     ( getTemporaryDirectory, removeFile )++import System.IO.Storage    ( putValue, delValue )+import Config.Dyre.Options  ( customOptions, getMasterBinary, getStatePersist )+import Config.Dyre.Compat   ( customExec, getPIDString )++-- | Just relaunch the master binary. We don't have any important+--   state to worry about. (Or, like when 'relaunchWithState' calls+--   it, we're managing state on our own.)+relaunchMaster :: Maybe [String] -> IO ()+relaunchMaster otherArgs = do+    masterPath <- fmap fromJust getMasterBinary+    customExec masterPath otherArgs++-- | Relaunch the master binary, but first preserve the program+--   state so that we can use the 'restoreState' functions to get+--   it back again later. Since we're not trying to be fancy here,+--   we'll just use 'show' to write it out.+relaunchWithState :: Show a => a -> Maybe [String] -> IO ()+relaunchWithState state otherArgs = do+    -- Calculate the path to the state file+    pidString <- getPIDString+    tempDir   <- getTemporaryDirectory+    let statePath = tempDir </> pidString ++ ".state"+    -- Write the state to the file and store the path+    writeFile statePath . show $ state+    putValue "dyre" "persistState" statePath+    -- Relaunch+    relaunchMaster otherArgs++-- | Restore state that was previously saved by the 'relaunchWithState'+--   function. If unsuccessful, it will simply return Nothing.+maybeRestoreState :: Read a => IO (Maybe a)+-- This could probably be a lot simpler if I grokked+-- monad transformers better.+maybeRestoreState = do+    statePath <- getStatePersist+    case statePath of+         Nothing -> return Nothing+         Just sp -> do+             stateData <- readFile sp+--             removeFile sp+             delValue "dyre" "persistState"+             result <- try $ readIO stateData+             case result of+                  Left  _ -> return $ Nothing+                  Right v -> return $ Just v++-- | Restore state using the 'maybeRestoreState' function, but return+--   the provided default state if we can't get anything better.+restoreState :: Read a => a -> IO a+restoreState d = fmap (fromMaybe d) maybeRestoreState
+ LICENSE view
@@ -0,0 +1,28 @@+Copyright (c) 2009, Will Donnelly+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above copyright+      notice, this list of conditions and the following disclaimer in the+      documentation and/or other materials provided with the distribution.++    * Neither the name of the software nor the names of its contributors+      may be used to endorse or promote products derived from this software+      without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED+OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,5 @@+#!/usr/bin/env runhaskell++import Distribution.Simple++main = defaultMain
+ dyre.cabal view
@@ -0,0 +1,45 @@+name:          dyre+version:       0.4+category:      Development+synopsis:      Dynamic reconfiguration in Haskell++description:   Dyre implements dynamic reconfiguration facilities after the+               style of Xmonad. Dyre aims to be as simple as possible without+               sacrificing features, and places an emphasis on simplicity of+               integration with an application.++               A full introduction with a complete example project can be found+               in the documentation for 'Config.Dyre'++homepage:      http://github.com/willdonnelly/dyre+bug-reports:   http://github.com/willdonnelly/dyre/issues+stability:     alpha+author:        Will Donnelly+maintainer:    Will Donnelly <will.donnelly@gmail.com>+copyright:     (c) 2009 Will Donnelly+license:       BSD3+license-file:  LICENSE++build-type:    Simple+cabal-version: >= 1.6++library+  exposed-modules: Config.Dyre,+                   Config.Dyre.Paths,+                   Config.Dyre.Compat,+                   Config.Dyre.Params,+                   Config.Dyre.Options,+                   Config.Dyre.Compile,+                   Config.Dyre.Relaunch+  build-depends:   base >= 4 && < 5, process, filepath,+                   directory, ghc-paths, old-time,+                   executable-path, xdg-basedir, io-storage++  if os(windows)+      build-depends: Win32+  else+      build-depends: unix++source-repository head+  type:      git+  location:  git://github.com/willdonnelly/dyre.git