@@ -0,0 +1,174 @@
1+{-# LANGUAGE RecordWildCards #-}
2+{-# LANGUAGE OverloadedStrings #-}
3+
4+module Cabal2nix
5+ ( main, cabal2nix
6+ )
7+ where
8+
9+import Control.Exception ( bracket )
10+import Control.Lens
11+import Data.Maybe ( fromMaybe )
12+import Data.Monoid ( (<>) )
13+import qualified Data.Set as Set
14+import Data.String
15+import qualified Distribution.Compat.ReadP as P
16+import Distribution.Compiler
17+import Distribution.Nixpkgs.Fetch
18+import Distribution.Nixpkgs.Haskell
19+import Distribution.Nixpkgs.Haskell.FromCabal
20+import Distribution.Nixpkgs.Haskell.PackageSourceSpec
21+import Distribution.Nixpkgs.Meta
22+import Distribution.PackageDescription ( FlagName(..), FlagAssignment )
23+import Distribution.Simple.Utils ( lowercase )
24+import Distribution.System
25+import Distribution.Text
26+import Language.Nix
27+import Options.Applicative
28+import Paths_cabal2nix ( version )
29+import System.Environment ( getArgs )
30+import System.IO ( hFlush, stdout, stderr )
31+import qualified Text.PrettyPrint.ANSI.Leijen as P2 hiding ( (<$>), (<>) )
32+import Text.PrettyPrint.HughesPJClass ( Doc, Pretty(..), text, vcat, hcat, semi )
33+
34+data Options = Options
35+ { optSha256 :: Maybe String
36+ , optMaintainer :: [String]
37+--, optPlatform :: [String] -- TODO: fix command line handling of platforms
38+ , optHaddock :: Bool
39+ , optDoCheck :: Bool
40+ , optJailbreak :: Bool
41+ , optRevision :: Maybe String
42+ , optHyperlinkSource :: Bool
43+ , optEnableLibraryProfiling :: Bool
44+ , optEnableExecutableProfiling :: Bool
45+ , optEnableProfiling :: Maybe Bool
46+ , optExtraArgs :: [String]
47+ , optHackageDb :: Maybe FilePath
48+ , optNixShellOutput :: Bool
49+ , optFlags :: [String]
50+ , optCompiler :: CompilerId
51+ , optSystem :: Platform
52+ , optSubpath :: Maybe FilePath
53+ , optUrl :: String
54+ }
55+ deriving (Show)
56+
57+options :: Parser Options
58+options = Options
59+ <$> optional (strOption $ long "sha256" <> metavar "HASH" <> help "sha256 hash of source tarball")
60+ <*> many (strOption $ long "maintainer" <> metavar "MAINTAINER" <> help "maintainer of this package (may be specified multiple times)")
61+-- <*> many (strOption $ long "platform" <> metavar "PLATFORM" <> help "supported build platforms (may be specified multiple times)")
62+ <*> flag True False (long "no-haddock" <> help "don't run Haddock when building this package")
63+ <*> flag True False (long "no-check" <> help "don't run regression test suites of this package")
64+ <*> switch (long "jailbreak" <> help "disregard version restrictions on build inputs")
65+ <*> optional (strOption $ long "revision" <> help "revision to use when fetching from VCS")
66+ <*> flag True False (long "no-hyperlink-source" <> help "don't generate pretty-printed source code for the documentation")
67+ <*> switch (long "enable-library-profiling" <> help "enable library profiling in the generated build")
68+ <*> switch (long "enable-executable-profiling" <> help "enable executable profiling in the generated build")
69+ <*> optional (switch (long "enable-profiling" <> help "enable both library and executable profiling in the generated build"))
70+ <*> many (strOption $ long "extra-arguments" <> help "extra parameters required for the function body")
71+ <*> optional (strOption $ long "hackage-db" <> metavar "PATH" <> help "path to the local hackage db in tar format")
72+ <*> switch (long "shell" <> help "generate output suitable for nix-shell")
73+ <*> many (strOption $ short 'f' <> long "flag" <> help "Cabal flag (may be specified multiple times)")
74+ <*> option (readP parse) (long "compiler" <> help "compiler to use when evaluating the Cabal file" <> value buildCompilerId <> showDefaultWith display)
75+ <*> option (readP parsePlatform) (long "system" <> help "target system to use when evaluating the Cabal file" <> value buildPlatform <> showDefaultWith display)
76+ <*> optional (strOption $ long "subpath" <> metavar "PATH" <> help "Path to Cabal file's directory relative to the URI (default is root directory)")
77+ <*> strArgument (metavar "URI")
78+
79+readP :: P.ReadP a a -> ReadM a
80+readP p = eitherReader $ \s -> case [ r' | (r',"") <- P.readP_to_S p s ] of
81+ (r:_) -> Right r
82+ _ -> Left ("invalid value " ++ show s)
83+
84+parsePlatform :: P.ReadP r Platform
85+parsePlatform = do arch <- P.choice [P.string "i686" >> return I386, P.string "x86_64" >> return X86_64]
86+ _ <- P.char '-'
87+ os <- P.choice [P.string "linux" >> return Linux, P.string "darwin" >> return OSX]
88+ return (Platform arch os)
89+
90+pinfo :: ParserInfo Options
91+pinfo = info
92+ ( helper
93+ <*> infoOption ("cabal2nix " ++ display version) (long "version" <> help "Show version number")
94+ <*> options
95+ )
96+ ( fullDesc
97+ <> header "cabal2nix converts Cabal files into build instructions for Nix."
98+ <> progDescDoc (Just (P2.vcat
99+ [ P2.text ""
100+ , P2.text "Recognized URI schemes:"
101+ , P2.text ""
102+ , P2.text " cabal://pkgname-pkgversion download the specified package from Hackage"
103+ , P2.text " cabal://pkgname download latest version of this package from Hackage"
104+ , P2.text " file:///local/path load the Cabal file from the local disk"
105+ , P2.text " /local/path abbreviated version of file URI"
106+ , P2.text " <git/svn/bzr/hg URL> download the source from the specified repository"
107+ , P2.text ""
108+ , P2.fillSep (map P2.text (words ( "If the URI refers to a cabal file, information for building the package "
109+ ++ "will be retrieved from that file, but hackage will be used as a source "
110+ ++ "for the derivation. Otherwise, the supplied URI will be used to as the "
111+ ++ "souce for the derivation and the information is taken from the cabal file "
112+ ++ "at the root of the downloaded source."
113+ )))
114+ ]))
115+ )
116+
117+main :: IO ()
118+main = bracket (return ()) (\() -> hFlush stdout >> hFlush stderr) $ \() ->
119+ cabal2nix =<< getArgs
120+
121+cabal2nix :: [String] -> IO ()
122+cabal2nix args = do
123+ Options {..} <- handleParseResult $ execParserPure defaultPrefs pinfo args
124+
125+ pkg <- getPackage optHackageDb $ Source optUrl (fromMaybe "" optRevision) (maybe UnknownHash Guess optSha256) (fromMaybe "" optSubpath)
126+
127+ let
128+ deriv :: Derivation
129+ deriv = fromGenericPackageDescription (const True)
130+ (\i -> Just (binding # (i, path # [i])))
131+ optSystem
132+ (unknownCompilerInfo optCompiler NoAbiTag)
133+ (readFlagList optFlags)
134+ []
135+ (pkgCabal pkg)
136+ & src .~ pkgSource pkg
137+ & subpath .~ (fromMaybe "." optSubpath)
138+ & runHaddock .~ optHaddock
139+ & jailbreak .~ optJailbreak
140+ & hyperlinkSource .~ optHyperlinkSource
141+ & enableLibraryProfiling .~ (fromMaybe False optEnableProfiling || optEnableLibraryProfiling)
142+ & enableExecutableProfiling .~ (fromMaybe False optEnableProfiling || optEnableExecutableProfiling)
143+ & metaSection.maintainers .~ Set.fromList (map (review ident) optMaintainer)
144+-- & metaSection.platforms .~ Set.fromList optPlatform
145+ & doCheck &&~ optDoCheck
146+ & extraFunctionArgs %~ Set.union (Set.fromList ("inherit stdenv":map (fromString . ("inherit " ++)) optExtraArgs))
147+
148+ shell :: Doc
149+ shell = vcat
150+ [ text "{ nixpkgs ? import <nixpkgs> {}, compiler ? \"default\" }:"
151+ , text ""
152+ , text "let"
153+ , text ""
154+ , text " inherit (nixpkgs) pkgs;"
155+ , text ""
156+ , hcat [ text " f = ", pPrint deriv, semi ]
157+ , text ""
158+ , text " haskellPackages = if compiler == \"default\""
159+ , text " then pkgs.haskellPackages"
160+ , text " else pkgs.haskell.packages.${compiler};"
161+ , text ""
162+ , text " drv = haskellPackages.callPackage f {};"
163+ , text ""
164+ , text "in"
165+ , text ""
166+ , text " if pkgs.lib.inNixShell then drv.env else drv"
167+ ]
168+
169+ print (if optNixShellOutput then shell else pPrint deriv)
170+
171+readFlagList :: [String] -> FlagAssignment
172+readFlagList = map tagWithValue
173+ where tagWithValue ('-':fname) = (FlagName (lowercase fname), False)
174+ tagWithValue fname = (FlagName (lowercase fname), True)