2 {-# LANGUAGE DeriveDataTypeable #-}
3 {-# LANGUAGE LambdaCase #-}
4 {-# LANGUAGE NamedFieldPuns #-}
5 {-# LANGUAGE RecordWildCards #-}
7 -- | Handling project configuration.
8 module Distribution
.Client
.ProjectConfig
9 ( -- * Types for project config
11 , ProjectConfigBuildOnly
(..)
12 , ProjectConfigShared
(..)
13 , ProjectConfigProvenance
(..)
23 -- * Project config files
26 , readProjectLocalExtraConfig
27 , readProjectLocalFreezeConfig
31 , withProjectOrGlobalConfig
32 , writeProjectLocalExtraConfig
33 , writeProjectLocalFreezeConfig
34 , writeProjectConfigFile
35 , commandLineFlagsToProjectConfig
37 -- * Packages within projects
38 , ProjectPackageLocation
(..)
39 , BadPackageLocations
(..)
40 , BadPackageLocation
(..)
41 , BadPackageLocationMatch
(..)
43 , fetchAndReadSourcePackages
45 -- * Resolving configuration
46 , lookupLocalPackageConfig
47 , projectConfigWithBuilderRepoContext
48 , projectConfigWithSolverRepoContext
50 , resolveSolverSettings
51 , BuildTimeSettings
(..)
52 , resolveBuildTimeSettings
54 -- * Checking configuration
55 , checkBadPerPackageCompilerPaths
56 , BadPerPackageCompilerPaths
(..)
59 import Distribution
.Client
.Compat
.Prelude
62 import Distribution
.Client
.Glob
63 ( isTrivialFilePathGlob
65 import Distribution
.Client
.ProjectConfig
.Legacy
66 import Distribution
.Client
.ProjectConfig
.Types
67 import Distribution
.Client
.RebuildMonad
68 import Distribution
.Client
.VCS
69 ( SourceRepoProblem
(..)
77 import Distribution
.Client
.BuildReports
.Types
80 import Distribution
.Client
.Config
84 import Distribution
.Client
.DistDirLayout
90 import Distribution
.Client
.GlobalFlags
94 import Distribution
.Client
.HttpUtils
100 import Distribution
.Client
.Types
101 import Distribution
.Client
.Utils
.Parsec
(renderParseError
)
103 import Distribution
.Solver
.Types
.PackageConstraint
104 ( PackageProperty
(..)
106 import Distribution
.Solver
.Types
.Settings
107 import Distribution
.Solver
.Types
.SourcePackage
109 import Distribution
.Client
.Setup
110 ( defaultMaxBackjumps
113 import Distribution
.Client
.SrcDist
116 import Distribution
.Client
.Types
.SourceRepo
118 , SourceRepositoryPackage
(..)
121 import Distribution
.Client
.Utils
124 import qualified Distribution
.Deprecated
.ParseUtils
as OldParser
129 import Distribution
.Fields
135 import Distribution
.Package
141 import Distribution
.PackageDescription
.Parsec
142 ( parseGenericPackageDescription
144 import Distribution
.Simple
.Compiler
148 import Distribution
.Simple
.InstallDirs
151 , initialPathTemplateEnv
155 import Distribution
.Simple
.Program
156 ( ConfiguredProgram
(..)
158 import Distribution
.Simple
.Setup
166 import Distribution
.Simple
.Utils
167 ( createDirectoryIfMissingVerbose
175 import Distribution
.System
178 import Distribution
.Types
.GenericPackageDescription
179 ( GenericPackageDescription
181 import Distribution
.Types
.PackageVersionConstraint
182 ( PackageVersionConstraint
(..)
184 import Distribution
.Types
.SourceRepo
187 import Distribution
.Utils
.NubList
190 import Distribution
.Verbosity
194 import Distribution
.Version
198 import qualified Codec
.Archive
.Tar
as Tar
199 import qualified Codec
.Archive
.Tar
.Entry
as Tar
200 import qualified Distribution
.Client
.GZipUtils
as GZipUtils
201 import qualified Distribution
.Client
.Tar
as Tar
203 import Control
.Monad
.Trans
(liftIO
)
204 import qualified Data
.ByteString
as BS
205 import qualified Data
.ByteString
.Lazy
as LBS
206 import qualified Data
.Hashable
as Hashable
207 import qualified Data
.List
.NonEmpty
as NE
208 import qualified Data
.Map
as Map
209 import qualified Data
.Set
as Set
210 import Numeric
(showHex
)
212 import Distribution
.Client
.Errors
219 import System
.Directory
220 import System
.FilePath hiding (combine
)
226 ----------------------------------------
227 -- Resolving configuration to settings
230 -- | Look up a 'PackageConfig' field in the 'ProjectConfig' for a specific
231 -- 'PackageName'. This returns the configuration that applies to all local
232 -- packages plus any package-specific configuration for this package.
233 lookupLocalPackageConfig
234 :: (Semigroup a
, Monoid a
)
235 => (PackageConfig
-> a
)
239 lookupLocalPackageConfig
242 { projectConfigLocalPackages
243 , projectConfigSpecificPackage
246 field projectConfigLocalPackages
250 (Map
.lookup pkgname
(getMapMappend projectConfigSpecificPackage
))
252 -- | Use a 'RepoContext' based on the 'BuildTimeSettings'.
253 projectConfigWithBuilderRepoContext
256 -> (RepoContext
-> IO a
)
258 projectConfigWithBuilderRepoContext verbosity BuildTimeSettings
{..} =
261 buildSettingRemoteRepos
262 buildSettingLocalNoIndexRepos
264 buildSettingHttpTransport
265 (Just buildSettingIgnoreExpiry
)
266 buildSettingProgPathExtra
268 -- | Use a 'RepoContext', but only for the solver. The solver does not use the
269 -- full facilities of the 'RepoContext' so we can get away with making one
270 -- that doesn't have an http transport. And that avoids having to have access
271 -- to the 'BuildTimeSettings'
272 projectConfigWithSolverRepoContext
274 -> ProjectConfigShared
275 -> ProjectConfigBuildOnly
276 -> (RepoContext
-> IO a
)
278 projectConfigWithSolverRepoContext
280 ProjectConfigShared
{..}
281 ProjectConfigBuildOnly
{..} =
284 (fromNubList projectConfigRemoteRepos
)
285 (fromNubList projectConfigLocalNoIndexRepos
)
288 "projectConfigWithSolverRepoContext: projectConfigCacheDir"
290 projectConfigCacheDir
292 (flagToMaybe projectConfigHttpTransport
)
293 (flagToMaybe projectConfigIgnoreExpiry
)
294 (fromNubList projectConfigProgPathExtra
)
296 -- | Resolve the project configuration, with all its optional fields, into
297 -- 'SolverSettings' with no optional fields (by applying defaults).
298 resolveSolverSettings
:: ProjectConfig
-> SolverSettings
299 resolveSolverSettings
301 { projectConfigShared
302 , projectConfigLocalPackages
303 , projectConfigSpecificPackage
307 -- TODO: [required eventually] some of these settings need validation, e.g.
308 -- the flag assignments need checking.
309 solverSettingRemoteRepos
= fromNubList projectConfigRemoteRepos
310 solverSettingLocalNoIndexRepos
= fromNubList projectConfigLocalNoIndexRepos
311 solverSettingConstraints
= projectConfigConstraints
312 solverSettingPreferences
= projectConfigPreferences
313 solverSettingFlagAssignment
= packageConfigFlagAssignment projectConfigLocalPackages
314 solverSettingFlagAssignments
=
316 packageConfigFlagAssignment
317 (getMapMappend projectConfigSpecificPackage
)
318 solverSettingCabalVersion
= flagToMaybe projectConfigCabalVersion
319 solverSettingSolver
= fromFlag projectConfigSolver
320 solverSettingAllowOlder
= fromMaybe mempty projectConfigAllowOlder
321 solverSettingAllowNewer
= fromMaybe mempty projectConfigAllowNewer
322 solverSettingMaxBackjumps
= case fromFlag projectConfigMaxBackjumps
of
325 |
otherwise -> Just n
326 solverSettingReorderGoals
= fromFlag projectConfigReorderGoals
327 solverSettingCountConflicts
= fromFlag projectConfigCountConflicts
328 solverSettingFineGrainedConflicts
= fromFlag projectConfigFineGrainedConflicts
329 solverSettingMinimizeConflictSet
= fromFlag projectConfigMinimizeConflictSet
330 solverSettingStrongFlags
= fromFlag projectConfigStrongFlags
331 solverSettingAllowBootLibInstalls
= fromFlag projectConfigAllowBootLibInstalls
332 solverSettingOnlyConstrained
= fromFlag projectConfigOnlyConstrained
333 solverSettingIndexState
= flagToMaybe projectConfigIndexState
334 solverSettingActiveRepos
= flagToMaybe projectConfigActiveRepos
335 solverSettingIndependentGoals
= fromFlag projectConfigIndependentGoals
336 solverSettingPreferOldest
= fromFlag projectConfigPreferOldest
337 -- solverSettingShadowPkgs = fromFlag projectConfigShadowPkgs
338 -- solverSettingReinstall = fromFlag projectConfigReinstall
339 -- solverSettingAvoidReinstalls = fromFlag projectConfigAvoidReinstalls
340 -- solverSettingOverrideReinstall = fromFlag projectConfigOverrideReinstall
341 -- solverSettingUpgradeDeps = fromFlag projectConfigUpgradeDeps
343 ProjectConfigShared
{..} = defaults
<> projectConfigShared
347 { projectConfigSolver
= Flag defaultSolver
348 , projectConfigAllowOlder
= Just
(AllowOlder mempty
)
349 , projectConfigAllowNewer
= Just
(AllowNewer mempty
)
350 , projectConfigMaxBackjumps
= Flag defaultMaxBackjumps
351 , projectConfigReorderGoals
= Flag
(ReorderGoals
False)
352 , projectConfigCountConflicts
= Flag
(CountConflicts
True)
353 , projectConfigFineGrainedConflicts
= Flag
(FineGrainedConflicts
True)
354 , projectConfigMinimizeConflictSet
= Flag
(MinimizeConflictSet
False)
355 , projectConfigStrongFlags
= Flag
(StrongFlags
False)
356 , projectConfigAllowBootLibInstalls
= Flag
(AllowBootLibInstalls
False)
357 , projectConfigOnlyConstrained
= Flag OnlyConstrainedNone
358 , projectConfigIndependentGoals
= Flag
(IndependentGoals
False)
359 , projectConfigPreferOldest
= Flag
(PreferOldest
False)
360 -- projectConfigShadowPkgs = Flag False,
361 -- projectConfigReinstall = Flag False,
362 -- projectConfigAvoidReinstalls = Flag False,
363 -- projectConfigOverrideReinstall = Flag False,
364 -- projectConfigUpgradeDeps = Flag False
367 -- | Resolve the project configuration, with all its optional fields, into
368 -- 'BuildTimeSettings' with no optional fields (by applying defaults).
369 resolveBuildTimeSettings
374 resolveBuildTimeSettings
380 { projectConfigShared
=
382 { projectConfigRemoteRepos
383 , projectConfigLocalNoIndexRepos
384 , projectConfigProgPathExtra
386 , projectConfigBuildOnly
388 BuildTimeSettings
{..}
390 buildSettingDryRun
= fromFlag projectConfigDryRun
391 buildSettingOnlyDeps
= fromFlag projectConfigOnlyDeps
392 buildSettingOnlyDownload
= fromFlag projectConfigOnlyDownload
393 buildSettingSummaryFile
= fromNubList projectConfigSummaryFile
394 -- buildSettingLogFile -- defined below, more complicated
395 -- buildSettingLogVerbosity -- defined below, more complicated
396 buildSettingBuildReports
= fromFlag projectConfigBuildReports
397 buildSettingSymlinkBinDir
= flagToList projectConfigSymlinkBinDir
398 buildSettingNumJobs
=
399 if fromFlag projectConfigUseSemaphore
400 then UseSem
(determineNumJobs projectConfigNumJobs
)
401 else case (determineNumJobs projectConfigNumJobs
) of
403 n
-> NumJobs
(Just n
)
404 buildSettingKeepGoing
= fromFlag projectConfigKeepGoing
405 buildSettingOfflineMode
= fromFlag projectConfigOfflineMode
406 buildSettingKeepTempFiles
= fromFlag projectConfigKeepTempFiles
407 buildSettingRemoteRepos
= fromNubList projectConfigRemoteRepos
408 buildSettingLocalNoIndexRepos
= fromNubList projectConfigLocalNoIndexRepos
409 buildSettingCacheDir
= fromFlag projectConfigCacheDir
410 buildSettingHttpTransport
= flagToMaybe projectConfigHttpTransport
411 buildSettingIgnoreExpiry
= fromFlag projectConfigIgnoreExpiry
412 buildSettingReportPlanningFailure
=
413 fromFlag projectConfigReportPlanningFailure
414 buildSettingProgPathExtra
= fromNubList projectConfigProgPathExtra
415 buildSettingHaddockOpen
= False
417 ProjectConfigBuildOnly
{..} =
419 <> projectConfigBuildOnly
423 { projectConfigDryRun
= toFlag
False
424 , projectConfigOnlyDeps
= toFlag
False
425 , projectConfigOnlyDownload
= toFlag
False
426 , projectConfigBuildReports
= toFlag NoReports
427 , projectConfigReportPlanningFailure
= toFlag
False
428 , projectConfigKeepGoing
= toFlag
False
429 , projectConfigOfflineMode
= toFlag
False
430 , projectConfigKeepTempFiles
= toFlag
False
431 , projectConfigIgnoreExpiry
= toFlag
False
434 -- The logging logic: what log file to use and what verbosity.
436 -- If the user has specified --remote-build-reporting=detailed, use the
437 -- default log file location. If the --build-log option is set, use the
438 -- provided location. Otherwise don't use logging, unless building in
439 -- parallel (in which case the default location is used).
450 | useDefaultTemplate
= Just
(substLogFileName defaultTemplate
)
451 |
otherwise = fmap substLogFileName givenTemplate
459 givenTemplate
= flagToMaybe projectConfigLogFile
462 | buildSettingBuildReports
== DetailedReports
= True
463 |
isJust givenTemplate
= False
464 | isParallelBuild buildSettingNumJobs
= True
474 substLogFileName template compiler platform pkgid uid
=
475 fromPathTemplate
(substPathTemplate env template
)
478 initialPathTemplateEnv
481 (compilerInfo compiler
)
484 -- If the user has specified --remote-build-reporting=detailed or
485 -- --build-log, use more verbose logging.
487 buildSettingLogVerbosity
:: Verbosity
488 buildSettingLogVerbosity
489 | overrideVerbosity
= modifyVerbosity
(max verbose
) verbosity
490 |
otherwise = verbosity
492 overrideVerbosity
:: Bool
494 | buildSettingBuildReports
== DetailedReports
= True
495 |
isJust givenTemplate
= True
496 | isParallelBuild buildSettingNumJobs
= False
499 ---------------------------------------------
500 -- Reading and writing project config files
503 -- | Find the root of this project.
505 -- The project directory will be one of the following:
506 -- 1. @mprojectDir@ when present
507 -- 2. The first directory containing @mprojectFile@/@cabal.project@, starting from the current directory
508 -- and recursively checking parent directories
509 -- 3. The current directory
513 -- ^ Explicit project directory
515 -- ^ Explicit project file
516 -> IO (Either BadProjectRoot ProjectRoot
)
517 findProjectRoot verbosity mprojectDir mprojectFile
= do
520 | Just file
<- mprojectFile
521 , isAbsolute file
-> do
523 "Specifying an absolute path to the project file is deprecated."
524 <> " Use --project-dir to set the project's directory."
526 doesFileExist file
>>= \case
527 False -> left
(BadProjectRootExplicitFile file
)
528 True -> uncurry projectRoot
=<< first dropTrailingPathSeparator
. splitFileName
<$> canonicalizePath file
529 |
otherwise -> probeProjectRoot mprojectFile
531 doesDirectoryExist dir
>>= \case
532 False -> left
(BadProjectRootDir dir
)
534 projectDir
<- canonicalizePath dir
537 Nothing
-> pure
$ Right
(ProjectRootExplicit projectDir defaultProjectFile
)
539 | isAbsolute projectFile
->
540 doesFileExist projectFile
>>= \case
541 False -> left
(BadProjectRootAbsoluteFile projectFile
)
542 True -> Right
. ProjectRootExplicitAbsolute dir
<$> canonicalizePath projectFile
544 doesFileExist (projectDir
</> projectFile
) >>= \case
545 False -> left
(BadProjectRootDirFile dir projectFile
)
546 True -> projectRoot projectDir projectFile
550 projectRoot projectDir projectFile
=
551 pure
$ Right
(ProjectRootExplicit projectDir projectFile
)
553 probeProjectRoot
:: Maybe FilePath -> IO (Either BadProjectRoot ProjectRoot
)
554 probeProjectRoot mprojectFile
= do
555 startdir
<- getCurrentDirectory
556 homedir
<- getHomeDirectory
557 probe startdir homedir
559 projectFileName
:: String
560 projectFileName
= fromMaybe defaultProjectFile mprojectFile
562 -- Search upwards. If we get to the users home dir or the filesystem root,
563 -- then use the current dir
564 probe
:: FilePath -> String -> IO (Either BadProjectRoot ProjectRoot
)
565 probe startdir homedir
= go startdir
567 go
:: FilePath -> IO (Either BadProjectRoot ProjectRoot
)
568 go dir | isDrive dir || dir
== homedir
=
570 Nothing
-> return (Right
(ProjectRootImplicit startdir
))
571 Just file
-> return (Left
(BadProjectRootExplicitFile file
))
573 exists
<- doesFileExist (dir
</> projectFileName
)
575 then return (Right
(ProjectRootExplicit dir projectFileName
))
576 else go
(takeDirectory dir
)
578 -- | Errors returned by 'findProjectRoot'.
580 = BadProjectRootExplicitFile
FilePath
581 | BadProjectRootDir
FilePath
582 | BadProjectRootAbsoluteFile
FilePath
583 | BadProjectRootDirFile
FilePath FilePath
584 #if MIN_VERSION_base
(4,8,0)
585 deriving (Show, Typeable
)
589 instance Show BadProjectRoot
where
590 show = renderBadProjectRoot
593 #if MIN_VERSION_base
(4,8,0)
594 instance Exception BadProjectRoot
where
595 displayException
= renderBadProjectRoot
597 instance Exception BadProjectRoot
600 renderBadProjectRoot
:: BadProjectRoot
-> String
601 renderBadProjectRoot
= \case
602 BadProjectRootExplicitFile projectFile
->
603 "The given project file '" ++ projectFile
++ "' does not exist."
604 BadProjectRootDir dir
->
605 "The given project directory '" <> dir
<> "' does not exist."
606 BadProjectRootAbsoluteFile file
->
607 "The given project file '" <> file
<> "' does not exist."
608 BadProjectRootDirFile dir file
->
609 "The given project directory/file combination '" <> dir
</> file
<> "' does not exist."
615 -- ^ @--cabal-config@
616 -> (ProjectConfig
-> IO a
)
619 withGlobalConfig verbosity gcf with
= do
620 globalConfig
<- runRebuild
"" $ readGlobalConfig verbosity gcf
623 withProjectOrGlobalConfig
627 -- ^ whether to ignore local project (--ignore-project flag)
629 -- ^ @--cabal-config@
632 -> (ProjectConfig
-> IO a
)
635 withProjectOrGlobalConfig verbosity
(Flag
True) gcf _with without
= do
636 globalConfig
<- runRebuild
"" $ readGlobalConfig verbosity gcf
638 withProjectOrGlobalConfig verbosity _ignorePrj gcf with without
=
639 withProjectOrGlobalConfig
' verbosity gcf with without
641 withProjectOrGlobalConfig
'
645 -> (ProjectConfig
-> IO a
)
647 withProjectOrGlobalConfig
' verbosity globalConfigFlag with without
= do
648 globalConfig
<- runRebuild
"" $ readGlobalConfig verbosity globalConfigFlag
652 (BadPackageLocations prov locs
)
653 | prov
== Set
.singleton Implicit
655 isGlobErr
(BadLocGlobEmptyMatch _
) = True
657 , any isGlobErr locs
->
661 -- | Read all the config relevant for a project. This includes the project
662 -- file if any, plus other global config.
667 -- ^ @--ignore-project@
670 -> Rebuild ProjectConfigSkeleton
671 readProjectConfig verbosity _
(Flag
True) configFileFlag _
= do
672 global
<- singletonProjectConfigSkeleton
<$> readGlobalConfig verbosity configFileFlag
673 return (global
<> singletonProjectConfigSkeleton defaultImplicitProjectConfig
)
674 readProjectConfig verbosity httpTransport _ configFileFlag distDirLayout
= do
675 global
<- singletonProjectConfigSkeleton
<$> readGlobalConfig verbosity configFileFlag
676 local
<- readProjectLocalConfigOrDefault verbosity httpTransport distDirLayout
677 freeze
<- readProjectLocalFreezeConfig verbosity httpTransport distDirLayout
678 extra
<- readProjectLocalExtraConfig verbosity httpTransport distDirLayout
679 return (global
<> local
<> freeze
<> extra
)
681 -- | Reads an explicit @cabal.project@ file in the given project root dir,
682 -- or returns the default project config for an implicitly defined project.
683 readProjectLocalConfigOrDefault
687 -> Rebuild ProjectConfigSkeleton
688 readProjectLocalConfigOrDefault verbosity httpTransport distDirLayout
= do
689 let projectFile
= distProjectFile distDirLayout
""
690 usesExplicitProjectRoot
<- liftIO
$ doesFileExist projectFile
691 if usesExplicitProjectRoot
693 readProjectFileSkeleton verbosity httpTransport distDirLayout
"" "project file"
695 monitorFiles
[monitorNonExistentFile projectFile
]
696 return (singletonProjectConfigSkeleton defaultImplicitProjectConfig
)
698 defaultImplicitProjectConfig
:: ProjectConfig
699 defaultImplicitProjectConfig
=
701 { -- We expect a package in the current directory.
702 projectPackages
= ["./*.cabal"]
703 , projectConfigProvenance
= Set
.singleton Implicit
706 -- | Reads a @cabal.project.local@ file in the given project root dir,
707 -- or returns empty. This file gets written by @cabal configure@, or in
708 -- principle can be edited manually or by other tools.
709 readProjectLocalExtraConfig
713 -> Rebuild ProjectConfigSkeleton
714 readProjectLocalExtraConfig verbosity httpTransport distDirLayout
=
715 readProjectFileSkeleton
720 "project local configuration file"
722 -- | Reads a @cabal.project.freeze@ file in the given project root dir,
723 -- or returns empty. This file gets written by @cabal freeze@, or in
724 -- principle can be edited manually or by other tools.
725 readProjectLocalFreezeConfig
729 -> Rebuild ProjectConfigSkeleton
730 readProjectLocalFreezeConfig verbosity httpTransport distDirLayout
=
731 readProjectFileSkeleton
736 "project freeze file"
738 -- | Reads a named extended (with imports and conditionals) config file in the given project root dir, or returns empty.
739 readProjectFileSkeleton
:: Verbosity
-> HttpTransport
-> DistDirLayout
-> String -> String -> Rebuild ProjectConfigSkeleton
740 readProjectFileSkeleton
743 DistDirLayout
{distProjectFile
, distDownloadSrcDirectory
}
745 extensionDescription
= do
746 exists
<- liftIO
$ doesFileExist extensionFile
749 monitorFiles
[monitorFileHashed extensionFile
]
750 pcs
<- liftIO readExtensionFile
751 monitorFiles
$ map monitorFileHashed
(projectSkeletonImports pcs
)
754 monitorFiles
[monitorNonExistentFile extensionFile
]
757 extensionFile
= distProjectFile extensionName
760 reportParseResult verbosity extensionDescription extensionFile
761 =<< parseProjectSkeleton distDownloadSrcDirectory httpTransport verbosity
[] extensionFile
762 =<< BS
.readFile extensionFile
764 -- | Render the 'ProjectConfig' format.
766 -- For the moment this is implemented in terms of a pretty printer for the
767 -- legacy configuration types, plus a conversion.
768 showProjectConfig
:: ProjectConfig
-> String
770 showLegacyProjectConfig
. convertToLegacyProjectConfig
772 -- | Write a @cabal.project.local@ file in the given project root dir.
773 writeProjectLocalExtraConfig
:: DistDirLayout
-> ProjectConfig
-> IO ()
774 writeProjectLocalExtraConfig DistDirLayout
{distProjectFile
} =
775 writeProjectConfigFile
(distProjectFile
"local")
777 -- | Write a @cabal.project.freeze@ file in the given project root dir.
778 writeProjectLocalFreezeConfig
:: DistDirLayout
-> ProjectConfig
-> IO ()
779 writeProjectLocalFreezeConfig DistDirLayout
{distProjectFile
} =
780 writeProjectConfigFile
(distProjectFile
"freeze")
782 -- | Write in the @cabal.project@ format to the given file.
783 writeProjectConfigFile
:: FilePath -> ProjectConfig
-> IO ()
784 writeProjectConfigFile file
=
785 writeFile file
. showProjectConfig
787 -- | Read the user's cabal-install config file.
788 readGlobalConfig
:: Verbosity
-> Flag
FilePath -> Rebuild ProjectConfig
789 readGlobalConfig verbosity configFileFlag
= do
790 config
<- liftIO
(loadConfig verbosity configFileFlag
)
791 configFile
<- liftIO
(getConfigFilePath configFileFlag
)
792 monitorFiles
[monitorFileHashed configFile
]
793 return (convertLegacyGlobalConfig config
)
795 reportParseResult
:: Verbosity
-> String -> FilePath -> OldParser
.ParseResult ProjectConfigSkeleton
-> IO ProjectConfigSkeleton
796 reportParseResult verbosity _filetype filename
(OldParser
.ParseOk warnings x
) = do
797 unless (null warnings
) $
798 let msg
= unlines (map (OldParser
.showPWarning
(intercalate
", " $ filename
: projectSkeletonImports x
)) warnings
)
799 in warn verbosity msg
801 reportParseResult verbosity filetype filename
(OldParser
.ParseFailed err
) =
802 let (line
, msg
) = OldParser
.locatedErrorMsg err
803 errLineNo
= maybe "" (\n -> ':' : show n
) line
804 in dieWithException verbosity
$ ReportParseResult filetype filename errLineNo msg
806 ---------------------------------------------
807 -- Finding packages in the project
810 -- | The location of a package as part of a project. Local file paths are
811 -- either absolute (if the user specified it as such) or they are relative
812 -- to the project root.
813 data ProjectPackageLocation
814 = ProjectPackageLocalCabalFile
FilePath
815 | ProjectPackageLocalDirectory
FilePath FilePath -- dir and .cabal file
816 | ProjectPackageLocalTarball
FilePath
817 | ProjectPackageRemoteTarball URI
818 | ProjectPackageRemoteRepo SourceRepoList
819 | ProjectPackageNamed PackageVersionConstraint
822 -- | Exception thrown by 'findProjectPackages'.
823 data BadPackageLocations
824 = BadPackageLocations
(Set ProjectConfigProvenance
) [BadPackageLocation
]
825 #if MIN_VERSION_base
(4,8,0)
826 deriving (Show, Typeable
)
830 instance Show BadPackageLocations
where
831 show = renderBadPackageLocations
834 #if MIN_VERSION_base
(4,8,0)
835 instance Exception BadPackageLocations
where
836 displayException
= renderBadPackageLocations
838 instance Exception BadPackageLocations
840 -- TODO: [nice to have] custom exception subclass for Doc rendering, colour etc
842 data BadPackageLocation
843 = BadPackageLocationFile BadPackageLocationMatch
844 | BadLocGlobEmptyMatch
String
845 | BadLocGlobBadMatches
String [BadPackageLocationMatch
]
846 | BadLocUnexpectedUriScheme
String
847 | BadLocUnrecognisedUri
String
848 | BadLocUnrecognised
String
851 data BadPackageLocationMatch
852 = BadLocUnexpectedFile
String
853 | BadLocNonexistantFile
String
854 | BadLocDirNoCabalFile
String
855 | BadLocDirManyCabalFiles
String
858 renderBadPackageLocations
:: BadPackageLocations
-> String
859 renderBadPackageLocations
(BadPackageLocations provenance bpls
)
860 -- There is no provenance information,
861 -- render standard bad package error information.
862 | Set
.null provenance
= renderErrors renderBadPackageLocation
863 -- The configuration is implicit, render bad package locations
864 -- using possibly specialized error messages.
865 | Set
.singleton Implicit
== provenance
=
866 renderErrors renderImplicitBadPackageLocation
867 -- The configuration contains both implicit and explicit provenance.
868 -- This should not occur, and a message is output to assist debugging.
869 | Implicit `Set
.member` provenance
=
870 "Warning: both implicit and explicit configuration is present."
872 -- The configuration was read from one or more explicit path(s),
873 -- list the locations and render the bad package error information.
874 -- The intent is to supersede this with the relevant location information
875 -- per package error.
876 |
otherwise = renderExplicit
878 renderErrors f
= unlines (map f bpls
)
881 "When using configuration(s) from "
882 ++ intercalate
", " (mapMaybe getExplicit
(Set
.toList provenance
))
883 ++ ", the following errors occurred:\n"
884 ++ renderErrors renderBadPackageLocation
886 getExplicit
(Explicit path
) = Just path
887 getExplicit Implicit
= Nothing
889 -- TODO: [nice to have] keep track of the config file (and src loc) packages
890 -- were listed, to use in error messages
892 -- | Render bad package location error information for the implicit
893 -- @cabal.project@ configuration.
895 -- TODO: This is currently not fully realized, with only one of the implicit
896 -- cases handled. More cases should be added with informative help text
897 -- about the issues related specifically when having no project configuration
899 renderImplicitBadPackageLocation
:: BadPackageLocation
-> String
900 renderImplicitBadPackageLocation bpl
= case bpl
of
901 BadLocGlobEmptyMatch pkglocstr
->
902 "No cabal.project file or cabal file matching the default glob '"
905 ++ "Please create a package description file <pkgname>.cabal "
906 ++ "or a cabal.project file referencing the packages you "
908 _
-> renderBadPackageLocation bpl
910 renderBadPackageLocation
:: BadPackageLocation
-> String
911 renderBadPackageLocation bpl
= case bpl
of
912 BadPackageLocationFile badmatch
->
913 renderBadPackageLocationMatch badmatch
914 BadLocGlobEmptyMatch pkglocstr
->
915 "The package location glob '"
917 ++ "' does not match any files or directories."
918 BadLocGlobBadMatches pkglocstr failures
->
919 "The package location glob '"
921 ++ "' does not match any "
922 ++ "recognised forms of package. "
923 ++ concatMap ((' ' :) . renderBadPackageLocationMatch
) failures
924 BadLocUnexpectedUriScheme pkglocstr
->
925 "The package location URI '"
927 ++ "' does not use a "
928 ++ "supported URI scheme. The supported URI schemes are http, https and "
930 BadLocUnrecognisedUri pkglocstr
->
931 "The package location URI '"
933 ++ "' does not appear to "
934 ++ "be a valid absolute URI."
935 BadLocUnrecognised pkglocstr
->
936 "The package location syntax '" ++ pkglocstr
++ "' is not recognised."
938 renderBadPackageLocationMatch
:: BadPackageLocationMatch
-> String
939 renderBadPackageLocationMatch bplm
= case bplm
of
940 BadLocUnexpectedFile pkglocstr
->
941 "The package location '"
943 ++ "' is not recognised. The "
944 ++ "supported file targets are .cabal files, .tar.gz tarballs or package "
945 ++ "directories (i.e. directories containing a .cabal file)."
946 BadLocNonexistantFile pkglocstr
->
947 "The package location '" ++ pkglocstr
++ "' does not exist."
948 BadLocDirNoCabalFile pkglocstr
->
949 "The package directory '"
951 ++ "' does not contain any "
953 BadLocDirManyCabalFiles pkglocstr
->
954 "The package directory '"
956 ++ "' contains multiple "
957 ++ ".cabal files (which is not currently supported)."
959 -- | Given the project config,
961 -- Throws 'BadPackageLocations'.
965 -> Rebuild
[ProjectPackageLocation
]
967 DistDirLayout
{distProjectRootDirectory
}
968 ProjectConfig
{..} = do
969 requiredPkgs
<- findPackageLocations
True projectPackages
970 optionalPkgs
<- findPackageLocations
False projectPackagesOptional
971 let repoPkgs
= map ProjectPackageRemoteRepo projectPackagesRepo
972 namedPkgs
= map ProjectPackageNamed projectPackagesNamed
974 return (concat [requiredPkgs
, optionalPkgs
, repoPkgs
, namedPkgs
])
976 findPackageLocations
:: Bool -> [String] -> Rebuild
[ProjectPackageLocation
]
977 findPackageLocations required pkglocstr
= do
978 (problems
, pkglocs
) <-
979 partitionEithers
<$> traverse
(findPackageLocation required
) pkglocstr
980 unless (null problems
) $
983 BadPackageLocations projectConfigProvenance problems
984 return (concat pkglocs
)
992 [ProjectPackageLocation
]
994 findPackageLocation _required
@True pkglocstr
=
995 -- strategy: try first as a file:// or http(s):// URL.
996 -- then as a file glob (usually encompassing single file)
997 -- finally as a single file, for files that fail to parse as globs
998 checkIsUriPackage pkglocstr
999 `mplusMaybeT` checkIsFileGlobPackage pkglocstr
1000 `mplusMaybeT` checkIsSingleFilePackage pkglocstr
1001 >>= maybe (return (Left
(BadLocUnrecognised pkglocstr
))) return
1002 findPackageLocation _required
@False pkglocstr
= do
1003 -- just globs for optional case
1004 res
<- checkIsFileGlobPackage pkglocstr
1006 Nothing
-> return (Left
(BadLocUnrecognised pkglocstr
))
1007 Just
(Left _
) -> return (Right
[]) -- it's optional
1008 Just
(Right pkglocs
) -> return (Right pkglocs
)
1011 , checkIsFileGlobPackage
1012 , checkIsSingleFilePackage
1018 [ProjectPackageLocation
]
1021 checkIsUriPackage pkglocstr
=
1022 case parseAbsoluteURI pkglocstr
of
1025 { uriScheme
= scheme
1026 , uriAuthority
= Just URIAuth
{uriRegName
= host
}
1029 , uriFragment
= frag
1031 | recognisedScheme
&& not (null host
) ->
1032 return (Just
(Right
[ProjectPackageRemoteTarball uri
]))
1033 | scheme
== "file:" && null host
&& null query
&& null frag
->
1034 checkIsSingleFilePackage path
1035 |
not recognisedScheme
&& not (null host
) ->
1036 return (Just
(Left
(BadLocUnexpectedUriScheme pkglocstr
)))
1037 | recognisedScheme
&& null host
->
1038 return (Just
(Left
(BadLocUnrecognisedUri pkglocstr
)))
1042 || scheme
== "https:"
1043 || scheme
== "file:"
1046 checkIsFileGlobPackage pkglocstr
=
1047 case simpleParsec pkglocstr
of
1048 Nothing
-> return Nothing
1049 Just glob
-> liftM Just
$ do
1050 matches
<- matchFileGlob glob
1053 |
isJust (isTrivialFilePathGlob glob
) ->
1056 ( BadPackageLocationFile
1057 (BadLocNonexistantFile pkglocstr
)
1060 [] -> return (Left
(BadLocGlobEmptyMatch pkglocstr
))
1062 (failures
, pkglocs
) <-
1064 <$> traverse checkFilePackageMatch matches
1065 return $! case (failures
, pkglocs
) of
1067 |
isJust (isTrivialFilePathGlob glob
) ->
1068 Left
(BadPackageLocationFile failure
)
1069 (_
, []) -> Left
(BadLocGlobBadMatches pkglocstr failures
)
1072 checkIsSingleFilePackage pkglocstr
= do
1073 let filename
= distProjectRootDirectory
</> pkglocstr
1074 isFile
<- liftIO
$ doesFileExist filename
1075 isDir
<- liftIO
$ doesDirectoryExist filename
1078 checkFilePackageMatch pkglocstr
1080 (return . Just
. Left
. BadPackageLocationFile
)
1081 (return . Just
. Right
. (\x
-> [x
]))
1084 checkFilePackageMatch
1088 BadPackageLocationMatch
1089 ProjectPackageLocation
1091 checkFilePackageMatch pkglocstr
= do
1092 -- The pkglocstr may be absolute or may be relative to the project root.
1093 -- Either way, </> does the right thing here. We return relative paths if
1094 -- they were relative in the first place.
1095 let abspath
= distProjectRootDirectory
</> pkglocstr
1096 isFile
<- liftIO
$ doesFileExist abspath
1097 isDir
<- liftIO
$ doesDirectoryExist abspath
1098 parentDirExists
<- case takeDirectory abspath
of
1100 dir
-> liftIO
$ doesDirectoryExist dir
1105 matches
<- matchFileGlob
(globStarDotCabal pkglocstr
)
1110 ( ProjectPackageLocalDirectory
1115 [] -> return (Left
(BadLocDirNoCabalFile pkglocstr
))
1116 _
-> return (Left
(BadLocDirManyCabalFiles pkglocstr
))
1117 | extensionIsTarGz pkglocstr
->
1118 return (Right
(ProjectPackageLocalTarball pkglocstr
))
1119 | takeExtension pkglocstr
== ".cabal" ->
1120 return (Right
(ProjectPackageLocalCabalFile pkglocstr
))
1122 return (Left
(BadLocUnexpectedFile pkglocstr
))
1123 | parentDirExists
->
1124 return (Left
(BadLocNonexistantFile pkglocstr
))
1126 return (Left
(BadLocUnexpectedFile pkglocstr
))
1128 extensionIsTarGz f
=
1129 takeExtension f
== ".gz"
1130 && takeExtension
(dropExtension f
) == ".tar"
1132 -- | A glob to find all the cabal files in a directory.
1134 -- For a directory @some/dir/@, this is a glob of the form @some/dir/\*.cabal@.
1135 -- The directory part can be either absolute or relative.
1136 globStarDotCabal
:: FilePath -> FilePathGlob
1137 globStarDotCabal dir
=
1139 (if isAbsolute dir
then FilePathRoot root
else FilePathRelative
)
1141 (\d
-> GlobDir
[Literal d
])
1142 (GlobFile
[WildCard
, Literal
".cabal"])
1146 (root
, dirComponents
) = fmap splitDirectories
(splitDrive dir
)
1148 -- TODO: [code cleanup] use sufficiently recent transformers package
1149 mplusMaybeT
:: Monad m
=> m
(Maybe a
) -> m
(Maybe a
) -> m
(Maybe a
)
1150 mplusMaybeT ma mb
= do
1154 Just x
-> return (Just x
)
1156 -------------------------------------------------
1157 -- Fetching and reading packages in the project
1160 -- | Read the @.cabal@ files for a set of packages. For remote tarballs and
1161 -- VCS source repos this also fetches them if needed.
1163 -- Note here is where we convert from project-root relative paths to absolute
1165 fetchAndReadSourcePackages
1168 -> ProjectConfigShared
1169 -> ProjectConfigBuildOnly
1170 -> [ProjectPackageLocation
]
1171 -> Rebuild
[PackageSpecifier
(SourcePackage UnresolvedPkgLoc
)]
1172 fetchAndReadSourcePackages
1176 projectConfigBuildOnly
1178 pkgsLocalDirectory
<-
1180 [ readSourcePackageLocalDirectory verbosity dir cabalFile
1181 | location
<- pkgLocations
1182 , (dir
, cabalFile
) <- projectPackageLocal location
1187 [ readSourcePackageLocalTarball verbosity path
1188 | ProjectPackageLocalTarball path
<- pkgLocations
1191 pkgsRemoteTarball
<- do
1193 delayInitSharedResource
$
1197 preferredHttpTransport
1199 [ fetchAndReadSourcePackageRemoteTarball
1204 | ProjectPackageRemoteTarball uri
<- pkgLocations
1208 syncAndReadSourcePackagesRemoteRepos
1212 [repo | ProjectPackageRemoteRepo repo
<- pkgLocations
]
1215 [ NamedPackage pkgname
[PackagePropertyVersion verrange
]
1216 | ProjectPackageNamed
(PackageVersionConstraint pkgname verrange
) <- pkgLocations
1221 [ pkgsLocalDirectory
1228 projectPackageLocal
(ProjectPackageLocalDirectory dir file
) = [(dir
, file
)]
1229 projectPackageLocal
(ProjectPackageLocalCabalFile file
) = [(dir
, file
)]
1231 dir
= takeDirectory file
1232 projectPackageLocal _
= []
1234 progPathExtra
= fromNubList
(projectConfigProgPathExtra projectConfigShared
)
1235 preferredHttpTransport
=
1236 flagToMaybe
(projectConfigHttpTransport projectConfigBuildOnly
)
1238 -- | A helper for 'fetchAndReadSourcePackages' to handle the case of
1239 -- 'ProjectPackageLocalDirectory' and 'ProjectPackageLocalCabalFile'.
1240 -- We simply read the @.cabal@ file.
1241 readSourcePackageLocalDirectory
1244 -- ^ The package directory
1246 -- ^ The package @.cabal@ file
1247 -> Rebuild
(PackageSpecifier
(SourcePackage UnresolvedPkgLoc
))
1248 readSourcePackageLocalDirectory verbosity dir cabalFile
= do
1249 monitorFiles
[monitorFileHashed cabalFile
]
1251 let location
= LocalUnpackedPackage
(root
</> dir
)
1253 fmap (mkSpecificSourcePackage location
)
1254 . readSourcePackageCabalFile verbosity cabalFile
1255 =<< BS
.readFile (root
</> cabalFile
)
1257 -- | A helper for 'fetchAndReadSourcePackages' to handle the case of
1258 -- 'ProjectPackageLocalTarball'. We scan through the @.tar.gz@ file to find
1259 -- the @.cabal@ file and read that.
1260 readSourcePackageLocalTarball
1263 -> Rebuild
(PackageSpecifier
(SourcePackage UnresolvedPkgLoc
))
1264 readSourcePackageLocalTarball verbosity tarballFile
= do
1265 monitorFiles
[monitorFile tarballFile
]
1267 let location
= LocalTarballPackage
(root
</> tarballFile
)
1269 fmap (mkSpecificSourcePackage location
)
1270 . uncurry (readSourcePackageCabalFile verbosity
)
1271 =<< extractTarballPackageCabalFile
(root
</> tarballFile
)
1273 -- | A helper for 'fetchAndReadSourcePackages' to handle the case of
1274 -- 'ProjectPackageRemoteTarball'. We download the tarball to the dist src dir
1275 -- and after that handle it like the local tarball case.
1276 fetchAndReadSourcePackageRemoteTarball
1279 -> Rebuild HttpTransport
1281 -> Rebuild
(PackageSpecifier
(SourcePackage UnresolvedPkgLoc
))
1282 fetchAndReadSourcePackageRemoteTarball
1285 { distDownloadSrcDirectory
1289 -- The tarball download is expensive so we use another layer of file
1290 -- monitor to avoid it whenever possible.
1291 rerunIfChanged verbosity monitor tarballUri
$ do
1293 transport
<- getTransport
1295 transportCheckHttps verbosity transport tarballUri
1296 notice verbosity
("Downloading " ++ show tarballUri
)
1297 createDirectoryIfMissingVerbose
1300 distDownloadSrcDirectory
1301 _
<- downloadURI transport verbosity tarballUri tarballFile
1305 monitorFiles
[monitorFile tarballFile
]
1306 let location
= RemoteTarballPackage tarballUri tarballFile
1308 fmap (mkSpecificSourcePackage location
)
1309 . uncurry (readSourcePackageCabalFile verbosity
)
1310 =<< extractTarballPackageCabalFile tarballFile
1312 tarballStem
:: FilePath
1314 distDownloadSrcDirectory
1315 </> localFileNameForRemoteTarball tarballUri
1316 tarballFile
:: FilePath
1317 tarballFile
= tarballStem
<.> "tar.gz"
1319 monitor
:: FileMonitor URI
(PackageSpecifier
(SourcePackage UnresolvedPkgLoc
))
1320 monitor
= newFileMonitor
(tarballStem
<.> "cache")
1322 -- | A helper for 'fetchAndReadSourcePackages' to handle all the cases of
1323 -- 'ProjectPackageRemoteRepo'.
1324 syncAndReadSourcePackagesRemoteRepos
1327 -> ProjectConfigShared
1329 -> Rebuild
[PackageSpecifier
(SourcePackage UnresolvedPkgLoc
)]
1330 syncAndReadSourcePackagesRemoteRepos
1332 DistDirLayout
{distDownloadSrcDirectory
}
1334 { projectConfigProgPathExtra
1338 either reportSourceRepoProblems
return $
1339 validateSourceRepos repos
1341 -- All 'SourceRepo's grouped by referring to the "same" remote repo
1342 -- instance. So same location but can differ in commit/tag/branch/subdir.
1346 [(SourceRepoList
, RepoType
)]
1350 [ ((rtype
, rloc
), [(repo
, vcsRepoType vcs
)])
1351 |
(repo
, rloc
, rtype
, vcs
) <- repos
'
1354 -- TODO: pass progPathExtra on to 'configureVCS'
1355 let _progPathExtra
= fromNubList projectConfigProgPathExtra
1356 getConfiguredVCS
<- delayInitSharedResources
$ \repoType
->
1357 let vcs
= Map
.findWithDefault
(error $ "Unknown VCS: " ++ prettyShow repoType
) repoType knownVCSs
1358 in configureVCS verbosity
{-progPathExtra-} vcs
1362 [ rerunIfChanged verbosity monitor repoGroup
' $ do
1363 vcs
' <- getConfiguredVCS repoType
1364 syncRepoGroupAndReadSourcePackages vcs
' pathStem repoGroup
'
1365 | repoGroup
@((primaryRepo
, repoType
) : _
) <- Map
.elems reposByLocation
1366 , let repoGroup
' = map fst repoGroup
1368 distDownloadSrcDirectory
1369 </> localFileNameForRemoteRepo primaryRepo
1373 [PackageSpecifier
(SourcePackage UnresolvedPkgLoc
)]
1374 monitor
= newFileMonitor
(pathStem
<.> "cache")
1377 syncRepoGroupAndReadSourcePackages
1378 :: VCS ConfiguredProgram
1381 -> Rebuild
[PackageSpecifier
(SourcePackage UnresolvedPkgLoc
)]
1382 syncRepoGroupAndReadSourcePackages vcs pathStem repoGroup
= do
1384 createDirectoryIfMissingVerbose
1387 distDownloadSrcDirectory
1389 -- For syncing we don't care about different 'SourceRepo' values that
1390 -- are just different subdirs in the same repo.
1395 |
(repo
, _
, repoPath
) <- repoGroupWithPaths
1398 -- Run post-checkout-command if it is specified
1399 for_ repoGroupWithPaths
$ \(repo
, _
, repoPath
) ->
1400 for_
(nonEmpty
(srpCommand repo
)) $ \(cmd
:| args
) -> liftIO
$ do
1401 maybeExit
$ rawSystemIOWithEnv verbosity cmd args
(Just repoPath
) Nothing Nothing Nothing Nothing
1403 -- But for reading we go through each 'SourceRepo' including its subdir
1404 -- value and have to know which path each one ended up in.
1406 [ readPackageFromSourceRepo repoWithSubdir repoPath
1407 |
(_
, reposWithSubdir
, repoPath
) <- repoGroupWithPaths
1408 , repoWithSubdir
<- NE
.toList reposWithSubdir
1411 -- So to do both things above, we pair them up here.
1413 :: [(SourceRepositoryPackage Proxy
, NonEmpty
(SourceRepositoryPackage
Maybe), FilePath)]
1414 repoGroupWithPaths
=
1416 (\(x
, y
) z
-> (x
, y
, z
))
1418 [ (repo
{srpSubdir
= Proxy
}, repo
)
1419 | repo
<- foldMap
(NE
.toList
. srpFanOut
) repoGroup
1424 mapGroup
:: Ord k
=> [(k
, v
)] -> [(k
, NonEmpty v
)]
1425 mapGroup
= Map
.toList
. Map
.fromListWith
(<>) . map (\(k
, v
) -> (k
, pure v
))
1427 -- The repos in a group are given distinct names by simple enumeration
1428 -- foo, foo-2, foo-3 etc
1429 repoPaths
:: [FilePath]
1432 : [pathStem
++ "-" ++ show (i
:: Int) | i
<- [2 ..]]
1434 readPackageFromSourceRepo
1435 :: SourceRepositoryPackage
Maybe
1437 -> Rebuild
(PackageSpecifier
(SourcePackage UnresolvedPkgLoc
))
1438 readPackageFromSourceRepo repo repoPath
= do
1439 let packageDir
:: FilePath
1440 packageDir
= maybe repoPath
(repoPath
</>) (srpSubdir repo
)
1442 entries
<- liftIO
$ getDirectoryContents packageDir
1443 -- TODO: dcoutts 2018-06-23: wrap exceptions
1444 case filter (\e
-> takeExtension e
== ".cabal") entries
of
1445 [] -> liftIO
$ throwIO
$ NoCabalFileFound packageDir
1446 (_
: _
: _
) -> liftIO
$ throwIO
$ MultipleCabalFilesFound packageDir
1447 [cabalFileName
] -> do
1448 let cabalFilePath
= packageDir
</> cabalFileName
1449 monitorFiles
[monitorFileHashed cabalFilePath
]
1450 gpd
<- liftIO
$ readSourcePackageCabalFile verbosity cabalFilePath
=<< BS
.readFile cabalFilePath
1452 -- write sdist tarball, to repoPath-pgkid
1453 tarball
<- liftIO
$ packageDirToSdist verbosity gpd packageDir
1454 let tarballPath
= repoPath
++ "-" ++ prettyShow
(packageId gpd
) ++ ".tar.gz"
1455 liftIO
$ LBS
.writeFile tarballPath tarball
1457 let location
= RemoteSourceRepoPackage repo tarballPath
1458 return $ mkSpecificSourcePackage location gpd
1460 reportSourceRepoProblems
:: [(SourceRepoList
, SourceRepoProblem
)] -> Rebuild a
1461 reportSourceRepoProblems
= liftIO
. dieWithException verbosity
. ReportSourceRepoProblems
. renderSourceRepoProblems
1463 renderSourceRepoProblems
:: [(SourceRepoList
, SourceRepoProblem
)] -> String
1464 renderSourceRepoProblems
= unlines . map show -- "TODO: the repo problems"
1466 -- | Utility used by all the helpers of 'fetchAndReadSourcePackages' to make an
1467 -- appropriate @'PackageSpecifier' ('SourcePackage' (..))@ for a given package
1468 -- from a given location.
1469 mkSpecificSourcePackage
1470 :: PackageLocation
FilePath
1471 -> GenericPackageDescription
1472 -> PackageSpecifier
(SourcePackage UnresolvedPkgLoc
)
1473 mkSpecificSourcePackage location pkg
=
1474 SpecificSourcePackage
1476 { srcpkgPackageId
= packageId pkg
1477 , srcpkgDescription
= pkg
1478 , srcpkgSource
= fmap Just location
1479 , srcpkgDescrOverride
= Nothing
1482 -- | Errors reported upon failing to parse a @.cabal@ file.
1483 data CabalFileParseError
1484 = CabalFileParseError
1486 -- ^ @.cabal@ file path
1488 -- ^ @.cabal@ file contents
1492 -- ^ We might discover the spec version the package needs
1497 -- | Manual instance which skips file contents
1498 instance Show CabalFileParseError
where
1499 showsPrec d
(CabalFileParseError fp _ es mv ws
) =
1500 showParen (d
> 10) $
1501 showString "CabalFileParseError"
1505 . showsPrec 11 ("" :: String)
1513 instance Exception CabalFileParseError
1514 #if MIN_VERSION_base
(4,8,0)
1516 displayException
= renderCabalFileParseError
1519 renderCabalFileParseError
:: CabalFileParseError
-> String
1520 renderCabalFileParseError
(CabalFileParseError filePath contents errors _ warnings
) =
1521 renderParseError filePath contents errors warnings
1523 -- | Wrapper for the @.cabal@ file parser. It reports warnings on higher
1524 -- verbosity levels and throws 'CabalFileParseError' on failure.
1525 readSourcePackageCabalFile
1529 -> IO GenericPackageDescription
1530 readSourcePackageCabalFile verbosity pkgfilename content
=
1531 case runParseResult
(parseGenericPackageDescription content
) of
1532 (warnings
, Right pkg
) -> do
1533 unless (null warnings
) $
1534 info verbosity
(formatWarnings warnings
)
1536 (warnings
, Left
(mspecVersion
, errors
)) ->
1537 throwIO
$ CabalFileParseError pkgfilename content errors mspecVersion warnings
1539 formatWarnings warnings
=
1540 "The package description file "
1542 ++ " has warnings: "
1543 ++ unlines (map (showPWarning pkgfilename
) warnings
)
1545 -- | When looking for a package's @.cabal@ file we can find none, or several,
1546 -- both of which are failures.
1547 data CabalFileSearchFailure
1548 = NoCabalFileFound
FilePath
1549 | MultipleCabalFilesFound
FilePath
1550 deriving (Show, Typeable
)
1552 instance Exception CabalFileSearchFailure
1554 -- | Find the @.cabal@ file within a tarball file and return it by value.
1556 -- Can fail with a 'Tar.FormatError' or 'CabalFileSearchFailure' exception.
1557 extractTarballPackageCabalFile
:: FilePath -> IO (FilePath, BS
.ByteString
)
1558 extractTarballPackageCabalFile tarballFile
=
1559 withBinaryFile tarballFile ReadMode
$ \hnd
-> do
1560 content
<- LBS
.hGetContents hnd
1561 case extractTarballPackageCabalFilePure tarballFile content
of
1562 Left
(Left e
) -> throwIO e
1563 Left
(Right e
) -> throwIO e
1564 Right
(fileName
, fileContent
) ->
1565 (,) fileName
<$> evaluate
(LBS
.toStrict fileContent
)
1567 -- | Scan through a tar file stream and collect the @.cabal@ file, or fail.
1568 extractTarballPackageCabalFilePure
1574 CabalFileSearchFailure
1576 (FilePath, LBS
.ByteString
)
1577 extractTarballPackageCabalFilePure tarballFile
=
1580 . Tar
.filterEntries isCabalFile
1582 . GZipUtils
.maybeDecompress
1586 (\m e
-> Map
.insert (Tar
.entryTarPath e
) e m
)
1589 check
(Left
(e
, _m
)) = Left
(Left e
)
1590 check
(Right m
) = case Map
.elems m
of
1591 [] -> Left
(Right
$ NoCabalFileFound tarballFile
)
1592 [file
] -> case Tar
.entryContent file
of
1593 Tar
.NormalFile content _
-> Right
(Tar
.entryPath file
, content
)
1594 _
-> Left
(Right
$ NoCabalFileFound tarballFile
)
1595 _files
-> Left
(Right
$ MultipleCabalFilesFound tarballFile
)
1597 isCabalFile e
= case splitPath
(Tar
.entryPath e
) of
1598 [_dir
, file
] -> takeExtension file
== ".cabal"
1599 [".", _dir
, file
] -> takeExtension file
== ".cabal"
1602 -- | The name to use for a local file for a remote tarball 'SourceRepo'.
1603 -- This is deterministic based on the remote tarball URI, and is intended
1604 -- to produce non-clashing file names for different tarballs.
1605 localFileNameForRemoteTarball
:: URI
-> FilePath
1606 localFileNameForRemoteTarball uri
=
1609 ++ showHex locationHash
""
1616 . dropTrailingPathSeparator
1619 locationHash
:: Word
1620 locationHash
= fromIntegral (Hashable
.hash
(uriToString
id uri
""))
1622 -- | The name to use for a local file or dir for a remote 'SourceRepo'.
1623 -- This is deterministic based on the source repo identity details, and
1624 -- intended to produce non-clashing file names for different repos.
1625 localFileNameForRemoteRepo
:: SourceRepoList
-> FilePath
1626 localFileNameForRemoteRepo SourceRepositoryPackage
{srpType
, srpLocation
} =
1627 mangleName srpLocation
++ "-" ++ showHex locationHash
""
1633 . dropTrailingPathSeparator
1635 -- just the parts that make up the "identity" of the repo
1636 locationHash
:: Word
1638 fromIntegral (Hashable
.hash
(show srpType
, srpLocation
))
1640 -- | Truncate a string, with a visual indication that it is truncated.
1641 truncateString
:: Int -> String -> String
1644 |
otherwise = take (n
- 1) s
++ "_"
1646 -- TODO: add something like this, here or in the project planning
1647 -- Based on the package location, which packages will be built inplace in the
1648 -- build tree vs placed in the store. This has various implications on what we
1649 -- can do with the package, e.g. can we run tests, ghci etc.
1651 -- packageIsLocalToProject :: ProjectPackageLocation -> Bool
1653 ---------------------------------------------
1654 -- Checking configuration sanity
1657 data BadPerPackageCompilerPaths
1658 = BadPerPackageCompilerPaths
[(PackageName
, String)]
1659 #if MIN_VERSION_base
(4,8,0)
1660 deriving (Show, Typeable
)
1664 instance Show BadPerPackageCompilerPaths
where
1665 show = renderBadPerPackageCompilerPaths
1668 #if MIN_VERSION_base
(4,8,0)
1669 instance Exception BadPerPackageCompilerPaths
where
1670 displayException
= renderBadPerPackageCompilerPaths
1672 instance Exception BadPerPackageCompilerPaths
1674 -- TODO: [nice to have] custom exception subclass for Doc rendering, colour etc
1676 renderBadPerPackageCompilerPaths
:: BadPerPackageCompilerPaths
-> String
1677 renderBadPerPackageCompilerPaths
1678 (BadPerPackageCompilerPaths
((pkgname
, progname
) : _
)) =
1679 "The path to the compiler program (or programs used by the compiler) "
1680 ++ "cannot be specified on a per-package basis in the cabal.project file "
1681 ++ "(i.e. setting the '"
1683 ++ "-location' for package '"
1684 ++ prettyShow pkgname
1685 ++ "'). All packages have to use the same compiler, so "
1686 ++ "specify the path in a global 'program-locations' section."
1687 -- TODO: [nice to have] better format control so we can pretty-print the
1688 -- offending part of the project file. Currently the line wrapping breaks any
1690 renderBadPerPackageCompilerPaths _
= error "renderBadPerPackageCompilerPaths"
1692 -- | The project configuration is not allowed to specify program locations for
1693 -- programs used by the compiler as these have to be the same for each set of
1696 -- We cannot check this until we know which programs the compiler uses, which
1697 -- in principle is not until we've configured the compiler.
1699 -- Throws 'BadPerPackageCompilerPaths'
1700 checkBadPerPackageCompilerPaths
1701 :: [ConfiguredProgram
]
1702 -> Map PackageName PackageConfig
1704 checkBadPerPackageCompilerPaths compilerPrograms packagesConfig
=
1705 case [ (pkgname
, progname
)
1706 |
let compProgNames
= Set
.fromList
(map programId compilerPrograms
)
1707 , (pkgname
, pkgconf
) <- Map
.toList packagesConfig
1708 , progname
<- Map
.keys
(getMapLast
(packageConfigProgramPaths pkgconf
))
1709 , progname `Set
.member` compProgNames
1712 ps
-> throwIO
(BadPerPackageCompilerPaths ps
)