-
Notifications
You must be signed in to change notification settings - Fork 701
/
CmdInstall.hs
1398 lines (1277 loc) · 51.1 KB
/
CmdInstall.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
-- | cabal-install CLI command: build
module Distribution.Client.CmdInstall
( -- * The @build@ CLI and action
installCommand
, installAction
-- * Internals exposed for testing
, selectPackageTargets
, selectComponentTarget
-- * Internals exposed for CmdRepl + CmdRun
, establishDummyDistDirLayout
, establishDummyProjectBaseContext
) where
import Distribution.Client.Compat.Prelude
import Distribution.Compat.Directory
( doesPathExist
)
import Prelude ()
import Distribution.Client.CmdErrorMessages
import Distribution.Client.CmdSdist
import Distribution.Client.ProjectOrchestration
import Distribution.Client.TargetProblem
( TargetProblem (..)
, TargetProblem'
)
import Distribution.Client.CmdInstall.ClientInstallFlags
import Distribution.Client.CmdInstall.ClientInstallTargetSelector
import Distribution.Client.Config
( SavedConfig (..)
, defaultInstallPath
, loadConfig
)
import Distribution.Client.DistDirLayout
( CabalDirLayout (..)
, DistDirLayout (..)
, StoreDirLayout (..)
, cabalStoreDirLayout
, mkCabalDirLayout
)
import Distribution.Client.IndexUtils
( getInstalledPackages
, getSourcePackages
)
import qualified Distribution.Client.InstallPlan as InstallPlan
import Distribution.Client.InstallSymlink
( Symlink (..)
, promptRun
, symlinkBinary
, symlinkableBinary
, trySymlink
)
import Distribution.Client.NixStyleOptions
( NixStyleFlags (..)
, defaultNixStyleFlags
, nixStyleOptions
)
import Distribution.Client.ProjectConfig
( ProjectPackageLocation (..)
, fetchAndReadSourcePackages
, projectConfigWithBuilderRepoContext
, resolveBuildTimeSettings
, withGlobalConfig
, withProjectOrGlobalConfig
)
import Distribution.Client.ProjectConfig.Types
( MapMappend (..)
, PackageConfig (..)
, ProjectConfig (..)
, ProjectConfigBuildOnly (..)
, ProjectConfigShared (..)
, getMapLast
, getMapMappend
, projectConfigBuildOnly
, projectConfigConfigFile
, projectConfigLogsDir
, projectConfigStoreDir
)
import Distribution.Client.ProjectFlags (ProjectFlags (..))
import Distribution.Client.ProjectPlanning
( storePackageInstallDirs'
)
import Distribution.Client.ProjectPlanning.Types
( ElaboratedInstallPlan
)
import Distribution.Client.RebuildMonad
( runRebuild
)
import Distribution.Client.Setup
( CommonSetupFlags (..)
, ConfigFlags (..)
, GlobalFlags (..)
, InstallFlags (..)
)
import Distribution.Client.Types
( PackageLocation (..)
, PackageSpecifier (..)
, SourcePackageDb (..)
, UnresolvedSourcePackage
, mkNamedPackage
, pkgSpecifierTarget
)
import Distribution.Client.Types.OverwritePolicy
( OverwritePolicy (..)
)
import Distribution.Package
( Package (..)
, PackageName
, mkPackageName
, unPackageName
)
import Distribution.Simple.BuildPaths
( exeExtension
)
import Distribution.Simple.Command
( CommandUI (..)
, optionName
, usageAlternatives
)
import Distribution.Simple.Compiler
( Compiler (..)
, CompilerFlavor (..)
, CompilerId (..)
, PackageDBCWD
, PackageDBStackCWD
, PackageDBX (..)
)
import Distribution.Simple.Configure
( configCompilerEx
)
import Distribution.Simple.Flag
( flagElim
, flagToMaybe
, fromFlagOrDefault
)
import Distribution.Simple.GHC
( GhcEnvironmentFileEntry (..)
, GhcImplInfo (..)
, ParseErrorExc
, getGhcAppDir
, getImplInfo
, ghcPlatformAndVersionString
, readGhcEnvironmentFile
, renderGhcEnvironmentFile
)
import qualified Distribution.Simple.InstallDirs as InstallDirs
import qualified Distribution.Simple.PackageIndex as PI
import Distribution.Simple.Program.Db
( defaultProgramDb
, prependProgramSearchPath
, userSpecifyArgss
, userSpecifyPaths
)
import Distribution.Simple.Setup
( Flag (..)
, installDirsOptions
)
import Distribution.Simple.Utils
( createDirectoryIfMissingVerbose
, dieWithException
, notice
, ordNub
, safeHead
, warn
, withTempDirectory
, wrapText
)
import Distribution.Solver.Types.PackageConstraint
( PackageProperty (..)
)
import Distribution.Solver.Types.PackageIndex
( lookupPackageName
, searchByName
)
import Distribution.Solver.Types.SourcePackage
( SourcePackage (..)
)
import Distribution.System
( OS (Windows)
, Platform
, buildOS
)
import Distribution.Types.InstalledPackageInfo
( InstalledPackageInfo (..)
)
import Distribution.Types.PackageId
( PackageIdentifier (..)
)
import Distribution.Types.UnitId
( UnitId
)
import Distribution.Types.UnqualComponentName
( UnqualComponentName
, unUnqualComponentName
)
import Distribution.Types.Version
( Version
, nullVersion
)
import Distribution.Types.VersionRange
( thisVersion
)
import Distribution.Utils.Generic
( writeFileAtomic
)
import Distribution.Verbosity
( lessVerbose
, normal
)
import qualified Data.ByteString.Lazy.Char8 as BS
import qualified Data.List.NonEmpty as NE
import qualified Data.Map as Map
import Data.Ord
( Down (..)
)
import qualified Data.Set as S
import Distribution.Client.Errors
import Distribution.Utils.NubList
( fromNubList
)
import Network.URI (URI)
import System.Directory
( copyFile
, createDirectoryIfMissing
, doesDirectoryExist
, doesFileExist
, getTemporaryDirectory
, makeAbsolute
, removeDirectory
, removeFile
)
import System.FilePath
( takeBaseName
, takeDirectory
, (<.>)
, (</>)
)
-- | Check or check then install an exe. The check is to see if the overwrite
-- policy allows installation.
data InstallCheck
= -- | Only check if install is permitted.
InstallCheckOnly
| -- | Actually install but check first if permitted.
InstallCheckInstall
type InstallAction =
Verbosity
-> OverwritePolicy
-> InstallExe
-> (UnitId, [(ComponentTarget, NonEmpty TargetSelector)])
-> IO ()
data InstallCfg = InstallCfg
{ verbosity :: Verbosity
, baseCtx :: ProjectBaseContext
, buildCtx :: ProjectBuildContext
, platform :: Platform
, compiler :: Compiler
, installConfigFlags :: ConfigFlags
, installClientFlags :: ClientInstallFlags
}
-- | A record of install method, install directory and file path functions
-- needed by actions that either check if an install is possible or actually
-- perform an installation. This is for installation of executables only.
data InstallExe = InstallExe
{ installMethod :: InstallMethod
, installDir :: FilePath
, mkSourceBinDir :: UnitId -> FilePath
-- ^ A function to get an UnitId's store directory.
, mkExeName :: UnqualComponentName -> FilePath
-- ^ A function to get an exe's filename.
, mkFinalExeName :: UnqualComponentName -> FilePath
-- ^ A function to get an exe's final possibly different to the name in the
-- store.
}
installCommand :: CommandUI (NixStyleFlags ClientInstallFlags)
installCommand =
CommandUI
{ commandName = "v2-install"
, commandSynopsis = "Install packages."
, commandUsage =
usageAlternatives
"v2-install"
["[TARGETS] [FLAGS]"]
, commandDescription = Just $ \_ ->
wrapText $
"Installs one or more packages. This is done by installing them "
++ "in the store and symlinking or copying the executables in the directory "
++ "specified by the --installdir flag (`~/.local/bin/` by default). "
++ "If you want the installed executables to be available globally, "
++ "make sure that the PATH environment variable contains that directory. "
++ "\n\n"
++ "If TARGET is a library and --lib (provisional) is used, "
++ "it will be added to the global environment. "
++ "When doing this, cabal will try to build a plan that includes all "
++ "the previously installed libraries. This is currently not implemented."
, commandNotes = Just $ \pname ->
"Examples:\n"
++ " "
++ pname
++ " v2-install\n"
++ " Install the package in the current directory\n"
++ " "
++ pname
++ " v2-install pkgname\n"
++ " Install the package named pkgname"
++ " (fetching it from hackage if necessary)\n"
++ " "
++ pname
++ " v2-install ./pkgfoo\n"
++ " Install the package in the ./pkgfoo directory\n"
, commandOptions = \x -> filter notInstallDirOpt $ nixStyleOptions clientInstallOptions x
, commandDefaultFlags = defaultNixStyleFlags defaultClientInstallFlags
}
where
-- install doesn't take installDirs flags, since it always installs into the store in a fixed way.
notInstallDirOpt x = not $ optionName x `elem` installDirOptNames
installDirOptNames = map optionName installDirsOptions
-- | The @install@ command actually serves four different needs. It installs:
-- * exes:
-- For example a program from hackage. The behavior is similar to the old
-- install command, except that now conflicts between separate runs of the
-- command are impossible thanks to the store.
-- Exes are installed in the store like a normal dependency, then they are
-- symlinked/copied in the directory specified by --installdir.
-- To do this we need a dummy projectBaseContext containing the targets as
-- extra packages and using a temporary dist directory.
-- * libraries
-- Libraries install through a similar process, but using GHC environment
-- files instead of symlinks. This means that 'v2-install'ing libraries
-- only works on GHC >= 8.0.
--
-- For more details on how this works, see the module
-- "Distribution.Client.ProjectOrchestration"
installAction :: NixStyleFlags ClientInstallFlags -> [String] -> GlobalFlags -> IO ()
installAction flags@NixStyleFlags{extraFlags, configFlags, installFlags, projectFlags} targetStrings globalFlags = do
-- Ensure there were no invalid configuration options specified.
verifyPreconditionsOrDie verbosity configFlags'
-- We cannot use establishDummyProjectBaseContext to get these flags, since
-- it requires one of them as an argument. Normal establishProjectBaseContext
-- does not, and this is why this is done only for the install command
clientInstallFlags <- getClientInstallFlags verbosity globalFlags extraFlags
let
installLibs = fromFlagOrDefault False (cinstInstallLibs clientInstallFlags)
normalisedTargetStrings = if null targetStrings then ["."] else targetStrings
-- Note the logic here is rather goofy. Target selectors of the form "foo:bar" also parse as uris.
-- However, we want install to also take uri arguments. Hence, we only parse uri arguments in the case where
-- no project file is present (including an implicit one derived from being in a package directory)
-- or where the --ignore-project flag is passed explicitly. In such a case we only parse colon-free target selectors
-- as selectors, and otherwise parse things as URIs.
-- However, in the special case where --ignore-project is passed with no selectors, we want to act as though this is
-- a "normal" ignore project that actually builds and installs the selected package.
(pkgSpecs, uris, targetSelectors, config) <-
let
with = do
(pkgSpecs, targetSelectors, baseConfig) <-
withProject verbosity cliConfig normalisedTargetStrings installLibs
-- No URIs in this case, see note above
return (pkgSpecs, [], targetSelectors, baseConfig)
without =
withGlobalConfig verbosity globalConfigFlag $ \globalConfig ->
withoutProject verbosity (globalConfig <> cliConfig) normalisedTargetStrings
in
-- If there's no targets it does not make sense to not be in a project.
if null targetStrings
then with
else withProjectOrGlobalConfig ignoreProject with without
-- NOTE: CmdInstall and project local packages.
--
-- CmdInstall always installs packages from a source distribution that, in case of unpackage
-- packages, is created automatically. This is implemented in getSpecsAndTargetSelectors.
--
-- This has the inconvenience that the planner will consider all packages as non-local
-- (see `ProjectPlanning.shouldBeLocal`) and that any project or cli configuration will
-- not apply to them.
--
-- We rectify this here. In the project configuration, we copy projectConfigLocalPackages to a
-- new projectConfigSpecificPackage entry for each package corresponding to a target selector.
--
-- See #8637 and later #7297, #8909, #7236.
let
ProjectConfig
{ projectConfigBuildOnly =
ProjectConfigBuildOnly
{ projectConfigLogsDir
}
, projectConfigShared =
ProjectConfigShared
{ projectConfigHcFlavor
, projectConfigHcPath
, projectConfigHcPkg
, projectConfigStoreDir
, projectConfigProgPathExtra
, projectConfigPackageDBs
}
, projectConfigLocalPackages =
PackageConfig
{ packageConfigProgramPaths
, packageConfigProgramArgs
, packageConfigProgramPathExtra
}
} = config
hcFlavor = flagToMaybe projectConfigHcFlavor
hcPath = flagToMaybe projectConfigHcPath
hcPkg = flagToMaybe projectConfigHcPkg
extraPath = fromNubList packageConfigProgramPathExtra ++ fromNubList projectConfigProgPathExtra
configProgDb <- prependProgramSearchPath verbosity extraPath [] defaultProgramDb
let
-- ProgramDb with directly user specified paths
preProgDb =
userSpecifyPaths (Map.toList (getMapLast packageConfigProgramPaths))
. userSpecifyArgss (Map.toList (getMapMappend packageConfigProgramArgs))
$ configProgDb
-- progDb is a program database with compiler tools configured properly
(compiler@Compiler{compilerId = CompilerId compilerFlavor compilerVersion}, platform, progDb) <-
configCompilerEx hcFlavor hcPath hcPkg preProgDb verbosity
let
GhcImplInfo{supportsPkgEnvFiles} = getImplInfo compiler
(usedPackageEnvFlag, envFile) <- getEnvFile clientInstallFlags platform compilerVersion
(usedExistingPkgEnvFile, existingEnvEntries) <-
getExistingEnvEntries verbosity compilerFlavor supportsPkgEnvFiles envFile
packageDbs <- getPackageDbStack compiler projectConfigStoreDir projectConfigLogsDir projectConfigPackageDBs
installedIndex <- getInstalledPackages verbosity compiler packageDbs progDb
let
(envSpecs, nonGlobalEnvEntries) =
getEnvSpecsAndNonGlobalEntries installedIndex existingEnvEntries installLibs
-- Second, we need to use a fake project to let Cabal build the
-- installables correctly. For that, we need a place to put a
-- temporary dist directory.
globalTmp <- getTemporaryDirectory
withTempDirectory verbosity globalTmp "cabal-install." $ \tmpDir -> do
distDirLayout <- establishDummyDistDirLayout verbosity config tmpDir
uriSpecs <-
runRebuild tmpDir $
fetchAndReadSourcePackages
verbosity
distDirLayout
compiler
(projectConfigShared config)
(projectConfigBuildOnly config)
[ProjectPackageRemoteTarball uri | uri <- uris]
-- check for targets already in env
let getPackageName :: PackageSpecifier UnresolvedSourcePackage -> PackageName
getPackageName = pkgSpecifierTarget
targetNames = S.fromList $ map getPackageName (pkgSpecs ++ uriSpecs)
envNames = S.fromList $ map getPackageName envSpecs
forceInstall = fromFlagOrDefault False $ installOverrideReinstall installFlags
nameIntersection = S.intersection targetNames envNames
-- we check for intersections in targets with the existing env
(envSpecs', nonGlobalEnvEntries') <-
if null nameIntersection
then pure (envSpecs, map snd nonGlobalEnvEntries)
else
if forceInstall
then
let es = filter (\e -> not $ getPackageName e `S.member` nameIntersection) envSpecs
nge = map snd . filter (\e -> not $ fst e `S.member` nameIntersection) $ nonGlobalEnvEntries
in pure (es, nge)
else dieWithException verbosity $ PackagesAlreadyExistInEnvfile envFile (map prettyShow $ S.toList nameIntersection)
-- we construct an installed index of files in the cleaned target environment (absent overwrites) so that
-- we can solve with regards to packages installed locally but not in the upstream repo
let installedPacks = PI.allPackagesByName installedIndex
newEnvNames = S.fromList $ map getPackageName envSpecs'
installedIndex' = PI.fromList . concatMap snd . filter (\p -> fst p `S.member` newEnvNames) $ installedPacks
baseCtx <-
establishDummyProjectBaseContext
verbosity
config
distDirLayout
(envSpecs' ++ pkgSpecs ++ uriSpecs)
InstallCommand
buildCtx <- constructProjectBuildContext verbosity (baseCtx{installedPackages = Just installedIndex'}) targetSelectors
printPlan verbosity baseCtx buildCtx
let installCfg = InstallCfg verbosity baseCtx buildCtx platform compiler configFlags clientInstallFlags
let
dryRun =
buildSettingDryRun (buildSettings baseCtx)
|| buildSettingOnlyDownload (buildSettings baseCtx)
-- Before building, check if we could install any built exe by symlinking or
-- copying it?
unless
(dryRun || installLibs)
(traverseInstall (installCheckUnitExes InstallCheckOnly) installCfg)
buildOutcomes <- runProjectBuildPhase verbosity baseCtx buildCtx
runProjectPostBuildPhase verbosity baseCtx buildCtx buildOutcomes
-- Having built everything, do the install.
unless dryRun $
if installLibs
then
installLibraries
verbosity
buildCtx
installedIndex
compiler
packageDbs
envFile
nonGlobalEnvEntries'
(not usedExistingPkgEnvFile && not usedPackageEnvFlag)
else -- Install any built exe by symlinking or copying it we don't use
-- BuildOutcomes because we also need the component names
traverseInstall (installCheckUnitExes InstallCheckInstall) installCfg
where
configFlags' = disableTestsBenchsByDefault . ignoreProgramAffixes $ configFlags
verbosity = fromFlagOrDefault normal (setupVerbosity $ configCommonFlags configFlags')
ignoreProject = flagIgnoreProject projectFlags
cliConfig =
commandLineFlagsToProjectConfig
globalFlags
flags{configFlags = configFlags'}
extraFlags
globalConfigFlag = projectConfigConfigFile (projectConfigShared cliConfig)
-- Do the install action for each executable in the install configuration.
traverseInstall :: InstallAction -> InstallCfg -> IO ()
traverseInstall action cfg@InstallCfg{verbosity = v, buildCtx, installClientFlags} = do
let overwritePolicy = fromFlagOrDefault NeverOverwrite $ cinstOverwritePolicy installClientFlags
actionOnExe <- action v overwritePolicy <$> prepareExeInstall cfg
traverse_ actionOnExe . Map.toList $ targetsMap buildCtx
withProject
:: Verbosity
-> ProjectConfig
-> [String]
-> Bool
-> IO ([PackageSpecifier UnresolvedSourcePackage], [TargetSelector], ProjectConfig)
withProject verbosity cliConfig targetStrings installLibs = do
-- First, we need to learn about what's available to be installed.
baseCtx <- establishProjectBaseContext reducedVerbosity cliConfig InstallCommand
(pkgSpecs, targetSelectors) <-
-- If every target is already resolved to a package id, we can return without any further parsing.
if null unresolvedTargetStrings
then return (parsedPkgSpecs, parsedTargets)
else do
-- Anything that could not be parsed as a packageId (e.g. a package name without a version or
-- a target syntax using colons) must be resolved inside the project context.
(resolvedPkgSpecs, resolvedTargets) <-
resolveTargetSelectorsInProjectBaseContext verbosity baseCtx unresolvedTargetStrings targetFilter
return (resolvedPkgSpecs ++ parsedPkgSpecs, resolvedTargets ++ parsedTargets)
-- Apply the local configuration (e.g. cli flags) to all direct targets of install command, see note
-- in 'installAction'.
--
-- NOTE: If a target string had to be resolved inside the project context, then pkgSpecs will include
-- the project packages turned into source distributions (getSpecsAndTargetSelectors does this).
-- We want to apply the local configuration only to the actual targets.
let config =
addLocalConfigToPkgs (projectConfig baseCtx) $
concatMap (targetPkgNames $ localPackages baseCtx) targetSelectors
return (pkgSpecs, targetSelectors, config)
where
reducedVerbosity = lessVerbose verbosity
-- We take the targets and try to parse them as package ids (with name and version).
-- The ones who don't parse will have to be resolved in the project context.
(unresolvedTargetStrings, parsedPackageIds) =
partitionEithers $
flip map targetStrings $ \s ->
case eitherParsec s of
Right pkgId@PackageIdentifier{pkgVersion}
| pkgVersion /= nullVersion ->
pure pkgId
_ -> Left s
-- For each packageId, we output a NamedPackage specifier (i.e. a package only known by
-- its name) and a target selector.
(parsedPkgSpecs, parsedTargets) =
unzip
[ (mkNamedPackage pkgId, TargetPackageNamed (pkgName pkgId) targetFilter)
| pkgId <- parsedPackageIds
]
targetFilter = if installLibs then Just LibKind else Just ExeKind
resolveTargetSelectorsInProjectBaseContext
:: Verbosity
-> ProjectBaseContext
-> [String]
-> Maybe ComponentKindFilter
-> IO ([PackageSpecifier UnresolvedSourcePackage], [TargetSelector])
resolveTargetSelectorsInProjectBaseContext verbosity baseCtx targetStrings targetFilter = do
let reducedVerbosity = lessVerbose verbosity
sourcePkgDb <-
projectConfigWithBuilderRepoContext
reducedVerbosity
(buildSettings baseCtx)
(getSourcePackages verbosity)
targetSelectors <-
readTargetSelectors (localPackages baseCtx) Nothing targetStrings
>>= \case
Left problems -> reportTargetSelectorProblems verbosity problems
Right ts -> return ts
getSpecsAndTargetSelectors
verbosity
reducedVerbosity
sourcePkgDb
targetSelectors
(distDirLayout baseCtx)
baseCtx
targetFilter
withoutProject
:: Verbosity
-> ProjectConfig
-> [String]
-> IO ([PackageSpecifier UnresolvedSourcePackage], [URI], [TargetSelector], ProjectConfig)
withoutProject verbosity globalConfig targetStrings = do
tss <- traverse (parseWithoutProjectTargetSelector verbosity) targetStrings
let
ProjectConfigBuildOnly
{ projectConfigLogsDir
} = projectConfigBuildOnly globalConfig
ProjectConfigShared
{ projectConfigStoreDir
} = projectConfigShared globalConfig
mlogsDir = flagToMaybe projectConfigLogsDir
mstoreDir = flagToMaybe projectConfigStoreDir
cabalDirLayout <- mkCabalDirLayout mstoreDir mlogsDir
let buildSettings = resolveBuildTimeSettings verbosity cabalDirLayout globalConfig
SourcePackageDb{packageIndex} <-
projectConfigWithBuilderRepoContext
verbosity
buildSettings
(getSourcePackages verbosity)
for_ (concatMap woPackageNames tss) $ \name -> do
when (null (lookupPackageName packageIndex name)) $ do
let xs = searchByName packageIndex (unPackageName name)
let emptyIf True _ = []
emptyIf False zs = zs
str2 =
emptyIf
(null xs)
[ "Did you mean any of the following?\n"
, unlines (("- " ++) . unPackageName . fst <$> xs)
]
dieWithException verbosity $ WithoutProject (unPackageName name) str2
let
packageSpecifiers :: [PackageSpecifier UnresolvedSourcePackage]
(uris, packageSpecifiers) = partitionEithers $ map woPackageSpecifiers tss
packageTargets = map woPackageTargets tss
-- Apply the local configuration (e.g. cli flags) to all direct targets of install command,
-- see note in 'installAction'
let config = addLocalConfigToPkgs globalConfig (concatMap woPackageNames tss)
return (packageSpecifiers, uris, packageTargets, config)
addLocalConfigToPkgs :: ProjectConfig -> [PackageName] -> ProjectConfig
addLocalConfigToPkgs config pkgs =
config
{ projectConfigSpecificPackage =
projectConfigSpecificPackage config
<> MapMappend (Map.fromList targetPackageConfigs)
}
where
localConfig = projectConfigLocalPackages config
targetPackageConfigs = map (,localConfig) pkgs
targetPkgNames
:: [PackageSpecifier UnresolvedSourcePackage]
-- ^ The local packages, to resolve 'TargetAllPackages' selectors
-> TargetSelector
-> [PackageName]
targetPkgNames localPkgs = \case
TargetPackage _ pkgIds _ -> map pkgName pkgIds
TargetPackageNamed name _ -> [name]
TargetAllPackages _ -> map pkgSpecifierTarget localPkgs
-- Note how the target may select a component only, but we will always apply
-- the local flags to the whole package in which that component is contained.
-- The reason is that our finest level of configuration is per-package, so
-- there is no interface to configure options to a component only. It is not
-- trivial to say whether we could indeed support per-component configuration
-- because of legacy packages which we may always have to build whole.
TargetComponent pkgId _ _ -> [pkgName pkgId]
TargetComponentUnknown name _ _ -> [name]
-- | Verify that invalid config options were not passed to the install command.
--
-- If an invalid configuration is found the command will @dieWithException@.
verifyPreconditionsOrDie :: Verbosity -> ConfigFlags -> IO ()
verifyPreconditionsOrDie verbosity configFlags = do
-- We never try to build tests/benchmarks for remote packages.
-- So we set them as disabled by default and error if they are explicitly
-- enabled.
when (configTests configFlags == Flag True) $
dieWithException verbosity ConfigTests
when (configBenchmarks configFlags == Flag True) $
dieWithException verbosity ConfigBenchmarks
-- | Apply the given 'ClientInstallFlags' on top of one coming from the global configuration.
getClientInstallFlags :: Verbosity -> GlobalFlags -> ClientInstallFlags -> IO ClientInstallFlags
getClientInstallFlags verbosity globalFlags existingClientInstallFlags = do
let configFileFlag = globalConfigFile globalFlags
savedConfig <- loadConfig verbosity configFileFlag
pure $ savedClientInstallFlags savedConfig `mappend` existingClientInstallFlags
getSpecsAndTargetSelectors
:: Verbosity
-> Verbosity
-> SourcePackageDb
-> [TargetSelector]
-> DistDirLayout
-> ProjectBaseContext
-> Maybe ComponentKindFilter
-> IO ([PackageSpecifier UnresolvedSourcePackage], [TargetSelector])
getSpecsAndTargetSelectors verbosity reducedVerbosity sourcePkgDb targetSelectors distDirLayout baseCtx targetFilter =
withInstallPlan reducedVerbosity baseCtx $ \elaboratedPlan _ -> do
-- Split into known targets and hackage packages.
(targetsMap, hackageNames) <-
partitionToKnownTargetsAndHackagePackages
verbosity
sourcePkgDb
elaboratedPlan
targetSelectors
let
planMap = InstallPlan.toMap elaboratedPlan
sdistize (SpecificSourcePackage spkg) =
SpecificSourcePackage spkg'
where
sdistPath = distSdistFile distDirLayout (packageId spkg)
spkg' = spkg{srcpkgSource = LocalTarballPackage sdistPath}
sdistize named = named
localPkgs = sdistize <$> localPackages baseCtx
gatherTargets :: UnitId -> TargetSelector
gatherTargets targetId = TargetPackageNamed pkgName targetFilter
where
targetUnit = Map.findWithDefault (error "cannot find target unit") targetId planMap
PackageIdentifier{..} = packageId targetUnit
localTargets = map gatherTargets (Map.keys targetsMap)
hackagePkgs :: [PackageSpecifier UnresolvedSourcePackage]
hackagePkgs = [NamedPackage pn [] | pn <- hackageNames]
hackageTargets :: [TargetSelector]
hackageTargets = [TargetPackageNamed pn targetFilter | pn <- hackageNames]
createDirectoryIfMissing True (distSdistDirectory distDirLayout)
unless (Map.null targetsMap) $ for_ (localPackages baseCtx) $ \case
SpecificSourcePackage pkg ->
packageToSdist
verbosity
(distProjectRootDirectory distDirLayout)
TarGzArchive
(distSdistFile distDirLayout (packageId pkg))
pkg
NamedPackage _ _ ->
-- This may happen if 'extra-packages' are listed in the project file.
-- We don't need to do extra work for NamedPackages since they will be
-- fetched from Hackage rather than locally 'sdistize'-d. Note how,
-- below, we already return the local 'sdistize'-d packages together
-- with the 'hackagePkgs' (which are 'NamedPackage's), and that
-- 'sdistize' is a no-op for 'NamedPackages', meaning the
-- 'NamedPackage's in 'localPkgs' will be treated just like
-- 'hackagePkgs' as they should.
pure ()
if null targetsMap
then return (hackagePkgs, hackageTargets)
else return (localPkgs ++ hackagePkgs, localTargets ++ hackageTargets)
-- | Partitions the target selectors into known local targets and hackage packages.
partitionToKnownTargetsAndHackagePackages
:: Verbosity
-> SourcePackageDb
-> ElaboratedInstallPlan
-> [TargetSelector]
-> IO (TargetsMap, [PackageName])
partitionToKnownTargetsAndHackagePackages verbosity pkgDb elaboratedPlan targetSelectors = do
let mTargets =
resolveTargets
selectPackageTargets
selectComponentTarget
elaboratedPlan
(Just pkgDb)
targetSelectors
case mTargets of
Right targets ->
-- Everything is a local dependency.
return (targets, [])
Left errs -> do
-- Not everything is local.
let
(errs', hackageNames) = partitionEithers . flip fmap errs $ \case
TargetAvailableInIndex name -> Right name
err -> Left err
-- report incorrect case for known package.
for_ errs' $ \case
TargetNotInProject hn ->
case searchByName (packageIndex pkgDb) (unPackageName hn) of
[] -> return ()
xs ->
dieWithException verbosity $ UnknownPackage (unPackageName hn) (("- " ++) . unPackageName . fst <$> xs)
_ -> return ()
when (not . null $ errs') $ reportBuildTargetProblems verbosity errs'
let
targetSelectors' = flip filter targetSelectors $ \case
TargetComponentUnknown name _ _
| name `elem` hackageNames -> False
TargetPackageNamed name _
| name `elem` hackageNames -> False
_ -> True
-- This can't fail, because all of the errors are
-- removed (or we've given up).
targets <-
either (reportBuildTargetProblems verbosity) return $
resolveTargets
selectPackageTargets
selectComponentTarget
elaboratedPlan
Nothing
targetSelectors'
return (targets, hackageNames)
constructProjectBuildContext
:: Verbosity
-> ProjectBaseContext
-- ^ The synthetic base context to use to produce the full build context.
-> [TargetSelector]
-> IO ProjectBuildContext
constructProjectBuildContext verbosity baseCtx targetSelectors = do
runProjectPreBuildPhase verbosity baseCtx $ \elaboratedPlan -> do
-- Interpret the targets on the command line as build targets
targets <-
either (reportBuildTargetProblems verbosity) return $
resolveTargets
selectPackageTargets
selectComponentTarget
elaboratedPlan
Nothing
targetSelectors
let prunedToTargetsElaboratedPlan =
pruneInstallPlanToTargets TargetActionBuild targets elaboratedPlan
prunedElaboratedPlan <-
if buildSettingOnlyDeps (buildSettings baseCtx)
then
either (reportCannotPruneDependencies verbosity) return $
pruneInstallPlanToDependencies
(Map.keysSet targets)
prunedToTargetsElaboratedPlan
else return prunedToTargetsElaboratedPlan
return (prunedElaboratedPlan, targets)
-- | From an install configuration, prepare the record needed by actions that
-- will either check if an install of a single executable is possible or
-- actually perform its installation.
prepareExeInstall :: InstallCfg -> IO InstallExe
prepareExeInstall
InstallCfg{verbosity, baseCtx, buildCtx, platform, compiler, installConfigFlags, installClientFlags} = do
installPath <- defaultInstallPath
let storeDirLayout = cabalStoreDirLayout $ cabalDirLayout baseCtx
prefix = fromFlagOrDefault "" (fmap InstallDirs.fromPathTemplate (configProgPrefix installConfigFlags))
suffix = fromFlagOrDefault "" (fmap InstallDirs.fromPathTemplate (configProgSuffix installConfigFlags))
mkUnitBinDir :: UnitId -> FilePath
mkUnitBinDir =
InstallDirs.bindir
. storePackageInstallDirs' storeDirLayout compiler
mkExeName :: UnqualComponentName -> FilePath
mkExeName exe = unUnqualComponentName exe <.> exeExtension platform
mkFinalExeName :: UnqualComponentName -> FilePath
mkFinalExeName exe = prefix <> unUnqualComponentName exe <> suffix <.> exeExtension platform
installdirUnknown =
"installdir is not defined. Set it in your cabal config file "
++ "or use --installdir=<path>. Using default installdir: "
++ show installPath
installdir <-
fromFlagOrDefault
(warn verbosity installdirUnknown >> pure installPath)
$ pure <$> cinstInstalldir installClientFlags
createDirectoryIfMissingVerbose verbosity True installdir
warnIfNoExes verbosity buildCtx
-- This is in IO as we will make environment checks, to decide which install
-- method is best.
let defaultMethod :: IO InstallMethod
defaultMethod
-- Try symlinking in temporary directory, if it works default to
-- symlinking even on windows.
| buildOS == Windows = do
symlinks <- trySymlink verbosity
return $ if symlinks then InstallMethodSymlink else InstallMethodCopy
| otherwise = return InstallMethodSymlink
installMethod <- flagElim defaultMethod return $ cinstInstallMethod installClientFlags
return $ InstallExe installMethod installdir mkUnitBinDir mkExeName mkFinalExeName
-- | Install any built library by adding it to the default ghc environment
installLibraries
:: Verbosity
-> ProjectBuildContext
-> PI.PackageIndex InstalledPackageInfo
-> Compiler
-> PackageDBStackCWD
-> FilePath
-- ^ Environment file
-> [GhcEnvironmentFileEntry FilePath]
-> Bool
-- ^ Whether we need to show a warning (i.e. we created a new environment
-- file, and the user did not use --package-env)
-> IO ()
installLibraries
verbosity
buildCtx
installedIndex
compiler
packageDbs'
envFile
envEntries
showWarning = do
if supportsPkgEnvFiles $ getImplInfo compiler
then do
let validDb (SpecificPackageDB fp) = doesPathExist fp
validDb _ = pure True
-- if a user "installs" a global package and no existing cabal db exists, none will be created.
-- this ensures we don't add the "phantom" path to the file.
packageDbs <- filterM validDb packageDbs'
let
getLatest =
(=<<) (maybeToList . safeHead . snd)
. take 1
. sortBy (comparing (Down . fst))
. PI.lookupPackageName installedIndex
globalLatest = concat (getLatest <$> globalPackages)
globalEntries = GhcEnvFilePackageId . installedUnitId <$> globalLatest
baseEntries =
GhcEnvFileClearPackageDbStack : fmap GhcEnvFilePackageDb packageDbs
pkgEntries =
ordNub $
globalEntries
++ envEntries