1 {-# LANGUAGE RecordWildCards #-}
3 module Distribution
.Client
.Init
.Utils
7 , retrieveModuleImports
8 , retrieveModuleExtensions
10 , retrieveDependencies
25 import Distribution
.Client
.Compat
.Prelude
hiding (Parsec
, empty, many
, putStrLn, readFile)
26 import Distribution
.Utils
.Generic
(isInfixOf
, safeLast
)
27 import qualified Prelude
()
29 import Control
.Monad
(forM
)
31 import qualified Data
.List
.NonEmpty
as NE
32 import qualified Data
.Map
as M
33 import Language
.Haskell
.Extension
(Extension
(..))
34 import System
.FilePath
36 import Distribution
.CabalSpecVersion
(CabalSpecVersion
(..))
37 import Distribution
.Client
.Init
.Defaults
38 import Distribution
.Client
.Init
.Types
39 import Distribution
.Client
.Utils
(pvpize
)
40 import qualified Distribution
.Compat
.NonEmptySet
as NES
41 import Distribution
.InstalledPackageInfo
(InstalledPackageInfo
, exposed
)
42 import Distribution
.ModuleName
(ModuleName
)
43 import qualified Distribution
.Package
as P
44 import Distribution
.Simple
.PackageIndex
(InstalledPackageIndex
, moduleNameIndex
)
45 import Distribution
.Simple
.Setup
(Flag
(..))
46 import Distribution
.Types
.Dependency
(Dependency
, mkDependency
)
47 import Distribution
.Types
.LibraryName
48 import Distribution
.Types
.PackageName
49 import Distribution
.Utils
.String (trim
)
50 import Distribution
.Verbosity
(silent
)
51 import Distribution
.Version
53 -- | Data type of source files found in the working directory
54 data SourceFileEntry
= SourceFileEntry
55 { relativeSourcePath
:: FilePath
56 , moduleName
:: ModuleName
57 , fileExtension
:: String
58 , imports
:: [ModuleName
]
59 , extensions
:: [Extension
]
63 -- Unfortunately we cannot use the version exported by Distribution.Simple.Program
64 knownSuffixHandlers
:: CabalSpecVersion
-> String -> String
65 knownSuffixHandlers v s
66 | v
< CabalSpecV3_0
= case s
of
75 |
otherwise = case s
of
76 ".gc" -> "greencard:greencard"
78 ".hsc" -> "hsc2hs:hsc2hs"
81 ".ly" -> "happy:happy"
85 -- | Check if a given file has main file characteristics
86 isMain
:: String -> Bool
88 (isInfixOf
"Main" f || isInfixOf
"main" f
)
90 ||
isSuffixOf ".lhs" f
92 -- | Check if a given file has a Haskell extension
93 isHaskell
:: String -> Bool
94 isHaskell f
= isSuffixOf ".hs" f ||
isSuffixOf ".lhs" f
96 isBuildTool
:: CabalSpecVersion
-> String -> Bool
97 isBuildTool v
= not . null . knownSuffixHandlers v
. takeExtension
99 retrieveBuildTools
:: Interactive m
=> CabalSpecVersion
-> FilePath -> m
[Dependency
]
100 retrieveBuildTools v fp
= do
101 exists
<- doesDirectoryExist fp
104 files
<- fmap takeExtension
<$> listFilesRecursive fp
107 [ mkStringyDep
(knownSuffixHandlers v f
)
115 retrieveSourceFiles
:: Interactive m
=> FilePath -> m
[SourceFileEntry
]
116 retrieveSourceFiles fp
= do
117 exists
<- doesDirectoryExist fp
120 files
<- filter isHaskell
<$> listFilesRecursive fp
122 entries
<- forM files
$ \f -> do
123 exists
' <- doesFileExist f
126 maybeModuleName
<- retrieveModuleName f
127 case maybeModuleName
of
128 Nothing
-> return Nothing
129 Just moduleName
-> do
130 let fileExtension
= takeExtension f
131 relativeSourcePath
<- makeRelative f
<$> getCurrentDirectory
132 imports
<- retrieveModuleImports f
133 extensions
<- retrieveModuleExtensions f
135 return . Just
$ SourceFileEntry
{..}
138 return . catMaybes $ entries
141 -- | Given a module, retrieve its name
142 retrieveModuleName
:: Interactive m
=> FilePath -> m
(Maybe ModuleName
)
143 retrieveModuleName m
= do
144 rawModule
<- trim
. grabModuleName
<$> readFile m
146 if isInfixOf rawModule
(dirToModuleName m
)
147 then return $ Just
$ fromString rawModule
150 "Warning: found module that doesn't match directory structure: "
154 dirToModuleName
= map (\x
-> if x
== '/' || x
== '\\' then '.' else x
)
156 stop c
= (c
/= '\n') && (c
/= ' ')
158 grabModuleName
[] = []
159 grabModuleName
('-' : '-' : xs
) = grabModuleName
$ dropWhile' (/= '\n') xs
160 grabModuleName
('m
' : 'o
' : 'd
' : 'u
' : 'l
' : 'e
' : ' ' : xs
) = takeWhile' stop xs
161 grabModuleName
(_
: xs
) = grabModuleName xs
163 -- | Given a module, retrieve all of its imports
164 retrieveModuleImports
:: Interactive m
=> FilePath -> m
[ModuleName
]
165 retrieveModuleImports m
= do
166 map (fromString
. trim
) . grabModuleImports
<$> readFile m
168 stop c
= (c
/= '\n') && (c
/= ' ') && (c
/= '(')
170 grabModuleImports
[] = []
171 grabModuleImports
('-' : '-' : xs
) = grabModuleImports
$ dropWhile' (/= '\n') xs
172 grabModuleImports
('i
' : 'm
' : 'p
' : 'o
' : 'r
' : 't
' : ' ' : xs
) = case trim xs
of -- in case someone uses a weird formatting
173 ('q
' : 'u
' : 'a
' : 'l
' : 'i
' : 'f
' : 'i
' : 'e
' : 'd
' : ' ' : ys
) -> takeWhile' stop ys
: grabModuleImports
(dropWhile' stop ys
)
174 _
-> takeWhile' stop xs
: grabModuleImports
(dropWhile' stop xs
)
175 grabModuleImports
(_
: xs
) = grabModuleImports xs
177 -- | Given a module, retrieve all of its language pragmas
178 retrieveModuleExtensions
:: Interactive m
=> FilePath -> m
[Extension
]
179 retrieveModuleExtensions m
= do
180 catMaybes <$> map (simpleParsec
. trim
) . grabModuleExtensions
<$> readFile m
182 stop c
= (c
/= '\n') && (c
/= ' ') && (c
/= ',') && (c
/= '#')
184 grabModuleExtensions
[] = []
185 grabModuleExtensions
('-' : '-' : xs
) = grabModuleExtensions
$ dropWhile' (/= '\n') xs
186 grabModuleExtensions
('L
' : 'A
' : 'N
' : 'G
' : 'U
' : 'A
' : 'G
' : 'E
' : xs
) = takeWhile' stop xs
: grabModuleExtensions
' (dropWhile' stop xs
)
187 grabModuleExtensions
(_
: xs
) = grabModuleExtensions xs
189 grabModuleExtensions
' [] = []
190 grabModuleExtensions
' ('#' : xs
) = grabModuleExtensions xs
191 grabModuleExtensions
' (',' : xs
) = takeWhile' stop xs
: grabModuleExtensions
' (dropWhile' stop xs
)
192 grabModuleExtensions
' (_
: xs
) = grabModuleExtensions xs
194 takeWhile' :: (Char -> Bool) -> String -> String
195 takeWhile' p
= takeWhile p
. trim
197 dropWhile' :: (Char -> Bool) -> String -> String
198 dropWhile' p
= dropWhile p
. trim
200 -- | Check whether a potential source file is located in one of the
201 -- source directories.
202 isSourceFile
:: Maybe [FilePath] -> SourceFileEntry
-> Bool
203 isSourceFile Nothing sf
= isSourceFile
(Just
["."]) sf
204 isSourceFile
(Just srcDirs
) sf
= any (equalFilePath
(relativeSourcePath sf
)) srcDirs
206 retrieveDependencies
:: Interactive m
=> Verbosity
-> InitFlags
-> [(ModuleName
, ModuleName
)] -> InstalledPackageIndex
-> m
[P
.Dependency
]
207 retrieveDependencies v flags mods
' pkgIx
= do
210 modMap
:: M
.Map ModuleName
[InstalledPackageInfo
]
211 modMap
= M
.map (filter exposed
) $ moduleNameIndex pkgIx
213 modDeps
:: [(ModuleName
, ModuleName
, Maybe [InstalledPackageInfo
])]
214 modDeps
= map (\(mn
, ds
) -> (mn
, ds
, M
.lookup ds modMap
)) mods
215 -- modDeps = map (id &&& flip M.lookup modMap) mods
217 message v Info
"Guessing dependencies..."
218 nub . catMaybes <$> traverse
(chooseDep v flags
) modDeps
220 -- Given a module and a list of installed packages providing it,
221 -- choose a dependency (i.e. package + version range) to use for that
227 -> (ModuleName
, ModuleName
, Maybe [InstalledPackageInfo
])
228 -> m
(Maybe P
.Dependency
)
229 chooseDep v flags
(importer
, m
, mipi
) = case mipi
of
230 -- We found some packages: group them by name.
232 case NE
.groupBy (\x y
-> P
.pkgName x
== P
.pkgName y
) $ map P
.packageId ps
of
233 -- if there's only one group, i.e. multiple versions of a single package,
234 -- we make it into a dependency, choosing the latest-ish version.
236 -- Given a list of available versions of the same package, pick a dependency.
237 [grp
] -> fmap Just
$ case grp
of
238 -- If only one version, easy. We change e.g. 0.4.2 into 0.4.*
243 (pvpize desugar
. P
.pkgVersion
$ pid
)
244 P
.mainLibSet
-- TODO sublibraries
246 -- Otherwise, choose the latest version and issue a warning.
248 message v Warning
("multiple versions of " ++ prettyShow
(P
.pkgName
. NE
.head $ pids
) ++ " provide " ++ prettyShow m
++ ", choosing the latest.")
251 (P
.pkgName
. NE
.head $ pids
)
252 (pvpize desugar
. maximum . fmap P
.pkgVersion
$ pids
)
253 P
.mainLibSet
-- TODO take into account sublibraries
255 -- if multiple packages are found, we refuse to choose between
256 -- different packages and make the user do it
258 message v Warning
("multiple packages found providing " ++ prettyShow m
++ ": " ++ intercalate
", " (fmap (prettyShow
. P
.pkgName
. NE
.head) grps
))
259 message v Warning
"You will need to pick one and manually add it to the build-depends field."
262 message v Warning
("no package found providing " ++ prettyShow m
++ " in " ++ prettyShow importer
++ ".")
265 -- desugar if cabal version lower than 2.0
266 desugar
= case cabalVersion flags
of
267 Flag x
-> x
< CabalSpecV2_0
268 NoFlag
-> defaultCabalVersion
< CabalSpecV2_0
270 filePathToPkgName
:: Interactive m
=> FilePath -> m P
.PackageName
272 fmap (mkPackageName
. repair
. fromMaybe "" . safeLast
. splitDirectories
)
273 . canonicalizePathNoThrow
275 -- Treat each span of non-alphanumeric characters as a hyphen. Each
276 -- hyphenated component of a package name must contain at least one
277 -- alphabetic character. An arbitrary character ('x') will be prepended if
278 -- this is not the case for the first component, and subsequent components
279 -- will simply be run together. For example, "1+2_foo-3" will become
281 repair
= repair
' ('x
' :) id
282 repair
' invalid valid x
= case dropWhile (not . isAlphaNum) x
of
283 "" -> repairComponent
""
285 let (c
, r
) = first repairComponent
$ span
isAlphaNum x
'
289 |
all isDigit c
= invalid c
290 |
otherwise = valid c
291 repairRest
= repair
' id ('-' :)
293 currentDirPkgName
:: Interactive m
=> m P
.PackageName
294 currentDirPkgName
= filePathToPkgName
=<< getCurrentDirectory
296 mkPackageNameDep
:: PackageName
-> Dependency
297 mkPackageNameDep pkg
= mkDependency pkg anyVersion
(NES
.singleton LMainLibName
)
299 -- when cabal-version < 1.18, extra-doc-files is not supported
300 -- so whatever the user wants as doc files should be dumped into
303 fixupDocFiles
:: Interactive m
=> Verbosity
-> PkgDescription
-> m PkgDescription
304 fixupDocFiles v pkgDesc
305 | _pkgCabalVersion pkgDesc
< CabalSpecV1_18
= do
308 [ "Cabal spec versions < 1.18 do not support extra-doc-files. "
309 , "Doc files will be treated as extra-src-files."
314 { _pkgExtraSrcFiles
=
315 _pkgExtraSrcFiles pkgDesc
316 <> fromMaybe mempty
(_pkgExtraDocFiles pkgDesc
)
317 , _pkgExtraDocFiles
= Nothing
319 |
otherwise = return pkgDesc
321 mkStringyDep
:: String -> Dependency
322 mkStringyDep
= mkPackageNameDep
. mkPackageName
324 getBaseDep
:: Interactive m
=> InstalledPackageIndex
-> InitFlags
-> m
[Dependency
]
325 getBaseDep pkgIx flags
=
329 [(fromString
"Prelude", fromString
"Prelude")]
332 -- Add package name as dependency of test suite
334 addLibDepToTest
:: PackageName
-> Maybe TestTarget
-> Maybe TestTarget
335 addLibDepToTest _ Nothing
= Nothing
336 addLibDepToTest n
(Just t
) =
339 { _testDependencies
= _testDependencies t
++ [mkPackageNameDep n
]
342 -- Add package name as dependency of executable
344 addLibDepToExe
:: PackageName
-> ExeTarget
-> ExeTarget
345 addLibDepToExe n exe
=
347 { _exeDependencies
= _exeDependencies exe
++ [mkPackageNameDep n
]