diff --git a/.gitignore b/.gitignore index a9213ae9c7f..0afbc7c0e1c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .cabal-sandbox/ cabal.sandbox.config cabal.project.local +cabal.config.local .ghc.environment.* cabal-dev/ .hpc/ diff --git a/Cabal/doc/nix-local-build.rst b/Cabal/doc/nix-local-build.rst index 4488118e268..57eca67ac06 100644 --- a/Cabal/doc/nix-local-build.rst +++ b/Cabal/doc/nix-local-build.rst @@ -481,11 +481,17 @@ following sources (later entries override earlier ones): 1. ``~/.cabal/config`` (the user-wide global configuration) -2. ``cabal.project`` (the project configuratoin) +2. ``cabal.config.local`` (the project-wide global configuration) -3. ``cabal.project.freeze`` (the output of ``cabal new-freeze``) +3. ``cabal.project`` (the project configuration) -4. ``cabal.project.local`` (the output of ``cabal new-configure``) +4. ``cabal.project.freeze`` (the output of ``cabal new-freeze``) + +5. ``cabal.project.local`` (the output of ``cabal new-configure``) + +The project configuration files ``cabal.project`` and ``cabal.project.local`` +specify options for project packages only, while the global configuration files +``~/.cabal/config`` and ``cabal.config.local`` affect *all* packages. Specifying the local packages diff --git a/cabal-install/Distribution/Client/CmdInstall.hs b/cabal-install/Distribution/Client/CmdInstall.hs index 197124b4422..36ed427ecab 100644 --- a/cabal-install/Distribution/Client/CmdInstall.hs +++ b/cabal-install/Distribution/Client/CmdInstall.hs @@ -242,7 +242,7 @@ establishDummyProjectBaseContext verbosity cliConfig tmpDir localPackages = do createDirectoryIfMissingVerbose verbosity True $ distProjectCacheDirectory distDirLayout globalConfig <- runRebuild "" - $ readGlobalConfig verbosity + $ readGlobalConfig verbosity distDirLayout $ projectConfigConfigFile $ projectConfigShared cliConfig let projectConfig = globalConfig <> cliConfig diff --git a/cabal-install/Distribution/Client/Config.hs b/cabal-install/Distribution/Client/Config.hs index a2d0ecb11d2..0330507fab1 100644 --- a/cabal-install/Distribution/Client/Config.hs +++ b/cabal-install/Distribution/Client/Config.hs @@ -16,6 +16,7 @@ module Distribution.Client.Config ( SavedConfig(..), loadConfig, + loadExactConfig, getConfigFilePath, showConfig, @@ -92,7 +93,7 @@ import Distribution.Simple.Command import Distribution.Simple.Program ( defaultProgramDb ) import Distribution.Simple.Utils - ( die', notice, warn, lowercase, cabalVersion ) + ( debug, die', notice, warn, lowercase, cabalVersion ) import Distribution.Compiler ( CompilerFlavor(..), defaultCompilerFlavor ) import Distribution.Verbosity @@ -613,31 +614,41 @@ extendToEffectiveConfig config = do loadRawConfig :: Verbosity -> Flag FilePath -> IO SavedConfig loadRawConfig verbosity configFileFlag = do (source, configFile) <- getConfigFilePathAndSource configFileFlag + debug verbosity $ "Global config file path source is " ++ sourceMsg source ++ "." + minp <- loadExactConfig verbosity configFile + case minp of + Nothing -> createDefaultConfigFile verbosity configFile + Just conf -> return conf + + where + sourceMsg CommandlineOption = "commandline option" + sourceMsg EnvironmentVariable = "env var CABAL_CONFIG" + sourceMsg Default = "default config file" + +data ConfigFileSource = CommandlineOption + | EnvironmentVariable + | Default + +-- | Like 'loadRawConfig', but loads only exactly the file given. +-- +loadExactConfig :: Verbosity -> FilePath -> IO (Maybe SavedConfig) +loadExactConfig verbosity configFile = do minp <- readConfigFile mempty configFile case minp of Nothing -> do - notice verbosity $ "Config file path source is " ++ sourceMsg source ++ "." - notice verbosity $ "Config file " ++ configFile ++ " not found." - createDefaultConfigFile verbosity configFile + debug verbosity $ "Config file " ++ configFile ++ " not found." + return Nothing Just (ParseOk ws conf) -> do + debug verbosity $ "Config file " ++ configFile ++ " loaded." unless (null ws) $ warn verbosity $ unlines (map (showPWarning configFile) ws) - return conf + return (Just conf) Just (ParseFailed err) -> do let (line, msg) = locatedErrorMsg err die' verbosity $ "Error parsing config file " ++ configFile ++ maybe "" (\n -> ':' : show n) line ++ ":\n" ++ msg - where - sourceMsg CommandlineOption = "commandline option" - sourceMsg EnvironmentVariable = "env var CABAL_CONFIG" - sourceMsg Default = "default config file" - -data ConfigFileSource = CommandlineOption - | EnvironmentVariable - | Default - -- | Returns the config file path, without checking that the file exists. -- The order of precedence is: input flag, CABAL_CONFIG, default location. getConfigFilePath :: Flag FilePath -> IO FilePath diff --git a/cabal-install/Distribution/Client/ProjectConfig.hs b/cabal-install/Distribution/Client/ProjectConfig.hs index ca67237157f..8598515b335 100644 --- a/cabal-install/Distribution/Client/ProjectConfig.hs +++ b/cabal-install/Distribution/Client/ProjectConfig.hs @@ -66,7 +66,7 @@ import Distribution.Client.GlobalFlags import Distribution.Client.BuildReports.Types ( ReportLevel(..) ) import Distribution.Client.Config - ( loadConfig, getConfigFilePath ) + ( getConfigFilePath, loadConfig, loadExactConfig ) import Distribution.Solver.Types.SourcePackage import Distribution.Solver.Types.Settings @@ -413,7 +413,7 @@ renderBadProjectRoot (BadProjectRootExplicitFile projectFile) = -- readProjectConfig :: Verbosity -> Flag FilePath -> DistDirLayout -> Rebuild ProjectConfig readProjectConfig verbosity configFileFlag distDirLayout = do - global <- readGlobalConfig verbosity configFileFlag + global <- readGlobalConfig verbosity distDirLayout configFileFlag local <- readProjectLocalConfig verbosity distDirLayout freeze <- readProjectLocalFreezeConfig verbosity distDirLayout extra <- readProjectLocalExtraConfig verbosity distDirLayout @@ -542,13 +542,25 @@ writeProjectConfigFile file = writeFile file . showProjectConfig --- | Read the user's @~/.cabal/config@ file. --- -readGlobalConfig :: Verbosity -> Flag FilePath -> Rebuild ProjectConfig -readGlobalConfig verbosity configFileFlag = do - config <- liftIO (loadConfig verbosity configFileFlag) - configFile <- liftIO (getConfigFilePath configFileFlag) - monitorFiles [monitorFileHashed configFile] +-- | Read the user's @~/.cabal/config@ file and any @cabal.config.local@ file in the +-- project root directory. +-- +readGlobalConfig + :: Verbosity + -> DistDirLayout + -> Flag FilePath + -> Rebuild ProjectConfig +readGlobalConfig verbosity layout configFileFlag = do + userConfig <- liftIO (loadConfig verbosity configFileFlag) + userConfigFile <- liftIO (getConfigFilePath configFileFlag) + let localConfigFile = distProjectRootDirectory layout "cabal.config.local" + localConfig <- liftIO (loadExactConfig verbosity localConfigFile) + monitorFiles + [ + monitorFileHashed userConfigFile, + monitorFileHashed localConfigFile + ] + let config = userConfig <> fromMaybe mempty localConfig return (convertLegacyGlobalConfig config) reportParseResult :: Verbosity -> String -> FilePath -> ParseResult a -> IO a diff --git a/cabal-install/Distribution/Client/ProjectConfig/Legacy.hs b/cabal-install/Distribution/Client/ProjectConfig/Legacy.hs index f584040c64c..8375d25cb5e 100644 --- a/cabal-install/Distribution/Client/ProjectConfig/Legacy.hs +++ b/cabal-install/Distribution/Client/ProjectConfig/Legacy.hs @@ -192,9 +192,9 @@ convertLegacyGlobalConfig savedHaddockFlags = haddockFlags } = mempty { - projectConfigShared = configAllPackages, - projectConfigLocalPackages = configLocalPackages, - projectConfigBuildOnly = configBuildOnly + projectConfigShared = configAllPackages, + projectConfigGlobalPackages = configLocalPackages, + projectConfigBuildOnly = configBuildOnly } where --TODO: [code cleanup] eliminate use of default*Flags here and specify the @@ -240,6 +240,7 @@ convertLegacyProjectConfig projectConfigBuildOnly = configBuildOnly, projectConfigShared = configAllPackages, projectConfigProvenance = mempty, + projectConfigGlobalPackages = mempty, projectConfigLocalPackages = configLocalPackages, projectConfigSpecificPackage = fmap perPackage legacySpecificConfig } diff --git a/cabal-install/Distribution/Client/ProjectConfig/Types.hs b/cabal-install/Distribution/Client/ProjectConfig/Types.hs index c93e17c15f4..7ebe463ca61 100644 --- a/cabal-install/Distribution/Client/ProjectConfig/Types.hs +++ b/cabal-install/Distribution/Client/ProjectConfig/Types.hs @@ -113,6 +113,10 @@ data ProjectConfig projectConfigShared :: ProjectConfigShared, projectConfigProvenance :: Set ProjectConfigProvenance, + -- | Configuration to be applied to all packages, + -- whether named in `cabal.project` or not. + projectConfigGlobalPackages :: PackageConfig, + -- | Configuration to be applied to *local* packages; i.e., -- any packages which are explicitly named in `cabal.project`. projectConfigLocalPackages :: PackageConfig, diff --git a/cabal-install/Distribution/Client/ProjectPlanning.hs b/cabal-install/Distribution/Client/ProjectPlanning.hs index bb93c286229..3eccb0fad9b 100644 --- a/cabal-install/Distribution/Client/ProjectPlanning.hs +++ b/cabal-install/Distribution/Client/ProjectPlanning.hs @@ -586,6 +586,7 @@ rebuildInstallPlan verbosity , ElaboratedSharedConfig ) phaseElaboratePlan ProjectConfig { projectConfigShared, + projectConfigGlobalPackages, projectConfigLocalPackages, projectConfigSpecificPackage, projectConfigBuildOnly @@ -613,6 +614,7 @@ rebuildInstallPlan verbosity sourcePackageHashes defaultInstallDirs projectConfigShared + projectConfigGlobalPackages projectConfigLocalPackages (getMapMappend projectConfigSpecificPackage) let instantiatedPlan = instantiateInstallPlan elaboratedPlan @@ -1138,6 +1140,7 @@ elaborateInstallPlan -> InstallDirs.InstallDirTemplates -> ProjectConfigShared -> PackageConfig + -> PackageConfig -> Map PackageName PackageConfig -> LogProgress (ElaboratedInstallPlan, ElaboratedSharedConfig) elaborateInstallPlan verbosity platform compiler compilerprogdb pkgConfigDB @@ -1146,7 +1149,8 @@ elaborateInstallPlan verbosity platform compiler compilerprogdb pkgConfigDB solverPlan localPackages sourcePackageHashes defaultInstallDirs - sharedPackageConfig + sharedConfig + globalPackagesConfig localPackagesConfig perPackageConfig = do x <- elaboratedInstallPlan @@ -1240,7 +1244,7 @@ elaborateInstallPlan verbosity platform compiler compilerprogdb pkgConfigDB -- For ease of testing, we let per-component builds be toggled -- at the top level cuz_flag - | fromFlagOrDefault True (projectConfigPerComponent sharedPackageConfig) + | fromFlagOrDefault True (projectConfigPerComponent sharedConfig) = [] | otherwise = cuz "you passed --disable-per-component" @@ -1754,14 +1758,15 @@ elaborateInstallPlan verbosity platform compiler compilerprogdb pkgConfigDB lookupPerPkgOption :: (Package pkg, Monoid m) => pkg -> (PackageConfig -> m) -> m - lookupPerPkgOption pkg f - -- the project config specifies values that apply to packages local to - -- but by default non-local packages get all default config values - -- the project, and can specify per-package values for any package, - | isLocalToProject pkg = local `mappend` perpkg - | otherwise = perpkg + lookupPerPkgOption pkg f = mconcat [global, local, perpkg] where - local = f localPackagesConfig + local + -- the project config specifies values that apply to packages local to + -- but by default non-local packages get all default config values + -- the project, and can specify per-package values for any package, + | isLocalToProject pkg = f localPackagesConfig + | otherwise = mempty + global = f globalPackagesConfig perpkg = maybe mempty f (Map.lookup (packageName pkg) perPackageConfig) inplacePackageDbs = storePackageDbs diff --git a/cabal-install/changelog b/cabal-install/changelog index 274d0491df8..f512b13f975 100644 --- a/cabal-install/changelog +++ b/cabal-install/changelog @@ -35,6 +35,8 @@ stay within the load command size limits of macOSs mach-o linker. * Use [lfxtb] letters to differentiate component kind instead of opaque "c" in dist-dir layout. + * New config file 'cabal.config.local' to specify project-wide + global options for project commands. 2.0.0.1 Mikhail Glushenkov October 2017 * Support for GHC's numeric -g debug levels (#4673). diff --git a/cabal-install/tests/UnitTests/Distribution/Client/ProjectConfig.hs b/cabal-install/tests/UnitTests/Distribution/Client/ProjectConfig.hs index 4124c4e51c0..e554e20b79b 100644 --- a/cabal-install/tests/UnitTests/Distribution/Client/ProjectConfig.hs +++ b/cabal-install/tests/UnitTests/Distribution/Client/ProjectConfig.hs @@ -284,6 +284,7 @@ instance Arbitrary ProjectConfig where <*> arbitrary <*> arbitrary <*> arbitrary + <*> arbitrary <*> (MapMappend . fmap getNonMEmpty . Map.fromList <$> shortListOf 3 arbitrary) -- package entries with no content are equivalent to @@ -297,7 +298,8 @@ instance Arbitrary ProjectConfig where , projectConfigShared = x5 , projectConfigProvenance = x6 , projectConfigLocalPackages = x7 - , projectConfigSpecificPackage = x8 } = + , projectConfigSpecificPackage = x8 + , projectConfigGlobalPackages = x9 } = [ ProjectConfig { projectPackages = x0' , projectPackagesOptional = x1' , projectPackagesRepo = x2' @@ -306,11 +308,12 @@ instance Arbitrary ProjectConfig where , projectConfigShared = x5' , projectConfigProvenance = x6' , projectConfigLocalPackages = x7' - , projectConfigSpecificPackage = (MapMappend - (fmap getNonMEmpty x8')) } - | ((x0', x1', x2', x3'), (x4', x5', x6', x7', x8')) + , projectConfigSpecificPackage = + MapMappend (fmap getNonMEmpty x8') + , projectConfigGlobalPackages = x9' } + | ((x0', x1', x2', x3'), (x4', x5', x6', x7', x8', x9')) <- shrink ((x0, x1, x2, x3), - (x4, x5, x6, x7, fmap NonMEmpty (getMapMappend x8))) + (x4, x5, x6, x7, fmap NonMEmpty (getMapMappend x8), x9)) ] newtype PackageLocationString