From 45a13b1f372e1acf7febf6af58803939069da711 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20P=2E=20Ren=C3=A9=20de=20Cotret?= Date: Sat, 8 Feb 2025 18:04:35 -0500 Subject: [PATCH 1/7] Add the `freeze --lock` flag to promote a freeze f ile to a lock file --- .../src/Distribution/Client/CmdFreeze.hs | 50 +++++++++++++++---- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/cabal-install/src/Distribution/Client/CmdFreeze.hs b/cabal-install/src/Distribution/Client/CmdFreeze.hs index 29718b5d441..5c562e5e8b2 100644 --- a/cabal-install/src/Distribution/Client/CmdFreeze.hs +++ b/cabal-install/src/Distribution/Client/CmdFreeze.hs @@ -5,6 +5,7 @@ module Distribution.Client.CmdFreeze ( freezeCommand , freezeAction + , ClientFreezeFlags(..), ) where import Distribution.Client.Compat.Prelude @@ -38,7 +39,7 @@ import Distribution.Solver.Types.ConstraintSource import Distribution.Solver.Types.PackageConstraint ( PackageProperty (..) ) - +import Distribution.Solver.Types.Settings (OnlyConstrained(..)) import Distribution.Client.Setup ( CommonSetupFlags (setupVerbosity) , ConfigFlags (..) @@ -59,6 +60,7 @@ import Distribution.Simple.Utils , notice , wrapText ) +import Distribution.Simple.Setup (trueArg) import Distribution.Verbosity ( normal ) @@ -74,10 +76,16 @@ import qualified Data.Map as Map import Distribution.Client.Errors import Distribution.Simple.Command ( CommandUI (..) + , OptionField + , ShowOrParseArgs + , usageAlternatives + , option , usageAlternatives ) -freezeCommand :: CommandUI (NixStyleFlags ()) +newtype ClientFreezeFlags = ClientFreezeFlags {lockDependencies :: Flag Bool} + +freezeCommand :: CommandUI (NixStyleFlags ClientFreezeFlags) freezeCommand = CommandUI { commandName = "v2-freeze" @@ -99,7 +107,10 @@ freezeCommand = ++ "one approach is to try variations using 'v2-build --dry-run' with " ++ "solver flags such as '--constraint=\"pkg < 1.2\"' and once you have " ++ "a satisfactory solution to freeze it using the 'v2-freeze' command " - ++ "with the same set of flags." + ++ "with the same set of flags.\n\n" + ++ "By default, a freeze file is a set of constaints; unconstrained packages " + ++ "can still be included in the build plan. If you wish to restrict dependencies " + ++ "to those included in the freeze file, use the '--lock' flag." , commandNotes = Just $ \pname -> "Examples:\n" ++ " " @@ -108,23 +119,38 @@ freezeCommand = ++ " Freeze the configuration of the current project\n\n" ++ " " ++ pname + ++ " v2-freeze --lock\n" + ++ " Freeze the configuration of the current project and only allow frozen dependencies in future builds\n\n" + ++ " " + ++ pname ++ " v2-build --dry-run --constraint=\"aeson < 1\"\n" ++ " Check what a solution with the given constraints would look like\n" ++ " " ++ pname ++ " v2-freeze --constraint=\"aeson < 1\"\n" ++ " Freeze a solution using the given constraints\n" - , commandDefaultFlags = defaultNixStyleFlags () - , commandOptions = nixStyleOptions (const []) + , commandDefaultFlags = defaultNixStyleFlags (ClientFreezeFlags (Flag False)) + , commandOptions = nixStyleOptions freezeOptions } +freezeOptions :: ShowOrParseArgs -> [OptionField ClientFreezeFlags] +freezeOptions _ = + [ option + [] + ["lock"] + "Promote the resulting freeze file to a lock file" + lockDependencies + (\v f -> f{lockDependencies = v}) + trueArg + ] + -- | To a first approximation, the @freeze@ command runs the first phase of -- the @build@ command where we bring the install plan up to date, and then -- based on the install plan we write out a @cabal.project.freeze@ config file. -- -- For more details on how this works, see the module -- "Distribution.Client.ProjectOrchestration" -freezeAction :: NixStyleFlags () -> [String] -> GlobalFlags -> IO () +freezeAction :: NixStyleFlags ClientFreezeFlags -> [String] -> GlobalFlags -> IO () freezeAction flags@NixStyleFlags{..} extraArgs globalFlags = do unless (null extraArgs) $ dieWithException verbosity $ @@ -148,7 +174,7 @@ freezeAction flags@NixStyleFlags{..} extraArgs globalFlags = do localPackages Nothing - let freezeConfig = projectFreezeConfig elaboratedPlan totalIndexState activeRepos + let freezeConfig = projectFreezeConfig extraFlags elaboratedPlan totalIndexState activeRepos dryRun = buildSettingDryRun buildSettings || buildSettingOnlyDownload buildSettings @@ -170,11 +196,12 @@ freezeAction flags@NixStyleFlags{..} extraArgs globalFlags = do -- | Given the install plan, produce a config value with constraints that -- freezes the versions of packages used in the plan. projectFreezeConfig - :: ElaboratedInstallPlan + :: ClientFreezeFlags + -> ElaboratedInstallPlan -> TotalIndexState -> ActiveRepos -> ProjectConfig -projectFreezeConfig elaboratedPlan totalIndexState activeRepos0 = +projectFreezeConfig freezeFlags elaboratedPlan totalIndexState activeRepos0 = mempty { projectConfigShared = mempty @@ -182,12 +209,17 @@ projectFreezeConfig elaboratedPlan totalIndexState activeRepos0 = concat (Map.elems (projectFreezeConstraints elaboratedPlan)) , projectConfigIndexState = Flag totalIndexState , projectConfigActiveRepos = Flag activeRepos + , projectConfigOnlyConstrained = onlyConstrainedFlag freezeFlags } } where activeRepos :: ActiveRepos activeRepos = filterSkippedActiveRepos activeRepos0 + onlyConstrainedFlag :: ClientFreezeFlags -> Flag OnlyConstrained + onlyConstrainedFlag ClientFreezeFlags{lockDependencies=Flag True} = Flag OnlyConstrainedAll + onlyConstrainedFlag ClientFreezeFlags{lockDependencies=_} = NoFlag + -- | Given the install plan, produce solver constraints that will ensure the -- solver picks the same solution again in future in different environments. projectFreezeConstraints From 9e06b42367259d1445d70de6795b263ff93c3479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20P=2E=20Ren=C3=A9=20de=20Cotret?= Date: Sat, 8 Feb 2025 18:20:29 -0500 Subject: [PATCH 2/7] Fixed whitespace --- cabal-install/src/Distribution/Client/CmdFreeze.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cabal-install/src/Distribution/Client/CmdFreeze.hs b/cabal-install/src/Distribution/Client/CmdFreeze.hs index 5c562e5e8b2..1685328813b 100644 --- a/cabal-install/src/Distribution/Client/CmdFreeze.hs +++ b/cabal-install/src/Distribution/Client/CmdFreeze.hs @@ -217,7 +217,7 @@ projectFreezeConfig freezeFlags elaboratedPlan totalIndexState activeRepos0 = activeRepos = filterSkippedActiveRepos activeRepos0 onlyConstrainedFlag :: ClientFreezeFlags -> Flag OnlyConstrained - onlyConstrainedFlag ClientFreezeFlags{lockDependencies=Flag True} = Flag OnlyConstrainedAll + onlyConstrainedFlag ClientFreezeFlags{lockDependencies=Flag True} = Flag OnlyConstrainedAll onlyConstrainedFlag ClientFreezeFlags{lockDependencies=_} = NoFlag -- | Given the install plan, produce solver constraints that will ensure the From 8eeea78b1b27b405fd5b75207eb0c25df58a04e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20P=2E=20Ren=C3=A9=20de=20Cotret?= Date: Sat, 8 Feb 2025 18:26:53 -0500 Subject: [PATCH 3/7] Updated changelog --- changelog.d/pr-10785.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 changelog.d/pr-10785.md diff --git a/changelog.d/pr-10785.md b/changelog.d/pr-10785.md new file mode 100644 index 00000000000..952bb7a5b07 --- /dev/null +++ b/changelog.d/pr-10785.md @@ -0,0 +1,8 @@ +--- +synopsis: Added a `--lock` flag to `cabal freeze` to promote a freeze file to a lock file +packages: [cabal-install] +prs: 10785 +issues: 10784 +--- + +Added a `--lock` flag to `cabal freeze`, to promote a freeze file to a lock file. By calling `cabal freeze --lock`, the resulting freeze file will ensure that only dependencies whose constraints are specified, will be accepted by future build plans. This flag can be used to ensure that no unaudited packages are added to the build plan. From d89462a5204a7c47ba553b3a7cd1925a2be92b7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20P=2E=20Ren=C3=A9=20de=20Cotret?= Date: Sat, 8 Feb 2025 18:27:06 -0500 Subject: [PATCH 4/7] Added documentation on the new freeze --lock flag --- doc/cabal-commands.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/cabal-commands.rst b/doc/cabal-commands.rst index 7f0b37ac48f..d1ee155eb8e 100644 --- a/doc/cabal-commands.rst +++ b/doc/cabal-commands.rst @@ -532,6 +532,13 @@ users see a consistent set of dependencies. For libraries, this is not recommended: users often need to build against different versions of libraries than what you developed against. +A freeze file is really a set of constraint; by default, such files do not +prevent new dependencies from being included in the build plan. In this sense, +a freeze file is not, by default, a **lockfile**. To turn a freeze file into a lockfile, +use the ``--lock`` flag when invocating ``cabal freeze``. This will prevent future +builds from including new dependencies. This can be helpful in situations where +every dependency must be explicitly audited and approved, for example. + cabal gen-bounds ^^^^^^^^^^^^^^^^ From 773e8cb9dbc3ba9bc71ee707c464259cb6aeffce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20P=2E=20Ren=C3=A9=20de=20Cotret?= Date: Sat, 8 Feb 2025 18:30:36 -0500 Subject: [PATCH 5/7] Appeased fourmolu --- .../src/Distribution/Client/CmdFreeze.hs | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/cabal-install/src/Distribution/Client/CmdFreeze.hs b/cabal-install/src/Distribution/Client/CmdFreeze.hs index 1685328813b..5122f782f41 100644 --- a/cabal-install/src/Distribution/Client/CmdFreeze.hs +++ b/cabal-install/src/Distribution/Client/CmdFreeze.hs @@ -5,7 +5,7 @@ module Distribution.Client.CmdFreeze ( freezeCommand , freezeAction - , ClientFreezeFlags(..), + , ClientFreezeFlags (..) ) where import Distribution.Client.Compat.Prelude @@ -28,23 +28,16 @@ import Distribution.Client.ProjectConfig ) import Distribution.Client.ProjectOrchestration import Distribution.Client.ProjectPlanning -import Distribution.Client.Targets - ( UserConstraint (..) - , UserConstraintScope (..) - , UserQualifier (..) - ) -import Distribution.Solver.Types.ConstraintSource - ( ConstraintSource (..) - ) -import Distribution.Solver.Types.PackageConstraint - ( PackageProperty (..) - ) -import Distribution.Solver.Types.Settings (OnlyConstrained(..)) import Distribution.Client.Setup ( CommonSetupFlags (setupVerbosity) , ConfigFlags (..) , GlobalFlags ) +import Distribution.Client.Targets + ( UserConstraint (..) + , UserConstraintScope (..) + , UserQualifier (..) + ) import Distribution.Package ( PackageName , packageName @@ -55,12 +48,19 @@ import Distribution.PackageDescription , nullFlagAssignment ) import Distribution.Simple.Flag (Flag (..), fromFlagOrDefault) +import Distribution.Simple.Setup (trueArg) import Distribution.Simple.Utils ( dieWithException , notice , wrapText ) -import Distribution.Simple.Setup (trueArg) +import Distribution.Solver.Types.ConstraintSource + ( ConstraintSource (..) + ) +import Distribution.Solver.Types.PackageConstraint + ( PackageProperty (..) + ) +import Distribution.Solver.Types.Settings (OnlyConstrained (..)) import Distribution.Verbosity ( normal ) @@ -78,7 +78,6 @@ import Distribution.Simple.Command ( CommandUI (..) , OptionField , ShowOrParseArgs - , usageAlternatives , option , usageAlternatives ) @@ -217,8 +216,8 @@ projectFreezeConfig freezeFlags elaboratedPlan totalIndexState activeRepos0 = activeRepos = filterSkippedActiveRepos activeRepos0 onlyConstrainedFlag :: ClientFreezeFlags -> Flag OnlyConstrained - onlyConstrainedFlag ClientFreezeFlags{lockDependencies=Flag True} = Flag OnlyConstrainedAll - onlyConstrainedFlag ClientFreezeFlags{lockDependencies=_} = NoFlag + onlyConstrainedFlag ClientFreezeFlags{lockDependencies = Flag True} = Flag OnlyConstrainedAll + onlyConstrainedFlag ClientFreezeFlags{lockDependencies = _} = NoFlag -- | Given the install plan, produce solver constraints that will ensure the -- solver picks the same solution again in future in different environments. From e6ac5aaa548f99a8c5dcbae1b427722f72f4a541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20P=2E=20Ren=C3=A9=20de=20Cotret?= Date: Sat, 8 Feb 2025 18:49:35 -0500 Subject: [PATCH 6/7] Added test --- cabal-testsuite/PackageTests/Freeze/freeze-lock.out | 5 +++++ cabal-testsuite/PackageTests/Freeze/freeze-lock.test.hs | 6 ++++++ 2 files changed, 11 insertions(+) create mode 100644 cabal-testsuite/PackageTests/Freeze/freeze-lock.out create mode 100644 cabal-testsuite/PackageTests/Freeze/freeze-lock.test.hs diff --git a/cabal-testsuite/PackageTests/Freeze/freeze-lock.out b/cabal-testsuite/PackageTests/Freeze/freeze-lock.out new file mode 100644 index 00000000000..e330b8a640b --- /dev/null +++ b/cabal-testsuite/PackageTests/Freeze/freeze-lock.out @@ -0,0 +1,5 @@ +# cabal v2-update +Downloading the latest package list from test-local-repo +# cabal v2-freeze +Resolving dependencies... +Wrote freeze file: /cabal.project.freeze diff --git a/cabal-testsuite/PackageTests/Freeze/freeze-lock.test.hs b/cabal-testsuite/PackageTests/Freeze/freeze-lock.test.hs new file mode 100644 index 00000000000..54c27258b83 --- /dev/null +++ b/cabal-testsuite/PackageTests/Freeze/freeze-lock.test.hs @@ -0,0 +1,6 @@ +import Test.Cabal.Prelude +main = cabalTest $ do + withRepo "repo" $ do + cabal "v2-freeze" ["--lock"] + cwd <- fmap testCurrentDir getTestEnv + assertFileDoesContain (cwd "cabal.project.freeze") "reject-unconstrained-dependencies: all" From 6b8995ffc1c4ccbe6fefb8b66ecd23641c44f967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20P=2E=20Ren=C3=A9=20de=20Cotret?= Date: Sat, 8 Feb 2025 20:52:13 -0500 Subject: [PATCH 7/7] Added link between `freeze --lock` and `--reject-unconstrained-dependencies` --- changelog.d/pr-10785.md | 2 ++ doc/cabal-commands.rst | 3 +++ 2 files changed, 5 insertions(+) diff --git a/changelog.d/pr-10785.md b/changelog.d/pr-10785.md index 952bb7a5b07..e9ca4290b23 100644 --- a/changelog.d/pr-10785.md +++ b/changelog.d/pr-10785.md @@ -6,3 +6,5 @@ issues: 10784 --- Added a `--lock` flag to `cabal freeze`, to promote a freeze file to a lock file. By calling `cabal freeze --lock`, the resulting freeze file will ensure that only dependencies whose constraints are specified, will be accepted by future build plans. This flag can be used to ensure that no unaudited packages are added to the build plan. + +This new `--lock` flag reuses the mechanism behind `--reject-unconstrained-dependencies`, by writing the equivalent of `--reject-unconstrained-dependencies=all` to the freeze file. diff --git a/doc/cabal-commands.rst b/doc/cabal-commands.rst index d1ee155eb8e..9c0ffdd5b7a 100644 --- a/doc/cabal-commands.rst +++ b/doc/cabal-commands.rst @@ -538,6 +538,9 @@ a freeze file is not, by default, a **lockfile**. To turn a freeze file into a l use the ``--lock`` flag when invocating ``cabal freeze``. This will prevent future builds from including new dependencies. This can be helpful in situations where every dependency must be explicitly audited and approved, for example. +Under the hood, the ``--lock`` flag reuses the mechanism behind +``--reject-unconstrained-dependencies``, by writing the equivalent of +``--reject-unconstrained-dependencies=all`` to the freeze file. cabal gen-bounds ^^^^^^^^^^^^^^^^