packages feed

crunghc (empty) → 0.1.0.0

raw patch · 4 files changed

+398/−0 lines, 4 filesdep +SHAdep +basedep +bytestringsetup-changed

Dependencies added: SHA, base, bytestring, directory, filelock, filepath, process, text, time, transformers, unix

Files

+ LICENSE view
@@ -0,0 +1,121 @@+Creative Commons Legal Code++CC0 1.0 Universal++    CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE+    LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN+    ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS+    INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES+    REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS+    PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM+    THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED+    HEREUNDER.++Statement of Purpose++The laws of most jurisdictions throughout the world automatically confer+exclusive Copyright and Related Rights (defined below) upon the creator+and subsequent owner(s) (each and all, an "owner") of an original work of+authorship and/or a database (each, a "Work").++Certain owners wish to permanently relinquish those rights to a Work for+the purpose of contributing to a commons of creative, cultural and+scientific works ("Commons") that the public can reliably and without fear+of later claims of infringement build upon, modify, incorporate in other+works, reuse and redistribute as freely as possible in any form whatsoever+and for any purposes, including without limitation commercial purposes.+These owners may contribute to the Commons to promote the ideal of a free+culture and the further production of creative, cultural and scientific+works, or to gain reputation or greater distribution for their Work in+part through the use and efforts of others.++For these and/or other purposes and motivations, and without any+expectation of additional consideration or compensation, the person+associating CC0 with a Work (the "Affirmer"), to the extent that he or she+is an owner of Copyright and Related Rights in the Work, voluntarily+elects to apply CC0 to the Work and publicly distribute the Work under its+terms, with knowledge of his or her Copyright and Related Rights in the+Work and the meaning and intended legal effect of CC0 on those rights.++1. Copyright and Related Rights. A Work made available under CC0 may be+protected by copyright and related or neighboring rights ("Copyright and+Related Rights"). Copyright and Related Rights include, but are not+limited to, the following:++  i. the right to reproduce, adapt, distribute, perform, display,+     communicate, and translate a Work;+ ii. moral rights retained by the original author(s) and/or performer(s);+iii. publicity and privacy rights pertaining to a person's image or+     likeness depicted in a Work;+ iv. rights protecting against unfair competition in regards to a Work,+     subject to the limitations in paragraph 4(a), below;+  v. rights protecting the extraction, dissemination, use and reuse of data+     in a Work;+ vi. database rights (such as those arising under Directive 96/9/EC of the+     European Parliament and of the Council of 11 March 1996 on the legal+     protection of databases, and under any national implementation+     thereof, including any amended or successor version of such+     directive); and+vii. other similar, equivalent or corresponding rights throughout the+     world based on applicable law or treaty, and any national+     implementations thereof.++2. Waiver. To the greatest extent permitted by, but not in contravention+of, applicable law, Affirmer hereby overtly, fully, permanently,+irrevocably and unconditionally waives, abandons, and surrenders all of+Affirmer's Copyright and Related Rights and associated claims and causes+of action, whether now known or unknown (including existing as well as+future claims and causes of action), in the Work (i) in all territories+worldwide, (ii) for the maximum duration provided by applicable law or+treaty (including future time extensions), (iii) in any current or future+medium and for any number of copies, and (iv) for any purpose whatsoever,+including without limitation commercial, advertising or promotional+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each+member of the public at large and to the detriment of Affirmer's heirs and+successors, fully intending that such Waiver shall not be subject to+revocation, rescission, cancellation, termination, or any other legal or+equitable action to disrupt the quiet enjoyment of the Work by the public+as contemplated by Affirmer's express Statement of Purpose.++3. Public License Fallback. Should any part of the Waiver for any reason+be judged legally invalid or ineffective under applicable law, then the+Waiver shall be preserved to the maximum extent permitted taking into+account Affirmer's express Statement of Purpose. In addition, to the+extent the Waiver is so judged Affirmer hereby grants to each affected+person a royalty-free, non transferable, non sublicensable, non exclusive,+irrevocable and unconditional license to exercise Affirmer's Copyright and+Related Rights in the Work (i) in all territories worldwide, (ii) for the+maximum duration provided by applicable law or treaty (including future+time extensions), (iii) in any current or future medium and for any number+of copies, and (iv) for any purpose whatsoever, including without+limitation commercial, advertising or promotional purposes (the+"License"). The License shall be deemed effective as of the date CC0 was+applied by Affirmer to the Work. Should any part of the License for any+reason be judged legally invalid or ineffective under applicable law, such+partial invalidity or ineffectiveness shall not invalidate the remainder+of the License, and in such case Affirmer hereby affirms that he or she+will not (i) exercise any of his or her remaining Copyright and Related+Rights in the Work or (ii) assert any associated claims and causes of+action with respect to the Work, in either case contrary to Affirmer's+express Statement of Purpose.++4. Limitations and Disclaimers.++ a. No trademark or patent rights held by Affirmer are waived, abandoned,+    surrendered, licensed or otherwise affected by this document.+ b. Affirmer offers the Work as-is and makes no representations or+    warranties of any kind concerning the Work, express, implied,+    statutory or otherwise, including without limitation warranties of+    title, merchantability, fitness for a particular purpose, non+    infringement, or the absence of latent or other defects, accuracy, or+    the present or absence of errors, whether or not discoverable, all to+    the greatest extent permissible under applicable law.+ c. Affirmer disclaims responsibility for clearing rights of other persons+    that may apply to the Work or any use thereof, including without+    limitation any person's Copyright and Related Rights in the Work.+    Further, Affirmer disclaims responsibility for obtaining any necessary+    consents, permissions or other rights required for any use of the+    Work.+ d. Affirmer understands and acknowledges that Creative Commons is not a+    party to this document and has no duty or obligation with respect to+    this CC0 or use of the Work.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ crunghc.cabal view
@@ -0,0 +1,33 @@+-- Initial crunghc.cabal generated by cabal init.  For further +-- documentation, see http://haskell.org/cabal/users-guide/++name:                crunghc+version:             0.1.0.0+synopsis:            A runghc replacement with transparent caching+description:         crunghc is a program that acts like runghc. When it+                     executes a Haskell script, it automatically stores a+                     compiled cache of the script to the disk, so that the+                     compilation overhead can be avoided in subsequent runs.+license:             PublicDomain+license-file:        LICENSE+author:              Takano Akio+maintainer:          aljee@hyper.cx+-- copyright:           +category:            Development+build-type:          Simple+-- extra-source-files:  +cabal-version:       >=1.10++executable crunghc+  main-is:  main.hs+  -- other-modules:       +  -- other-extensions:    +  build-depends:       base >=4.5 && <4.7, process >= 1.0, filepath >= 1.3,+                       directory >= 1.2, text >= 0.11, bytestring >= 0.9.2.1, SHA >= 1.6,+                       transformers >= 0.3, filelock >= 0.0, time >= 1.4+  -- hs-source-dirs:      +  default-language:    Haskell2010++  if !os(windows)+    build-depends:    unix+    cpp-options:      -DUSE_UNIX
+ main.hs view
@@ -0,0 +1,242 @@+{-# LANGUAGE CPP #-}++import Control.Applicative+import qualified Control.Exception as E+import Control.Monad+import Control.Monad.IO.Class+import Control.Monad.Trans.Maybe+import qualified Data.ByteString as S+import qualified Data.ByteString.Lazy as L+import Data.Char+import qualified Data.Digest.Pure.SHA as SHA+import Data.List+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import Data.Time.Clock+import Data.Maybe+import Data.Monoid+import System.Directory+import System.Environment+import System.Exit+import System.FileLock+import System.FilePath+import System.IO+import System.IO.Error+import System.Process++#if USE_UNIX+import System.Posix.Files (createLink)+#endif++data Config = Config+  { cmdGhcFlags :: [String]+  , cmdScriptPath :: FilePath+  , cmdScriptArgs :: [String]+  }++data CachePlan = CP+  { cpCachedir :: FilePath+  , cpStrategy :: CacheStrategy+  }++data CacheStrategy+  = HashPath FilePath+  | HashContent+      S.ByteString -- ^ 0-separated ghc args+      S.ByteString -- ^ content++main :: IO ()+main = do+  config <- fromRightIO . parseConfig =<< getArgs+  plan <- chooseCachePlan config+  createDirectoryIfMissing True $ cpCachedir plan+  (tempExeCopy, cleanup) <- withFileLock (lockfile plan) Exclusive $ \_ -> do+    needRecomp <- testRecompilationNeeded config plan+    when needRecomp $ recompile config plan++    -- Here we make a copy of the cached executable, and run that copy.+    -- This is needed for two reasons:+    --+    -- 1. we want to avoid the race condition where some other process replaces+    --  the cached executable after we unlock it and before we execute it.+    -- 2. on Windows you cannot replace an executable while it's running.+    makeTemporaryExeCopy plan+  runTemp config tempExeCopy `E.finally` cleanup++parseConfig :: [String] -> Either String Config+parseConfig args = case span ("-" `isPrefixOf`) args of+  (flags, path:scriptArgs) -> Right Config+    { cmdGhcFlags = flags+    , cmdScriptPath = path+    , cmdScriptArgs = scriptArgs+    }+  _ -> Left "No file is given"++lockfile :: CachePlan -> FilePath+lockfile plan = cpCachedir plan </> "lock"++-- | Decide where to store the cache, and which strategy to use.+chooseCachePlan :: Config -> IO CachePlan+chooseCachePlan config = do+  contentHead <- readPrefix (contentHashThreshold + 1) $ cmdScriptPath config+  let contentAndGhcArgs = contentHead <> packedGhcArgs+  (toHash, strat) <- if S.length contentAndGhcArgs <= contentHashThreshold+    then return (contentAndGhcArgs, HashContent packedGhcArgs contentHead)+    else do+      canonicalScriptPath <- canonicalizePath $ cmdScriptPath config+      return+        ( T.encodeUtf8 $ T.pack canonicalScriptPath+        , HashPath canonicalScriptPath+        )+  appDir <- getAppUserDataDirectory "crunghc"+  ghcVersion <- filter (/='\n') <$> readProcess "ghc" ["--numeric-version"] ""+  return $ CP+    { cpCachedir = appDir </> ghcVersion </> hashToDirname toHash+    , cpStrategy = strat+    }+  where+    -- | Use filepath hashing if the total size of the ghc command line and the+    -- script itself exceeds this value. In particular it should be large enough+    -- to allow the boilerplate Setup.hs to be content-hashed.+    contentHashThreshold = 50++    packedGhcArgs = S.intercalate (S.singleton 0) $+      map (T.encodeUtf8 . T.pack) $ cmdGhcFlags config++readPrefix :: Int -> FilePath -> IO S.ByteString+readPrefix n path = withFile path ReadMode $ \h -> S.hGet h n++-- | Does the script need to be recompiled?+testRecompilationNeeded :: Config -> CachePlan -> IO Bool+testRecompilationNeeded config CP{cpCachedir=cachedir, cpStrategy=strat}+    = fmap isNothing $ runMaybeT $ do+  case strat of+    HashPath canonicalScriptPath -> do+      shouldEqual canonicalScriptPath $+        readFile $ cachedir </> "path"+    HashContent ghcArgs content -> do+      shouldEqual ghcArgs $ S.readFile $ cachedir </> "ghc-args"+      shouldEqual content $ S.readFile $ cachedir </> "content"+  exeModTime <- checkIOError $+    getModificationTime $ cachedir </> "cached.exe"+  deps <- readDependencies $ cachedir </> "deps"+  if length deps == 1+    then do -- Single-file program+      scriptModTime <- checkIOError $ getModificationTime $+        cmdScriptPath config+      guard $ exeModTime > scriptModTime+    else do+      -- Make sure that the working directory is the same as when the cache+      -- was created.+      wdir <- liftIO getCurrentDirectory+      cachedWdir <- checkIOError $ readFile $ cachedir </> "wdir"+      guard $ wdir == cachedWdir+      -- Check for modification times of all the source files+      forM_ deps $ \srcPath -> do+        modTime <- checkIOError $ getModificationTime srcPath+        guard $ exeModTime > modTime+  where+    shouldEqual val action = do+      r <- checkIOError action+      guard $ val == r++-- | Compile the script and populate the cache directory.+recompile :: Config -> CachePlan -> IO ()+recompile config CP{cpCachedir=cachedir, cpStrategy=strat} = do+  createDirectoryIfMissing True $ cachedir </> "build"+  runGhc $ fullGhcArgs ["-M", "-dep-makefile", cachedir </> "deps"]+  runGhc $ fullGhcArgs+    [ "-hidir", builddir, "-odir", builddir+    , "-threaded"+    , "-o", cachedir </> "cached.exe"+    ]+  -- Try to strip it, but don't care if it fails.+  void (rawSystem "strip" [cachedir </> "cached.exe"])+    `E.catch` \e -> const (return ()) (e::E.IOException)+  wdir <- getCurrentDirectory+  writeFile (cachedir </> "wdir") wdir+  case strat of+    HashPath canonicalScriptPath ->+      writeFile (cachedir </> "path") canonicalScriptPath+    HashContent ghcArgs content -> do+      S.writeFile (cachedir </> "ghc-args") ghcArgs+      S.writeFile (cachedir </> "content") content+  where+    fullGhcArgs flags = cmdGhcFlags config ++ flags ++ [cmdScriptPath config]+    builddir = cachedir </> "build"+    runGhc args = do+      (exitCode, _, err) <- readProcessWithExitCode "ghc" args ""+      case exitCode of+        ExitSuccess -> return ()+        ExitFailure{} -> die err++-- | Create a temporary copy or hard link of the cached executable for+-- exclusive use by this process. Return the path of the copy and a cleanup+-- action.+makeTemporaryExeCopy :: CachePlan -> IO (FilePath, IO ())+makeTemporaryExeCopy plan = do+  createDirectoryIfMissing True tempdir+  mydir <- createFreshDirectoryIn tempdir+  let tempPath = mydir </> "temp.exe"+  linkOrCopy (cpCachedir plan </> "cached.exe") tempPath+  return (tempPath, removeDirectoryRecursive mydir)+  where+    tempdir = cpCachedir plan </> "temp"+#if USE_UNIX+    -- Use hard links if available, because they are much cheaper to create.+    linkOrCopy = createLink+#else+    linkOrCopy = copyFile+#endif++createFreshDirectoryIn :: FilePath -> IO FilePath+createFreshDirectoryIn dir = loop =<< randomNumber+  where+    loop n = do+      let fresh = dir </> show n+      r <- E.tryJust (guard . isAlreadyExistsError) $ createDirectory fresh+      case r of+        Left{} -> loop $ n + 1+        Right{} -> return fresh++    randomNumber = do+      tod <- utctDayTime <$> getCurrentTime+      return $ flip mod (100000::Int) $+        floor $ realToFrac tod * (1000000 :: Double)++-- | Run the cached executable.+runTemp :: Config -> FilePath -> IO ()+runTemp config path = exitWith =<< rawSystem path (cmdScriptArgs config)++readDependencies :: FilePath -> MaybeT IO [FilePath]+readDependencies depFile = do+  lns <- checkIOError $ lines <$> readFile depFile+  return $ mapMaybe extractDependency lns++extractDependency :: String -> Maybe FilePath+extractDependency ln+  | looksLikeSourceFile = Just $ takeWhileEnd (not . isSpace) ln+  | otherwise = Nothing+  where+    looksLikeSourceFile =+      ".hs" `isSuffixOf` ln ||+      ".lhs" `isSuffixOf` ln++takeWhileEnd :: (a -> Bool) -> [a] -> [a]+takeWhileEnd p = reverse . takeWhile p . reverse++checkIOError :: IO a -> MaybeT IO a+checkIOError action = MaybeT $+  (Just <$> action) `E.catch` \e -> const (return Nothing) (e::IOError)++hashToDirname :: S.ByteString -> FilePath+hashToDirname = SHA.showDigest . SHA.sha1 . L.fromChunks . (:[])++fromRightIO :: Either String a -> IO a+fromRightIO = either die return++die :: String -> IO a+die msg = do+  prog <- getProgName+  hPutStrLn stderr $ prog ++ ": " ++ msg+  exitFailure