1module Cabal2Nix.Package where
3import qualified Cabal2Nix.Hackage as DB
4import Control.Applicative
5import qualified Control.Exception as Exception
7import Control.Monad.IO.Class
8import Control.Monad.Trans.Maybe
9import Data.List ( isSuffixOf, isPrefixOf )
12import Distribution.Nixpkgs.Fetch
13import qualified Distribution.Package as Cabal
14import qualified Distribution.PackageDescription as Cabal
15import qualified Distribution.PackageDescription.Parse as Cabal
16import qualified Distribution.ParseUtils as ParseUtils
17import Distribution.Text ( simpleParse )
18import System.Directory ( doesDirectoryExist, doesFileExist, createDirectoryIfMissing, getHomeDirectory, getDirectoryContents )
19import System.Exit ( exitFailure )
20import System.FilePath ( (</>), (<.>) )
21import System.IO ( hPutStrLn, stderr, hPutStr )
24 { pkgSource :: DerivationSource
25 , pkgCabal :: Cabal.GenericPackageDescription
29getPackage :: Maybe String -> Source -> IO Package
30getPackage optHackageDB source = do
31 (derivSource, pkgDesc) <- fetchOrFromDB optHackageDB source
32 flip Package pkgDesc <$> maybe (sourceFromHackage (sourceHash source) $ showPackageIdentifier pkgDesc) return derivSource
35fetchOrFromDB :: Maybe String -> Source -> IO (Maybe DerivationSource, Cabal.GenericPackageDescription)
36fetchOrFromDB optHackageDB src
37 | "cabal://" `isPrefixOf` sourceUrl src = fmap ((,) Nothing) . fromDB optHackageDB . drop (length "cabal://") $ sourceUrl src
39 r <- fetch cabalFromPath src
42 hPutStrLn stderr "*** failed to fetch source. Does the URL exist?" >> exitFailure
43 Just (derivSource, (externalSource, pkgDesc)) ->
44 return (derivSource <$ guard externalSource, pkgDesc)
46fromDB :: Maybe String -> String -> IO Cabal.GenericPackageDescription
47fromDB optHackageDB pkg = do
48 pkgDesc <- (lookupVersion <=< DB.lookup name) <$> maybe DB.readHashedHackage DB.readHackage' optHackageDB
51 Nothing -> hPutStrLn stderr "*** no such package in the cabal database (did you run cabal update?). " >> exitFailure
53 pkgId = fromMaybe (error ("invalid Haskell package id " ++ show pkg)) (simpleParse pkg)
54 Cabal.PackageName name = Cabal.pkgName pkgId
55 version = Cabal.pkgVersion pkgId
57 lookupVersion :: DB.Map DB.Version Cabal.GenericPackageDescription -> Maybe Cabal.GenericPackageDescription
59 | null (versionBranch version) = fmap snd . listToMaybe . reverse . DB.toAscList
60 | otherwise = DB.lookup version
63readFileMay :: String -> IO (Maybe String)
65 e <- doesFileExist file
67 then Just <$> readFile file
70hashCachePath :: String -> IO String
72 home <- getHomeDirectory
73 let cacheDir = home </> ".cache/cabal2nix"
74 createDirectoryIfMissing True cacheDir
75 return $ cacheDir </> pid <.> "sha256"
77sourceFromHackage :: Maybe String -> String -> IO DerivationSource
78sourceFromHackage optHash pkgId = do
79 cacheFile <- hashCachePath pkgId
80 let cachedHash = MaybeT $ maybe (readFileMay cacheFile) (return . Just) optHash
81 url = "mirror://hackage/" ++ pkgId ++ ".tar.gz"
83 -- Use the cached hash (either from cache file or given on cmdline via sha256 opt)
84 -- if available, otherwise download from hackage to compute hash.
85 maybeHash <- runMaybeT $ cachedHash <|> derivHash . fst <$> fetchWith (False, "url", []) (Source url "" Nothing)
88 -- We need to force the hash here. If we didn't do this, then when reading the
89 -- hash from the cache file, the cache file will still be open for reading
90 -- (because lazy io) when writeFile opens the file again for writing. By forcing
91 -- the hash here, we ensure that the file is closed before opening it again.
93 DerivationSource "url" url "" hash <$ writeFile cacheFile hash
95 hPutStr stderr $ unlines
96 [ "*** cannot compute hash. (Not a hackage project?)"
97 , " If your project is not on hackage, please supply the path to the root directory of"
98 , " the project, not to the cabal file."
100 , " If your project is on hackage but you still want to specify the hash manually, you"
101 , " can use the --sha256 option."
105showPackageIdentifier :: Cabal.GenericPackageDescription -> String
106showPackageIdentifier pkgDesc = name ++ "-" ++ showVersion version where
107 pkgId = Cabal.package . Cabal.packageDescription $ pkgDesc
108 Cabal.PackageName name = Cabal.packageName pkgId
109 version = Cabal.packageVersion pkgId
111cabalFromPath :: FilePath -> MaybeT IO (Bool, Cabal.GenericPackageDescription)
112cabalFromPath path = do
113 d <- liftIO $ doesDirectoryExist path
115 then cabalFromDirectory path
116 else cabalFromFile False path
118cabalFromDirectory :: FilePath -> MaybeT IO Cabal.GenericPackageDescription
119cabalFromDirectory dir = do
120 cabals <- liftIO $ getDirectoryContents dir >>= filterM doesFileExist . map (dir </>) . filter (".cabal" `isSuffixOf`)
122 [cabalFile] -> cabalFromFile True cabalFile
123 _ -> liftIO $ hPutStrLn stderr "*** found zero or more than one cabal file. Exiting." >> exitFailure
125handleIO :: (Exception.IOException -> IO a) -> IO a -> IO a
126handleIO = Exception.handle
128cabalFromFile :: Bool -> FilePath -> MaybeT IO Cabal.GenericPackageDescription
129cabalFromFile failHard file =
130 -- readFile throws an error if it's used on binary files which contain sequences
131 -- that do not represent valid characters. To catch that exception, we need to
132 -- wrap the whole block in `catchIO`, because of lazy IO. The `case` will force
133 -- the reading of the file, so we will always catch the expression here.
134 MaybeT $ handleIO (\err -> Nothing <$ hPutStrLn stderr ("*** parsing cabal file: " ++ show err)) $ do
135 content <- readFile file
136 case Cabal.parsePackageDescription content of
137 Cabal.ParseFailed e | failHard -> do
138 let (line, err) = ParseUtils.locatedErrorMsg e
139 msg = maybe "" ((++ ": ") . show) line ++ err
140 putStrLn $ "*** error parsing cabal file: " ++ msg
142 Cabal.ParseFailed _ -> return Nothing
143 Cabal.ParseOk _ a -> return (Just a)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list