packages feed

stackage-sandbox (empty) → 0.1.0

raw patch · 6 files changed

+518/−0 lines, 6 filesdep +basedep +filepathdep +optparse-applicativesetup-changed

Dependencies added: base, filepath, optparse-applicative, process, stackage-cli, system-fileio, system-filepath, text

Files

+ ChangeLog.md view
@@ -0,0 +1,3 @@+## 0.1.0++* Initial release
+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2012 FP Complete, http://www.fpcomplete.com/++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be+included in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ README.md view
@@ -0,0 +1,51 @@+stackage-cli+============++[![Build Status](https://travis-ci.org/fpco/stackage-cli.svg)](https://travis-ci.org/fpco/stackage-cli)++A command-line interface for leveraging stackage.++You must have `ghc`, `ghc-pkg`, and `cabal` on your $PATH. This program will make various calls to those executables on your behalf.++This package provides a number of executables. Primary among these is the `stackage` executable (and its alias `stk`). The `stackage` command-line program will inspect your path for stackage plugins, and will dispatch to them. Anything on your $PATH prefixed `stackage-` that responds to the `--summary` flag is considered a stackage plugin.++(This package also provides a library, `Stackage.CLI`, which is intended to make the process of writing stackage plugins easier.)++This package provides the following stackage plugins:++## stackage init++Downloads a `cabal.config` file from `stackage.org`. That's all it does! The `cabal.config` file constrains the package versions used to build your project. Stackage.org is constantly calculating the best compatible build plans for a big subset of Hackage.++## stackage purge++Calls `ghc-pkg unregister` on your package databases. (You will be prompted on a per-db basis; use `stackage purge --force` if you don't want to be prompted.) This is an easy way to clean out your sandbox.++## stackage upgrade++A `stackage purge` followed by `stackage init`.++## stackage sandbox++The sandbox plugin has a few commands of its own that mirror the above three stackage commands:++### stackage sandbox init++In addition to downloading a `cabal.config` file, this will also set up a shared sandbox (via `cabal.sandbox.config`) corresponding to whatever stackage snapshot is used. By default, the latest available Stackage LTS snapshot will be used.++Shared sandboxes are located at `$HOME/.stackage/sandboxes`. Be aware! Anything you `cabal install` to this sandbox will be available to any other projects that share the same sandbox. The `cabal.config` file should help make sure you don't install anything incompatible.++### stackage sandbox delete++Delete `cabal.config` and `cabal.sandbox.config`.++Unlike `stackage purge`, this does not alter any package databases. The shared sandbox will remain untouched. You can use `stackage sandbox delete NAME` to delete the shared sandbox called NAME (e.g. lts-2.0) from your system.++### stackage sandbox upgrade++Replace `cabal.config` and `cabal.sandbox.config` with newer versions and (possibly) a new shared sandbox.+++## Further reading++See also: [this example on the stackage-cli wiki](https://github.com/fpco/stackage-cli/wiki/Example).
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ main/Sandbox.hs view
@@ -0,0 +1,403 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE TemplateHaskell #-}++module Main where++import Control.Exception (Exception, bracket_, catch, throwIO)+import Control.Monad (when)+import Data.Maybe+import Data.Monoid+import Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.IO as T+import Data.Typeable (Typeable)+import Filesystem.Path.CurrentOS as Path+import Filesystem+import Options.Applicative hiding (header, progDesc)+import Stackage.CLI+import System.Environment (lookupEnv)+import System.Exit+import System.FilePath (addTrailingPathSeparator)+import System.IO (hPutStrLn, stderr)+import System.IO.Error (isDoesNotExistError)+import System.Process (callProcess, readProcess)+import qualified Paths_stackage_sandbox as CabalInfo++type Snapshot = Text+type Package = Text+data Action+  = Init (Maybe Snapshot)+  | PackageDb+  | List (Maybe Package)+  | Unregister Package+  | Delete (Maybe Snapshot)+  | Upgrade (Maybe Snapshot)++data SandboxException+  = NoHomeEnvironmentVariable+  | AlreadyAtSnapshot+  | PackageDbLocMismatch Text Text+  | MissingConfig Bool Bool+  | SnapshotMismatch Text Text+  | NonStackageCabalConfig+  | EmptyCabalConfig+  | DecodePathFail Text+  | ConfigAlreadyExists+  | InvalidSnapshot Snapshot+  | GhcVersionFail+  | PackageDbNotFound+  deriving (Show, Typeable)+instance Exception SandboxException++mSnapshotToArgs :: Maybe Snapshot -> [String]+mSnapshotToArgs = fmap T.unpack . maybeToList++version :: String+version = $(simpleVersion CabalInfo.version)++header :: String+header = "Manages shared stackage sandboxes"++progDesc :: String+progDesc = header++snapshotParser :: Parser Snapshot+snapshotParser = T.pack <$> strArgument mods where+  mods = metavar "SNAPSHOT"++packageParser :: Parser Package+packageParser = T.pack <$> strArgument mods where+  mods = metavar "PACKAGE"++packageDbDesc :: String+packageDbDesc = "Prints '--package-db $db', or prints nothing"++listDesc :: String+listDesc = "Calls `ghc-pkg list` with the sandbox package-db"++unregisterDesc :: String+unregisterDesc = "Calls `ghc-pkg unregister PACKAGE with the sandbox package-db"++deleteDesc :: String+deleteDesc = "Deletes cabal.config and cabal.sandbox.config. "+  <> "If provided with a SNAPSHOT, instead deletes that snapshot's folder."++upgradeDesc :: String+upgradeDesc = "Upgrade to the given SNAPSHOT. Defaults to the latest LTS."++subcommands = do+  addCommand "init" "Init" Init (optional snapshotParser)+  addCommand "package-db" packageDbDesc (const PackageDb) (pure ())+  addCommand "list" listDesc List (optional packageParser)+  addCommand "unregister" unregisterDesc Unregister packageParser+  addCommand "delete" deleteDesc Delete (optional snapshotParser)+  addCommand "upgrade" upgradeDesc Upgrade (optional snapshotParser)++toText' :: Path.FilePath -> IO Text+toText' p = case toText p of+  Left e -> throwIO $ DecodePathFail e+  Right t -> return t++cabalSandboxInit :: Path.FilePath -> IO ()+cabalSandboxInit dir = do+  dirText <- toText' dir+  let args =+        [ "sandbox"+        , "init"+        , "--sandbox"+        , T.unpack dirText+        ]+  callProcess "cabal" args++-- precondition: cabal.config exists+parseConfigSnapshot :: IO Snapshot+parseConfigSnapshot = do+  ls <- T.lines <$> T.readFile "cabal.config"+  let p = "-- Stackage snapshot from: http://www.stackage.org/snapshot/"+  case ls of+    (l:_) -> case T.stripPrefix p l of+      Just snapshot -> return snapshot+      Nothing -> throwIO NonStackageCabalConfig+    _ -> throwIO EmptyCabalConfig++-- TODO: a stricter check?+snapshotEq :: Snapshot -> Snapshot -> Bool+snapshotEq short full = T.isPrefixOf (T.replace "/" "-" short) full++-- TODO: verify things about the packages in the sandbox+sandboxVerify :: IO ()+sandboxVerify = do+  cabalConfigExists <- isFile "cabal.config"+  cabalSandboxConfigExists <- isFile "cabal.sandbox.config"+  if (cabalConfigExists && cabalSandboxConfigExists)+    then do+      packageDb <- getPackageDb+      snapshot <- parseConfigSnapshot+      snapshotDir <- getSnapshotDir snapshot+      snapshotDirText <- toText' snapshotDir+      when (not $ T.isPrefixOf snapshotDirText packageDb) $ do+        throwIO $ PackageDbLocMismatch snapshotDirText packageDb+    else do+      throwIO $ MissingConfig cabalConfigExists cabalSandboxConfigExists++getGhcVersion :: IO Text+getGhcVersion = do+  output <- readProcess "ghc" ["--version"] ""+  case words output of+    [] -> throwIO $ GhcVersionFail+    ws -> return $ "ghc-" <> T.pack (last ws)++sandboxInit :: Maybe Snapshot -> IO ()+sandboxInit msnapshot = do+  cabalSandboxConfigExists <- isFile "cabal.sandbox.config"+  when cabalSandboxConfigExists $ do+    throwIO ConfigAlreadyExists++  cabalConfigExists <- isFile "cabal.config"+  when (not cabalConfigExists) $ do+    runStackagePlugin "init" (mSnapshotToArgs msnapshot)++  configSnapshot <- parseConfigSnapshot+  snapshot <- case msnapshot of+    Just s | snapshotEq s configSnapshot -> return configSnapshot+    Just s -> throwIO $ SnapshotMismatch s configSnapshot+    Nothing -> return configSnapshot++  T.putStrLn $ "Initializing at snapshot: " <> snapshot++  dir <- getSnapshotDir snapshot+  createTree dir+  cabalSandboxInit dir+  sandboxVerify++getHome :: IO Text+getHome = T.pack <$> do+  mHome <- lookupEnv "HOME"+  case mHome of+    Just home -> return home+    Nothing -> do+      mHomePath <- lookupEnv "HOMEPATH"+      mHomeDrive <- lookupEnv "HOMEDRIVE"+      case (++) <$> mHomeDrive <*> mHomePath of+        Just home -> return home+        Nothing -> throwIO NoHomeEnvironmentVariable++getSnapshotDirPrefix :: IO Path.FilePath+getSnapshotDirPrefix = do+  home <- getHome+  ghcVersion <- getGhcVersion+  let dir = Path.fromText home </> ".stackage" </> "sandboxes"+        </> Path.fromText ghcVersion+  return dir++getSnapshotDir :: Snapshot -> IO Path.FilePath+getSnapshotDir snapshot = do+  dir <- getSnapshotDirPrefix+  return $ dir </> Path.fromText snapshot+++-- copied from Purge.hs, tweaked+-- TODO: remove duplication+parsePackageDb :: IO (Maybe Text)+parsePackageDb = do+  cabalSandboxConfigExists <- isFile "cabal.sandbox.config"+  if cabalSandboxConfigExists+    then do+      t <- T.readFile "cabal.sandbox.config"+      let packageDbLine = T.stripPrefix "package-db: "+      return $ listToMaybe $ mapMaybe packageDbLine $ T.lines t+    else+      return Nothing++getPackageDb :: IO Text+getPackageDb = parsePackageDb >>= \mdb -> case mdb of+  Just packageDb -> return packageDb+  Nothing -> throwIO $ PackageDbNotFound++getPackageDbArg :: IO Text+getPackageDbArg = parsePackageDb >>= \mdb -> case mdb of+  Just packageDb -> return $ "--package-db=" <> packageDb+  Nothing -> return ""+++printPackageDb :: IO ()+printPackageDb = getPackageDbArg >>= T.putStrLn++ghcPkgList :: Maybe Package -> IO ()+ghcPkgList mPackage = do+  packageDb <- getPackageDbArg+  let args+        = ["list"]+       <> case mPackage of+            Nothing -> []+            Just package -> [T.unpack package]+       <> [T.unpack packageDb]+  callProcess "ghc-pkg" args++ghcPkgUnregister :: Package -> IO ()+ghcPkgUnregister package = do+  packageDb <- getPackageDbArg+  callProcess "ghc-pkg" ["unregister", T.unpack package, T.unpack packageDb]++sandboxDelete :: IO ()+sandboxDelete = do+  cabalConfigExists <- isFile "cabal.config"+  when cabalConfigExists $ do+    removeFile "cabal.config"+  cabalSandboxConfigExists <- isFile "cabal.sandbox.config"+  when cabalSandboxConfigExists $ do+    oldSandboxNotice+    removeFile "cabal.sandbox.config"++snapshotSanityCheck :: Snapshot -> IO ()+snapshotSanityCheck snapshot =+  if any (`T.isInfixOf` snapshot) ["..", "/"]+    then throwIO $ InvalidSnapshot snapshot+    else return ()++sandboxDeleteSnapshot :: Snapshot -> IO ()+sandboxDeleteSnapshot snapshot = do+  snapshotSanityCheck snapshot+  getSnapshotDir snapshot >>= removeTree++-- Find the canonical name for a snapshot by looking it up on stackage.org.+-- This can change over time. e.g. "lts" used to mean lts-1.0.+downloadSnapshot :: Maybe Snapshot -> IO Snapshot+downloadSnapshot mSnapshot = do+  workingDir <- getWorkingDirectory+  let tempDir = ".stackage-sandbox-tmp"+      enterTempDir = do+        createDirectory False tempDir+        setWorkingDirectory tempDir+      exitTempDir = do+        setWorkingDirectory workingDir+        removeTree tempDir+  bracket_ enterTempDir exitTempDir $ do+    runStackagePlugin "init" (mSnapshotToArgs mSnapshot)+    parseConfigSnapshot+++sandboxUpgrade :: Maybe Snapshot -> IO ()+sandboxUpgrade mSnapshot = do+  cabalConfigExists <- isFile "cabal.config"+  cabalSandboxConfigExists <- isFile "cabal.sandbox.config"++  mConfigSnapshot <- if cabalConfigExists+    then Just <$> parseConfigSnapshot+    else return Nothing++  snapshot <- downloadSnapshot mSnapshot++  when (Just snapshot == mConfigSnapshot && cabalSandboxConfigExists) $ do+    packageDb <- getPackageDb+    snapshotDir <- getSnapshotDir snapshot+    snapshotDirText <- toText' snapshotDir+    if T.isPrefixOf snapshotDirText packageDb+      then do+        T.putStrLn $ "Already at snapshot: " <> snapshot+        -- TODO: more verification+        throwIO AlreadyAtSnapshot+      else return ()++  sandboxDelete+  sandboxInit mSnapshot++getSandboxPrefix :: IO Text+getSandboxPrefix = do+  dirPath <- getSnapshotDirPrefix+  dirPathText <- toText' dirPath+  let viaString f = T.pack . f . T.unpack+  return $ viaString addTrailingPathSeparator dirPathText++oldSandboxNotice :: IO ()+oldSandboxNotice = do+  db <- getPackageDb+  sandboxPrefix <- getSandboxPrefix+  case T.stripPrefix sandboxPrefix db of+    Nothing -> do+      putStrLn "Notice: Your old sandbox remains intact:"+      T.putStrLn db+    Just db' -> case T.takeWhile (not . flip elem ("/\\" :: String)) db' of+      snapshot | not (T.null snapshot) -> do+        T.putStrLn $ "Notice: The " <> snapshot <> " shared sandbox remains intact."+        T.putStrLn $ "You may delete it from your system by calling:"+        T.putStrLn $ "stackage sandbox delete " <> snapshot+      _ -> do+        putStrLn "Notice: Your old sandbox remains untouched:"+        T.putStrLn db+  T.putStrLn ""+++handleSandboxExceptions :: SandboxException -> IO ()+handleSandboxExceptions NoHomeEnvironmentVariable = do+  hPutStrLn stderr "Couldn't find the HOME environment variable"+  exitFailure+handleSandboxExceptions AlreadyAtSnapshot = exitSuccess+handleSandboxExceptions (PackageDbLocMismatch dir db) = do+  hPutStrLn stderr "verify: package db isn't in the expected location:"+  T.hPutStrLn stderr $ "dir: " <> dir+  T.hPutStrLn stderr $ "db: " <> db+  exitFailure+handleSandboxExceptions (MissingConfig cabalConfigExists cabalSandboxConfigExists) = do+  when (not cabalConfigExists) $+    hPutStrLn stderr "verify: cabal.config not present"+  when (not cabalSandboxConfigExists) $+    hPutStrLn stderr "verify: cabal.sandbox.config not present"+  exitFailure+handleSandboxExceptions (SnapshotMismatch s configSnapshot) = do+  T.hPutStrLn stderr+     $ "Warning: given snapshot [" <> s <> "] "+    <> "doesn't match cabal.config snapshot [" <> configSnapshot <> "]"+  hPutStrLn stderr "No action taken"+  exitFailure+handleSandboxExceptions ConfigAlreadyExists = do+  hPutStrLn stderr $ "Warning: cabal.sandbox.config already exists"+  hPutStrLn stderr $ "No action taken"+  exitFailure+handleSandboxExceptions NonStackageCabalConfig = do+  hPutStrLn stderr $ "cabal.config doesn't look like it's from stackage"+  exitFailure+handleSandboxExceptions EmptyCabalConfig = do+  hPutStrLn stderr $ "No contents found in cabal.config"+  exitFailure+handleSandboxExceptions (DecodePathFail e) = do+  hPutStrLn stderr $ "Unexpected failure decoding path:"+  T.hPutStrLn stderr e+  exitFailure+handleSandboxExceptions (InvalidSnapshot snapshot) = do+  T.hPutStrLn stderr $ "Invalid snapshot: " <> snapshot+  exitFailure+handleSandboxExceptions GhcVersionFail = do+  hPutStrLn stderr $ "Couldn't determine ghc version"+  exitFailure+handleSandboxExceptions PackageDbNotFound = do+  hPutStrLn stderr $ "Couldn't find sandbox package-db"+  exitFailure++handlePluginExceptions :: PluginException -> IO ()+handlePluginExceptions (PluginNotFound _ p) = do+  hPutStrLn stderr $ "stackage-sandbox: requires plugin stackage " <> T.unpack p+  exitFailure+handlePluginExceptions (PluginExitFailure _ i) = do+  exitWith (ExitFailure i)++main :: IO ()+main = do+  ((), action) <- simpleOptions+    version+    header+    progDesc+    (pure ())+    subcommands+  let go = case action of+        Init mSnapshot -> sandboxInit mSnapshot+        PackageDb -> printPackageDb+        List mPackage -> ghcPkgList mPackage+        Unregister package -> ghcPkgUnregister package+        Delete mSnapshot -> case mSnapshot of+          Just snapshot -> sandboxDeleteSnapshot snapshot+          Nothing -> sandboxDelete+        Upgrade mSnapshot -> sandboxUpgrade mSnapshot+  go `catch` handleSandboxExceptions+     `catch` handlePluginExceptions
+ stackage-sandbox.cabal view
@@ -0,0 +1,39 @@+name:                stackage-sandbox+version:             0.1.0+synopsis:+  Work with shared stackage sandboxes+description:+  Work with shared stackage sandboxes+homepage:+  https://www.stackage.org/package/stackage-sandbox+bug-reports:+  https://github.com/fpco/stackage-sandbox/issues++license:             MIT+license-file:        LICENSE+author:              Dan Burton+maintainer:          danburton@fpcomplete.com+copyright:           2015 FP Complete Corporation++build-type:          Simple+cabal-version:       >=1.10+category: Development+extra-source-files:  README.md ChangeLog.md++source-repository head+  type:     git+  location: git://github.com/fpco/stackage-sandbox.git++executable stackage-sandbox+  hs-source-dirs:      main+  main-is:             Sandbox.hs+  build-depends:+      base >=4.7 && <5+    , text+    , stackage-cli+    , system-fileio+    , system-filepath+    , optparse-applicative+    , process+    , filepath+  default-language:    Haskell2010