cabal2nix 1.68 → 1.69
raw patch · 12 files changed
+406/−135 lines, 12 filesdep +transformersdep −HTTP
Dependencies added: transformers
Dependencies removed: HTTP
Files
- README.md +47/−2
- cabal2nix.cabal +16/−8
- src/Cabal2Nix/Generate.hs +4/−5
- src/Cabal2Nix/Hackage.hs +0/−84
- src/Cabal2Nix/Name.hs +1/−0
- src/Cabal2Nix/Package.hs +144/−0
- src/Cabal2Nix/PostProcess.hs +6/−4
- src/Distribution/NixOS/Derivation/Cabal.hs +30/−8
- src/Distribution/NixOS/Fetch.hs +133/−0
- src/cabal2nix.hs +18/−21
- src/hackage4nix.hs +5/−2
- test/doc-test.hs +2/−1
README.md view
@@ -44,8 +44,10 @@ `cabal://NAME-VERSION`, which will automatically download the file from Hackage. Alternatively, a direct `http://host/path/pkg.cabal` URL can be provided, as well as a `file:///local/path/pkg.cabal` URI that doesn't-depend on network access. Run the utility with `--help` to see the-complete list of supported command line flags.+depend on network access. However, if the source hash is not already in `cabal2nix`'s cache or+provided using the `--sha256` option, `cabal2nix` still needs to download the source code to+compute the hash, which obviously still causes network traffic.+Run the utility with `--help` to see the complete list of supported command line flags. To add a new package to Nix, checkout the Nixpkgs project from https://github.com/nixos/nixpkgs.git and run@@ -58,6 +60,49 @@ `pkgs/top-level/haskell-packages.nix`, for example: foo = callPackage ../development/libraries/haskell/foo {};++`cabal2nix` can also build derivations for projects from other+sources than hackage. You only need to provide an URI that points+to a cabal project. The most common usecase for this is probably to+generate a derivation for a project on the local file system:++ aeson:$ cabal2nix ./.+ # This file was auto-generated by cabal2nix. Please do NOT edit manually!++ { cabal, attoparsec, blazeBuilder, deepseq, dlist, hashable, HUnit+ , mtl, QuickCheck, scientific, syb, testFramework+ , testFrameworkHunit, testFrameworkQuickcheck2, text, time+ , unorderedContainers, vector+ }++ cabal.mkDerivation (self: {+ pname = "aeson";+ version = "0.7.0.2";+ src = ./.;+ buildDepends = [+ attoparsec blazeBuilder deepseq dlist hashable mtl scientific syb+ text time unorderedContainers vector+ ];+ testDepends = [+ attoparsec HUnit QuickCheck testFramework testFrameworkHunit+ testFrameworkQuickcheck2 text time unorderedContainers vector+ ];+ meta = {+ homepage = "https://github.com/bos/aeson";+ description = "Fast JSON parsing and encoding";+ license = self.stdenv.lib.licenses.bsd3;+ platforms = self.ghc.meta.platforms;+ };+ })++This derivation will not fetch from hackage, but instead use the directory which+contains the derivation as the source repository.++`cabal2nix` currently supports the following respository types:++* directory+* source archive (zip, tar.gz, ...) from http or https URL or local file.+* git, mercurial, svn or bazaar repository ### Hackage4nix
cabal2nix.cabal view
@@ -1,5 +1,5 @@ Name: cabal2nix-Version: 1.68+Version: 1.69 Copyright: Peter Simons, Andres Loeh License: BSD3 License-File: LICENSE@@ -16,7 +16,7 @@ The @cabal2nix@ utility converts Cabal files into Nix build instructions. The commandline syntax is: .- > Usage: cabal2nix [options] url-to-cabal-file+ > Usage: cabal2nix [options] url-to-cabal-file-or-repo > -h --help show this help text > --hackage-db=FILEPATH path to the local hackage db in tar format > --sha256=HASH sha256 hash of source tarball@@ -33,16 +33,22 @@ > cabal://pkgname download latest version of the specified package from Hackage > http://host/path fetch the Cabal file via HTTP > file:///local/path load the Cabal file from the local disk- > /local/path abbreviated version of file URI+ > /local/path.cabal abbreviated version of file URI+ > <git/svn/bzr/hg URL> download the source from the specified repository . The only required argument is the path to the cabal file. For example: .- > cabal2nix http://hackage.haskell.org/packages/archive/cabal2nix/1.68/cabal2nix.cabal- > cabal2nix cabal://cabal2nix-1.68+ > cabal2nix http://hackage.haskell.org/packages/archive/cabal2nix/1.69/cabal2nix.cabal+ > cabal2nix cabal://cabal2nix-1.69 . If the @--sha256@ option has not been specified, cabal2nix calls @nix-prefetch-url@ to determine the hash automatically. This causes network traffic, obviously.+ .+ If the argument refers to a source repository instead of a cabal file,+ cabal2nix will use that source repository to fetch from instead of hackage.+ Currently, cabal2nix supports directories, archives (fetched via http or https) and+ git, mercurial, svn or bazaar repositories. Source-Repository head Type: git@@ -52,17 +58,18 @@ main-is: cabal2nix.hs hs-source-dirs: src Build-Depends: base >= 3 && < 5, regex-posix, pretty, Cabal >= 1.16,- filepath, directory, process, HTTP, hackage-db+ filepath, directory, process, hackage-db, transformers Extensions: PatternGuards, RecordWildCards, CPP Ghc-Options: -Wall other-modules: Cabal2Nix.CorePackages Cabal2Nix.Flags Cabal2Nix.Generate- Cabal2Nix.Hackage+ Cabal2Nix.Package Cabal2Nix.License Cabal2Nix.Name Cabal2Nix.Normalize Cabal2Nix.PostProcess+ Distribution.NixOS.Fetch Distribution.NixOS.Derivation.Cabal Distribution.NixOS.Derivation.License Distribution.NixOS.Derivation.Meta@@ -73,7 +80,7 @@ main-is: hackage4nix.hs hs-source-dirs: src Build-Depends: base >= 3 && < 5, regex-posix, pretty, Cabal >= 1.16,- mtl, containers, directory, filepath, hackage-db+ mtl, containers, directory, filepath, hackage-db, transformers, process Extensions: PatternGuards, RecordWildCards, CPP Ghc-Options: -Wall other-modules: Cabal2Nix.CorePackages@@ -83,6 +90,7 @@ Cabal2Nix.Name Cabal2Nix.Normalize Cabal2Nix.PostProcess+ Distribution.NixOS.Fetch Distribution.NixOS.Derivation.Cabal Distribution.NixOS.Derivation.License Distribution.NixOS.Derivation.Meta
src/Cabal2Nix/Generate.hs view
@@ -1,23 +1,22 @@ module Cabal2Nix.Generate ( cabal2nix ) where +import Cabal2Nix.Flags import Cabal2Nix.License-import Cabal2Nix.PostProcess import Cabal2Nix.Normalize-import Cabal2Nix.Flags+import Cabal2Nix.PostProcess import Data.Maybe import Distribution.Compiler+import Distribution.NixOS.Derivation.Cabal import qualified Distribution.Package as Cabal import qualified Distribution.PackageDescription as Cabal import Distribution.PackageDescription.Configuration import Distribution.System-import Distribution.Version-import Distribution.NixOS.Derivation.Cabal cabal2nix :: Cabal.GenericPackageDescription -> Derivation cabal2nix cabal = normalize $ postProcess MkDerivation { pname = let Cabal.PackageName x = Cabal.pkgName pkg in x , version = Cabal.pkgVersion pkg- , sha256 = "cabal2nix left the sha256 field undefined"+ , src = error "cabal2nix left the src field undefined" , isLibrary = isJust (Cabal.library tpkg) , isExecutable = not (null (Cabal.executables tpkg)) , extraFunctionArgs = []
− src/Cabal2Nix/Hackage.hs
@@ -1,84 +0,0 @@-module Cabal2Nix.Hackage ( hashPackage, readCabalFile ) where--import Control.Monad ( unless )-import Data.List ( isPrefixOf )-import Data.Maybe ( fromJust )-import Data.Version ( showVersion, versionBranch )-import Distribution.Package ( PackageIdentifier(..), PackageName(..) )-import Distribution.Text-import Network.HTTP ( getRequest, rspBody )-import Network.Browser ( browse, request, setCheckForProxy, setDebugLog, setOutHandler )-import System.Directory ( doesFileExist, getHomeDirectory, createDirectoryIfMissing )-import System.FilePath ( dropFileName, (</>), (<.>) )-import System.Process ( readProcess )-import Control.Exception (handle, SomeException(..))--import qualified Distribution.Hackage.DB as DB--data Ext = TarGz | Cabal deriving Eq--showExt :: Ext -> String-showExt TarGz = ".tar.gz"-showExt Cabal = ".cabal"--hackagePath :: PackageIdentifier -> Ext -> String-hackagePath (PackageIdentifier (PackageName name) version') ext =- "http://hackage.haskell.org/packages/archive/" ++- name ++ "/" ++ version ++ "/" ++ name ++- (if ext == TarGz then '-' : version else "") ++- showExt ext- where- version = showVersion version'--hashPackage :: PackageIdentifier -> IO String-hashPackage pkg = do- cachePath <- hashCachePath pkg- exists <- doesFileExist cachePath- hash' <- if exists- then readFile cachePath- else getHackageHash- let hash = reverse (dropWhile (=='\n') (reverse hash'))- unless exists $ do- createDirectoryIfMissing True (dropFileName cachePath)- writeFile cachePath hash- return hash- where getHackageHash = do- let command = "exec nix-prefetch-url 2>/dev/tty " ++ hackagePath pkg TarGz- handle handlePrefetchError (readProcess "bash" ["-c", command] "")- handlePrefetchError (SomeException _) =- error $ "\nError: Cannot compute hash. (Not a hackage project?)\n" ++- "Specify hash explicitly via --sha256 and add appropriate \"src\" attribute " ++- "to resulting nix expression."--hashCachePath :: PackageIdentifier -> IO FilePath-hashCachePath (PackageIdentifier (PackageName name) version') = do- home <- getHomeDirectory- return $ home ++ "/.cache/cabal2nix" </> name ++ "-" ++ version <.> "sha256"- where- version = showVersion version'--readCabalFile :: Maybe FilePath -> FilePath -> IO String-readCabalFile hackageDbPath path- | "cabal://" `isPrefixOf` path = do let pid p = fromJust $ simpleParse p- packageName = drop 8 path- case versionBranch $ pkgVersion $ pid packageName of- [] -> do- packageDescription <- DB.lookup packageName `fmap` maybe DB.readHackage DB.readHackage' hackageDbPath- case packageDescription of- Just d -> do- let version = showVersion $ last $ DB.keys d- readCabalFile Nothing $ hackagePath (pid $ packageName ++ "-" ++ version) Cabal- Nothing -> error "No such package"- _ -> readCabalFile Nothing $ hackagePath (pid packageName) Cabal- | "http://" `isPrefixOf` path = fetchUrl path- | "file://" `isPrefixOf` path = readCabalFile Nothing (drop 7 path)- | otherwise = readFile path--fetchUrl :: String -> IO String-fetchUrl url = do- (_,rsp) <- browse $ do- setCheckForProxy True- setDebugLog Nothing- setOutHandler (\_ -> return ())- request (getRequest url)- return (rspBody rsp)
src/Cabal2Nix/Name.hs view
@@ -48,6 +48,7 @@ libNixName "gstreamer-plugins-base-0.10" = return "gstreamer-plugins-base" libNixName "gthread-2.0" = return "glib" libNixName "gtk+-2.0" = return "gtk"+libNixName "gtk+-3.0" = return "gtk3" libNixName "gtkglext-1.0" = return "gtkglext" libNixName "gtksourceview-2.0" = return "gtksourceview" libNixName "icudata" = return "icu"
+ src/Cabal2Nix/Package.hs view
@@ -0,0 +1,144 @@+module Cabal2Nix.Package where++import Control.Applicative+import Control.Monad+import Control.Monad.IO.Class+import Control.Monad.Trans.Maybe+import Data.List ( isSuffixOf, isPrefixOf )+import Data.Maybe ( listToMaybe )+import Distribution.NixOS.Derivation.Cabal+import Distribution.NixOS.Fetch+import Distribution.Text ( simpleParse )+import System.Directory ( doesDirectoryExist, doesFileExist, createDirectoryIfMissing, getHomeDirectory, getDirectoryContents )+import System.Exit ( exitFailure )+import System.FilePath ( (</>), (<.>) )+import System.IO ( hPutStrLn, stderr, hPutStr )++import qualified Distribution.Hackage.DB as DB+import qualified Distribution.Package as Cabal+import qualified Distribution.PackageDescription as Cabal+import qualified Distribution.PackageDescription.Parse as Cabal+import qualified Distribution.ParseUtils as ParseUtils++import qualified Control.Exception as Exception++data Package = Package+ { source :: DerivationSource+ , cabal :: Cabal.GenericPackageDescription+ }++getPackage :: Maybe String -> Source -> IO Package+getPackage optHackageDB source = do+ (derivSource, pkgDesc) <- fetchOrFromDB optHackageDB source+ flip Package pkgDesc <$> maybe (sourceFromHackage (sourceHash source) $ showPackageIdentifier pkgDesc) return derivSource+++fetchOrFromDB :: Maybe String -> Source -> IO (Maybe DerivationSource, Cabal.GenericPackageDescription)+fetchOrFromDB optHackageDB src+ | "cabal://" `isPrefixOf` sourceUrl src = fmap ((,) Nothing) . fromDB optHackageDB . drop (length "cabal://") $ sourceUrl src+ | otherwise = do+ r <- fetch cabalFromPath src+ case r of+ Nothing ->+ hPutStrLn stderr "*** failed to fetch source. Does the URL exist?" >> exitFailure+ Just (derivSource, (externalSource, pkgDesc)) ->+ return (derivSource <$ guard externalSource, pkgDesc)++fromDB :: Maybe String -> String -> IO Cabal.GenericPackageDescription+fromDB optHackageDB pkg = do+ pkgDesc <- (lookupVersion <=< DB.lookup name) <$> maybe DB.readHackage DB.readHackage' optHackageDB+ case pkgDesc of+ Just r -> return r+ Nothing -> hPutStrLn stderr "*** no such package in the cabal database (did you run cabal update?). " >> exitFailure+ where+ Just pkgId = simpleParse pkg+ Cabal.PackageName name = Cabal.pkgName pkgId+ version = Cabal.pkgVersion pkgId++ lookupVersion :: DB.Map DB.Version Cabal.GenericPackageDescription -> Maybe Cabal.GenericPackageDescription+ lookupVersion+ | null (versionBranch version) = fmap snd . listToMaybe . reverse . DB.toAscList+ | otherwise = DB.lookup version+++readFileMay :: String -> IO (Maybe String)+readFileMay file = do+ e <- doesFileExist file+ if e+ then Just <$> readFile file+ else return Nothing++hashCachePath :: String -> IO String+hashCachePath pid = do+ home <- getHomeDirectory+ let cacheDir = home </> ".cache/cabal2nix"+ createDirectoryIfMissing True cacheDir+ return $ cacheDir </> pid <.> "sha256"++sourceFromHackage :: Maybe String -> String -> IO DerivationSource+sourceFromHackage optHash pkgId = do+ cacheFile <- hashCachePath pkgId+ let cachedHash = MaybeT $ maybe (readFileMay cacheFile) (return . Just) optHash+ url = "mirror://hackage/" ++ pkgId ++ ".tar.gz"++ -- Use the cached hash (either from cache file or given on cmdline via sha256 opt)+ -- if available, otherwise download from hackage to compute hash.+ maybeHash <- runMaybeT $ cachedHash <|> derivHash . fst <$> fetchWith (False, "url") (Source url "" Nothing)+ case maybeHash of+ Just hash ->+ -- We need to force the hash here. If we didn't do this, then when reading the+ -- hash from the cache file, the cache file will still be open for reading+ -- (because lazy io) when writeFile opens the file again for writing. By forcing+ -- the hash here, we ensure that the file is closed before opening it again.+ seq (length hash) $+ DerivationSource "url" url "" hash <$ writeFile cacheFile hash+ Nothing -> do+ hPutStr stderr $ unlines+ [ "*** cannot compute hash. (Not a hackage project?)"+ , " If your project is not on hackage, please supply the path to the root directory of"+ , " the project, not to the cabal file."+ , ""+ , " If your project is on hackage but you still want to specify the hash manually, you"+ , " can use the --sha256 option."+ ]+ exitFailure++showPackageIdentifier :: Cabal.GenericPackageDescription -> String+showPackageIdentifier pkgDesc = name ++ "-" ++ showVersion version where+ pkgId = Cabal.package . Cabal.packageDescription $ pkgDesc+ Cabal.PackageName name = Cabal.packageName pkgId+ version = Cabal.packageVersion pkgId++cabalFromPath :: FilePath -> MaybeT IO (Bool, Cabal.GenericPackageDescription)+cabalFromPath path = do+ d <- liftIO $ doesDirectoryExist path+ (,) d <$> if d+ then cabalFromDirectory path+ else cabalFromFile False path++cabalFromDirectory :: FilePath -> MaybeT IO Cabal.GenericPackageDescription+cabalFromDirectory dir = do+ cabals <- liftIO $ getDirectoryContents dir >>= filterM doesFileExist . map (dir </>) . filter (".cabal" `isSuffixOf`)+ case cabals of+ [cabalFile] -> cabalFromFile True cabalFile+ _ -> liftIO $ hPutStrLn stderr "*** found zero or more than one cabal file. Exiting." >> exitFailure++handleIO :: (Exception.IOException -> IO a) -> IO a -> IO a+handleIO = Exception.handle++cabalFromFile :: Bool -> FilePath -> MaybeT IO Cabal.GenericPackageDescription+cabalFromFile failHard file =+ -- readFile throws an error if it's used on binary files which contain sequences+ -- that do not represent valid characters. To catch that exception, we need to+ -- wrap the whole block in `catchIO`, because of lazy IO. The `case` will force+ -- the reading of the file, so we will always catch the expression here.+ MaybeT $ handleIO (const $ return Nothing) $ do+ content <- readFile file+ case Cabal.parsePackageDescription content of+ Cabal.ParseFailed e | failHard -> do+ let (line, err) = ParseUtils.locatedErrorMsg e+ msg = maybe "" ((++ ": ") . show) line ++ err+ putStrLn $ "*** error parsing cabal file: " ++ msg+ exitFailure+ Cabal.ParseFailed _ -> return Nothing+ Cabal.ParseOk _ a -> return (Just a)
src/Cabal2Nix/PostProcess.hs view
@@ -15,7 +15,9 @@ | pname == "alex" && version >= Version [3,1] [] = deriv { buildTools = "perl":"happy":buildTools } | pname == "bindings-GLFW" = deriv { extraLibs = "libXext":"libXfixes":extraLibs }+ | pname == "bits-extras" = deriv { configureFlags = "--ghc-option=-lgcc_s":configureFlags, extraLibs = filter (/= "gcc_s") extraLibs } | pname == "cabal2nix" = deriv { doCheck = True, phaseOverrides = cabal2nixDoCheckHook }+ | pname == "cabal-bounds" = deriv { buildTools = "cabalInstall":buildTools } | pname == "cabal-install" && version >= Version [0,14] [] = deriv { phaseOverrides = cabalInstallPostInstall } | pname == "cairo" = deriv { extraLibs = "pkgconfig":"libc":"cairo":"zlib":extraLibs }@@ -43,6 +45,7 @@ | pname == "gtksourceview2" = deriv { extraLibs = "pkgconfig":"libc":extraLibs } | pname == "haddock" && version < Version [2,14] [] = deriv { buildTools = "alex":"happy":buildTools }+ | pname == "haddock" = deriv { phaseOverrides = haddockPreCheck } | pname == "happy" = deriv { buildTools = "perl":buildTools } | pname == "haskeline" = deriv { buildDepends = "utf8String":buildDepends } | pname == "haskell-src" = deriv { buildTools = "happy":buildTools }@@ -95,7 +98,6 @@ = deriv { doCheck = True, phaseOverrides = sybDoCheck } | pname == "tar" = deriv { runHaddock = True, phaseOverrides = tarNoHaddock } | pname == "terminfo" = deriv { extraLibs = "ncurses":extraLibs }- | pname == "text-icu" = deriv { doCheck = True, phaseOverrides = textIcuDoCheckHook } | pname == "threadscope" = deriv { configureFlags = "--ghc-options=-rtsopts":configureFlags } | pname == "thyme" = deriv { buildTools = "cpphs":buildTools } | pname == "transformers" && version >= Version [0,4,1] []@@ -257,9 +259,6 @@ cookieDoCheckHook :: String cookieDoCheckHook = "doCheck = self.stdenv.lib.versionOlder \"7.8\" self.ghc.version;" -textIcuDoCheckHook :: String-textIcuDoCheckHook = "doCheck = !self.stdenv.isDarwin;"- eitherNoHaddock :: String eitherNoHaddock = "noHaddock = self.stdenv.lib.versionOlder self.ghc.version \"7.6\";" @@ -302,3 +301,6 @@ sybDoCheck, splitDoCheck :: String sybDoCheck = "doCheck = self.stdenv.lib.versionOlder self.ghc.version \"7.9\";" splitDoCheck = sybDoCheck++haddockPreCheck :: String+haddockPreCheck = "preCheck = \"unset GHC_PACKAGE_PATH\";"
src/Distribution/NixOS/Derivation/Cabal.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE PatternGuards, CPP #-}+{-# LANGUAGE PatternGuards, RecordWildCards, CPP #-} {- | Module : Distribution.NixOS.Derivation.Cabal License : BSD3@@ -14,16 +14,18 @@ module Distribution.NixOS.Derivation.Cabal ( Derivation(..) , parseDerivation+ , DerivationSource(..) , module Distribution.NixOS.Derivation.Meta , module Data.Version ) where import Distribution.NixOS.Derivation.Meta+import Distribution.NixOS.Fetch import Distribution.NixOS.PrettyPrinting import Distribution.NixOS.Regex hiding ( empty )-import Distribution.Text import Distribution.Package+import Distribution.Text #ifdef __HADDOCK__ import Distribution.PackageDescription ( PackageDescription ) #endif@@ -39,11 +41,10 @@ -- -- Note that the "Text" instance definition provides pretty-printing, -- but no parsing as of now!- data Derivation = MkDerivation { pname :: String , version :: Version- , sha256 :: String+ , src :: DerivationSource , isLibrary :: Bool , isExecutable :: Bool , extraFunctionArgs :: [String]@@ -82,7 +83,7 @@ , nest 2 $ vcat [ attr "pname" $ string (pname deriv) , attr "version" $ doubleQuotes (disp (version deriv))- , attr "sha256" $ string (sha256 deriv)+ , sourceAttr (src deriv) , boolattr "isLibrary" (not (isLibrary deriv) || isExecutable deriv) (isLibrary deriv) , boolattr "isExecutable" (not (isLibrary deriv) || isExecutable deriv) (isExecutable deriv) , listattr "buildDepends" empty (buildDepends deriv)@@ -106,20 +107,36 @@ where inputs = nub $ sortBy (compare `on` map toLower) $ filter (/="cabal") $ filter (not . isPrefixOf "self.") $ buildDepends deriv ++ testDepends deriv ++ buildTools deriv ++ extraLibs deriv ++ pkgConfDeps deriv ++ extraFunctionArgs deriv+ ++ ["fetch" ++ derivKind (src deriv) | derivKind (src deriv) /= "" && not isHackagePackage] renderedFlags = [ text "-f" <> (if enable then empty else char '-') <> text f | (FlagName f, enable) <- cabalFlags deriv ] ++ map text (configureFlags deriv)+ isHackagePackage = "mirror://hackage/" `isPrefixOf` derivUrl (src deriv)+ sourceAttr (DerivationSource{..})+ | isHackagePackage = attr "sha256" $ string derivHash+ | derivKind /= "" = vcat+ [ text "src" <+> equals <+> text ("fetch" ++ derivKind) <+> lbrace+ , nest 2 $ vcat+ [ attr "url" $ string derivUrl+ , attr "sha256" $ string derivHash+ , if derivRevision /= "" then attr "rev" (string derivRevision) else empty+ ]+ , rbrace <> semi+ ]+ | otherwise = attr "src" $ text derivUrl -- | A very incomplete parser that extracts 'pname', 'version', -- 'sha256', 'platforms', 'hydraPlatforms', 'maintainers', 'doCheck', -- 'jailbreak', and 'runHaddock' from the given Nix expression.- parseDerivation :: String -> Maybe Derivation parseDerivation buf | buf =~ "cabal.mkDerivation" , [name] <- buf `regsubmatch` "pname *= *\"([^\"]+)\"" , [vers'] <- buf `regsubmatch` "version *= *\"([^\"]+)\"" , Just vers <- simpleParse vers'- , [sha] <- buf `regsubmatch` "sha256 *= *\"([^\"]+)\""+ , sha <- buf `regsubmatch` "sha256 *= *\"([^\"]+)\""+ , sr <- buf `regsubmatch` "src *= *([^;]+);"+ , url <- buf `regsubmatch` "url *= *\"?([^;\"]+)\"? *;"+ , rev <- buf `regsubmatch` "rev *= *\"([^\"]+)\"" , hplats <- buf `regsubmatch` "hydraPlatforms *= *([^;]+);" , plats <- buf `regsubmatch` "platforms *= *([^;]+);" , maint <- buf `regsubmatch` "maintainers *= *(with [^;]+;)? \\[([^\"]+)]"@@ -131,7 +148,12 @@ = Just MkDerivation { pname = name , version = vers- , sha256 = sha+ , src = case (sr,url) of+ ([] , _ ) -> DerivationSource "url" ("mirror://hackage/" ++ name ++ "-" ++ vers' ++ ".tar.gz") "" $ head sha+ (sr':_, [] ) -> DerivationSource "" sr' "" ""+ (sr':_, url':_ ) -> DerivationSource (drop (length "fetch") . head . words $ sr')+ url' (head $ rev ++ [""]) $ head sha+ , isLibrary = False , isExecutable = False , extraFunctionArgs = []
+ src/Distribution/NixOS/Fetch.hs view
@@ -0,0 +1,133 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+module Distribution.NixOS.Fetch+ ( Source(..)+ , DerivationSource(..), fromDerivationSource+ , fetch+ , fetchWith+ ) where++import Control.Applicative+import Control.Monad+import Control.Monad.IO.Class+import Control.Monad.Trans.Maybe+import Data.Maybe+import System.Directory+import System.Environment+import System.Exit+import System.IO+import System.Process++-- | A source is a location from which we can fetch, such as a HTTP URL, a GIT URL, ....+data Source = Source+ { sourceUrl :: String -- ^ URL to fetch from.+ , sourceRevision :: String -- ^ Revision to use. For protocols where this doesn't make sense (such as HTTP), this+ -- should be the empty string.+ , sourceHash :: Maybe String -- ^ The expected hash of the source, if available.+ } deriving (Show, Eq, Ord)++-- | A source for a derivation. It always needs a hash and also has a protocol attached to it (url, git, svn, ...).+-- A @DerivationSource@ also always has it's revision fully resolved (not relative revisions like @master@, @HEAD@, etc).+data DerivationSource = DerivationSource+ { derivKind :: String -- ^ The kind of the source. The name of the build-support fetch derivation should be fetch<kind>.+ , derivUrl :: String -- ^ URL to fetch from.+ , derivRevision :: String -- ^ Revision to use. Leave empty if the fetcher doesn't support revisions.+ , derivHash :: String -- ^ The hash of the source.+ } deriving (Show, Eq, Ord)++fromDerivationSource :: DerivationSource -> Source+fromDerivationSource DerivationSource{..} = Source derivUrl derivRevision $ Just derivHash++-- | Fetch a source, trying any of the various nix-prefetch-* scripts.+fetch :: forall a. (String -> MaybeT IO a) -- ^ This function is passed the output path name as an argument.+ -- It should return 'Nothing' if the file doesn't match the expected format.+ -- This is required, because we cannot always check if a download succeeded otherwise.+ -> Source -- ^ The source to fetch from.+ -> IO (Maybe (DerivationSource, a)) -- ^ The derivation source and the result of the processing function. Returns Nothing if the download failed.+fetch f = runMaybeT . fetchers where+ fetchers :: Source -> MaybeT IO (DerivationSource, a)+ fetchers source = msum . (fetchLocal source :) $ map (\fetcher -> fetchWith fetcher source >>= process)+ [ (False, "zip")+ , (False, "url")+ , (True, "git")+ , (True, "hg")+ , (True, "svn")+ , (True, "bzr")+ ]++ fetchLocal :: Source -> MaybeT IO (DerivationSource, a)+ fetchLocal src = do+ let path = stripPrefix "file://" $ sourceUrl src+ existsFile <- liftIO $ doesFileExist path+ existsDir <- liftIO $ doesDirectoryExist path+ guard $ existsDir || existsFile+ let path' | '/' `elem` path = path+ | otherwise = "./" ++ path+ process (DerivationSource "" path' "" "", path') <|> localArchive path'++ localArchive :: FilePath -> MaybeT IO (DerivationSource, a)+ localArchive path = do+ absolutePath <- liftIO $ canonicalizePath path+ unpacked <- snd <$> fetchWith (False, "zip") (Source ("file://" ++ absolutePath) "" Nothing)+ process (DerivationSource "" absolutePath "" "", unpacked)++ process :: (DerivationSource, FilePath) -> MaybeT IO (DerivationSource, a)+ process (derivSource, file) = (,) derivSource <$> f file++-- | Like 'fetch', but allows to specify which script to use.+fetchWith :: (Bool, String) -> Source -> MaybeT IO (DerivationSource, FilePath)+fetchWith (supportsRev, kind) source = do+ unless ((sourceRevision source /= "") || isNothing (sourceHash source) || not supportsRev) $+ liftIO (hPutStrLn stderr "** need a revision for VCS when the hash is given. skipping.") >> mzero++ MaybeT $ liftIO $ do+ envs <- getEnvironment+ (Nothing, Just stdoutH, _, processH) <- createProcess (proc script args)+ { env = Just $ ("PRINT_PATH", "1") : envs+ , std_in = Inherit+ , std_err = Inherit+ , std_out = CreatePipe+ }++ exitCode <- waitForProcess processH+ case exitCode of+ ExitSuccess -> fmap Just . processOutputSwitch . lines =<< hGetContents stdoutH+ ExitFailure _ -> return Nothing++ where++ script :: String+ script = "nix-prefetch-" ++ kind++ args :: [String]+ args = sourceUrl source : [ sourceRevision source | supportsRev ] ++ maybeToList (sourceHash source)++ processOutputWithRev :: [String] -> IO (DerivationSource, FilePath)+ processOutputWithRev [rev,hash,path] = return (DerivationSource kind (sourceUrl source) (extractRevision rev) hash, path)+ processOutputWithRev out = unexpectedOutput out++ extractRevision :: String -> String+ extractRevision = unwords . drop 3 . words -- drop the "<vcs> revision is" prefix++ processOutput :: [String] -> IO (DerivationSource, FilePath)+ processOutput [hash,path] = return (DerivationSource kind (sourceUrl source) (sourceRevision source) hash, path)+ processOutput out = unexpectedOutput out++ unexpectedOutput :: [String] -> IO a+ unexpectedOutput out = do+ hPutStrLn stderr $ "*** unexpected output from " ++ "nix-prefetch-" ++ kind ++ " script: "+ hPutStr stderr $ unlines out+ exitFailure++ processOutputSwitch :: [String] -> IO (DerivationSource, FilePath)+ processOutputSwitch+ | supportsRev && isNothing (sourceHash source) = processOutputWithRev+ | otherwise = processOutput++stripPrefix :: Eq a => [a] -> [a] -> [a]+stripPrefix prefix as+ | prefix' == prefix = stripped+ | otherwise = as+ where+ (prefix', stripped) = splitAt (length prefix) as
src/cabal2nix.hs view
@@ -1,14 +1,13 @@ module Main ( main ) where -import Cabal2Nix.Hackage ( hashPackage, readCabalFile ) import Cabal2Nix.Generate ( cabal2nix ) import Cabal2Nix.Normalize ( normalize )+import Cabal2Nix.Package import Distribution.NixOS.Derivation.Cabal+import Distribution.NixOS.Fetch import Control.Exception ( bracket ) import Control.Monad ( when )-import Distribution.PackageDescription ( package, packageDescription )-import Distribution.PackageDescription.Parse ( parsePackageDescription, ParseResult(..) ) import Distribution.Text ( disp ) import System.Console.GetOpt ( OptDescr(..), ArgDescr(..), ArgOrder(..), usageInfo, getOpt ) import System.Environment ( getArgs )@@ -24,6 +23,7 @@ , optHaddock :: Bool , optDoCheck :: Bool , optJailbreak :: Bool+ , optRevision :: String , optHyperlinkSource :: Bool , optHackageDb :: Maybe FilePath }@@ -39,6 +39,7 @@ , optHaddock = True , optDoCheck = True , optJailbreak = False+ , optRevision = "" , optHyperlinkSource = True , optHackageDb = Nothing }@@ -53,19 +54,25 @@ , Option "" ["jailbreak"] (NoArg (\o -> o { optJailbreak = True })) "don't honor version restrictions on build inputs" , Option "" ["no-haddock"] (NoArg (\o -> o { optHaddock = False })) "don't run Haddock when building this package" , Option "" ["no-check"] (NoArg (\o -> o { optDoCheck = False })) "don't run regression test suites of this package"+ , Option "" ["rev"] (ReqArg (\x o -> o { optRevision = x }) "REVISION") "revision, only used when fetching from VCS" , Option "" ["no-hyperlink-source"] (NoArg (\o -> o { optHyperlinkSource = False })) "don't add pretty-printed source code to the documentation" ] usage :: String-usage = usageInfo "Usage: cabal2nix [options] url-to-cabal-file" options ++ unlines+usage = usageInfo "Usage: cabal2nix [options] URI" options ++ unlines [ "" , "Recognized URI schemes:" , ""- , " cabal://pkgname-pkgversion download the specified package from Hackage"- , " cabal://pkgname download latest version of the specified package from Hackage"- , " http://host/path fetch the Cabal file via HTTP"- , " file:///local/path load the Cabal file from the local disk"- , " /local/path abbreviated version of file URI"+ , " cabal://pkgname-pkgversion download the specified package from Hackage"+ , " cabal://pkgname download latest version of the specified package from Hackage"+ , " file:///local/path load the Cabal file from the local disk"+ , " /local/path abbreviated version of file URI"+ , " <git/svn/bzr/hg URL> download the source from the specified repository"+ , ""+ , "If the URI refers to a cabal file, information for building the package will be retrieved from that file, but "+ , "hackage will be used as a source for the derivation."+ , "Otherwise, the supplied URI will be used to as the source for the derivation and the information is taken"+ , "from the cabal file at the root of the downloaded source." ] cmdlineError :: String -> IO a@@ -82,19 +89,9 @@ when (optPrintHelp cfg) (putStr usage >> exitSuccess) when (length args /= 1) (cmdlineError "*** exactly one url-to-cabal-file must be specified\n") - cabal' <- fmap parsePackageDescription (readCabalFile (optHackageDb cfg) (head args))- cabal <- case cabal' of- ParseOk _ a -> return a- ParseFailed err -> do- hPutStrLn stderr ("*** cannot parse cabal file: " ++ show err)- exitFailure-- let packageId = package (packageDescription cabal)- sha <- case optSha256 cfg of- Just hash -> return hash- Nothing -> hashPackage packageId+ pkg <- getPackage (optHackageDb cfg) $ Source (head args) (optRevision cfg) (optSha256 cfg) - let deriv = (cabal2nix cabal) { sha256 = sha, runHaddock = optHaddock cfg, jailbreak = optJailbreak cfg }+ let deriv = (cabal2nix $ cabal pkg) { src = source pkg, runHaddock = optHaddock cfg, jailbreak = optJailbreak cfg } deriv' = deriv { metaSection = (metaSection deriv) { maintainers = optMaintainer cfg , platforms = optPlatform cfg
src/hackage4nix.hs view
@@ -1,7 +1,7 @@ module Main ( main ) where -import Cabal2Nix.Normalize import Cabal2Nix.Generate+import Cabal2Nix.Normalize import Control.Exception ( bracket ) import Control.Monad.RWS import Data.List@@ -111,6 +111,7 @@ nix' <- io (readFile file) >>= parseNixFile file flip (maybe (return ())) nix' $ \nix -> modify (Set.insert nix)+ -- Re-generate all Haskell packages in-place (unless -- 'regenerateDerivation' decided that this particular package -- shouldn't be touched.@@ -124,10 +125,11 @@ maints = maintainers (metaSection deriv) plats = platforms (metaSection deriv) hplats = if isLatest deriv then hydraPlatforms (metaSection deriv) else ["self.stdenv.lib.platforms.none"]+ when regenerate $ do msgDebug ("re-generate " ++ path) pkg <- getCabalPackage (pname deriv) (version deriv)- let deriv' = (cabal2nix pkg) { sha256 = sha256 deriv+ let deriv' = (cabal2nix pkg) { src = src deriv , runHaddock = runHaddock deriv , doCheck = doCheck deriv , jailbreak = jailbreak deriv@@ -143,6 +145,7 @@ } } io $ writeFile path (show (disp (normalize deriv'')))+ -- Discover available updates and print the appropriate cabal2nix -- command line to the console for the user to execute at his/her -- discretion.
test/doc-test.hs view
@@ -20,13 +20,14 @@ , "src/Cabal2Nix/CorePackages.hs" , "src/Cabal2Nix/Flags.hs" , "src/Cabal2Nix/Generate.hs"- , "src/Cabal2Nix/Hackage.hs"+ , "src/Cabal2Nix/Package.hs" , "src/Cabal2Nix/Normalize.hs" , "src/Cabal2Nix/Name.hs" , "src/Cabal2Nix/PostProcess.hs" , "src/Distribution/NixOS/Derivation/License.hs" , "src/Distribution/NixOS/Derivation/Cabal.hs" , "src/Distribution/NixOS/Derivation/Meta.hs"+ , "src/Distribution/NixOS/Fetch.hs" , "src/Distribution/NixOS/PrettyPrinting.hs" , "src/Distribution/NixOS/Regex.hs" ]