Skip to content

Commit ffe8d2c

Browse files
committed
Refactor CmdInstall
CmdInstall.installAction is ~300 lines long and full of nested scopes and ad-hoc logic. This change hopes to make it more readable and understandable. - Lift withProject and withoutProject out of installAction and split their relative concerns. E.g. not parsing URIs is installAction's concern not withProject's (which would just return a constant []). - Split an intermediate step into a separate function, resolveTargetSelectorsInProjectBaseContext. - Reuse withGlobalConfig and fromPkgId (renamed) - Fix a bug introduced in 802a326 where establishProjectBaseContext is called in a non-project setting. Also simplify its original implementation by moving the change into withProject rather than calling establishProjectBaseContext a second time.
1 parent 0a0b339 commit ffe8d2c

File tree

6 files changed

+205
-181
lines changed

6 files changed

+205
-181
lines changed

cabal-install/src/Distribution/Client/CmdInstall.hs

Lines changed: 167 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ import Distribution.Client.ProjectConfig
7070
, fetchAndReadSourcePackages
7171
, projectConfigWithBuilderRepoContext
7272
, resolveBuildTimeSettings
73+
, withGlobalConfig
7374
, withProjectOrGlobalConfig
7475
)
7576
import Distribution.Client.ProjectConfig.Types
@@ -344,153 +345,47 @@ installCommand =
344345
-- For more details on how this works, see the module
345346
-- "Distribution.Client.ProjectOrchestration"
346347
installAction :: NixStyleFlags ClientInstallFlags -> [String] -> GlobalFlags -> IO ()
347-
installAction flags@NixStyleFlags{extraFlags = clientInstallFlags', ..} targetStrings globalFlags = do
348+
installAction flags@NixStyleFlags{extraFlags, configFlags, installFlags, projectFlags} targetStrings globalFlags = do
348349
-- Ensure there were no invalid configuration options specified.
349350
verifyPreconditionsOrDie verbosity configFlags'
350351

351352
-- We cannot use establishDummyProjectBaseContext to get these flags, since
352353
-- it requires one of them as an argument. Normal establishProjectBaseContext
353354
-- does not, and this is why this is done only for the install command
354-
clientInstallFlags <- getClientInstallFlags verbosity globalFlags clientInstallFlags'
355-
355+
clientInstallFlags <- getClientInstallFlags verbosity globalFlags extraFlags
356+
-- FIXME: below commandLineFlagsToProjectConfig uses extraFlags
356357
let
357358
installLibs = fromFlagOrDefault False (cinstInstallLibs clientInstallFlags)
358-
targetFilter = if installLibs then Just LibKind else Just ExeKind
359-
targetStrings' = if null targetStrings then ["."] else targetStrings
360-
361-
-- Note the logic here is rather goofy. Target selectors of the form "foo:bar" also parse as uris.
362-
-- However, we want install to also take uri arguments. Hence, we only parse uri arguments in the case where
363-
-- no project file is present (including an implicit one derived from being in a package directory)
364-
-- or where the --ignore-project flag is passed explicitly. In such a case we only parse colon-free target selectors
365-
-- as selectors, and otherwise parse things as URIs.
366-
367-
-- However, in the special case where --ignore-project is passed with no selectors, we want to act as though this is
368-
-- a "normal" ignore project that actually builds and installs the selected package.
369-
370-
withProject :: IO ([PackageSpecifier UnresolvedSourcePackage], [URI], [TargetSelector], ProjectConfig)
371-
withProject = do
372-
let reducedVerbosity = lessVerbose verbosity
373-
374-
-- First, we need to learn about what's available to be installed.
375-
localBaseCtx <-
376-
establishProjectBaseContext reducedVerbosity baseCliConfig InstallCommand
377-
let localDistDirLayout = distDirLayout localBaseCtx
378-
pkgDb <-
379-
projectConfigWithBuilderRepoContext
380-
reducedVerbosity
381-
(buildSettings localBaseCtx)
382-
(getSourcePackages verbosity)
383-
384-
let
385-
(targetStrings'', packageIds) =
386-
partitionEithers
387-
. flip fmap targetStrings'
388-
$ \str -> case simpleParsec str of
389-
Just (pkgId :: PackageId)
390-
| pkgVersion pkgId /= nullVersion -> Right pkgId
391-
_ -> Left str
392-
packageSpecifiers =
393-
flip fmap packageIds $ \case
394-
PackageIdentifier{..}
395-
| pkgVersion == nullVersion -> NamedPackage pkgName []
396-
| otherwise ->
397-
NamedPackage
398-
pkgName
399-
[ PackagePropertyVersion
400-
(thisVersion pkgVersion)
401-
]
402-
packageTargets =
403-
flip TargetPackageNamed targetFilter . pkgName <$> packageIds
404-
405-
if null targetStrings'' -- if every selector is already resolved as a packageid, return without further parsing.
406-
then return (packageSpecifiers, [], packageTargets, projectConfig localBaseCtx)
407-
else do
408-
targetSelectors <-
409-
either (reportTargetSelectorProblems verbosity) return
410-
=<< readTargetSelectors
411-
(localPackages localBaseCtx)
412-
Nothing
413-
targetStrings''
414-
415-
(specs, selectors) <-
416-
getSpecsAndTargetSelectors
417-
verbosity
418-
reducedVerbosity
419-
pkgDb
420-
targetSelectors
421-
localDistDirLayout
422-
localBaseCtx
423-
targetFilter
424-
425-
return
426-
( specs ++ packageSpecifiers
427-
, []
428-
, selectors ++ packageTargets
429-
, projectConfig localBaseCtx
430-
)
431-
432-
withoutProject :: ProjectConfig -> IO ([PackageSpecifier UnresolvedSourcePackage], [URI], [TargetSelector], ProjectConfig)
433-
withoutProject _ | null targetStrings = withProject -- if there's no targets, we don't parse specially, but treat it as install in a standard cabal package dir
434-
withoutProject globalConfig = do
435-
tss <- traverse (parseWithoutProjectTargetSelector verbosity) targetStrings'
436-
let
437-
projectConfig = globalConfig <> baseCliConfig
438-
439-
ProjectConfigBuildOnly
440-
{ projectConfigLogsDir
441-
} = projectConfigBuildOnly projectConfig
442-
443-
ProjectConfigShared
444-
{ projectConfigStoreDir
445-
} = projectConfigShared projectConfig
446359

447-
mlogsDir = flagToMaybe projectConfigLogsDir
448-
mstoreDir = flagToMaybe projectConfigStoreDir
449-
cabalDirLayout <- mkCabalDirLayout mstoreDir mlogsDir
360+
normalisedTargetStrings = if null targetStrings then ["."] else targetStrings
450361

451-
let
452-
buildSettings =
453-
resolveBuildTimeSettings
454-
verbosity
455-
cabalDirLayout
456-
projectConfig
457-
458-
SourcePackageDb{packageIndex} <-
459-
projectConfigWithBuilderRepoContext
460-
verbosity
461-
buildSettings
462-
(getSourcePackages verbosity)
463-
464-
for_ (concatMap woPackageNames tss) $ \name -> do
465-
when (null (lookupPackageName packageIndex name)) $ do
466-
let xs = searchByName packageIndex (unPackageName name)
467-
let emptyIf True _ = []
468-
emptyIf False zs = zs
469-
str2 =
470-
emptyIf
471-
(null xs)
472-
[ "Did you mean any of the following?\n"
473-
, unlines (("- " ++) . unPackageName . fst <$> xs)
474-
]
475-
dieWithException verbosity $ WithoutProject (unPackageName name) str2
476-
477-
let
478-
(uris, packageSpecifiers) = partitionEithers $ map woPackageSpecifiers tss
479-
packageTargets = map woPackageTargets tss
480-
481-
return (packageSpecifiers, uris, packageTargets, projectConfig)
362+
-- Note the logic here is rather goofy. Target selectors of the form "foo:bar" also parse as uris.
363+
-- However, we want install to also take uri arguments. Hence, we only parse uri arguments in the case where
364+
-- no project file is present (including an implicit one derived from being in a package directory)
365+
-- or where the --ignore-project flag is passed explicitly. In such a case we only parse colon-free target selectors
366+
-- as selectors, and otherwise parse things as URIs.
482367

483-
(specs, uris, targetSelectors, baseConfig) <-
484-
withProjectOrGlobalConfig verbosity ignoreProject globalConfigFlag withProject withoutProject
368+
-- However, in the special case where --ignore-project is passed with no selectors, we want to act as though this is
369+
-- a "normal" ignore project that actually builds and installs the selected package.
485370

486-
-- We compute the base context again to determine packages available in the
487-
-- project to be installed, so we can list the available package names when
488-
-- the "all:..." variants of the target selectors are used.
489-
localPkgs <- localPackages <$> establishProjectBaseContext verbosity baseConfig InstallCommand
371+
(specs, uris, targetSelectors, config) <-
372+
let
373+
with = do
374+
(specs, targetSelectors, baseConfig) <-
375+
withProject verbosity cliConfig normalisedTargetStrings installLibs
376+
-- no URIs in this case
377+
return (specs, [], targetSelectors, baseConfig)
378+
379+
without =
380+
withGlobalConfig verbosity globalConfigFlag $ \globalConfig ->
381+
withoutProject verbosity (globalConfig <> cliConfig) normalisedTargetStrings
382+
in
383+
-- if there's no targets, we don't parse specially, but treat it as install in a standard cabal package dir
384+
if null targetStrings
385+
then with
386+
else withProjectOrGlobalConfig ignoreProject with without
490387

491388
let
492-
config = addLocalConfigToPkgs baseConfig (map pkgSpecifierTarget specs ++ concatMap (targetPkgNames localPkgs) targetSelectors)
493-
494389
ProjectConfig
495390
{ projectConfigBuildOnly =
496391
ProjectConfigBuildOnly
@@ -635,12 +530,13 @@ installAction flags@NixStyleFlags{extraFlags = clientInstallFlags', ..} targetSt
635530
configFlags' = disableTestsBenchsByDefault configFlags
636531
verbosity = fromFlagOrDefault normal (configVerbosity configFlags')
637532
ignoreProject = flagIgnoreProject projectFlags
638-
baseCliConfig =
533+
cliConfig =
639534
commandLineFlagsToProjectConfig
640535
globalFlags
641536
flags{configFlags = configFlags'}
642-
clientInstallFlags'
643-
globalConfigFlag = projectConfigConfigFile (projectConfigShared baseCliConfig)
537+
extraFlags
538+
539+
globalConfigFlag = projectConfigConfigFile (projectConfigShared cliConfig)
644540

645541
-- Do the install action for each executable in the install configuration.
646542
traverseInstall :: InstallAction -> InstallCfg -> IO ()
@@ -649,7 +545,133 @@ installAction flags@NixStyleFlags{extraFlags = clientInstallFlags', ..} targetSt
649545
actionOnExe <- action v overwritePolicy <$> prepareExeInstall cfg
650546
traverse_ actionOnExe . Map.toList $ targetsMap buildCtx
651547

652-
-- | Treat all direct targets of install command as local packages: #8637 and later #7297, #8909, #7236.
548+
withProject
549+
:: Verbosity
550+
-> ProjectConfig
551+
-> [String]
552+
-> Bool
553+
-> IO ([PackageSpecifier UnresolvedSourcePackage], [TargetSelector], ProjectConfig)
554+
withProject verbosity cliConfig targetStrings installLibs = do
555+
-- First, we need to learn about what's available to be installed.
556+
baseCtx <- establishProjectBaseContext reducedVerbosity cliConfig InstallCommand
557+
558+
(specs, selectors) <-
559+
-- if every selector is already resolved as a packageid, return without further parsing.
560+
if null unresolvedTargetStrings
561+
then return (parsedSpecifiers, parsedTargets)
562+
else do
563+
(resolvedSpecifiers, resolvedTargets) <-
564+
resolveTargetSelectorsInProjectBaseContext verbosity baseCtx targetStrings targetFilter
565+
return (resolvedSpecifiers ++ parsedSpecifiers, resolvedTargets ++ parsedTargets)
566+
567+
-- Treat all direct targets of install command as local packages: #8637 and later #7297, #8909, #7236.
568+
let config =
569+
addLocalConfigToPkgs (projectConfig baseCtx) $
570+
-- specifiers
571+
map pkgSpecifierTarget specs
572+
-- selectors
573+
++ concatMap (targetPkgNames $ localPackages baseCtx) selectors
574+
575+
return (specs, selectors, config)
576+
where
577+
reducedVerbosity = lessVerbose verbosity
578+
579+
(unresolvedTargetStrings, parsedPackageIds) =
580+
partitionEithers $
581+
flip map targetStrings $ \s ->
582+
case eitherParsec s of
583+
Right pkgId@PackageIdentifier{pkgVersion}
584+
| pkgVersion /= nullVersion ->
585+
pure pkgId
586+
_ -> Left s
587+
588+
parsedSpecifiers :: [PackageSpecifier pkg]
589+
parsedSpecifiers = map specFromPkgId parsedPackageIds
590+
591+
parsedTargets :: [TargetSelector]
592+
parsedTargets =
593+
[TargetPackageNamed (pkgName pkgId) targetFilter | pkgId <- parsedPackageIds]
594+
595+
targetFilter = if installLibs then Just LibKind else Just ExeKind
596+
597+
resolveTargetSelectorsInProjectBaseContext
598+
:: Verbosity
599+
-> ProjectBaseContext
600+
-> [String]
601+
-> Maybe ComponentKindFilter
602+
-> IO ([PackageSpecifier UnresolvedSourcePackage], [TargetSelector])
603+
resolveTargetSelectorsInProjectBaseContext verbosity baseCtx targetStrings targetFilter = do
604+
let reducedVerbosity = lessVerbose verbosity
605+
606+
pkgDb <-
607+
projectConfigWithBuilderRepoContext
608+
reducedVerbosity
609+
(buildSettings baseCtx)
610+
(getSourcePackages verbosity)
611+
612+
targetSelectors <-
613+
readTargetSelectors (localPackages baseCtx) Nothing targetStrings
614+
>>= \case
615+
Left problems -> reportTargetSelectorProblems verbosity problems
616+
Right ts -> return ts
617+
618+
getSpecsAndTargetSelectors
619+
verbosity
620+
reducedVerbosity
621+
pkgDb
622+
targetSelectors
623+
(distDirLayout baseCtx)
624+
baseCtx
625+
targetFilter
626+
627+
withoutProject
628+
:: Verbosity
629+
-> ProjectConfig
630+
-> [String]
631+
-> IO ([PackageSpecifier UnresolvedSourcePackage], [URI], [TargetSelector], ProjectConfig)
632+
withoutProject verbosity globalConfig targetStrings = do
633+
tss <- traverse (parseWithoutProjectTargetSelector verbosity) targetStrings
634+
let
635+
ProjectConfigBuildOnly
636+
{ projectConfigLogsDir
637+
} = projectConfigBuildOnly globalConfig
638+
639+
ProjectConfigShared
640+
{ projectConfigStoreDir
641+
} = projectConfigShared globalConfig
642+
643+
mlogsDir = flagToMaybe projectConfigLogsDir
644+
mstoreDir = flagToMaybe projectConfigStoreDir
645+
646+
cabalDirLayout <- mkCabalDirLayout mstoreDir mlogsDir
647+
648+
let buildSettings = resolveBuildTimeSettings verbosity cabalDirLayout globalConfig
649+
650+
SourcePackageDb{packageIndex} <-
651+
projectConfigWithBuilderRepoContext
652+
verbosity
653+
buildSettings
654+
(getSourcePackages verbosity)
655+
656+
for_ (concatMap woPackageNames tss) $ \name -> do
657+
when (null (lookupPackageName packageIndex name)) $ do
658+
let xs = searchByName packageIndex (unPackageName name)
659+
let emptyIf True _ = []
660+
emptyIf False zs = zs
661+
str2 =
662+
emptyIf
663+
(null xs)
664+
[ "Did you mean any of the following?\n"
665+
, unlines (("- " ++) . unPackageName . fst <$> xs)
666+
]
667+
dieWithException verbosity $ WithoutProject (unPackageName name) str2
668+
669+
let
670+
(uris, packageSpecifiers) = partitionEithers $ map woPackageSpecifiers tss
671+
packageTargets = map woPackageTargets tss
672+
673+
return (packageSpecifiers, uris, packageTargets, globalConfig)
674+
653675
addLocalConfigToPkgs :: ProjectConfig -> [PackageName] -> ProjectConfig
654676
addLocalConfigToPkgs config pkgs =
655677
config
@@ -707,8 +729,8 @@ getSpecsAndTargetSelectors
707729
-> ProjectBaseContext
708730
-> Maybe ComponentKindFilter
709731
-> IO ([PackageSpecifier UnresolvedSourcePackage], [TargetSelector])
710-
getSpecsAndTargetSelectors verbosity reducedVerbosity pkgDb targetSelectors localDistDirLayout localBaseCtx targetFilter =
711-
withInstallPlan reducedVerbosity localBaseCtx $ \elaboratedPlan _ -> do
732+
getSpecsAndTargetSelectors verbosity reducedVerbosity pkgDb targetSelectors distDirLayout baseCtx targetFilter =
733+
withInstallPlan reducedVerbosity baseCtx $ \elaboratedPlan _ -> do
712734
-- Split into known targets and hackage packages.
713735
(targets, hackageNames) <-
714736
partitionToKnownTargetsAndHackagePackages
@@ -724,11 +746,11 @@ getSpecsAndTargetSelectors verbosity reducedVerbosity pkgDb targetSelectors loca
724746
sdistize (SpecificSourcePackage spkg) =
725747
SpecificSourcePackage spkg'
726748
where
727-
sdistPath = distSdistFile localDistDirLayout (packageId spkg)
749+
sdistPath = distSdistFile distDirLayout (packageId spkg)
728750
spkg' = spkg{srcpkgSource = LocalTarballPackage sdistPath}
729751
sdistize named = named
730752

731-
local = sdistize <$> localPackages localBaseCtx
753+
local = sdistize <$> localPackages baseCtx
732754

733755
gatherTargets :: UnitId -> TargetSelector
734756
gatherTargets targetId = TargetPackageNamed pkgName targetFilter
@@ -745,15 +767,15 @@ getSpecsAndTargetSelectors verbosity reducedVerbosity pkgDb targetSelectors loca
745767
hackageTargets =
746768
flip TargetPackageNamed targetFilter <$> hackageNames
747769

748-
createDirectoryIfMissing True (distSdistDirectory localDistDirLayout)
770+
createDirectoryIfMissing True (distSdistDirectory distDirLayout)
749771

750-
unless (Map.null targets) $ for_ (localPackages localBaseCtx) $ \lpkg -> case lpkg of
772+
unless (Map.null targets) $ for_ (localPackages baseCtx) $ \case
751773
SpecificSourcePackage pkg ->
752774
packageToSdist
753775
verbosity
754-
(distProjectRootDirectory localDistDirLayout)
776+
(distProjectRootDirectory distDirLayout)
755777
TarGzArchive
756-
(distSdistFile localDistDirLayout (packageId pkg))
778+
(distSdistFile distDirLayout (packageId pkg))
757779
pkg
758780
NamedPackage pkgName _ -> error $ "Got NamedPackage " ++ prettyShow pkgName
759781

0 commit comments

Comments
 (0)