on master help sign in
Initial code
commit8825105005abfaf641cb7947b4c98363d69ff34a authorMichael Snoyman <michael@snoyman.com> dateTue 20 Nov 2012, 14:01 (14 years ago) parent7704a91
15 files changed+355−1
.gitignore+1 .gitmodules+3 LICENSE+20 README.md+6−1 Setup.hs+2 Stackage/HaskellPlatform.hs+53 Stackage/LoadDatabase.hs+92 Stackage/NarrowDatabase.hs+30 Stackage/PackageList.hs+31 Stackage/Types.hs+40 Stackage/Util.hs+12 app/gen-install-line.hs+19 haskell-platform+1 package-list.txt+8 stackage.cabal+37
.gitignore+1
@@ -5,3 +5,4 @@ cabal-dev
55*.chi
66*.chs.h
77.virthualenv
8+*.swp
.gitmodulesnew file+3
@@ -0,0 +1,3 @@
1+[submodule "haskell-platform"]
2+ path = haskell-platform
3+ url = https://github.com/haskell/haskell-platform.git
LICENSEnew file+20
@@ -0,0 +1,20 @@
1+Copyright (c) 2012 Michael Snoyman, http://www.yesodweb.com/
2+
3+Permission is hereby granted, free of charge, to any person obtaining
4+a copy of this software and associated documentation files (the
5+"Software"), to deal in the Software without restriction, including
6+without limitation the rights to use, copy, modify, merge, publish,
7+distribute, sublicense, and/or sell copies of the Software, and to
8+permit persons to whom the Software is furnished to do so, subject to
9+the following conditions:
10+
11+The above copyright notice and this permission notice shall be
12+included in all copies or substantial portions of the Software.
13+
14+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
README.md+6−1
@@ -1,4 +1,9 @@
11stackage
22========
33
4−"Stable Hackage," tools for creating a vetted set of packages from Hackage.
4+"Stable Hackage," tools for creating a vetted set of packages from Hackage.
5+
6+A note about the codebase: the goal is to minimize dependencies and have
7+the maximum range of supported compiler versions. Therefore, we avoid
8+anything "complicated." For example, instead of using the text package,
9+we use Strings everywhere.
Setup.hsnew file+2
@@ -0,0 +1,2 @@
1+import Distribution.Simple
2+main = defaultMain
Stackage/HaskellPlatform.hsnew file+53
@@ -0,0 +1,53 @@
1+module Stackage.HaskellPlatform
2+ ( loadHaskellPlatform
3+ ) where
4+
5+import Control.Monad (guard)
6+import Data.Char (isSpace)
7+import Data.List (foldl', isInfixOf, isPrefixOf, stripPrefix)
8+import Data.Maybe (mapMaybe)
9+import Data.Monoid (Monoid (..))
10+import Data.Set (singleton)
11+import Distribution.Text (simpleParse)
12+import Stackage.Types
13+
14+loadHaskellPlatform :: IO HaskellPlatform
15+loadHaskellPlatform = fmap parseHP $ readFile "haskell-platform/haskell-platform.cabal"
16+
17+data HPLine = HPLPackage PackageIdentifier
18+ | HPLBeginCore
19+ | HPLEndCore
20+ | HPLBeginPlatform
21+ | HPLEndPlatform
22+ deriving Show
23+
24+toHPLine :: String -> Maybe HPLine
25+toHPLine s
26+ | "begin core packages" `isInfixOf` s = Just HPLBeginCore
27+ | "end core packages" `isInfixOf` s = Just HPLEndCore
28+ | "begin platform packages" `isInfixOf` s = Just HPLBeginPlatform
29+ | "end platform packages" `isInfixOf` s = Just HPLEndPlatform
30+ | otherwise = do
31+ let s1 = dropWhile isSpace s
32+ guard $ not $ "--" `isPrefixOf` s1
33+ guard $ not $ null s1
34+ guard $ "==" `isInfixOf` s1
35+ let (package', s2) = break (== '=') s1
36+ package = takeWhile (not . isSpace) package'
37+ s3 <- stripPrefix "==" s2
38+ version <- simpleParse $ takeWhile (/= ',') s3
39+ Just $ HPLPackage $ PackageIdentifier (PackageName package) version
40+
41+parseHP :: String -> HaskellPlatform
42+parseHP =
43+ snd . foldl' addLine (notInBlock, mempty) . mapMaybe toHPLine . lines
44+ where
45+ notInBlock _ = mempty
46+ inCore x = HaskellPlatform (singleton x) mempty
47+ inPlatform x = HaskellPlatform mempty (singleton x)
48+
49+ addLine (fromPackage, hp) (HPLPackage vp) = (fromPackage, fromPackage vp `mappend` hp)
50+ addLine (_, hp) HPLBeginCore = (inCore, hp)
51+ addLine (_, hp) HPLEndCore = (notInBlock, hp)
52+ addLine (_, hp) HPLBeginPlatform = (inPlatform, hp)
53+ addLine (_, hp) HPLEndPlatform = (notInBlock, hp)
Stackage/LoadDatabase.hsnew file+92
@@ -0,0 +1,92 @@
1+module Stackage.LoadDatabase where
2+
3+import qualified Codec.Archive.Tar as Tar
4+import qualified Codec.Archive.Tar.Entry as TarEntry
5+import Control.Exception (throwIO)
6+import Control.Monad (guard)
7+import qualified Data.ByteString.Lazy as L
8+import qualified Data.ByteString.Lazy.Char8 as L8
9+import Data.List (stripPrefix)
10+import qualified Data.Map as Map
11+import Data.Monoid (Monoid (..))
12+import Data.Set (member)
13+import qualified Data.Set as Set
14+import Distribution.Package (Dependency (Dependency))
15+import Distribution.PackageDescription (condBenchmarks,
16+ condExecutables,
17+ condLibrary,
18+ condTestSuites,
19+ condTreeConstraints)
20+import Distribution.PackageDescription.Parse (ParseResult (ParseOk),
21+ parsePackageDescription)
22+import Distribution.Text (simpleParse)
23+import Distribution.Version (withinRange)
24+import Stackage.Types
25+import System.Directory (getAppUserDataDirectory)
26+import System.FilePath ((</>))
27+
28+-- | Load the raw package database.
29+--
30+-- We want to put in some restrictions:
31+--
32+-- * Drop all core packages. We never want to install a new version of
33+-- those, nor include them in the package list.
34+--
35+-- * For packages with a specific version bound, find the maximum matching
36+-- version.
37+--
38+-- * For other packages, select the maximum version number.
39+loadPackageDB :: Set PackageName -- ^ core packages
40+ -> Map PackageName VersionRange -- ^ additional deps
41+ -> IO PackageDB
42+loadPackageDB core deps = do
43+ c <- getAppUserDataDirectory "cabal"
44+ let tarName = c </> "packages" </> "hackage.haskell.org" </> "00-index.tar"
45+ lbs <- L.readFile tarName
46+ addEntries mempty $ Tar.read lbs
47+ where
48+ addEntries :: PackageDB -> Tar.Entries Tar.FormatError -> IO PackageDB
49+ addEntries _ (Tar.Fail e) = throwIO e
50+ addEntries db Tar.Done = return db
51+ addEntries db (Tar.Next e es) = addEntry db e >>= flip addEntries es
52+
53+ addEntry :: PackageDB -> Tar.Entry -> IO PackageDB
54+ addEntry pdb e =
55+ case getPackageVersion $ TarEntry.fromTarPathToPosixPath (TarEntry.entryTarPath e) of
56+ Nothing -> return pdb
57+ Just (p, v)
58+ | p `member` core -> return pdb
59+ | otherwise ->
60+ case Map.lookup p deps of
61+ Just vrange
62+ | not $ withinRange v vrange -> return pdb
63+ _ ->
64+ case Tar.entryContent e of
65+ Tar.NormalFile bs _ -> return $ mappend pdb $ PackageDB $ Map.singleton p PackageInfo
66+ { piVersion = v
67+ , piDeps = parseDeps bs
68+ }
69+ _ -> return pdb
70+
71+ parseDeps lbs =
72+ case parsePackageDescription $ L8.unpack lbs of
73+ ParseOk _ gpd -> mconcat
74+ [ maybe mempty go $ condLibrary gpd
75+ , mconcat $ map (go . snd) $ condExecutables gpd
76+ , mconcat $ map (go . snd) $ condTestSuites gpd
77+ , mconcat $ map (go . snd) $ condBenchmarks gpd
78+ ]
79+ _ -> mempty
80+ where
81+ go = Set.fromList . map (\(Dependency p _) -> p) . condTreeConstraints
82+
83+ getPackageVersion :: FilePath -> Maybe (PackageName, Version)
84+ getPackageVersion fp = do
85+ let (package', s1) = break (== '/') fp
86+ package = PackageName package'
87+ s2 <- stripPrefix "/" s1
88+ let (version', s3) = break (== '/') s2
89+ version <- simpleParse version'
90+ s4 <- stripPrefix "/" s3
91+ guard $ s4 == package' ++ ".cabal"
92+ Just (package, version)
Stackage/NarrowDatabase.hsnew file+30
@@ -0,0 +1,30 @@
1+module Stackage.NarrowDatabase where
2+
3+import qualified Data.Map as Map
4+import qualified Data.Set as Set
5+import Prelude hiding (pi)
6+import Stackage.Types
7+
8+-- | Narrow down the database to only the specified packages and all of
9+-- their dependencies.
10+narrowPackageDB :: PackageDB
11+ -> Set PackageName
12+ -> IO (Map PackageName Version)
13+narrowPackageDB (PackageDB pdb) =
14+ loop Map.empty . Set.map ((,) True)
15+ where
16+ loop result toProcess =
17+ case Set.minView toProcess of
18+ Nothing -> return result
19+ Just ((isOrig, p), toProcess') ->
20+ case Map.lookup p pdb of
21+ Nothing
22+ | isOrig -> error $ "Unknown package: " ++ show p
23+ | otherwise -> loop result toProcess'
24+ Just pi -> do
25+ let result' = Map.insert p (piVersion pi) result
26+ loop result' $ Set.foldl' (addDep result') toProcess' $ piDeps pi
27+ addDep result toProcess p =
28+ case Map.lookup p result of
29+ Nothing -> Set.insert (False, p) toProcess
30+ Just{} -> toProcess
Stackage/PackageList.hsnew file+31
@@ -0,0 +1,31 @@
1+module Stackage.PackageList where
2+
3+import Control.Monad (foldM)
4+import Data.Char (isSpace)
5+import qualified Data.Map as Map
6+import Distribution.Text (simpleParse)
7+import Distribution.Version (anyVersion)
8+import Stackage.Types
9+
10+loadPackageList :: FilePath -> IO (Map PackageName VersionRange)
11+loadPackageList fp =
12+ readFile fp >>= foldM addLine Map.empty . lines
13+ where
14+ addLine ps l'
15+ | null l = return ps
16+ | otherwise =
17+ case parseVersionRange v' of
18+ Nothing -> error $ "Invalid version range: " ++ show (p, v')
19+ Just v -> return $ Map.insert (PackageName p) v ps
20+ where
21+ l = cleanup l'
22+ (p, v') = break isSpace l
23+ cleanup = dropWhile isSpace . reverse . dropWhile isSpace . reverse . stripComments
24+
25+ parseVersionRange l
26+ | null $ cleanup l = Just anyVersion
27+ | otherwise = simpleParse l
28+
29+ stripComments "" = ""
30+ stripComments ('-':'-':_) = ""
31+ stripComments (c:cs) = c : stripComments cs
Stackage/Types.hsnew file+40
@@ -0,0 +1,40 @@
1+module Stackage.Types
2+ ( module X
3+ , module Stackage.Types
4+ ) where
5+
6+import Data.Map as X (Map)
7+import Data.Map (unionWith)
8+import Data.Monoid (Monoid (..))
9+import Data.Set as X (Set)
10+import Data.Version as X (Version)
11+import Distribution.Package as X (PackageIdentifier (..),
12+ PackageName (..))
13+import Distribution.Version as X (VersionRange (..))
14+
15+newtype PackageDB = PackageDB (Map PackageName PackageInfo)
16+ deriving (Show, Eq, Ord)
17+
18+instance Monoid PackageDB where
19+ mempty = PackageDB mempty
20+ PackageDB x `mappend` PackageDB y =
21+ PackageDB $ unionWith newest x y
22+ where
23+ newest pi1 pi2
24+ | piVersion pi1 > piVersion pi2 = pi1
25+ | otherwise = pi2
26+
27+data PackageInfo = PackageInfo
28+ { piVersion :: Version
29+ , piDeps :: Set PackageName
30+ }
31+ deriving (Show, Eq, Ord)
32+
33+data HaskellPlatform = HaskellPlatform
34+ { hpcore :: Set PackageIdentifier
35+ , hplibs :: Set PackageIdentifier
36+ }
37+ deriving (Show, Eq, Ord)
38+instance Monoid HaskellPlatform where
39+ mempty = HaskellPlatform mempty mempty
40+ HaskellPlatform a x `mappend` HaskellPlatform b y = HaskellPlatform (mappend a b) (mappend x y)
Stackage/Util.hsnew file+12
@@ -0,0 +1,12 @@
1+module Stackage.Util where
2+
3+import qualified Data.Map as Map
4+import qualified Data.Set as Set
5+import Distribution.Version (thisVersion)
6+import Stackage.Types
7+
8+identsToRanges :: Set PackageIdentifier -> Map PackageName VersionRange
9+identsToRanges =
10+ Map.unions . map go . Set.toList
11+ where
12+ go (PackageIdentifier package version) = Map.singleton package $ thisVersion version
app/gen-install-line.hsnew file+19
@@ -0,0 +1,19 @@
1+import qualified Data.Map as Map
2+import qualified Data.Set as Set
3+import Data.Version (showVersion)
4+import Stackage.HaskellPlatform
5+import Stackage.LoadDatabase
6+import Stackage.NarrowDatabase
7+import Stackage.PackageList
8+import Stackage.Types
9+import Stackage.Util
10+
11+main :: IO ()
12+main = do
13+ userPackages <- loadPackageList "package-list.txt"
14+ hp <- loadHaskellPlatform
15+ let allPackages = Map.union userPackages $ identsToRanges (hplibs hp)
16+ pdb <- loadPackageDB (Set.map (\(PackageIdentifier p _) -> p) (hpcore hp)) allPackages
17+ final <- narrowPackageDB pdb $ Set.fromList $ Map.keys allPackages
18+ putStr "cabal-dev install -fnetwork23 --enable-tests "
19+ mapM_ (\(PackageName p, v) -> putStr $ p ++ "-" ++ showVersion v ++ " ") $ Map.toList final
haskell-platformnew file+1

Submodule changed.

package-list.txtnew file+8
@@ -0,0 +1,8 @@
1+-- Michael Snoyman michael@snoyman.com
2+yesod < 1.4
3+yesod-newsfeed
4+yesod-sitemap
5+yesod-static
6+
7+-- Constraints
8+binary == 0.5.1.0
stackage.cabalnew file+37
@@ -0,0 +1,37 @@
1+-- Initial stackage.cabal generated by cabal init. For further
2+-- documentation, see http://haskell.org/cabal/users-guide/
3+
4+name: stackage
5+version: 0.1.0.0
6+synopsis: "Stable Hackage," tools for creating a vetted set of packages from Hackage.
7+-- description:
8+homepage: https://github.com/snoyberg/stackage
9+license: MIT
10+license-file: LICENSE
11+author: Michael Snoyman
12+maintainer: michael@snoyman.com
13+category: Distribution
14+build-type: Simple
15+cabal-version: >=1.8
16+
17+library
18+ exposed-modules: Stackage.PackageList
19+ Stackage.NarrowDatabase
20+ Stackage.LoadDatabase
21+ Stackage.HaskellPlatform
22+ Stackage.Util
23+ Stackage.Types
24+ build-depends: base >= 4 && < 5
25+ , containers
26+ , Cabal
27+ , tar >= 0.4
28+ , bytestring
29+ , directory
30+ , filepath
31+
32+executable stackage-gen-install-line
33+ hs-source-dirs: app
34+ main-is: gen-install-line.hs
35+ build-depends: base
36+ , stackage
37+ , containers