Skip to content

Qualified constraints: documentation and unit tests (issue #3502) #4236

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

Merged
5 commits merged into from Jan 17, 2017
Merged
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
82 changes: 70 additions & 12 deletions Cabal/doc/installing-packages.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1255,18 +1255,76 @@ Miscellaneous options

.. option:: --constraint=constraint

Restrict solutions involving a package to a given version range. For
example, ``cabal install --constraint="bar==2.1"`` will only
consider install plans that do not use ``bar`` at all, or ``bar`` of
version 2.1.

As a special case, ``cabal install --constraint="bar -none"``
prevents ``bar`` from being used at all (``-none`` abbreviates
``> 1 && < 1``); ``cabal install --constraint="bar installed"``
prevents reinstallation of the ``bar`` package;
``cabal install --constraint="bar +foo -baz"`` specifies that the
flag ``foo`` should be turned on and the ``baz`` flag should be
turned off.
Restrict solutions involving a package to given version
bounds, flag settings, and other properties. For example, to
consider only install plans that use version 2.1 of ``bar``
or do not use ``bar`` at all, write:

::

$ cabal install --constraint="bar == 2.1"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs another newline after the "::" to form a code block.


Version bounds have the same syntax as ``build-depends``. As
a special case, the following prevents ``bar`` from being
used at all:

::

# Note: this is just syntax sugar for '> 1 && < 1', and is
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw, why do we use this arbitrary > 1 && < 1 contradiction instead of the more canonical < 0 constraint?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Patches accepted :) But -none is literally implemented this way:

noVersion :: VersionRange
noVersion = IntersectVersionRanges (LaterVersion v) (EarlierVersion v)
  where v = mkVersion [1]

We could change this as long as it is actually impossible to have negative version numbers :)

# supported by build-depends.
$ cabal install --constraint="bar -none"

You can also specify flag assignments:

::

# Require bar to be installed with the foo flag turned on and
# the baz flag turned off.
$ cabal install --constraint="bar +foo -baz"

To specify multiple constraints, you may pass the
``constraint`` option multiple times.

There are also some more specialized constraints, which most people
don't generally need:

::

# Require that a version of bar be used that is already installed in
# the global package database.
$ cabal install --constraint="bar installed"

# Require the local source copy of bar to be used.
# (Note: By default, if we have a local package we will
# automatically use it, so it will generally not be necessary to
# specify this.)
$ cabal install --constraint="bar source"

# Require that bar have test suites and benchmarks enabled.
$ cabal install --constraint="bar test" --constraint="bar bench"

By default, constraints only apply to build dependencies
(``build-depends``), build dependencies of build
dependencies, and so on. Constraints normally do not apply to
dependencies of the ``Setup.hs`` script of any package
(``setup-depends``) nor do they apply to build tools
(``build-tool-depends``) or the dependencies of build
tools. To explicitly apply a constraint to a setup or build
tool dependency, you can add a qualifier to the constraint as
follows:

::

# Example use of the 'setup' qualifier. This constraint
# applies to package bar when it is a dependency of the
# Setup.hs script of package foo.
$ cabal install --constraint="foo:setup.bar == 1.0"

# Example use of the 'exe' (executable build tool)
# qualifier. This constraint applies to package baz when it
# is a dependency of the build tool bar being used to
# build package foo.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to build

$ cabal install --constraint="foo:bar:exe.baz == 1.0"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I have to say, if I saw this in a source code file, I would definitely have a hard time telling what the bar is supposed to mean.


.. option:: --preference=preference

Expand Down
65 changes: 9 additions & 56 deletions Cabal/doc/nix-local-build.rst
Original file line number Diff line number Diff line change
Expand Up @@ -613,68 +613,21 @@ Solver configuration options
The following settings control the behavior of the dependency solver:

.. cfg-field:: constraints: constraints list (comma separated)
--constrant="pkg >= 2.0"
--constraint="pkg >= 2.0"
:synopsis: Extra dependencies constraints.

Add extra constraints to the version bounds, flag settings, and
other properties a solver can pick for a package. For example, to
only consider install plans that do not use ``bar`` at all, or use
``bar-2.1``, write:

::

constraints: bar == 2.1

Version bounds have the same syntax as ``build-depends``. You can
also specify flag assignments:

::

-- Require bar to be installed with the foo flag turned on and
-- the baz flag turned off
constraints: bar +foo -baz

-- Require that bar NOT be present in the install plan. Note:
-- this is just syntax sugar for '> 1 && < 1', and is supported
-- by build-depends.
constraints: bar -none

