1{-# LANGUAGE BangPatterns #-} 2{-# LANGUAGE CPP #-} 3{-# LANGUAGE DeriveDataTypeable #-} 4{-# LANGUAGE DeriveGeneric #-} 5{-# LANGUAGE LambdaCase #-} 6{-# LANGUAGE NamedFieldPuns #-} 7{-# LANGUAGE RecordWildCards #-} 8 9-- | Handling project configuration. 10-- 11module Distribution.Client.ProjectConfig ( 12 13 -- * Types for project config 14 ProjectConfig(..), 15 ProjectConfigBuildOnly(..), 16 ProjectConfigShared(..), 17 ProjectConfigProvenance(..), 18 PackageConfig(..), 19 MapLast(..), 20 MapMappend(..), 21 22 -- * Project root 23 findProjectRoot, 24 ProjectRoot(..), 25 BadProjectRoot(..), 26 27 -- * Project config files 28 readProjectConfig, 29 readGlobalConfig, 30 readProjectLocalFreezeConfig, 31 withProjectOrGlobalConfig, 32 withProjectOrGlobalConfigIgn, 33 writeProjectLocalExtraConfig, 34 writeProjectLocalFreezeConfig, 35 writeProjectConfigFile, 36 commandLineFlagsToProjectConfig, 37 38 -- * Packages within projects 39 ProjectPackageLocation(..), 40 BadPackageLocations(..), 41 BadPackageLocation(..), 42 BadPackageLocationMatch(..), 43 findProjectPackages, 44 fetchAndReadSourcePackages, 45 46 -- * Resolving configuration 47 lookupLocalPackageConfig, 48 projectConfigWithBuilderRepoContext, 49 projectConfigWithSolverRepoContext, 50 SolverSettings(..), 51 resolveSolverSettings, 52 BuildTimeSettings(..), 53 resolveBuildTimeSettings, 54 55 -- * Checking configuration 56 checkBadPerPackageCompilerPaths, 57 BadPerPackageCompilerPaths(..) 58 ) where 59 60import Prelude () 61import Distribution.Client.Compat.Prelude 62 63import Distribution.Client.ProjectConfig.Types 64import Distribution.Client.ProjectConfig.Legacy 65import Distribution.Client.RebuildMonad 66import Distribution.Client.Glob 67 ( isTrivialFilePathGlob ) 68import Distribution.Client.VCS 69 ( validateSourceRepos, SourceRepoProblem(..) 70 , VCS(..), knownVCSs, configureVCS, syncSourceRepos ) 71 72import Distribution.Client.Types 73import Distribution.Client.DistDirLayout 74 ( DistDirLayout(..), CabalDirLayout(..), ProjectRoot(..) ) 75import Distribution.Client.GlobalFlags 76 ( RepoContext(..), withRepoContext' ) 77import Distribution.Client.BuildReports.Types 78 ( ReportLevel(..) ) 79import Distribution.Client.Config 80 ( loadConfig, getConfigFilePath ) 81import Distribution.Client.HttpUtils 82 ( HttpTransport, configureTransport, transportCheckHttps 83 , downloadURI ) 84import Distribution.Client.Utils.Parsec (renderParseError) 85 86import Distribution.Solver.Types.SourcePackage 87import Distribution.Solver.Types.Settings 88import Distribution.Solver.Types.PackageConstraint 89 ( PackageProperty(..) ) 90 91import Distribution.Package 92 ( PackageName, PackageId, packageId, UnitId ) 93import Distribution.Types.PackageVersionConstraint 94 ( PackageVersionConstraint(..) ) 95import Distribution.System 96 ( Platform ) 97import Distribution.Types.GenericPackageDescription 98 ( GenericPackageDescription ) 99import Distribution.PackageDescription.Parsec 100 ( parseGenericPackageDescription ) 101import Distribution.Fields 102 ( runParseResult, PError, PWarning, showPWarning) 103import Distribution.Pretty (prettyShow) 104import Distribution.Types.SourceRepo 105 ( RepoType(..) ) 106import Distribution.Client.SourceRepo 107 ( SourceRepoList, SourceRepositoryPackage (..), srpFanOut ) 108import Distribution.Simple.Compiler 109 ( Compiler, compilerInfo ) 110import Distribution.Simple.Program 111 ( ConfiguredProgram(..) ) 112import Distribution.Simple.Setup 113 ( Flag(Flag), toFlag, flagToMaybe, flagToList 114 , fromFlag, fromFlagOrDefault ) 115import Distribution.Client.Setup 116 ( defaultSolver, defaultMaxBackjumps ) 117import Distribution.Simple.InstallDirs 118 ( PathTemplate, fromPathTemplate 119 , toPathTemplate, substPathTemplate, initialPathTemplateEnv ) 120import Distribution.Simple.Utils 121 ( die', warn, notice, info, createDirectoryIfMissingVerbose ) 122import Distribution.Client.Utils 123 ( determineNumJobs ) 124import Distribution.Utils.NubList 125 ( fromNubList ) 126import Distribution.Verbosity 127 ( Verbosity, modifyVerbosity, verbose ) 128import Distribution.Version 129 ( Version ) 130import Distribution.Deprecated.Text 131import qualified Distribution.Deprecated.ParseUtils as OldParser 132 ( ParseResult(..), locatedErrorMsg, showPWarning ) 133 134import qualified Codec.Archive.Tar as Tar 135import qualified Codec.Archive.Tar.Entry as Tar 136import qualified Distribution.Client.Tar as Tar 137import qualified Distribution.Client.GZipUtils as GZipUtils 138 139import Control.Monad 140import Control.Monad.Trans (liftIO) 141import Control.Exception 142import Data.Either 143import qualified Data.ByteString as BS 144import qualified Data.ByteString.Lazy as LBS 145import qualified Data.Map as Map 146import qualified Data.List.NonEmpty as NE 147import qualified Data.Set as Set 148import qualified Data.Hashable as Hashable 149import Numeric (showHex) 150 151import System.FilePath hiding (combine) 152import System.IO 153 ( withBinaryFile, IOMode(ReadMode) ) 154import System.Directory 155import Network.URI 156 ( URI(..), URIAuth(..), parseAbsoluteURI, uriToString ) 157 158 159---------------------------------------- 160-- Resolving configuration to settings 161-- 162 163-- | Look up a 'PackageConfig' field in the 'ProjectConfig' for a specific 164-- 'PackageName'. This returns the configuration that applies to all local 165-- packages plus any package-specific configuration for this package. 166-- 167lookupLocalPackageConfig 168 :: (Semigroup a, Monoid a) 169 => (PackageConfig -> a) -> ProjectConfig -> PackageName 170 -> a 171lookupLocalPackageConfig field ProjectConfig { 172 projectConfigLocalPackages, 173 projectConfigSpecificPackage 174 } pkgname = 175 field projectConfigLocalPackages 176 <> maybe mempty field 177 (Map.lookup pkgname (getMapMappend projectConfigSpecificPackage)) 178 179 180-- | Use a 'RepoContext' based on the 'BuildTimeSettings'. 181-- 182projectConfigWithBuilderRepoContext :: Verbosity 183 -> BuildTimeSettings 184 -> (RepoContext -> IO a) -> IO a 185projectConfigWithBuilderRepoContext verbosity BuildTimeSettings{..} = 186 withRepoContext' 187 verbosity 188 buildSettingRemoteRepos 189 buildSettingLocalRepos 190 buildSettingLocalNoIndexRepos 191 buildSettingCacheDir 192 buildSettingHttpTransport 193 (Just buildSettingIgnoreExpiry) 194 buildSettingProgPathExtra 195 196 197-- | Use a 'RepoContext', but only for the solver. The solver does not use the 198-- full facilities of the 'RepoContext' so we can get away with making one 199-- that doesn't have an http transport. And that avoids having to have access 200-- to the 'BuildTimeSettings' 201-- 202projectConfigWithSolverRepoContext 203 :: Verbosity -> ProjectConfigShared -> ProjectConfigBuildOnly 204 -> (RepoContext -> IO a) 205 -> IO a 206projectConfigWithSolverRepoContext verbosity 207 ProjectConfigShared{..} 208 ProjectConfigBuildOnly{..} = 209 withRepoContext' 210 verbosity 211 (fromNubList projectConfigRemoteRepos) 212 (fromNubList projectConfigLocalRepos) 213 (fromNubList projectConfigLocalNoIndexRepos) 214 (fromFlagOrDefault 215 (error 216 "projectConfigWithSolverRepoContext: projectConfigCacheDir") 217 projectConfigCacheDir) 218 (flagToMaybe projectConfigHttpTransport) 219 (flagToMaybe projectConfigIgnoreExpiry) 220 (fromNubList projectConfigProgPathExtra) 221 222 223-- | Resolve the project configuration, with all its optional fields, into 224-- 'SolverSettings' with no optional fields (by applying defaults). 225-- 226resolveSolverSettings :: ProjectConfig -> SolverSettings 227resolveSolverSettings ProjectConfig{ 228 projectConfigShared, 229 projectConfigLocalPackages, 230 projectConfigSpecificPackage 231 } = 232 SolverSettings {..} 233 where 234 --TODO: [required eventually] some of these settings need validation, e.g. 235 -- the flag assignments need checking. 236 solverSettingRemoteRepos = fromNubList projectConfigRemoteRepos 237 solverSettingLocalRepos = fromNubList projectConfigLocalRepos 238 solverSettingLocalNoIndexRepos = fromNubList projectConfigLocalNoIndexRepos 239 solverSettingConstraints = projectConfigConstraints 240 solverSettingPreferences = projectConfigPreferences 241 solverSettingFlagAssignment = packageConfigFlagAssignment projectConfigLocalPackages 242 solverSettingFlagAssignments = fmap packageConfigFlagAssignment 243 (getMapMappend projectConfigSpecificPackage) 244 solverSettingCabalVersion = flagToMaybe projectConfigCabalVersion 245 solverSettingSolver = fromFlag projectConfigSolver 246 solverSettingAllowOlder = fromMaybe mempty projectConfigAllowOlder 247 solverSettingAllowNewer = fromMaybe mempty projectConfigAllowNewer 248 solverSettingMaxBackjumps = case fromFlag projectConfigMaxBackjumps of 249 n | n < 0 -> Nothing 250 | otherwise -> Just n 251 solverSettingReorderGoals = fromFlag projectConfigReorderGoals 252 solverSettingCountConflicts = fromFlag projectConfigCountConflicts 253 solverSettingFineGrainedConflicts = fromFlag projectConfigFineGrainedConflicts 254 solverSettingMinimizeConflictSet = fromFlag projectConfigMinimizeConflictSet 255 solverSettingStrongFlags = fromFlag projectConfigStrongFlags 256 solverSettingAllowBootLibInstalls = fromFlag projectConfigAllowBootLibInstalls 257 solverSettingOnlyConstrained = fromFlag projectConfigOnlyConstrained 258 solverSettingIndexState = flagToMaybe projectConfigIndexState 259 solverSettingIndependentGoals = fromFlag projectConfigIndependentGoals 260 --solverSettingShadowPkgs = fromFlag projectConfigShadowPkgs 261 --solverSettingReinstall = fromFlag projectConfigReinstall 262 --solverSettingAvoidReinstalls = fromFlag projectConfigAvoidReinstalls 263 --solverSettingOverrideReinstall = fromFlag projectConfigOverrideReinstall 264 --solverSettingUpgradeDeps = fromFlag projectConfigUpgradeDeps 265 266 ProjectConfigShared {..} = defaults <> projectConfigShared 267 268 defaults = mempty { 269 projectConfigSolver = Flag defaultSolver, 270 projectConfigAllowOlder = Just (AllowOlder mempty), 271 projectConfigAllowNewer = Just (AllowNewer mempty), 272 projectConfigMaxBackjumps = Flag defaultMaxBackjumps, 273 projectConfigReorderGoals = Flag (ReorderGoals False), 274 projectConfigCountConflicts = Flag (CountConflicts True), 275 projectConfigFineGrainedConflicts = Flag (FineGrainedConflicts True), 276 projectConfigMinimizeConflictSet = Flag (MinimizeConflictSet False), 277 projectConfigStrongFlags = Flag (StrongFlags False), 278 projectConfigAllowBootLibInstalls = Flag (AllowBootLibInstalls False), 279 projectConfigOnlyConstrained = Flag OnlyConstrainedNone, 280 projectConfigIndependentGoals = Flag (IndependentGoals False) 281 --projectConfigShadowPkgs = Flag False, 282 --projectConfigReinstall = Flag False, 283 --projectConfigAvoidReinstalls = Flag False, 284 --projectConfigOverrideReinstall = Flag False, 285 --projectConfigUpgradeDeps = Flag False 286 } 287 288 289-- | Resolve the project configuration, with all its optional fields, into 290-- 'BuildTimeSettings' with no optional fields (by applying defaults). 291-- 292resolveBuildTimeSettings :: Verbosity 293 -> CabalDirLayout 294 -> ProjectConfig 295 -> BuildTimeSettings 296resolveBuildTimeSettings verbosity 297 CabalDirLayout { 298 cabalLogsDirectory 299 } 300 ProjectConfig { 301 projectConfigShared = ProjectConfigShared { 302 projectConfigRemoteRepos, 303 projectConfigLocalRepos, 304 projectConfigLocalNoIndexRepos, 305 projectConfigProgPathExtra 306 }, 307 projectConfigBuildOnly 308 } = 309 BuildTimeSettings {..} 310 where 311 buildSettingDryRun = fromFlag projectConfigDryRun 312 buildSettingOnlyDeps = fromFlag projectConfigOnlyDeps 313 buildSettingSummaryFile = fromNubList projectConfigSummaryFile 314 --buildSettingLogFile -- defined below, more complicated 315 --buildSettingLogVerbosity -- defined below, more complicated 316 buildSettingBuildReports = fromFlag projectConfigBuildReports 317 buildSettingSymlinkBinDir = flagToList projectConfigSymlinkBinDir 318 buildSettingOneShot = fromFlag projectConfigOneShot 319 buildSettingNumJobs = determineNumJobs projectConfigNumJobs 320 buildSettingKeepGoing = fromFlag projectConfigKeepGoing 321 buildSettingOfflineMode = fromFlag projectConfigOfflineMode 322 buildSettingKeepTempFiles = fromFlag projectConfigKeepTempFiles 323 buildSettingRemoteRepos = fromNubList projectConfigRemoteRepos 324 buildSettingLocalRepos = fromNubList projectConfigLocalRepos 325 buildSettingLocalNoIndexRepos = fromNubList projectConfigLocalNoIndexRepos 326 buildSettingCacheDir = fromFlag projectConfigCacheDir 327 buildSettingHttpTransport = flagToMaybe projectConfigHttpTransport 328 buildSettingIgnoreExpiry = fromFlag projectConfigIgnoreExpiry 329 buildSettingReportPlanningFailure 330 = fromFlag projectConfigReportPlanningFailure 331 buildSettingProgPathExtra = fromNubList projectConfigProgPathExtra 332 333 ProjectConfigBuildOnly{..} = defaults 334 <> projectConfigBuildOnly 335 336 defaults = mempty { 337 projectConfigDryRun = toFlag False, 338 projectConfigOnlyDeps = toFlag False, 339 projectConfigBuildReports = toFlag NoReports, 340 projectConfigReportPlanningFailure = toFlag False, 341 projectConfigKeepGoing = toFlag False, 342 projectConfigOneShot = toFlag False, 343 projectConfigOfflineMode = toFlag False, 344 projectConfigKeepTempFiles = toFlag False, 345 projectConfigIgnoreExpiry = toFlag False 346 } 347 348 -- The logging logic: what log file to use and what verbosity. 349 -- 350 -- If the user has specified --remote-build-reporting=detailed, use the 351 -- default log file location. If the --build-log option is set, use the 352 -- provided location. Otherwise don't use logging, unless building in 353 -- parallel (in which case the default location is used). 354 -- 355 buildSettingLogFile :: Maybe (Compiler -> Platform 356 -> PackageId -> UnitId -> FilePath) 357 buildSettingLogFile 358 | useDefaultTemplate = Just (substLogFileName defaultTemplate) 359 | otherwise = fmap substLogFileName givenTemplate 360 361 defaultTemplate = toPathTemplate $ 362 cabalLogsDirectory </> 363 "$compiler" </> "$libname" <.> "log" 364 givenTemplate = flagToMaybe projectConfigLogFile 365 366 useDefaultTemplate 367 | buildSettingBuildReports == DetailedReports = True 368 | isJust givenTemplate = False 369 | isParallelBuild = True 370 | otherwise = False 371 372 isParallelBuild = buildSettingNumJobs >= 2 373 374 substLogFileName :: PathTemplate 375 -> Compiler -> Platform 376 -> PackageId -> UnitId -> FilePath 377 substLogFileName template compiler platform pkgid uid = 378 fromPathTemplate (substPathTemplate env template) 379 where 380 env = initialPathTemplateEnv 381 pkgid uid (compilerInfo compiler) platform 382 383 -- If the user has specified --remote-build-reporting=detailed or 384 -- --build-log, use more verbose logging. 385 -- 386 buildSettingLogVerbosity 387 | overrideVerbosity = modifyVerbosity (max verbose) verbosity 388 | otherwise = verbosity 389 390 overrideVerbosity 391 | buildSettingBuildReports == DetailedReports = True 392 | isJust givenTemplate = True 393 | isParallelBuild = False 394 | otherwise = False 395 396 397--------------------------------------------- 398-- Reading and writing project config files 399-- 400 401-- | Find the root of this project. 402-- 403-- Searches for an explicit @cabal.project@ file, in the current directory or 404-- parent directories. If no project file is found then the current dir is the 405-- project root (and the project will use an implicit config). 406-- 407findProjectRoot :: Maybe FilePath -- ^ starting directory, or current directory 408 -> Maybe FilePath -- ^ @cabal.project@ file name override 409 -> IO (Either BadProjectRoot ProjectRoot) 410findProjectRoot _ (Just projectFile) 411 | isAbsolute projectFile = do 412 exists <- doesFileExist projectFile 413 if exists 414 then do projectFile' <- canonicalizePath projectFile 415 let projectRoot = ProjectRootExplicit (takeDirectory projectFile') 416 (takeFileName projectFile') 417 return (Right projectRoot) 418 else return (Left (BadProjectRootExplicitFile projectFile)) 419 420findProjectRoot mstartdir mprojectFile = do 421 startdir <- maybe getCurrentDirectory canonicalizePath mstartdir 422 homedir <- getHomeDirectory 423 probe startdir homedir 424 where 425 projectFileName = fromMaybe "cabal.project" mprojectFile 426 427 -- Search upwards. If we get to the users home dir or the filesystem root, 428 -- then use the current dir 429 probe startdir homedir = go startdir 430 where 431 go dir | isDrive dir || dir == homedir = 432 case mprojectFile of 433 Nothing -> return (Right (ProjectRootImplicit startdir)) 434 Just file -> return (Left (BadProjectRootExplicitFile file)) 435 go dir = do 436 exists <- doesFileExist (dir </> projectFileName) 437 if exists 438 then return (Right (ProjectRootExplicit dir projectFileName)) 439 else go (takeDirectory dir) 440 441 --TODO: [nice to have] add compat support for old style sandboxes 442 443 444-- | Errors returned by 'findProjectRoot'. 445-- 446data BadProjectRoot = BadProjectRootExplicitFile FilePath 447#if MIN_VERSION_base(4,8,0) 448 deriving (Show, Typeable) 449#else 450 deriving (Typeable) 451 452instance Show BadProjectRoot where 453 show = renderBadProjectRoot 454#endif 455 456instance Exception BadProjectRoot where 457#if MIN_VERSION_base(4,8,0) 458 displayException = renderBadProjectRoot 459#endif 460 461renderBadProjectRoot :: BadProjectRoot -> String 462renderBadProjectRoot (BadProjectRootExplicitFile projectFile) = 463 "The given project file '" ++ projectFile ++ "' does not exist." 464 465-- | Like 'withProjectOrGlobalConfig', with an additional boolean 466-- which tells to ignore local project. 467-- 468-- Used to implement -z / --ignore-project behaviour 469-- 470withProjectOrGlobalConfigIgn 471 :: Bool -- ^ whether to ignore local project 472 -> Verbosity 473 -> Flag FilePath 474 -> IO a 475 -> (ProjectConfig -> IO a) 476 -> IO a 477withProjectOrGlobalConfigIgn True verbosity gcf _with without = do 478 globalConfig <- runRebuild "" $ readGlobalConfig verbosity gcf 479 without globalConfig 480withProjectOrGlobalConfigIgn False verbosity gcf with without = 481 withProjectOrGlobalConfig verbosity gcf with without 482 483withProjectOrGlobalConfig :: Verbosity 484 -> Flag FilePath 485 -> IO a 486 -> (ProjectConfig -> IO a) 487 -> IO a 488withProjectOrGlobalConfig verbosity globalConfigFlag with without = do 489 globalConfig <- runRebuild "" $ readGlobalConfig verbosity globalConfigFlag 490 491 let 492 res' = catch with 493 $ \case 494 (BadPackageLocations prov locs) 495 | prov == Set.singleton Implicit 496 , let 497 isGlobErr (BadLocGlobEmptyMatch _) = True 498 isGlobErr _ = False 499 , any isGlobErr locs -> 500 without globalConfig 501 err -> throwIO err 502 503 catch res' 504 $ \case 505 (BadProjectRootExplicitFile "") -> without globalConfig 506 err -> throwIO err 507 508-- | Read all the config relevant for a project. This includes the project 509-- file if any, plus other global config. 510-- 511readProjectConfig :: Verbosity 512 -> Flag FilePath 513 -> DistDirLayout 514 -> Rebuild ProjectConfig 515readProjectConfig verbosity configFileFlag distDirLayout = do 516 global <- readGlobalConfig verbosity configFileFlag 517 local <- readProjectLocalConfigOrDefault verbosity distDirLayout 518 freeze <- readProjectLocalFreezeConfig verbosity distDirLayout 519 extra <- readProjectLocalExtraConfig verbosity distDirLayout 520 return (global <> local <> freeze <> extra) 521 522 523-- | Reads an explicit @cabal.project@ file in the given project root dir, 524-- or returns the default project config for an implicitly defined project. 525-- 526readProjectLocalConfigOrDefault :: Verbosity 527 -> DistDirLayout 528 -> Rebuild ProjectConfig 529readProjectLocalConfigOrDefault verbosity distDirLayout = do 530 usesExplicitProjectRoot <- liftIO $ doesFileExist projectFile 531 if usesExplicitProjectRoot 532 then do 533 readProjectFile verbosity distDirLayout "" "project file" 534 else do 535 monitorFiles [monitorNonExistentFile projectFile] 536 return defaultImplicitProjectConfig 537 538 where 539 projectFile = distProjectFile distDirLayout "" 540 541 defaultImplicitProjectConfig :: ProjectConfig 542 defaultImplicitProjectConfig = 543 mempty { 544 -- We expect a package in the current directory. 545 projectPackages = [ "./*.cabal" ], 546 547 -- This is to automatically pick up deps that we unpack locally. 548 projectPackagesOptional = [ "./*/*.cabal" ], 549 550 projectConfigProvenance = Set.singleton Implicit 551 } 552 553-- | Reads a @cabal.project.local@ file in the given project root dir, 554-- or returns empty. This file gets written by @cabal configure@, or in 555-- principle can be edited manually or by other tools. 556-- 557readProjectLocalExtraConfig :: Verbosity -> DistDirLayout 558 -> Rebuild ProjectConfig 559readProjectLocalExtraConfig verbosity distDirLayout = 560 readProjectFile verbosity distDirLayout "local" 561 "project local configuration file" 562 563-- | Reads a @cabal.project.freeze@ file in the given project root dir, 564-- or returns empty. This file gets written by @cabal freeze@, or in 565-- principle can be edited manually or by other tools. 566-- 567readProjectLocalFreezeConfig :: Verbosity -> DistDirLayout 568 -> Rebuild ProjectConfig 569readProjectLocalFreezeConfig verbosity distDirLayout = 570 readProjectFile verbosity distDirLayout "freeze" 571 "project freeze file" 572 573-- | Reads a named config file in the given project root dir, or returns empty. 574-- 575readProjectFile :: Verbosity 576 -> DistDirLayout 577 -> String 578 -> String 579 -> Rebuild ProjectConfig 580readProjectFile verbosity DistDirLayout{distProjectFile} 581 extensionName extensionDescription = do 582 exists <- liftIO $ doesFileExist extensionFile 583 if exists 584 then do monitorFiles [monitorFileHashed extensionFile] 585 addProjectFileProvenance <$> liftIO readExtensionFile 586 else do monitorFiles [monitorNonExistentFile extensionFile] 587 return mempty 588 where 589 extensionFile = distProjectFile extensionName 590 591 readExtensionFile = 592 reportParseResult verbosity extensionDescription extensionFile 593 . parseProjectConfig 594 =<< readFile extensionFile 595 596 addProjectFileProvenance config = 597 config { 598 projectConfigProvenance = 599 Set.insert (Explicit extensionFile) (projectConfigProvenance config) 600 } 601 602 603-- | Parse the 'ProjectConfig' format. 604-- 605-- For the moment this is implemented in terms of parsers for legacy 606-- configuration types, plus a conversion. 607-- 608parseProjectConfig :: String -> OldParser.ParseResult ProjectConfig 609parseProjectConfig content = 610 convertLegacyProjectConfig <$> 611 parseLegacyProjectConfig content 612 613 614-- | Render the 'ProjectConfig' format. 615-- 616-- For the moment this is implemented in terms of a pretty printer for the 617-- legacy configuration types, plus a conversion. 618-- 619showProjectConfig :: ProjectConfig -> String 620showProjectConfig = 621 showLegacyProjectConfig . convertToLegacyProjectConfig 622 623 624-- | Write a @cabal.project.local@ file in the given project root dir. 625-- 626writeProjectLocalExtraConfig :: DistDirLayout -> ProjectConfig -> IO () 627writeProjectLocalExtraConfig DistDirLayout{distProjectFile} = 628 writeProjectConfigFile (distProjectFile "local") 629 630 631-- | Write a @cabal.project.freeze@ file in the given project root dir. 632-- 633writeProjectLocalFreezeConfig :: DistDirLayout -> ProjectConfig -> IO () 634writeProjectLocalFreezeConfig DistDirLayout{distProjectFile} = 635 writeProjectConfigFile (distProjectFile "freeze") 636 637 638-- | Write in the @cabal.project@ format to the given file. 639-- 640writeProjectConfigFile :: FilePath -> ProjectConfig -> IO () 641writeProjectConfigFile file = 642 writeFile file . showProjectConfig 643 644 645-- | Read the user's @~/.cabal/config@ file. 646-- 647readGlobalConfig :: Verbosity -> Flag FilePath -> Rebuild ProjectConfig 648readGlobalConfig verbosity configFileFlag = do 649 config <- liftIO (loadConfig verbosity configFileFlag) 650 configFile <- liftIO (getConfigFilePath configFileFlag) 651 monitorFiles [monitorFileHashed configFile] 652 return (convertLegacyGlobalConfig config) 653 654reportParseResult :: Verbosity -> String -> FilePath -> OldParser.ParseResult a -> IO a 655reportParseResult verbosity _filetype filename (OldParser.ParseOk warnings x) = do 656 unless (null warnings) $ 657 let msg = unlines (map (OldParser.showPWarning filename) warnings) 658 in warn verbosity msg 659 return x 660reportParseResult verbosity filetype filename (OldParser.ParseFailed err) = 661 let (line, msg) = OldParser.locatedErrorMsg err 662 in die' verbosity $ "Error parsing " ++ filetype ++ " " ++ filename 663 ++ maybe "" (\n -> ':' : show n) line ++ ":\n" ++ msg 664 665 666--------------------------------------------- 667-- Finding packages in the project 668-- 669 670-- | The location of a package as part of a project. Local file paths are 671-- either absolute (if the user specified it as such) or they are relative 672-- to the project root. 673-- 674data ProjectPackageLocation = 675 ProjectPackageLocalCabalFile FilePath 676 | ProjectPackageLocalDirectory FilePath FilePath -- dir and .cabal file 677 | ProjectPackageLocalTarball FilePath 678 | ProjectPackageRemoteTarball URI 679 | ProjectPackageRemoteRepo SourceRepoList 680 | ProjectPackageNamed PackageVersionConstraint 681 deriving Show 682 683 684-- | Exception thrown by 'findProjectPackages'. 685-- 686data BadPackageLocations 687 = BadPackageLocations (Set ProjectConfigProvenance) [BadPackageLocation] 688#if MIN_VERSION_base(4,8,0) 689 deriving (Show, Typeable) 690#else 691 deriving (Typeable) 692 693instance Show BadPackageLocations where 694 show = renderBadPackageLocations 695#endif 696 697instance Exception BadPackageLocations where 698#if MIN_VERSION_base(4,8,0) 699 displayException = renderBadPackageLocations 700#endif 701--TODO: [nice to have] custom exception subclass for Doc rendering, colour etc 702 703data BadPackageLocation 704 = BadPackageLocationFile BadPackageLocationMatch 705 | BadLocGlobEmptyMatch String 706 | BadLocGlobBadMatches String [BadPackageLocationMatch] 707 | BadLocUnexpectedUriScheme String 708 | BadLocUnrecognisedUri String 709 | BadLocUnrecognised String 710 deriving Show 711 712data BadPackageLocationMatch 713 = BadLocUnexpectedFile String 714 | BadLocNonexistantFile String 715 | BadLocDirNoCabalFile String 716 | BadLocDirManyCabalFiles String 717 deriving Show 718 719renderBadPackageLocations :: BadPackageLocations -> String 720renderBadPackageLocations (BadPackageLocations provenance bpls) 721 -- There is no provenance information, 722 -- render standard bad package error information. 723 | Set.null provenance = renderErrors renderBadPackageLocation 724 725 -- The configuration is implicit, render bad package locations 726 -- using possibly specialized error messages. 727 | Set.singleton Implicit == provenance = 728 renderErrors renderImplicitBadPackageLocation 729 730 -- The configuration contains both implicit and explicit provenance. 731 -- This should not occur, and a message is output to assist debugging. 732 | Implicit `Set.member` provenance = 733 "Warning: both implicit and explicit configuration is present." 734 ++ renderExplicit 735 736 -- The configuration was read from one or more explicit path(s), 737 -- list the locations and render the bad package error information. 738 -- The intent is to supersede this with the relevant location information 739 -- per package error. 740 | otherwise = renderExplicit 741 where 742 renderErrors f = unlines (map f bpls) 743 744 renderExplicit = 745 "When using configuration(s) from " 746 ++ intercalate ", " (mapMaybe getExplicit (Set.toList provenance)) 747 ++ ", the following errors occurred:\n" 748 ++ renderErrors renderBadPackageLocation 749 750 getExplicit (Explicit path) = Just path 751 getExplicit Implicit = Nothing 752 753--TODO: [nice to have] keep track of the config file (and src loc) packages 754-- were listed, to use in error messages 755 756-- | Render bad package location error information for the implicit 757-- @cabal.project@ configuration. 758-- 759-- TODO: This is currently not fully realized, with only one of the implicit 760-- cases handled. More cases should be added with informative help text 761-- about the issues related specifically when having no project configuration 762-- is present. 763renderImplicitBadPackageLocation :: BadPackageLocation -> String 764renderImplicitBadPackageLocation bpl = case bpl of 765 BadLocGlobEmptyMatch pkglocstr -> 766 "No cabal.project file or cabal file matching the default glob '" 767 ++ pkglocstr ++ "' was found.\n" 768 ++ "Please create a package description file <pkgname>.cabal " 769 ++ "or a cabal.project file referencing the packages you " 770 ++ "want to build." 771 _ -> renderBadPackageLocation bpl 772 773renderBadPackageLocation :: BadPackageLocation -> String 774renderBadPackageLocation bpl = case bpl of 775 BadPackageLocationFile badmatch -> 776 renderBadPackageLocationMatch badmatch 777 BadLocGlobEmptyMatch pkglocstr -> 778 "The package location glob '" ++ pkglocstr 779 ++ "' does not match any files or directories." 780 BadLocGlobBadMatches pkglocstr failures -> 781 "The package location glob '" ++ pkglocstr ++ "' does not match any " 782 ++ "recognised forms of package. " 783 ++ concatMap ((' ':) . renderBadPackageLocationMatch) failures 784 BadLocUnexpectedUriScheme pkglocstr -> 785 "The package location URI '" ++ pkglocstr ++ "' does not use a " 786 ++ "supported URI scheme. The supported URI schemes are http, https and " 787 ++ "file." 788 BadLocUnrecognisedUri pkglocstr -> 789 "The package location URI '" ++ pkglocstr ++ "' does not appear to " 790 ++ "be a valid absolute URI." 791 BadLocUnrecognised pkglocstr -> 792 "The package location syntax '" ++ pkglocstr ++ "' is not recognised." 793 794renderBadPackageLocationMatch :: BadPackageLocationMatch -> String 795renderBadPackageLocationMatch bplm = case bplm of 796 BadLocUnexpectedFile pkglocstr -> 797 "The package location '" ++ pkglocstr ++ "' is not recognised. The " 798 ++ "supported file targets are .cabal files, .tar.gz tarballs or package " 799 ++ "directories (i.e. directories containing a .cabal file)." 800 BadLocNonexistantFile pkglocstr -> 801 "The package location '" ++ pkglocstr ++ "' does not exist." 802 BadLocDirNoCabalFile pkglocstr -> 803 "The package directory '" ++ pkglocstr ++ "' does not contain any " 804 ++ ".cabal file." 805 BadLocDirManyCabalFiles pkglocstr -> 806 "The package directory '" ++ pkglocstr ++ "' contains multiple " 807 ++ ".cabal files (which is not currently supported)." 808 809-- | Given the project config, 810-- 811-- Throws 'BadPackageLocations'. 812-- 813findProjectPackages :: DistDirLayout -> ProjectConfig 814 -> Rebuild [ProjectPackageLocation] 815findProjectPackages DistDirLayout{distProjectRootDirectory} 816 ProjectConfig{..} = do 817 818 requiredPkgs <- findPackageLocations True projectPackages 819 optionalPkgs <- findPackageLocations False projectPackagesOptional 820 let repoPkgs = map ProjectPackageRemoteRepo projectPackagesRepo 821 namedPkgs = map ProjectPackageNamed projectPackagesNamed 822 823 return (concat [requiredPkgs, optionalPkgs, repoPkgs, namedPkgs]) 824 where 825 findPackageLocations required pkglocstr = do 826 (problems, pkglocs) <- 827 partitionEithers <$> mapM (findPackageLocation required) pkglocstr 828 unless (null problems) $ 829 liftIO $ throwIO $ BadPackageLocations projectConfigProvenance problems 830 return (concat pkglocs) 831 832 833 findPackageLocation :: Bool -> String 834 -> Rebuild (Either BadPackageLocation 835 [ProjectPackageLocation]) 836 findPackageLocation _required@True pkglocstr = 837 -- strategy: try first as a file:// or http(s):// URL. 838 -- then as a file glob (usually encompassing single file) 839 -- finally as a single file, for files that fail to parse as globs 840 checkIsUriPackage pkglocstr 841 `mplusMaybeT` checkIsFileGlobPackage pkglocstr 842 `mplusMaybeT` checkIsSingleFilePackage pkglocstr 843 >>= maybe (return (Left (BadLocUnrecognised pkglocstr))) return 844 845 846 findPackageLocation _required@False pkglocstr = do 847 -- just globs for optional case 848 res <- checkIsFileGlobPackage pkglocstr 849 case res of 850 Nothing -> return (Left (BadLocUnrecognised pkglocstr)) 851 Just (Left _) -> return (Right []) -- it's optional 852 Just (Right pkglocs) -> return (Right pkglocs) 853 854 855 checkIsUriPackage, checkIsFileGlobPackage, checkIsSingleFilePackage 856 :: String -> Rebuild (Maybe (Either BadPackageLocation 857 [ProjectPackageLocation])) 858 checkIsUriPackage pkglocstr = 859 case parseAbsoluteURI pkglocstr of 860 Just uri@URI { 861 uriScheme = scheme, 862 uriAuthority = Just URIAuth { uriRegName = host }, 863 uriPath = path, 864 uriQuery = query, 865 uriFragment = frag 866 } 867 | recognisedScheme && not (null host) -> 868 return (Just (Right [ProjectPackageRemoteTarball uri])) 869 870 | scheme == "file:" && null host && null query && null frag -> 871 checkIsSingleFilePackage path 872 873 | not recognisedScheme && not (null host) -> 874 return (Just (Left (BadLocUnexpectedUriScheme pkglocstr))) 875 876 | recognisedScheme && null host -> 877 return (Just (Left (BadLocUnrecognisedUri pkglocstr))) 878 where 879 recognisedScheme = scheme == "http:" || scheme == "https:" 880 || scheme == "file:" 881 882 _ -> return Nothing 883 884 885 checkIsFileGlobPackage pkglocstr = 886 case simpleParse pkglocstr of 887 Nothing -> return Nothing 888 Just glob -> liftM Just $ do 889 matches <- matchFileGlob glob 890 case matches of 891 [] | isJust (isTrivialFilePathGlob glob) 892 -> return (Left (BadPackageLocationFile 893 (BadLocNonexistantFile pkglocstr))) 894 895 [] -> return (Left (BadLocGlobEmptyMatch pkglocstr)) 896 897 _ -> do 898 (failures, pkglocs) <- partitionEithers <$> 899 mapM checkFilePackageMatch matches 900 return $! case (failures, pkglocs) of 901 ([failure], []) | isJust (isTrivialFilePathGlob glob) 902 -> Left (BadPackageLocationFile failure) 903 (_, []) -> Left (BadLocGlobBadMatches pkglocstr failures) 904 _ -> Right pkglocs 905 906 907 checkIsSingleFilePackage pkglocstr = do 908 let filename = distProjectRootDirectory </> pkglocstr 909 isFile <- liftIO $ doesFileExist filename 910 isDir <- liftIO $ doesDirectoryExist filename 911 if isFile || isDir 912 then checkFilePackageMatch pkglocstr 913 >>= either (return . Just . Left . BadPackageLocationFile) 914 (return . Just . Right . (\x->[x])) 915 else return Nothing 916 917 918 checkFilePackageMatch :: String -> Rebuild (Either BadPackageLocationMatch 919 ProjectPackageLocation) 920 checkFilePackageMatch pkglocstr = do 921 -- The pkglocstr may be absolute or may be relative to the project root. 922 -- Either way, </> does the right thing here. We return relative paths if 923 -- they were relative in the first place. 924 let abspath = distProjectRootDirectory </> pkglocstr 925 isFile <- liftIO $ doesFileExist abspath 926 isDir <- liftIO $ doesDirectoryExist abspath 927 parentDirExists <- case takeDirectory abspath of 928 [] -> return False 929 dir -> liftIO $ doesDirectoryExist dir 930 case () of 931 _ | isDir 932 -> do matches <- matchFileGlob (globStarDotCabal pkglocstr) 933 case matches of 934 [cabalFile] 935 -> return (Right (ProjectPackageLocalDirectory 936 pkglocstr cabalFile)) 937 [] -> return (Left (BadLocDirNoCabalFile pkglocstr)) 938 _ -> return (Left (BadLocDirManyCabalFiles pkglocstr)) 939 940 | extensionIsTarGz pkglocstr 941 -> return (Right (ProjectPackageLocalTarball pkglocstr)) 942 943 | takeExtension pkglocstr == ".cabal" 944 -> return (Right (ProjectPackageLocalCabalFile pkglocstr)) 945 946 | isFile 947 -> return (Left (BadLocUnexpectedFile pkglocstr)) 948 949 | parentDirExists 950 -> return (Left (BadLocNonexistantFile pkglocstr)) 951 952 | otherwise 953 -> return (Left (BadLocUnexpectedFile pkglocstr)) 954 955 956 extensionIsTarGz f = takeExtension f == ".gz" 957 && takeExtension (dropExtension f) == ".tar" 958 959 960-- | A glob to find all the cabal files in a directory. 961-- 962-- For a directory @some/dir/@, this is a glob of the form @some/dir/\*.cabal@. 963-- The directory part can be either absolute or relative. 964-- 965globStarDotCabal :: FilePath -> FilePathGlob 966globStarDotCabal dir = 967 FilePathGlob 968 (if isAbsolute dir then FilePathRoot root else FilePathRelative) 969 (foldr (\d -> GlobDir [Literal d]) 970 (GlobFile [WildCard, Literal ".cabal"]) dirComponents) 971 where 972 (root, dirComponents) = fmap splitDirectories (splitDrive dir) 973 974 975--TODO: [code cleanup] use sufficiently recent transformers package 976mplusMaybeT :: Monad m => m (Maybe a) -> m (Maybe a) -> m (Maybe a) 977mplusMaybeT ma mb = do 978 mx <- ma 979 case mx of 980 Nothing -> mb 981 Just x -> return (Just x) 982 983 984------------------------------------------------- 985-- Fetching and reading packages in the project 986-- 987 988-- | Read the @.cabal@ files for a set of packages. For remote tarballs and 989-- VCS source repos this also fetches them if needed. 990-- 991-- Note here is where we convert from project-root relative paths to absolute 992-- paths. 993-- 994fetchAndReadSourcePackages 995 :: Verbosity 996 -> DistDirLayout 997 -> ProjectConfigShared 998 -> ProjectConfigBuildOnly 999 -> [ProjectPackageLocation] 1000 -> Rebuild [PackageSpecifier (SourcePackage UnresolvedPkgLoc)] 1001fetchAndReadSourcePackages verbosity distDirLayout 1002 projectConfigShared 1003 projectConfigBuildOnly 1004 pkgLocations = do 1005 1006 pkgsLocalDirectory <- 1007 sequence 1008 [ readSourcePackageLocalDirectory verbosity dir cabalFile 1009 | location <- pkgLocations 1010 , (dir, cabalFile) <- projectPackageLocal location ] 1011 1012 pkgsLocalTarball <- 1013 sequence 1014 [ readSourcePackageLocalTarball verbosity path 1015 | ProjectPackageLocalTarball path <- pkgLocations ] 1016 1017 pkgsRemoteTarball <- do 1018 getTransport <- delayInitSharedResource $ 1019 configureTransport verbosity progPathExtra 1020 preferredHttpTransport 1021 sequence 1022 [ fetchAndReadSourcePackageRemoteTarball verbosity distDirLayout 1023 getTransport uri 1024 | ProjectPackageRemoteTarball uri <- pkgLocations ] 1025 1026 pkgsRemoteRepo <- 1027 syncAndReadSourcePackagesRemoteRepos 1028 verbosity distDirLayout 1029 projectConfigShared 1030 [ repo | ProjectPackageRemoteRepo repo <- pkgLocations ] 1031 1032 let pkgsNamed = 1033 [ NamedPackage pkgname [PackagePropertyVersion verrange] 1034 | ProjectPackageNamed (PackageVersionConstraint pkgname verrange) <- pkgLocations ] 1035 1036 return $ concat 1037 [ pkgsLocalDirectory 1038 , pkgsLocalTarball 1039 , pkgsRemoteTarball 1040 , pkgsRemoteRepo 1041 , pkgsNamed 1042 ] 1043 where 1044 projectPackageLocal (ProjectPackageLocalDirectory dir file) = [(dir, file)] 1045 projectPackageLocal (ProjectPackageLocalCabalFile file) = [(dir, file)] 1046 where dir = takeDirectory file 1047 projectPackageLocal _ = [] 1048 1049 progPathExtra = fromNubList (projectConfigProgPathExtra projectConfigShared) 1050 preferredHttpTransport = 1051 flagToMaybe (projectConfigHttpTransport projectConfigBuildOnly) 1052 1053-- | A helper for 'fetchAndReadSourcePackages' to handle the case of 1054-- 'ProjectPackageLocalDirectory' and 'ProjectPackageLocalCabalFile'. 1055-- We simply read the @.cabal@ file. 1056-- 1057readSourcePackageLocalDirectory 1058 :: Verbosity 1059 -> FilePath -- ^ The package directory 1060 -> FilePath -- ^ The package @.cabal@ file 1061 -> Rebuild (PackageSpecifier (SourcePackage UnresolvedPkgLoc)) 1062readSourcePackageLocalDirectory verbosity dir cabalFile = do 1063 monitorFiles [monitorFileHashed cabalFile] 1064 root <- askRoot 1065 let location = LocalUnpackedPackage (root </> dir) 1066 liftIO $ fmap (mkSpecificSourcePackage location) 1067 . readSourcePackageCabalFile verbosity cabalFile 1068 =<< BS.readFile (root </> cabalFile) 1069 1070 1071-- | A helper for 'fetchAndReadSourcePackages' to handle the case of 1072-- 'ProjectPackageLocalTarball'. We scan through the @.tar.gz@ file to find 1073-- the @.cabal@ file and read that. 1074-- 1075readSourcePackageLocalTarball 1076 :: Verbosity 1077 -> FilePath 1078 -> Rebuild (PackageSpecifier (SourcePackage UnresolvedPkgLoc)) 1079readSourcePackageLocalTarball verbosity tarballFile = do 1080 monitorFiles [monitorFile tarballFile] 1081 root <- askRoot 1082 let location = LocalTarballPackage (root </> tarballFile) 1083 liftIO $ fmap (mkSpecificSourcePackage location) 1084 . uncurry (readSourcePackageCabalFile verbosity) 1085 =<< extractTarballPackageCabalFile (root </> tarballFile) 1086 1087-- | A helper for 'fetchAndReadSourcePackages' to handle the case of 1088-- 'ProjectPackageRemoteTarball'. We download the tarball to the dist src dir 1089-- and after that handle it like the local tarball case. 1090-- 1091fetchAndReadSourcePackageRemoteTarball 1092 :: Verbosity 1093 -> DistDirLayout 1094 -> Rebuild HttpTransport 1095 -> URI 1096 -> Rebuild (PackageSpecifier (SourcePackage UnresolvedPkgLoc)) 1097fetchAndReadSourcePackageRemoteTarball verbosity 1098 DistDirLayout { 1099 distDownloadSrcDirectory 1100 } 1101 getTransport 1102 tarballUri = 1103 -- The tarball download is expensive so we use another layer of file 1104 -- monitor to avoid it whenever possible. 1105 rerunIfChanged verbosity monitor tarballUri $ do 1106 1107 -- Download 1108 transport <- getTransport 1109 liftIO $ do 1110 transportCheckHttps verbosity transport tarballUri 1111 notice verbosity ("Downloading " ++ show tarballUri) 1112 createDirectoryIfMissingVerbose verbosity True 1113 distDownloadSrcDirectory 1114 _ <- downloadURI transport verbosity tarballUri tarballFile 1115 return () 1116 1117 -- Read 1118 monitorFiles [monitorFile tarballFile] 1119 let location = RemoteTarballPackage tarballUri tarballFile 1120 liftIO $ fmap (mkSpecificSourcePackage location) 1121 . uncurry (readSourcePackageCabalFile verbosity) 1122 =<< extractTarballPackageCabalFile tarballFile 1123 where 1124 tarballStem = distDownloadSrcDirectory 1125 </> localFileNameForRemoteTarball tarballUri 1126 tarballFile = tarballStem <.> "tar.gz" 1127 1128 monitor :: FileMonitor URI (PackageSpecifier (SourcePackage UnresolvedPkgLoc)) 1129 monitor = newFileMonitor (tarballStem <.> "cache") 1130 1131 1132-- | A helper for 'fetchAndReadSourcePackages' to handle all the cases of 1133-- 'ProjectPackageRemoteRepo'. 1134-- 1135syncAndReadSourcePackagesRemoteRepos 1136 :: Verbosity 1137 -> DistDirLayout 1138 -> ProjectConfigShared 1139 -> [SourceRepoList] 1140 -> Rebuild [PackageSpecifier (SourcePackage UnresolvedPkgLoc)] 1141syncAndReadSourcePackagesRemoteRepos verbosity 1142 DistDirLayout{distDownloadSrcDirectory} 1143 ProjectConfigShared { 1144 projectConfigProgPathExtra 1145 } 1146 repos = do 1147 1148 repos' <- either reportSourceRepoProblems return $ 1149 validateSourceRepos repos 1150 1151 -- All 'SourceRepo's grouped by referring to the "same" remote repo 1152 -- instance. So same location but can differ in commit/tag/branch/subdir. 1153 let reposByLocation :: Map (RepoType, String) 1154 [(SourceRepoList, RepoType)] 1155 reposByLocation = Map.fromListWith (++) 1156 [ ((rtype, rloc), [(repo, vcsRepoType vcs)]) 1157 | (repo, rloc, rtype, vcs) <- repos' ] 1158 1159 --TODO: pass progPathExtra on to 'configureVCS' 1160 let _progPathExtra = fromNubList projectConfigProgPathExtra 1161 getConfiguredVCS <- delayInitSharedResources $ \repoType -> 1162 let vcs = Map.findWithDefault (error $ "Unknown VCS: " ++ prettyShow repoType) repoType knownVCSs in 1163 configureVCS verbosity {-progPathExtra-} vcs 1164 1165 concat <$> sequence 1166 [ rerunIfChanged verbosity monitor repoGroup' $ do 1167 vcs' <- getConfiguredVCS repoType 1168 syncRepoGroupAndReadSourcePackages vcs' pathStem repoGroup' 1169 | repoGroup@((primaryRepo, repoType):_) <- Map.elems reposByLocation 1170 , let repoGroup' = map fst repoGroup 1171 pathStem = distDownloadSrcDirectory 1172 </> localFileNameForRemoteRepo primaryRepo 1173 monitor :: FileMonitor 1174 [SourceRepoList] 1175 [PackageSpecifier (SourcePackage UnresolvedPkgLoc)] 1176 monitor = newFileMonitor (pathStem <.> "cache") 1177 ] 1178 where 1179 syncRepoGroupAndReadSourcePackages 1180 :: VCS ConfiguredProgram 1181 -> FilePath 1182 -> [SourceRepoList] 1183 -> Rebuild [PackageSpecifier (SourcePackage UnresolvedPkgLoc)] 1184 syncRepoGroupAndReadSourcePackages vcs pathStem repoGroup = do 1185 liftIO $ createDirectoryIfMissingVerbose verbosity False 1186 distDownloadSrcDirectory 1187 1188 -- For syncing we don't care about different 'SourceRepo' values that 1189 -- are just different subdirs in the same repo. 1190 syncSourceRepos verbosity vcs 1191 [ (repo, repoPath) 1192 | (repo, _, repoPath) <- repoGroupWithPaths ] 1193 1194 -- But for reading we go through each 'SourceRepo' including its subdir 1195 -- value and have to know which path each one ended up in. 1196 sequence 1197 [ readPackageFromSourceRepo repoWithSubdir repoPath 1198 | (_, reposWithSubdir, repoPath) <- repoGroupWithPaths 1199 , repoWithSubdir <- NE.toList reposWithSubdir ] 1200 where 1201 -- So to do both things above, we pair them up here. 1202 repoGroupWithPaths 1203 :: [(SourceRepositoryPackage Proxy, NonEmpty (SourceRepositoryPackage Maybe), FilePath)] 1204 repoGroupWithPaths = 1205 zipWith (\(x, y) z -> (x,y,z)) 1206 (mapGroup 1207 [ (repo { srpSubdir = Proxy }, repo) 1208 | repo <- foldMap (NE.toList . srpFanOut) repoGroup 1209 ]) 1210 repoPaths 1211 1212 mapGroup :: Ord k => [(k, v)] -> [(k, NonEmpty v)] 1213 mapGroup = Map.toList . Map.fromListWith (<>) . map (\(k, v) -> (k, pure v)) 1214 1215 -- The repos in a group are given distinct names by simple enumeration 1216 -- foo, foo-2, foo-3 etc 1217 repoPaths :: [FilePath] 1218 repoPaths = pathStem 1219 : [ pathStem ++ "-" ++ show (i :: Int) | i <- [2..] ] 1220 1221 readPackageFromSourceRepo 1222 :: SourceRepositoryPackage Maybe -> FilePath 1223 -> Rebuild (PackageSpecifier (SourcePackage UnresolvedPkgLoc)) 1224 readPackageFromSourceRepo repo repoPath = do 1225 let packageDir = maybe repoPath (repoPath </>) (srpSubdir repo) 1226 entries <- liftIO $ getDirectoryContents packageDir 1227 --TODO: wrap exceptions 1228 case filter (\e -> takeExtension e == ".cabal") entries of 1229 [] -> liftIO $ throwIO $ NoCabalFileFound packageDir 1230 (_:_:_) -> liftIO $ throwIO $ MultipleCabalFilesFound packageDir 1231 [cabalFileName] -> do 1232 monitorFiles [monitorFileHashed cabalFilePath] 1233 liftIO $ fmap (mkSpecificSourcePackage location) 1234 . readSourcePackageCabalFile verbosity cabalFilePath 1235 =<< BS.readFile cabalFilePath 1236 where 1237 cabalFilePath = packageDir </> cabalFileName 1238 location = RemoteSourceRepoPackage repo packageDir 1239 1240 1241 reportSourceRepoProblems :: [(SourceRepoList, SourceRepoProblem)] -> Rebuild a 1242 reportSourceRepoProblems = liftIO . die' verbosity . renderSourceRepoProblems 1243 1244 renderSourceRepoProblems :: [(SourceRepoList, SourceRepoProblem)] -> String 1245 renderSourceRepoProblems = unlines . map show -- "TODO: the repo problems" 1246 1247 1248-- | Utility used by all the helpers of 'fetchAndReadSourcePackages' to make an 1249-- appropriate @'PackageSpecifier' ('SourcePackage' (..))@ for a given package 1250-- from a given location. 1251-- 1252mkSpecificSourcePackage :: PackageLocation FilePath 1253 -> GenericPackageDescription 1254 -> PackageSpecifier 1255 (SourcePackage (PackageLocation (Maybe FilePath))) 1256mkSpecificSourcePackage location pkg = 1257 SpecificSourcePackage SourcePackage { 1258 packageInfoId = packageId pkg, 1259 packageDescription = pkg, 1260 --TODO: it is silly that we still have to use a Maybe FilePath here 1261 packageSource = fmap Just location, 1262 packageDescrOverride = Nothing 1263 } 1264 1265 1266-- | Errors reported upon failing to parse a @.cabal@ file. 1267-- 1268data CabalFileParseError = CabalFileParseError 1269 FilePath -- ^ @.cabal@ file path 1270 BS.ByteString -- ^ @.cabal@ file contents 1271 (NonEmpty PError) -- ^ errors 1272 (Maybe Version) -- ^ We might discover the spec version the package needs 1273 [PWarning] -- ^ warnings 1274 deriving (Typeable) 1275 1276-- | Manual instance which skips file contentes 1277instance Show CabalFileParseError where 1278 showsPrec d (CabalFileParseError fp _ es mv ws) = showParen (d > 10) 1279 $ showString "CabalFileParseError" 1280 . showChar ' ' . showsPrec 11 fp 1281 . showChar ' ' . showsPrec 11 ("" :: String) 1282 . showChar ' ' . showsPrec 11 es 1283 . showChar ' ' . showsPrec 11 mv 1284 . showChar ' ' . showsPrec 11 ws 1285 1286instance Exception CabalFileParseError 1287#if MIN_VERSION_base(4,8,0) 1288 where 1289 displayException = renderCabalFileParseError 1290#endif 1291 1292renderCabalFileParseError :: CabalFileParseError -> String 1293renderCabalFileParseError (CabalFileParseError filePath contents errors _ warnings) = 1294 renderParseError filePath contents errors warnings 1295 1296-- | Wrapper for the @.cabal@ file parser. It reports warnings on higher 1297-- verbosity levels and throws 'CabalFileParseError' on failure. 1298-- 1299readSourcePackageCabalFile :: Verbosity 1300 -> FilePath 1301 -> BS.ByteString 1302 -> IO GenericPackageDescription 1303readSourcePackageCabalFile verbosity pkgfilename content = 1304 case runParseResult (parseGenericPackageDescription content) of 1305 (warnings, Right pkg) -> do 1306 unless (null warnings) $ 1307 info verbosity (formatWarnings warnings) 1308 return pkg 1309 1310 (warnings, Left (mspecVersion, errors)) -> 1311 throwIO $ CabalFileParseError pkgfilename content errors mspecVersion warnings 1312 where 1313 formatWarnings warnings = 1314 "The package description file " ++ pkgfilename 1315 ++ " has warnings: " 1316 ++ unlines (map (showPWarning pkgfilename) warnings) 1317 1318 1319-- | When looking for a package's @.cabal@ file we can find none, or several, 1320-- both of which are failures. 1321-- 1322data CabalFileSearchFailure 1323 = NoCabalFileFound FilePath 1324 | MultipleCabalFilesFound FilePath 1325 deriving (Show, Typeable) 1326 1327instance Exception CabalFileSearchFailure 1328 1329 1330-- | Find the @.cabal@ file within a tarball file and return it by value. 1331-- 1332-- Can fail with a 'Tar.FormatError' or 'CabalFileSearchFailure' exception. 1333-- 1334extractTarballPackageCabalFile :: FilePath -> IO (FilePath, BS.ByteString) 1335extractTarballPackageCabalFile tarballFile = 1336 withBinaryFile tarballFile ReadMode $ \hnd -> do 1337 content <- LBS.hGetContents hnd 1338 case extractTarballPackageCabalFilePure tarballFile content of 1339 Left (Left e) -> throwIO e 1340 Left (Right e) -> throwIO e 1341 Right (fileName, fileContent) -> 1342 (,) fileName <$> evaluate (LBS.toStrict fileContent) 1343 1344 1345-- | Scan through a tar file stream and collect the @.cabal@ file, or fail. 1346-- 1347extractTarballPackageCabalFilePure :: FilePath 1348 -> LBS.ByteString 1349 -> Either (Either Tar.FormatError 1350 CabalFileSearchFailure) 1351 (FilePath, LBS.ByteString) 1352extractTarballPackageCabalFilePure tarballFile = 1353 check 1354 . accumEntryMap 1355 . Tar.filterEntries isCabalFile 1356 . Tar.read 1357 . GZipUtils.maybeDecompress 1358 where 1359 accumEntryMap = Tar.foldlEntries 1360 (\m e -> Map.insert (Tar.entryTarPath e) e m) 1361 Map.empty 1362 1363 check (Left (e, _m)) = Left (Left e) 1364 check (Right m) = case Map.elems m of 1365 [] -> Left (Right $ NoCabalFileFound tarballFile) 1366 [file] -> case Tar.entryContent file of 1367 Tar.NormalFile content _ -> Right (Tar.entryPath file, content) 1368 _ -> Left (Right $ NoCabalFileFound tarballFile) 1369 _files -> Left (Right $ MultipleCabalFilesFound tarballFile) 1370 1371 isCabalFile e = case splitPath (Tar.entryPath e) of 1372 [ _dir, file] -> takeExtension file == ".cabal" 1373 [".", _dir, file] -> takeExtension file == ".cabal" 1374 _ -> False 1375 1376 1377-- | The name to use for a local file for a remote tarball 'SourceRepo'. 1378-- This is deterministic based on the remote tarball URI, and is intended 1379-- to produce non-clashing file names for different tarballs. 1380-- 1381localFileNameForRemoteTarball :: URI -> FilePath 1382localFileNameForRemoteTarball uri = 1383 mangleName uri 1384 ++ "-" ++ showHex locationHash "" 1385 where 1386 mangleName = truncateString 10 . dropExtension . dropExtension 1387 . takeFileName . dropTrailingPathSeparator . uriPath 1388 1389 locationHash :: Word 1390 locationHash = fromIntegral (Hashable.hash (uriToString id uri "")) 1391 1392 1393-- | The name to use for a local file or dir for a remote 'SourceRepo'. 1394-- This is deterministic based on the source repo identity details, and 1395-- intended to produce non-clashing file names for different repos. 1396-- 1397localFileNameForRemoteRepo :: SourceRepoList -> FilePath 1398localFileNameForRemoteRepo SourceRepositoryPackage {srpType, srpLocation} = 1399 mangleName srpLocation ++ "-" ++ showHex locationHash "" 1400 where 1401 mangleName = truncateString 10 . dropExtension 1402 . takeFileName . dropTrailingPathSeparator 1403 1404 -- just the parts that make up the "identity" of the repo 1405 locationHash :: Word 1406 locationHash = 1407 fromIntegral (Hashable.hash (show srpType, srpLocation)) 1408 1409 1410-- | Truncate a string, with a visual indication that it is truncated. 1411truncateString :: Int -> String -> String 1412truncateString n s | length s <= n = s 1413 | otherwise = take (n-1) s ++ "_" 1414 1415 1416-- TODO: add something like this, here or in the project planning 1417-- Based on the package location, which packages will be built inplace in the 1418-- build tree vs placed in the store. This has various implications on what we 1419-- can do with the package, e.g. can we run tests, ghci etc. 1420-- 1421-- packageIsLocalToProject :: ProjectPackageLocation -> Bool 1422 1423 1424--------------------------------------------- 1425-- Checking configuration sanity 1426-- 1427 1428data BadPerPackageCompilerPaths 1429 = BadPerPackageCompilerPaths [(PackageName, String)] 1430#if MIN_VERSION_base(4,8,0) 1431 deriving (Show, Typeable) 1432#else 1433 deriving (Typeable) 1434 1435instance Show BadPerPackageCompilerPaths where 1436 show = renderBadPerPackageCompilerPaths 1437#endif 1438 1439instance Exception BadPerPackageCompilerPaths where 1440#if MIN_VERSION_base(4,8,0) 1441 displayException = renderBadPerPackageCompilerPaths 1442#endif 1443--TODO: [nice to have] custom exception subclass for Doc rendering, colour etc 1444 1445renderBadPerPackageCompilerPaths :: BadPerPackageCompilerPaths -> String 1446renderBadPerPackageCompilerPaths 1447 (BadPerPackageCompilerPaths ((pkgname, progname) : _)) = 1448 "The path to the compiler program (or programs used by the compiler) " 1449 ++ "cannot be specified on a per-package basis in the cabal.project file " 1450 ++ "(i.e. setting the '" ++ progname ++ "-location' for package '" 1451 ++ display pkgname ++ "'). All packages have to use the same compiler, so " 1452 ++ "specify the path in a global 'program-locations' section." 1453 --TODO: [nice to have] better format control so we can pretty-print the 1454 -- offending part of the project file. Currently the line wrapping breaks any 1455 -- formatting. 1456renderBadPerPackageCompilerPaths _ = error "renderBadPerPackageCompilerPaths" 1457 1458-- | The project configuration is not allowed to specify program locations for 1459-- programs used by the compiler as these have to be the same for each set of 1460-- packages. 1461-- 1462-- We cannot check this until we know which programs the compiler uses, which 1463-- in principle is not until we've configured the compiler. 1464-- 1465-- Throws 'BadPerPackageCompilerPaths' 1466-- 1467checkBadPerPackageCompilerPaths :: [ConfiguredProgram] 1468 -> Map PackageName PackageConfig 1469 -> IO () 1470checkBadPerPackageCompilerPaths compilerPrograms packagesConfig = 1471 case [ (pkgname, progname) 1472 | let compProgNames = Set.fromList (map programId compilerPrograms) 1473 , (pkgname, pkgconf) <- Map.toList packagesConfig 1474 , progname <- Map.keys (getMapLast (packageConfigProgramPaths pkgconf)) 1475 , progname `Set.member` compProgNames ] of 1476 [] -> return () 1477 ps -> throwIO (BadPerPackageCompilerPaths ps) 1478