2 {-# LANGUAGE DeriveFoldable #-}
3 {-# LANGUAGE DeriveFunctor #-}
4 {-# LANGUAGE DeriveGeneric #-}
5 {-# LANGUAGE DeriveTraversable #-}
6 {-# LANGUAGE ScopedTypeVariables #-}
8 -----------------------------------------------------------------------------
10 -----------------------------------------------------------------------------
13 -- Module : Distribution.Client.Targets
14 -- Copyright : (c) Duncan Coutts 2011
17 -- Maintainer : duncan@community.haskell.org
19 -- Handling for user-specified targets
20 module Distribution
.Client
.Targets
25 -- * Resolving user targets to package specifiers
28 -- ** Detailed interface
29 , UserTargetProblem
(..)
31 , reportUserTargetProblems
36 , PackageTargetProblem
(..)
37 , reportPackageTargetProblems
38 , disambiguatePackageTargets
39 , disambiguatePackageName
43 , UserConstraintScope
(..)
45 , userConstraintPackageName
47 , userToPackageConstraint
50 import Distribution
.Client
.Compat
.Prelude
53 import Distribution
.Client
.Types
54 ( PackageLocation
(..)
55 , PackageSpecifier
(..)
57 , UnresolvedSourcePackage
59 import Distribution
.Package
67 import Distribution
.Solver
.Types
.OptionalStanza
68 import Distribution
.Solver
.Types
.PackageConstraint
69 import Distribution
.Solver
.Types
.PackageIndex
(PackageIndex
)
70 import qualified Distribution
.Solver
.Types
.PackageIndex
as PackageIndex
71 import Distribution
.Solver
.Types
.PackagePath
72 import Distribution
.Solver
.Types
.SourcePackage
74 import qualified Codec
.Archive
.Tar
as Tar
75 import qualified Codec
.Archive
.Tar
.Entry
as Tar
76 import Distribution
.Client
.FetchUtils
77 import Distribution
.Client
.GlobalFlags
80 import qualified Distribution
.Client
.Tar
as Tar
81 import Distribution
.Client
.Utils
(tryReadGenericPackageDesc
)
82 import Distribution
.Types
.PackageVersionConstraint
83 ( PackageVersionConstraint
(..)
86 import Distribution
.PackageDescription
87 ( GenericPackageDescription
89 import Distribution
.Simple
.Utils
93 import Distribution
.Types
.Flag
94 ( parsecFlagAssignmentNonEmpty
96 import Distribution
.Version
100 import Distribution
.PackageDescription
.Parsec
101 ( parseGenericPackageDescriptionMaybe
104 import qualified Data
.ByteString
.Lazy
as BS
105 import qualified Data
.Map
as Map
106 import Distribution
.Client
.Errors
107 import qualified Distribution
.Client
.GZipUtils
as GZipUtils
108 import qualified Distribution
.Compat
.CharParsing
as P
109 import Distribution
.Utils
.Path
(makeSymbolicPath
)
115 import System
.Directory
119 import System
.FilePath
126 -- ------------------------------------------------------------
130 -- ------------------------------------------------------------
132 -- | Various ways that a user may specify a package or package collection.
134 = -- | A partially specified package, identified by name and possibly with
135 -- an exact version or a version constraint.
137 -- > cabal install foo
138 -- > cabal install foo-1.0
139 -- > cabal install 'foo < 2'
140 UserTargetNamed PackageVersionConstraint
141 |
-- | A specific package that is unpacked in a local directory, often the
142 -- current directory.
145 -- > cabal install ../lib/other
147 -- * Note: in future, if multiple @.cabal@ files are allowed in a single
148 -- directory then this will refer to the collection of packages.
149 UserTargetLocalDir
FilePath
150 |
-- | A specific local unpacked package, identified by its @.cabal@ file.
152 -- > cabal install foo.cabal
153 -- > cabal install ../lib/other/bar.cabal
154 UserTargetLocalCabalFile
FilePath
155 |
-- | A specific package that is available as a local tarball file
157 -- > cabal install dist/foo-1.0.tar.gz
158 -- > cabal install ../build/baz-1.0.tar.gz
159 UserTargetLocalTarball
FilePath
160 |
-- | A specific package that is available as a remote tarball file
162 -- > cabal install http://code.haskell.org/~user/foo/foo-0.9.tar.gz
163 UserTargetRemoteTarball URI
166 -- ------------------------------------------------------------
168 -- * Parsing and checking user targets
170 -- ------------------------------------------------------------
172 readUserTargets
:: Verbosity
-> [String] -> IO [UserTarget
]
173 readUserTargets verbosity targetStrs
= do
174 (problems
, targets
) <-
177 (traverse readUserTarget targetStrs
)
178 reportUserTargetProblems verbosity problems
181 data UserTargetProblem
182 = UserTargetUnexpectedFile
String
183 | UserTargetNonexistantFile
String
184 | UserTargetUnexpectedUriScheme
String
185 | UserTargetUnrecognisedUri
String
186 | UserTargetUnrecognised
String
189 readUserTarget
:: String -> IO (Either UserTargetProblem UserTarget
)
190 readUserTarget targetstr
=
191 case eitherParsec targetstr
of
192 Right dep
-> return (Right
(UserTargetNamed dep
))
194 fileTarget
<- testFileTargets targetstr
196 Just target
-> return target
198 case testUriTargets targetstr
of
199 Just target
-> return target
200 Nothing
-> return (Left
(UserTargetUnrecognised targetstr
))
202 testFileTargets
:: FilePath -> IO (Maybe (Either UserTargetProblem UserTarget
))
203 testFileTargets filename
= do
204 isDir
<- doesDirectoryExist filename
205 isFile
<- doesFileExist filename
206 parentDirExists
<- case takeDirectory filename
of
208 dir
-> doesDirectoryExist dir
209 let result
:: Maybe (Either UserTargetProblem UserTarget
)
212 Just
(Right
(UserTargetLocalDir filename
))
213 | isFile
&& extensionIsTarGz filename
=
214 Just
(Right
(UserTargetLocalTarball filename
))
215 | isFile
&& takeExtension filename
== ".cabal" =
216 Just
(Right
(UserTargetLocalCabalFile filename
))
218 Just
(Left
(UserTargetUnexpectedFile filename
))
220 Just
(Left
(UserTargetNonexistantFile filename
))
225 testUriTargets
:: String -> Maybe (Either UserTargetProblem UserTarget
)
227 case parseAbsoluteURI str
of
231 , uriAuthority
= Just URIAuth
{uriRegName
= host
}
233 | scheme
/= "http:" && scheme
/= "https:" ->
234 Just
(Left
(UserTargetUnexpectedUriScheme targetstr
))
236 Just
(Left
(UserTargetUnrecognisedUri targetstr
))
238 Just
(Right
(UserTargetRemoteTarball uri
))
241 extensionIsTarGz
:: FilePath -> Bool
243 takeExtension f
== ".gz"
244 && takeExtension
(dropExtension f
) == ".tar"
246 reportUserTargetProblems
:: Verbosity
-> [UserTargetProblem
] -> IO ()
247 reportUserTargetProblems verbosity problems
= do
248 case [target | UserTargetUnrecognised target
<- problems
] of
251 dieWithException verbosity
$ ReportUserTargetProblems target
252 case [target | UserTargetNonexistantFile target
<- problems
] of
255 dieWithException verbosity
$ ReportUserTargerNonexistantFile target
257 case [target | UserTargetUnexpectedFile target
<- problems
] of
260 dieWithException verbosity
$ ReportUserTargetUnexpectedFile target
262 case [target | UserTargetUnexpectedUriScheme target
<- problems
] of
265 dieWithException verbosity
$ ReportUserTargetUnexpectedUriScheme target
267 case [target | UserTargetUnrecognisedUri target
<- problems
] of
270 dieWithException verbosity
$ ReportUserTargetUnrecognisedUri target
272 -- ------------------------------------------------------------
274 -- * Resolving user targets to package specifiers
276 -- ------------------------------------------------------------
278 -- | Given a bunch of user-specified targets, try to resolve what it is they
279 -- refer to. They can either be specific packages (local dirs, tarballs etc)
280 -- or they can be named packages (with or without version info).
287 -> IO [PackageSpecifier UnresolvedSourcePackage
]
288 resolveUserTargets verbosity repoCtxt available userTargets
= do
289 -- given the user targets, get a list of fully or partially resolved
290 -- package references
292 traverse
(readPackageTarget verbosity
)
293 =<< traverse
(fetchPackageTarget verbosity repoCtxt
) . concat
294 =<< traverse
(expandUserTarget verbosity
) userTargets
296 -- users are allowed to give package names case-insensitively, so we must
297 -- disambiguate named package references
298 let (problems
, packageSpecifiers
) :: ([PackageTargetProblem
], [PackageSpecifier UnresolvedSourcePackage
]) =
299 disambiguatePackageTargets available availableExtra packageTargets
301 -- use any extra specific available packages to help us disambiguate
302 availableExtra
:: [PackageName
]
305 | PackageTargetLocation pkg
<- packageTargets
308 reportPackageTargetProblems verbosity problems
310 return packageSpecifiers
312 -- ------------------------------------------------------------
316 -- ------------------------------------------------------------
318 -- | An intermediate between a 'UserTarget' and a resolved 'PackageSpecifier'.
319 -- Unlike a 'UserTarget', a 'PackageTarget' refers only to a single package.
320 data PackageTarget pkg
321 = PackageTargetNamed PackageName
[PackageProperty
] UserTarget
322 |
-- | A package identified by name, but case insensitively, so it needs
323 -- to be resolved to the right case-sensitive name.
324 PackageTargetNamedFuzzy PackageName
[PackageProperty
] UserTarget
325 | PackageTargetLocation pkg
326 deriving (Show, Functor
, Foldable
, Traversable
)
328 -- ------------------------------------------------------------
330 -- * Converting user targets to package targets
332 -- ------------------------------------------------------------
334 -- | Given a user-specified target, expand it to a bunch of package targets
335 -- (each of which refers to only one package).
339 -> IO [PackageTarget
(PackageLocation
())]
340 expandUserTarget verbosity userTarget
= case userTarget
of
341 UserTargetNamed
(PackageVersionConstraint name vrange
) ->
343 [ PackagePropertyVersion vrange
344 |
not (isAnyVersion vrange
)
346 in return [PackageTargetNamedFuzzy name props userTarget
]
347 UserTargetLocalDir dir
->
348 return [PackageTargetLocation
(LocalUnpackedPackage dir
)]
349 UserTargetLocalCabalFile file
-> do
350 let dir
= takeDirectory file
351 _
<- tryReadGenericPackageDesc verbosity
(makeSymbolicPath dir
) (localPackageError dir
) -- just as a check
352 return [PackageTargetLocation
(LocalUnpackedPackage dir
)]
353 UserTargetLocalTarball tarballFile
->
354 return [PackageTargetLocation
(LocalTarballPackage tarballFile
)]
355 UserTargetRemoteTarball tarballURL
->
356 return [PackageTargetLocation
(RemoteTarballPackage tarballURL
())]
358 localPackageError
:: FilePath -> String
359 localPackageError dir
=
360 "Error reading local package.\nCouldn't find .cabal file in: " ++ dir
362 -- ------------------------------------------------------------
364 -- * Fetching and reading package targets
366 -- ------------------------------------------------------------
368 -- | Fetch any remote targets so that they can be read.
372 -> PackageTarget
(PackageLocation
())
373 -> IO (PackageTarget ResolvedPkgLoc
)
374 fetchPackageTarget verbosity repoCtxt
=
376 fetchPackage verbosity repoCtxt
. fmap (const Nothing
)
378 -- | Given a package target that has been fetched, read the .cabal file.
380 -- This only affects targets given by location, named targets are unaffected.
383 -> PackageTarget ResolvedPkgLoc
384 -> IO (PackageTarget UnresolvedSourcePackage
)
385 readPackageTarget verbosity
= traverse modifyLocation
387 modifyLocation
:: ResolvedPkgLoc
-> IO UnresolvedSourcePackage
388 modifyLocation location
= case location
of
389 LocalUnpackedPackage dir
-> do
390 pkg
<- tryReadGenericPackageDesc verbosity
(makeSymbolicPath dir
) (localPackageError dir
)
393 { srcpkgPackageId
= packageId pkg
394 , srcpkgDescription
= pkg
395 , srcpkgSource
= fmap Just location
396 , srcpkgDescrOverride
= Nothing
398 LocalTarballPackage tarballFile
->
399 readTarballPackageTarget location tarballFile tarballFile
400 RemoteTarballPackage tarballURL tarballFile
->
401 readTarballPackageTarget location tarballFile
(show tarballURL
)
402 RepoTarballPackage _repo _pkgid _
->
403 error "TODO: readPackageTarget RepoTarballPackage"
404 -- For repo tarballs this info should be obtained from the index.
406 RemoteSourceRepoPackage _srcRepo _
->
407 error "TODO: readPackageTarget RemoteSourceRepoPackage"
408 -- This can't happen, because it would have errored out already
409 -- in fetchPackage, via fetchPackageTarget before it gets to this
412 -- When that is corrected, this will also need to be fixed.
414 readTarballPackageTarget
:: ResolvedPkgLoc
-> FilePath -> FilePath -> IO UnresolvedSourcePackage
415 readTarballPackageTarget location tarballFile tarballOriginalLoc
= do
416 (filename
, content
) <-
417 extractTarballPackageCabalFile
420 case parsePackageDescription
' content
of
422 dieWithException verbosity
$ ReadTarballPackageTarget filename tarballFile
426 { srcpkgPackageId
= packageId pkg
427 , srcpkgDescription
= pkg
428 , srcpkgSource
= fmap Just location
429 , srcpkgDescrOverride
= Nothing
432 extractTarballPackageCabalFile
435 -> IO (FilePath, BS
.ByteString
)
436 extractTarballPackageCabalFile tarballFile tarballOriginalLoc
=
437 either (dieWithException verbosity
. ExtractTarballPackageErr
. formatErr
) return
440 . Tar
.filterEntries isCabalFile
442 . GZipUtils
.maybeDecompress
443 =<< BS
.readFile tarballFile
445 formatErr msg
= "Error reading " ++ tarballOriginalLoc
++ ": " ++ msg
448 :: Tar
.Entries Tar
.FormatError
449 -> Either (Tar
.FormatError
, Map Tar
.TarPath Tar
.Entry
) (Map Tar
.TarPath Tar
.Entry
)
452 (\m e
-> Map
.insert (Tar
.entryTarPath e
) e m
)
455 check
(Left e
) = Left
(show e
)
456 check
(Right m
) = case Map
.elems m
of
457 [] -> Left noCabalFile
458 [file
] -> case Tar
.entryContent file
of
459 Tar
.NormalFile content _
-> Right
(Tar
.entryPath file
, content
)
460 _
-> Left noCabalFile
461 _files
-> Left multipleCabalFiles
463 noCabalFile
= "No cabal file found"
464 multipleCabalFiles
= "Multiple cabal files found"
466 isCabalFile
:: Tar
.Entry
-> Bool
467 isCabalFile e
= case splitPath
(Tar
.entryPath e
) of
468 [_dir
, file
] -> takeExtension file
== ".cabal"
469 [".", _dir
, file
] -> takeExtension file
== ".cabal"
472 parsePackageDescription
' :: BS
.ByteString
-> Maybe GenericPackageDescription
473 parsePackageDescription
' bs
=
474 parseGenericPackageDescriptionMaybe
(BS
.toStrict bs
)
476 -- ------------------------------------------------------------
478 -- * Checking package targets
480 -- ------------------------------------------------------------
482 data PackageTargetProblem
483 = PackageNameUnknown PackageName UserTarget
484 | PackageNameAmbiguous PackageName
[PackageName
] UserTarget
487 -- | Users are allowed to give package names case-insensitively, so we must
488 -- disambiguate named package references.
489 disambiguatePackageTargets
493 -> [PackageTarget pkg
]
494 -> ( [PackageTargetProblem
]
495 , [PackageSpecifier pkg
]
497 disambiguatePackageTargets availablePkgIndex availableExtra targets
=
498 partitionEithers
(map disambiguatePackageTarget targets
)
500 disambiguatePackageTarget packageTarget
= case packageTarget
of
501 PackageTargetLocation pkg
-> Right
(SpecificSourcePackage pkg
)
502 PackageTargetNamed pkgname props userTarget
503 |
null (PackageIndex
.lookupPackageName availablePkgIndex pkgname
) ->
504 Left
(PackageNameUnknown pkgname userTarget
)
505 |
otherwise -> Right
(NamedPackage pkgname props
)
506 PackageTargetNamedFuzzy pkgname props userTarget
->
507 case disambiguatePackageName packageNameEnv pkgname
of
514 Ambiguous pkgnames
->
516 ( PackageNameAmbiguous
521 Unambiguous pkgname
' -> Right
(NamedPackage pkgname
' props
)
523 -- use any extra specific available packages to help us disambiguate
524 packageNameEnv
:: PackageNameEnv
527 (indexPackageNameEnv availablePkgIndex
)
528 (extraPackageNameEnv availableExtra
)
530 -- | Report problems to the user. That is, if there are any problems
531 -- then raise an exception.
532 reportPackageTargetProblems
534 -> [PackageTargetProblem
]
536 reportPackageTargetProblems verbosity problems
= do
537 case [pkg | PackageNameUnknown pkg _
<- problems
] of
540 dieWithException verbosity
$ ReportPackageTargetProblems pkgs
542 case [(pkg
, matches
) | PackageNameAmbiguous pkg matches _
<- problems
] of
545 dieWithException verbosity
$ PackageNameAmbiguousErr ambiguities
547 -- ------------------------------------------------------------
549 -- * Disambiguating package names
551 -- ------------------------------------------------------------
553 data MaybeAmbiguous a
= None | Unambiguous a | Ambiguous
[a
]
555 -- | Given a package name and a list of matching names, figure out
556 -- which one it might be referring to. If there is an exact
557 -- case-sensitive match then that's ok (i.e. returned via
558 -- 'Unambiguous'). If it matches just one package case-insensitively
559 -- or if it matches multiple packages case-insensitively, in that case
560 -- the result is 'Ambiguous'.
562 -- Note: Before cabal 2.2, when only a single package matched
563 -- case-insensitively it would be considered 'Unambiguous'.
564 disambiguatePackageName
567 -> MaybeAmbiguous PackageName
568 disambiguatePackageName
(PackageNameEnv pkgNameLookup
) name
=
569 case nub (pkgNameLookup name
) of
571 names
-> case find (name
==) names
of
572 Just name
' -> Unambiguous name
'
573 Nothing
-> Ambiguous names
575 newtype PackageNameEnv
= PackageNameEnv
(PackageName
-> [PackageName
])
577 instance Monoid PackageNameEnv
where
578 mempty
= PackageNameEnv
(const [])
581 instance Semigroup PackageNameEnv
where
582 PackageNameEnv lookupA
<> PackageNameEnv lookupB
=
583 PackageNameEnv
(\name
-> lookupA name
++ lookupB name
)
585 indexPackageNameEnv
:: PackageIndex pkg
-> PackageNameEnv
586 indexPackageNameEnv pkgIndex
= PackageNameEnv pkgNameLookup
588 pkgNameLookup pname
=
589 map fst (PackageIndex
.searchByName pkgIndex
$ unPackageName pname
)
591 extraPackageNameEnv
:: [PackageName
] -> PackageNameEnv
592 extraPackageNameEnv names
= PackageNameEnv pkgNameLookup
594 pkgNameLookup pname
=
596 |
let lname
= lowercase
(unPackageName pname
)
598 , lowercase
(unPackageName pname
') == lname
601 -- ------------------------------------------------------------
603 -- * Package constraints
605 -- ------------------------------------------------------------
607 -- | Version of 'Qualifier' that a user may specify on the
610 = -- | Top-level dependency.
612 |
-- | Setup dependency.
613 UserQualSetup PackageName
614 |
-- | Executable dependency.
615 UserQualExe PackageName PackageName
616 deriving (Eq
, Show, Generic
)
618 instance Binary UserQualifier
619 instance Structured UserQualifier
621 -- | Version of 'ConstraintScope' that a user may specify on the
623 data UserConstraintScope
624 = -- | Scope that applies to the package when it has the specified qualifier.
625 UserQualified UserQualifier PackageName
626 |
-- | Scope that applies to the package when it has a setup qualifier.
627 UserAnySetupQualifier PackageName
628 |
-- | Scope that applies to the package when it has any qualifier.
629 UserAnyQualifier PackageName
630 deriving (Eq
, Show, Generic
)
632 instance Binary UserConstraintScope
633 instance Structured UserConstraintScope
635 fromUserQualifier
:: UserQualifier
-> Qualifier
636 fromUserQualifier UserQualToplevel
= QualToplevel
637 fromUserQualifier
(UserQualSetup name
) = QualSetup name
638 fromUserQualifier
(UserQualExe name1 name2
) = QualExe name1 name2
640 fromUserConstraintScope
:: UserConstraintScope
-> ConstraintScope
641 fromUserConstraintScope
(UserQualified q pn
) =
642 ScopeQualified
(fromUserQualifier q
) pn
643 fromUserConstraintScope
(UserAnySetupQualifier pn
) = ScopeAnySetupQualifier pn
644 fromUserConstraintScope
(UserAnyQualifier pn
) = ScopeAnyQualifier pn
646 -- | Version of 'PackageConstraint' that the user can specify on
649 = UserConstraint UserConstraintScope PackageProperty
650 deriving (Eq
, Show, Generic
)
652 instance Binary UserConstraint
653 instance Structured UserConstraint
655 userConstraintPackageName
:: UserConstraint
-> PackageName
656 userConstraintPackageName
(UserConstraint scope _
) = scopePN scope
658 scopePN
(UserQualified _ pn
) = pn
659 scopePN
(UserAnyQualifier pn
) = pn
660 scopePN
(UserAnySetupQualifier pn
) = pn
662 userToPackageConstraint
:: UserConstraint
-> PackageConstraint
663 userToPackageConstraint
(UserConstraint scope prop
) =
664 PackageConstraint
(fromUserConstraintScope scope
) prop
666 readUserConstraint
:: String -> Either String UserConstraint
667 readUserConstraint str
=
668 case explicitEitherParsec parsec str
of
669 Left err
-> Left
$ msgCannotParse
++ err
673 "expected a (possibly qualified) package name followed by a "
674 ++ "constraint, which is either a version range, 'installed', "
675 ++ "'source', 'test', 'bench', or flags. "
677 instance Pretty UserConstraint
where
678 pretty
(UserConstraint scope prop
) =
679 dispPackageConstraint
$ PackageConstraint
(fromUserConstraintScope scope
) prop
681 instance Parsec UserConstraint
where
683 scope
<- parseConstraintScope
687 [ PackagePropertyFlags
<$> parsecFlagAssignmentNonEmpty
-- headed by "+-"
688 , PackagePropertyVersion
<$> parsec
-- headed by "<=>" (will be)
689 , PackagePropertyInstalled
<$ P
.string "installed"
690 , PackagePropertySource
<$ P
.string "source"
691 , PackagePropertyStanzas
[TestStanzas
] <$ P
.string "test"
692 , PackagePropertyStanzas
[BenchStanzas
] <$ P
.string "bench"
694 return (UserConstraint scope prop
)
696 parseConstraintScope
:: forall m
. CabalParsing m
=> m UserConstraintScope
697 parseConstraintScope
= do
700 [ P
.char
'.' *> withDot pn
701 , P
.char
':' *> withColon pn
702 , return (UserQualified UserQualToplevel pn
)
705 withDot
:: PackageName
-> m UserConstraintScope
707 | pn
== mkPackageName
"any" = UserAnyQualifier
<$> parsec
708 | pn
== mkPackageName
"setup" = UserAnySetupQualifier
<$> parsec
709 |
otherwise = P
.unexpected
$ "constraint scope: " ++ unPackageName pn
711 withColon
:: PackageName
-> m UserConstraintScope
713 UserQualified
(UserQualSetup pn
)