A package can be specified multiple times in ``constraints``, in
which case the specified constraints are intersected. This is
useful, since the syntax does not allow you to specify multiple
constraints at once. For example, to specify both version bounds and
flag assignments, you would write:

Add extra constraints to the version bounds, flag settings,
and other properties a solver can pick for a
package. For example:

::

constraints: bar == 2.1,
bar +foo -baz,

There are also some more specialized constraints, which most people
don't generally need:

::

-- Require bar to be preinstalled in the global package database
-- (this does NOT include the Nix-local build global store.)
constraints: bar installed
bar +foo -baz

-- Require the local source copy of bar to be used
-- (Note: By default, if we have a local package we will
-- automatically use it, so it generally not be necessary to
-- specify this)
constraints: bar source

-- Require that bar be solved with test suites and benchmarks enabled
-- (Note: By default, new-build configures the solver to make
-- a best-effort attempt to enable these stanzas, so this generally
-- should not be necessary.)
constraints: bar test,
bar bench

The command line variant of this field is
``--constraint="pkg >= 2.0"``; to specify multiple constraints, pass
the flag multiple times.
Valid constraints take the same form as for the `constraint
command line option
<installing-packages.html#cmdoption-setup-configure--constraint>`__.

.. cfg-field:: preferences: preference (comma separated)
--preference="pkg >= 2.0"
Expand Down
2 changes: 1 addition & 1 deletion cabal-install/Distribution/Client/Dependency.hs
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ dontUpgradeNonUpgradeablePackages params =
where
extraConstraints =
[ LabeledPackageConstraint
(PackageConstraint (scopeToplevel pkgname) PackagePropertyInstalled)
(PackageConstraint (ScopeAnyQualifier pkgname) PackagePropertyInstalled)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, did we seriously apply this to top-level only before? Worth a comment!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scopeTopLevel was only added in #4219 (originally unqualified). Before that, every constraint applied to all occurrences of the package, so the constraints above always prevented base, etc. from being installed.

ConstraintSourceNonUpgradeablePackage
| Set.notMember (mkPackageName "base") (depResolverTargets params)
-- If you change this enumeration, make sure to update the list in
Expand Down
15 changes: 11 additions & 4 deletions cabal-install/tests/UnitTests/Distribution/Client/ProjectConfig.hs
Original file line number Diff line number Diff line change
Expand Up @@ -565,15 +565,22 @@ instance Arbitrary RemoteRepo where
shortListOf1 5 (oneof [ choose ('0', '9')
, choose ('a', 'f') ])

instance Arbitrary UserQualifier where
arbitrary = oneof [ pure UserToplevel
, UserSetup <$> arbitrary
, UserExe <$> arbitrary <*> arbitrary
]

instance Arbitrary UserConstraint where
arbitrary =
oneof [ UserConstraint UserToplevel <$> arbitrary <*> prop
| prop <- [ PackagePropertyVersion <$> arbitrary
arbitrary = UserConstraint <$> arbitrary <*> arbitrary <*> arbitrary

instance Arbitrary PackageProperty where
arbitrary = oneof [ PackagePropertyVersion <$> arbitrary
, pure PackagePropertyInstalled
, pure PackagePropertySource
, PackagePropertyFlags <$> shortListOf1 3 arbitrary
, PackagePropertyStanzas . (\x->[x]) <$> arbitrary
] ]
]

instance Arbitrary OptionalStanza where
arbitrary = elements [minBound..maxBound]
Expand Down
105 changes: 68 additions & 37 deletions cabal-install/tests/UnitTests/Distribution/Client/Targets.hs
Original file line number Diff line number Diff line change
Expand Up @@ -4,61 +4,92 @@ module UnitTests.Distribution.Client.Targets (

import Distribution.Client.Targets (UserQualifier(..), UserConstraint(..)
,readUserConstraint)
import Distribution.Compat.ReadP (ReadP, readP_to_S)
import Distribution.Compat.ReadP (readP_to_S)
import Distribution.Package (mkPackageName)
import Distribution.PackageDescription (mkFlagName)
import Distribution.Version (anyVersion, thisVersion, mkVersion)
import Distribution.ParseUtils (parseCommaList)
import Distribution.Text (parse)

import Distribution.Solver.Types.PackageConstraint (PackageProperty(..))
import Distribution.Solver.Types.OptionalStanza (OptionalStanza(..))

import Test.Tasty
import Test.Tasty.HUnit

import Data.Char (isSpace)
import Data.List (intercalate)

tests :: [TestTree]
tests = [ testCase "readUserConstraint" readUserConstraintTest
, testCase "parseUserConstraint" parseUserConstraintTest
, testCase "readUserConstraints" readUserConstraintsTest
]
-- Helper function: makes a test group by mapping each element
-- of a list to a test case.
makeGroup :: String -> (a -> Assertion) -> [a] -> TestTree
makeGroup name f xs = testGroup name $
zipWith testCase (map show [0 :: Integer ..]) (map f xs)

readUserConstraintTest :: Assertion
readUserConstraintTest =
assertEqual ("Couldn't read constraint: '" ++ constr ++ "'") expected actual
tests :: [TestTree]
tests =
[ makeGroup "readUserConstraint" (uncurry readUserConstraintTest)
exampleConstraints

, makeGroup "parseUserConstraint" (uncurry parseUserConstraintTest)
exampleConstraints

, makeGroup "readUserConstraints" (uncurry readUserConstraintsTest)
[-- First example only.
(head exampleStrs, take 1 exampleUcs),
-- All examples separated by commas.
(intercalate ", " exampleStrs, exampleUcs)]
]
where
pkgName = "template-haskell"
constr = pkgName ++ " installed"
(exampleStrs, exampleUcs) = unzip exampleConstraints

expected = UserConstraint UserToplevel (mkPackageName pkgName)
PackagePropertyInstalled
actual = let (Right r) = readUserConstraint constr in r
exampleConstraints :: [(String, UserConstraint)]
exampleConstraints =
[ ("template-haskell installed",
UserConstraint UserToplevel (pn "template-haskell")
PackagePropertyInstalled)

, ("bytestring -any",
UserConstraint UserToplevel (pn "bytestring")
(PackagePropertyVersion anyVersion))

, ("process:setup.bytestring ==5.2",
UserConstraint (UserSetup (pn "process")) (pn "bytestring")
(PackagePropertyVersion (thisVersion (mkVersion [5, 2]))))

, ("network:setup.containers +foo -bar baz",
UserConstraint (UserSetup (pn "network")) (pn "containers")
(PackagePropertyFlags [(fn "foo", True),
(fn "bar", False),
(fn "baz", True)]))

, ("foo:happy:exe.template-haskell test",
UserConstraint (UserExe (pn "foo") (pn "happy")) (pn "template-haskell")
(PackagePropertyStanzas [TestStanzas]))
]
where
pn = mkPackageName
fn = mkFlagName

parseUserConstraintTest :: Assertion
parseUserConstraintTest =
assertEqual ("Couldn't parse constraint: '" ++ constr ++ "'") expected actual
readUserConstraintTest :: String -> UserConstraint -> Assertion
readUserConstraintTest str uc =
assertEqual ("Couldn't read constraint: '" ++ str ++ "'") expected actual
where
pkgName = "template-haskell"
constr = pkgName ++ " installed"
expected = uc
actual = let Right r = readUserConstraint str in r

expected = [UserConstraint UserToplevel (mkPackageName pkgName)
PackagePropertyInstalled]
actual = [ x | (x, ys) <- readP_to_S parseUserConstraint constr
parseUserConstraintTest :: String -> UserConstraint -> Assertion
parseUserConstraintTest str uc =
assertEqual ("Couldn't parse constraint: '" ++ str ++ "'") expected actual
where
expected = [uc]
actual = [ x | (x, ys) <- readP_to_S parse str
, all isSpace ys]

parseUserConstraint :: ReadP r UserConstraint
parseUserConstraint = parse

readUserConstraintsTest :: Assertion
readUserConstraintsTest =
assertEqual ("Couldn't read constraints: '" ++ constr ++ "'") expected actual
readUserConstraintsTest :: String -> [UserConstraint] -> Assertion
readUserConstraintsTest str ucs =
assertEqual ("Couldn't read constraints: '" ++ str ++ "'") expected actual
where
pkgName = "template-haskell"
constr = pkgName ++ " installed"

expected = [[UserConstraint UserToplevel (mkPackageName pkgName)
PackagePropertyInstalled]]
actual = [ x | (x, ys) <- readP_to_S parseUserConstraints constr
expected = [ucs]
actual = [ x | (x, ys) <- readP_to_S (parseCommaList parse) str
, all isSpace ys]

parseUserConstraints :: ReadP r [UserConstraint]
parseUserConstraints = parseCommaList parse