packages feed

hackage-proxy (empty) → 0.1.0.0

raw patch · 6 files changed

+268/−0 lines, 6 filesdep +Cabaldep +basedep +blaze-buildersetup-changed

Dependencies added: Cabal, base, blaze-builder, bytestring, case-insensitive, classy-prelude, conduit, filepath, http-conduit, http-types, optparse-applicative, tar, text, transformers, wai, warp, zlib, zlib-conduit

Files

+ HackageProxy.hs view
@@ -0,0 +1,95 @@+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PatternGuards     #-}+{-# LANGUAGE RecordWildCards   #-}+{-# LANGUAGE ViewPatterns      #-}+module HackageProxy where++import           Blaze.ByteString.Builder (fromByteString)+import           ClassyPrelude+import qualified Codec.Archive.Tar        as Tar+import           Codec.Compression.GZip   (compress)+import qualified Data.ByteString          as S+import           Data.CaseInsensitive     (CI)+import           Data.Conduit+import           Data.Conduit.Lazy        (lazyConsume)+import           Data.Conduit.Zlib        (ungzip)+import           Data.Text                (breakOnEnd)+import           Network.HTTP.Conduit+import           Network.HTTP.Types       (status200)+import           Network.Wai              (Response (ResponseSource), pathInfo,+                                           rawPathInfo, responseLBS)+import           Network.Wai.Handler.Warp (run)+import           System.FilePath          (takeExtension)+import           TweakCabal++data HackageProxySettings = HackageProxySettings+    { hpsPort     :: Int+    , hpsNoBounds :: Set Text+    , hpsSource   :: Text+    }++runHackageProxy :: HackageProxySettings -> IO ()+runHackageProxy HackageProxySettings {..} = do+    baseReq <- parseUrl $ unpack hpsSource+    run hpsPort $ app baseReq+        { checkStatus = \_ _ -> Nothing+        -- Sometimes Hackage can be slow at responding.+        , responseTimeout = Just 30000000+        }+  where+    tcs = TweakCabalSettings hpsNoBounds++    -- Keep-alive connections with the main Hackage server do not seem to work,+    -- so for now we're just creating a new manager for each connection. Note+    -- that we're /not/ using withManager: we want to reuse the original+    -- ResourceT of the WAI app so that we can keep resources open from the+    -- request to response.+    app baseReq waiReq = bracket (lift $ newManager def) (lift . closeManager) $ \man -> do+        res <- http req man+        (src, _) <- unwrapResumable $ responseBody res++        let resStatus = responseStatus res+            resHeaders = (filter ((`elem` safeResponseHeaders) . fst) (responseHeaders res))++        if isTarball res+            then do+                lbs <- fromChunks <$> lazyConsume (src $= ungzip)+                entries <- mapEntries tweakEntry $ Tar.read lbs+                return $ responseLBS resStatus resHeaders (compress $ Tar.write entries)+            else return $ ResponseSource resStatus resHeaders (mapOutput (Chunk . fromByteString) src)+      where+        isTarball res =+            ".tar.gz" `isSuffixOf` rawPathInfo waiReq+            && responseStatus res == status200+        req = baseReq { path = path baseReq `combine` rawPathInfo' }+        rawPathInfo' =+            case pathInfo waiReq of+                ["package", stripSuffix ".tar.gz" -> Just packver] | Just package <- mpackage ->+                    encodeUtf8 $ intercalate "/" [package, version, packver ++ ".tar.gz"]+                  where+                    (stripSuffix "-" -> mpackage, version) = breakOnEnd "-" packver+                _ -> rawPathInfo waiReq+        combine a b+            | "/" `isSuffixOf` a || "/" `S.isPrefixOf` b = a ++ b+            | otherwise = concat [a, "/", b]++    tweakEntry e@(Tar.entryContent -> Tar.NormalFile lbs _)+        | takeExtension (Tar.entryPath e) == ".cabal" = e+            { Tar.entryContent = Tar.NormalFile lbs' $ length lbs'+            }+      where+        lbs' = tweakCabal tcs lbs+    tweakEntry e = e++    mapEntries f (Tar.Next entry rest) = (f entry:) <$> mapEntries f rest+    mapEntries _ Tar.Done = return []+    mapEntries _ (Tar.Fail e) = throwIO e++safeResponseHeaders :: HashSet (CI ByteString)+safeResponseHeaders = pack+    [ "content-type"+    , "etag"+    , "expires"+    , "last-modified"+    ]
+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2013 Michael Snoyman++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.
+ Setup.hs view
@@ -0,0 +1,2 @@+import           Distribution.Simple+main = defaultMain
+ TweakCabal.hs view
@@ -0,0 +1,71 @@+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE RecordWildCards   #-}+module TweakCabal where++import           ClassyPrelude+import           Debug.Trace+import           Distribution.Package+import           Distribution.PackageDescription+import           Distribution.PackageDescription.Parse+import           Distribution.PackageDescription.PrettyPrint+import           Distribution.Text                           (display)+import           Distribution.Version++data TweakCabalSettings = TweakCabalSettings+    { tcsNoBounds :: Set Text -- ^ package names+    }++tweakCabal :: TweakCabalSettings -> LByteString -> LByteString+tweakCabal TweakCabalSettings {..} bs = fromMaybe bs $ do+    gpd <-+        case parsePackageDescription $ unpack $ decodeUtf8 bs of+            ParseFailed _ -> Nothing+            ParseOk _ x -> Just x+    let string = showGenericPackageDescription gpd+            { condLibrary = tweakCondTree <$> condLibrary gpd+            , condExecutables = second tweakCondTree <$> condExecutables gpd+            , condTestSuites = second (fixTestSuite . tweakCondTree) <$> condTestSuites gpd+            , condBenchmarks = second tweakCondTree <$> condBenchmarks gpd+            }+    -- Following added for:+    -- https://github.com/haskell/cabal/issues/1202+    case parsePackageDescription string of+            ParseFailed _ -> trace+                ("Cabal bug: could not parse then pretty-print: " ++ display (package $ packageDescription gpd))+                Nothing+            ParseOk _ _ -> Just $ encodeUtf8 $ pack string+  where+    tweakCondTree ct = ct+        { condTreeConstraints = tweakConstraints $ condTreeConstraints ct+        , condTreeComponents = tweakComponents <$> condTreeComponents ct+        }++    tweakConstraints = map tweakDependency++    tweakDependency orig@(Dependency name'@(PackageName name) _)+        | pack name `elem` tcsNoBounds = Dependency name' anyVersion+        | otherwise = orig++    tweakComponents (a, b, c) =+        ( a+        , tweakCondTree b+        , tweakCondTree <$> c+        )++    -- Following added for:+    -- https://github.com/haskell/cabal/issues/1202+    fixTestSuite ct = ct+        { condTreeComponents = fixTestSuiteComp (condTreeData ct) <$> condTreeComponents ct+        }++    fixTestSuiteComp ts (a, b, c) =+        ( a+        , fixTestSuiteTree ts b+        , fixTestSuiteTree ts <$> c+        )++    fixTestSuiteTree ts ct = fixTestSuite $ ct+        { condTreeData = (condTreeData ct)+            { testInterface = testInterface ts+            }+        }
+ hackage-proxy.cabal view
@@ -0,0 +1,35 @@+name:                hackage-proxy+version:             0.1.0.0+synopsis:            Provide a proxy for Hackage which modifies responses in some way.+description:         The motivating use case for this is testing packages with newer versions of GHC. In this case, upper bounds on base, process, and a few other packages will often prevent compilation. This proxy will allow you to strip those upper bounds and proceed with compilation. In the future, other features may be added as well, such as package replacement.+homepage:            http://github.com/snoyberg/hackage-proxy+license:             MIT+license-file:        LICENSE+author:              Michael Snoyman+maintainer:          michael@snoyman.com+category:            Development+build-type:          Simple+cabal-version:       >=1.8++executable hackage-proxy+  main-is:             hackage-proxy.hs+  other-modules:       HackageProxy+                       TweakCabal+  build-depends:       base >= 4.5 && < 5+                     , classy-prelude >= 0.5+                     , Cabal >= 1.12+                     , wai >=1.3+                     , warp >=1.3+                     , http-conduit >=1.8+                     , transformers >= 0.2+                     , case-insensitive+                     , conduit >= 0.5+                     , tar >=0.4+                     , filepath+                     , blaze-builder >= 0.3+                     , zlib-conduit >= 0.5+                     , http-types >= 0.7+                     , zlib >=0.5+                     , bytestring >= 0.9+                     , text >= 0.11+                     , optparse-applicative >= 0.5
+ hackage-proxy.hs view
@@ -0,0 +1,45 @@+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE OverloadedStrings #-}+import           ClassyPrelude+import           HackageProxy+import           Options.Applicative++hackageProxySettings :: Parser HackageProxySettings+hackageProxySettings = HackageProxySettings+    <$> option+        ( long "port"+       <> help "Port to listen on"+       <> short 'p'+       <> value 4200+       <> metavar "PORT"+        )+    <*> (fixNoBounds <$> many (strOption+        ( long "no-bounds"+       <> help "Packages to drop version bounds from"+       <> short 'n'+       <> metavar "PACKAGE"+        )))+    <*> (pack <$> strOption+        ( long "source"+       <> help "Hackage source URL we're proxying from"+       <> short 's'+       <> value "http://hackage.haskell.org/packages/archive"+       <> metavar "URL"+        ))+  where+    fixNoBounds [] = pack $ words "base process Cabal directory template-haskell"+    fixNoBounds x = pack $ map pack x++main :: IO ()+main = do+    hps <- execParser opts+    putStrLn $ "Listening on port: " ++ show (hpsPort hps)+    putStrLn $ "Dropping bounds for: " ++ unwords (unpack $ hpsNoBounds hps)+    putStrLn $ "Downloading from: " ++ hpsSource hps+    runHackageProxy hps+  where+    opts = info (helper <*> hackageProxySettings)+        ( fullDesc+       <> progDesc "Run a Hackage proxy, modifying packages in some way."+       <> header "hackage-proxy - Proxy for Hackage packages."+        )