Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a --lock flag to cabal freeze to promote a freeze file to a lock file #10785

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 51 additions & 20 deletions cabal-install/src/Distribution/Client/CmdFreeze.hs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
module Distribution.Client.CmdFreeze
( freezeCommand
, freezeAction
, ClientFreezeFlags (..)
) where

import Distribution.Client.Compat.Prelude
Expand All @@ -27,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.Client.Setup
( CommonSetupFlags (setupVerbosity)
, ConfigFlags (..)
, GlobalFlags
)
import Distribution.Client.Targets
( UserConstraint (..)
, UserConstraintScope (..)
, UserQualifier (..)
)
import Distribution.Package
( PackageName
, packageName
Expand All @@ -54,11 +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.Solver.Types.ConstraintSource
( ConstraintSource (..)
)
import Distribution.Solver.Types.PackageConstraint
( PackageProperty (..)
)
import Distribution.Solver.Types.Settings (OnlyConstrained (..))
import Distribution.Verbosity
( normal
)
Expand All @@ -74,10 +76,15 @@ import qualified Data.Map as Map
import Distribution.Client.Errors
import Distribution.Simple.Command
( CommandUI (..)
, OptionField
, ShowOrParseArgs
, option
, usageAlternatives
)

freezeCommand :: CommandUI (NixStyleFlags ())
newtype ClientFreezeFlags = ClientFreezeFlags {lockDependencies :: Flag Bool}

freezeCommand :: CommandUI (NixStyleFlags ClientFreezeFlags)
freezeCommand =
CommandUI
{ commandName = "v2-freeze"
Expand All @@ -99,7 +106,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"
++ " "
Expand All @@ -108,23 +118,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 $
Expand All @@ -148,7 +173,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
Expand All @@ -170,24 +195,30 @@ 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
{ projectConfigConstraints =
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
Expand Down
5 changes: 5 additions & 0 deletions cabal-testsuite/PackageTests/Freeze/freeze-lock.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# cabal v2-update
Downloading the latest package list from test-local-repo
# cabal v2-freeze
Resolving dependencies...
Wrote freeze file: <ROOT>/cabal.project.freeze
6 changes: 6 additions & 0 deletions cabal-testsuite/PackageTests/Freeze/freeze-lock.test.hs
Original file line number Diff line number Diff line change
@@ -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"
10 changes: 10 additions & 0 deletions changelog.d/pr-10785.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
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.

This new `--lock` flag reuses the mechanism behind `--reject-unconstrained-dependencies`, by writing the equivalent of `--reject-unconstrained-dependencies=all` to the freeze file.
10 changes: 10 additions & 0 deletions doc/cabal-commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,16 @@ 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.
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
^^^^^^^^^^^^^^^^

Expand Down
Loading