1 {-# LANGUAGE NamedFieldPuns, RecordWildCards #-}
3 -- | cabal-install CLI command: freeze
5 module Distribution
.Client
.CmdFreeze
(
10 import Distribution
.Client
.Compat
.Prelude
13 import Distribution
.Client
.NixStyleOptions
14 ( NixStyleFlags
(..), nixStyleOptions
, defaultNixStyleFlags
)
15 import Distribution
.Client
.ProjectOrchestration
16 import Distribution
.Client
.ProjectPlanning
17 import Distribution
.Client
.ProjectConfig
18 ( ProjectConfig
(..), ProjectConfigShared
(..)
19 , writeProjectLocalFreezeConfig
)
20 import Distribution
.Client
.IndexUtils
(TotalIndexState
, ActiveRepos
, filterSkippedActiveRepos
)
21 import Distribution
.Client
.Targets
22 ( UserQualifier
(..), UserConstraintScope
(..), UserConstraint
(..) )
23 import Distribution
.Solver
.Types
.PackageConstraint
24 ( PackageProperty
(..) )
25 import Distribution
.Solver
.Types
.ConstraintSource
26 ( ConstraintSource
(..) )
27 import Distribution
.Client
.DistDirLayout
28 ( DistDirLayout
(distProjectFile
) )
29 import qualified Distribution
.Client
.InstallPlan
as InstallPlan
32 import Distribution
.Package
33 ( PackageName
, packageName
, packageVersion
)
34 import Distribution
.Version
35 ( VersionRange
, thisVersion
36 , unionVersionRanges
, simplifyVersionRange
)
37 import Distribution
.PackageDescription
38 ( FlagAssignment
, nullFlagAssignment
)
39 import Distribution
.Client
.Setup
40 ( GlobalFlags
, ConfigFlags
(..) )
41 import Distribution
.Simple
.Flag
43 import Distribution
.Simple
.Flag
(Flag
(..))
44 import Distribution
.Simple
.Utils
45 ( die
', notice
, wrapText
)
46 import Distribution
.Verbosity
49 import qualified Data
.Map
as Map
51 import Distribution
.Simple
.Command
52 ( CommandUI
(..), usageAlternatives
)
54 freezeCommand
:: CommandUI
(NixStyleFlags
())
55 freezeCommand
= CommandUI
{
56 commandName
= "v2-freeze",
57 commandSynopsis
= "Freeze dependencies.",
58 commandUsage
= usageAlternatives
"v2-freeze" [ "[FLAGS]" ],
59 commandDescription
= Just
$ \_
-> wrapText
$
60 "The project configuration is frozen so that it will be reproducible "
63 ++ "The precise dependency configuration for the project is written to "
64 ++ "the 'cabal.project.freeze' file (or '$project_file.freeze' if "
65 ++ "'--project-file' is specified). This file extends the configuration "
66 ++ "from the 'cabal.project' file and thus is used as the project "
67 ++ "configuration for all other commands (such as 'v2-build', "
68 ++ "'v2-repl' etc).\n\n"
70 ++ "The freeze file can be kept in source control. To make small "
71 ++ "adjustments it may be edited manually, or to make bigger changes "
72 ++ "you may wish to delete the file and re-freeze. For more control, "
73 ++ "one approach is to try variations using 'v2-build --dry-run' with "
74 ++ "solver flags such as '--constraint=\"pkg < 1.2\"' and once you have "
75 ++ "a satisfactory solution to freeze it using the 'v2-freeze' command "
76 ++ "with the same set of flags.",
78 commandNotes
= Just
$ \pname
->
80 ++ " " ++ pname
++ " v2-freeze\n"
81 ++ " Freeze the configuration of the current project\n\n"
82 ++ " " ++ pname
++ " v2-build --dry-run --constraint=\"aeson < 1\"\n"
83 ++ " Check what a solution with the given constraints would look like\n"
84 ++ " " ++ pname
++ " v2-freeze --constraint=\"aeson < 1\"\n"
85 ++ " Freeze a solution using the given constraints\n"
87 , commandDefaultFlags
= defaultNixStyleFlags
()
88 , commandOptions
= nixStyleOptions
(const [])
91 -- | To a first approximation, the @freeze@ command runs the first phase of
92 -- the @build@ command where we bring the install plan up to date, and then
93 -- based on the install plan we write out a @cabal.project.freeze@ config file.
95 -- For more details on how this works, see the module
96 -- "Distribution.Client.ProjectOrchestration"
98 freezeAction
:: NixStyleFlags
() -> [String] -> GlobalFlags
-> IO ()
99 freezeAction flags
@NixStyleFlags
{..} extraArgs globalFlags
= do
101 unless (null extraArgs
) $
102 die
' verbosity
$ "'freeze' doesn't take any extra arguments: "
111 } <- establishProjectBaseContext verbosity cliConfig OtherCommand
113 (_
, elaboratedPlan
, _
, totalIndexState
, activeRepos
) <-
114 rebuildInstallPlan verbosity
115 distDirLayout cabalDirLayout
119 let freezeConfig
= projectFreezeConfig elaboratedPlan totalIndexState activeRepos
120 dryRun
= buildSettingDryRun buildSettings
121 || buildSettingOnlyDownload buildSettings
124 then notice verbosity
"Freeze file not written due to flag(s)"
126 writeProjectLocalFreezeConfig distDirLayout freezeConfig
128 "Wrote freeze file: " ++ distProjectFile distDirLayout
"freeze"
131 verbosity
= fromFlagOrDefault normal
(configVerbosity configFlags
)
132 cliConfig
= commandLineFlagsToProjectConfig globalFlags flags
133 mempty
-- ClientInstallFlags, not needed here
135 -- | Given the install plan, produce a config value with constraints that
136 -- freezes the versions of packages used in the plan.
139 :: ElaboratedInstallPlan
143 projectFreezeConfig elaboratedPlan totalIndexState activeRepos0
= mempty
144 { projectConfigShared
= mempty
145 { projectConfigConstraints
=
146 concat (Map
.elems (projectFreezeConstraints elaboratedPlan
))
147 , projectConfigIndexState
= Flag totalIndexState
148 , projectConfigActiveRepos
= Flag activeRepos
152 activeRepos
:: ActiveRepos
153 activeRepos
= filterSkippedActiveRepos activeRepos0
155 -- | Given the install plan, produce solver constraints that will ensure the
156 -- solver picks the same solution again in future in different environments.
158 projectFreezeConstraints
:: ElaboratedInstallPlan
159 -> Map PackageName
[(UserConstraint
, ConstraintSource
)]
160 projectFreezeConstraints plan
=
162 -- TODO: [required eventually] this is currently an underapproximation
163 -- since the constraints language is not expressive enough to specify the
164 -- precise solution. See https://github.com/haskell/cabal/issues/3502.
166 -- For the moment we deal with multiple versions in the solution by using
167 -- constraints that allow either version. Also, we do not include any
168 -- /version/ constraints for packages that are local to the project (e.g.
169 -- if the solution has two instances of Cabal, one from the local project
170 -- and one pulled in as a setup deps then we exclude all constraints on
171 -- Cabal, not just the constraint for the local instance since any
172 -- constraint would apply to both instances). We do however keep flag
173 -- constraints of local packages.
175 deleteLocalPackagesVersionConstraints
176 (Map
.unionWith
(++) versionConstraints flagConstraints
)
178 versionConstraints
:: Map PackageName
[(UserConstraint
, ConstraintSource
)]
181 (\p v
-> [(UserConstraint
(UserAnyQualifier p
) (PackagePropertyVersion v
),
182 ConstraintSourceFreeze
)])
185 versionRanges
:: Map PackageName VersionRange
187 Map
.map simplifyVersionRange
$
188 Map
.fromListWith unionVersionRanges
$
189 [ (packageName pkg
, thisVersion
(packageVersion pkg
))
190 | InstallPlan
.PreExisting pkg
<- InstallPlan
.toList plan
192 ++ [ (packageName pkg
, thisVersion
(packageVersion pkg
))
193 | InstallPlan
.Configured pkg
<- InstallPlan
.toList plan
196 flagConstraints
:: Map PackageName
[(UserConstraint
, ConstraintSource
)]
199 (\p f
-> [(UserConstraint
(UserQualified UserQualToplevel p
) (PackagePropertyFlags f
),
200 ConstraintSourceFreeze
)])
203 flagAssignments
:: Map PackageName FlagAssignment
207 | InstallPlan
.Configured elab
<- InstallPlan
.toList plan
208 , let flags
= elabFlagAssignment elab
209 pkgname
= packageName elab
210 , not (nullFlagAssignment flags
) ]
212 -- As described above, remove the version constraints on local packages,
213 -- but leave any flag constraints.
214 deleteLocalPackagesVersionConstraints
215 :: Map PackageName
[(UserConstraint
, ConstraintSource
)]
216 -> Map PackageName
[(UserConstraint
, ConstraintSource
)]
217 deleteLocalPackagesVersionConstraints
=
219 (\_pkgname
() constraints
->
220 case filter (not . isVersionConstraint
. fst) constraints
of
222 constraints
' -> Just constraints
')
226 isVersionConstraint
(UserConstraint _
(PackagePropertyVersion _
)) = True
227 isVersionConstraint _
= False
229 localPackages
:: Map PackageName
()
232 [ (packageName elab
, ())
233 | InstallPlan
.Configured elab
<- InstallPlan
.toList plan
234 , elabLocalToProject elab