diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000000..bfe127253e2 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# See: https://editorconfig.org +root = true + +[*] +charset = utf-8 + +[*.hs] +indent_style = space +indent_size = 2 + +[Makefile] +indent_style = tab diff --git a/.github/mergify.yml b/.github/mergify.yml index 46eae2f7a80..7f7712394dd 100644 --- a/.github/mergify.yml +++ b/.github/mergify.yml @@ -52,15 +52,7 @@ pull_request_rules: - label=merge+no rebase - '#approved-reviews-by>=2' - '#changes-requested-reviews-by=0' - # oy - # lifted these from branch protection imports - - check-success=fourmolu - - check-success=hlint - - check-success=Meta checks - - check-success=Doctest Cabal - - check-success=Validate post job - - check-success=Bootstrap post job - - 'check-success=docs/readthedocs.org:cabal' + - '#check-failure=0' # rebase+merge strategy - actions: @@ -73,6 +65,7 @@ pull_request_rules: - label=merge delay passed - '#approved-reviews-by>=2' - '-label~=^blocked:' + - '#check-failure=0' # merge+squash strategy - actions: @@ -85,6 +78,7 @@ pull_request_rules: - label=merge delay passed - '#approved-reviews-by>=2' - '-label~=^blocked:' + - '#check-failure=0' # merge+no rebase strategy - actions: @@ -97,6 +91,11 @@ pull_request_rules: - label=merge delay passed - '#approved-reviews-by>=2' - '-label~=^blocked:' + - '#check-failure=0' + # unlike the others, we need to force this one to be up to date + # because it's intended for when Mergify doesn't have permission + # to rebase + - '#commits-behind=0' # merge strategy for release branches - actions: @@ -109,6 +108,7 @@ pull_request_rules: - -body~=backport - '#approved-reviews-by>=2' - '-label~=^blocked:' + - '#check-failure=0' # merge+squash strategy for release branches - actions: @@ -121,6 +121,7 @@ pull_request_rules: - -body~=backport - '#approved-reviews-by>=2' - '-label~=^blocked:' + - '#check-failure=0' # merge strategy for backports: require 1 approver instead of 2 - actions: @@ -133,6 +134,7 @@ pull_request_rules: - body~=backport - '#approved-reviews-by>=1' - '-label~=^blocked:' + - '#check-failure=0' # merge+squash strategy for backports: require 1 approver instead of 2 - actions: @@ -145,6 +147,7 @@ pull_request_rules: - body~=backport - '#approved-reviews-by>=1' - '-label~=^blocked:' + - '#check-failure=0' # backports should be labeled as such - actions: diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index d54310be613..116537cf2ea 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -17,3 +17,4 @@ jobs: Cabal/**/*.hs Cabal-syntax/**/*.hs cabal-install/**/*.hs + cabal-validate/**/*.hs diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 947b47a5f12..23fe4e5e7d1 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -124,6 +124,11 @@ jobs: rm -rf ~/.config/cabal rm -rf ~/.cache/cabal + - name: "WIN: Setup TMP environment variable" + if: runner.os == 'Windows' + run: | + echo "TMP=${{ runner.temp }}" >> "$GITHUB_ENV" + - uses: actions/checkout@v4 # See https://github.com/haskell/cabal/blob/master/CONTRIBUTING.md#hackage-revisions @@ -236,29 +241,24 @@ jobs: name: cabal-${{ runner.os }}-${{ env.CABAL_ARCH }} path: ${{ env.CABAL_EXEC_TAR }} - - name: Validate lib-tests + - name: Validate tests env: # `rawSystemStdInOut reports text decoding errors` # test does not find ghc without the full path in windows GHCPATH: ${{ steps.setup-haskell.outputs.ghc-exe }} - run: sh validate.sh $FLAGS -s lib-tests - - - name: Validate lib-suite - run: sh validate.sh $FLAGS -s lib-suite - - - name: Validate cli-tests - run: sh validate.sh $FLAGS -s cli-tests - - - name: Validate cli-suite - run: sh validate.sh $FLAGS -s cli-suite - - - name: Validate solver-benchmarks-tests - if: matrix.ghc == env.GHC_FOR_SOLVER_BENCHMARKS - run: sh validate.sh $FLAGS -s solver-benchmarks-tests - - - name: Validate solver-benchmarks-run - if: matrix.ghc == env.GHC_FOR_SOLVER_BENCHMARKS - run: sh validate.sh $FLAGS -s solver-benchmarks-run + run: | + set +e + rc=0 + tests="lib-tests lib-suite cli-tests cli-suite" + if [ "${{ matrix.ghc }}" = "${{ env.GHC_FOR_SOLVER_BENCHMARKS }}" ]; then + tests="$tests solver-benchmarks-tests solver-benchmarks-run" + fi + for test in $tests; do + echo Validate "$test" + sh validate.sh $FLAGS -s "$test" || rc=1 + echo End "$test" + done + exit $rc validate-old-ghcs: name: Validate old ghcs ${{ matrix.extra-ghc }} @@ -312,11 +312,13 @@ jobs: restore-keys: ${{ runner.os }}-${{ env.GHC_FOR_RELEASE }}- - name: Validate build + id: build run: sh validate.sh ${{ env.COMMON_FLAGS }} -s build - name: "Validate lib-suite-extras --extra-hc ghc-${{ matrix.extra-ghc }}" env: EXTRA_GHC: ghc-${{ matrix.extra-ghc }} + continue-on-error: true run: sh validate.sh ${{ env.COMMON_FLAGS }} --lib-only -s lib-suite-extras --extra-hc "${{ env.EXTRA_GHC }}" build-alpine: @@ -399,7 +401,6 @@ jobs: # We need to build an array dynamically to inject the appropiate env var in a previous job, # see https://docs.github.com/en/actions/learn-github-actions/expressions#fromjson ghc: ${{ fromJSON (needs.validate.outputs.GHC_FOR_RELEASE) }} - defaults: run: shell: ${{ matrix.sys.shell }} @@ -416,12 +417,17 @@ jobs: esac echo "CABAL_ARCH=$arch" >> "$GITHUB_ENV" - - name: Work around XDG directories existence (haskell-actions/setup#62) + - name: "MAC: Work around XDG directories existence (haskell-actions/setup#62)" if: runner.os == 'macOS' run: | rm -rf ~/.config/cabal rm -rf ~/.cache/cabal + - name: "WIN: Setup TMP environment variable" + if: runner.os == 'Windows' + run: | + echo "TMP=${{ runner.temp }}" >> "$GITHUB_ENV" + - uses: actions/checkout@v4 - uses: haskell-actions/setup@v2 @@ -451,6 +457,8 @@ jobs: name: Create a GitHub prerelease with the binary artifacts runs-on: ubuntu-latest if: github.ref == 'refs/heads/master' + permissions: + contents: write # IMPORTANT! Any job added to the workflow should be added here too needs: [validate, validate-old-ghcs, build-alpine, dogfooding] @@ -459,32 +467,15 @@ jobs: # for now this is hardcoded. is there a better way? - uses: actions/download-artifact@v4 with: - name: cabal-Windows-x86_64 - - - uses: actions/download-artifact@v4 - with: - name: cabal-Linux-x86_64 - - - uses: actions/download-artifact@v4 - with: - name: cabal-Linux-static-x86_64 - - - uses: actions/download-artifact@v4 - with: - name: cabal-macOS-aarch64 + pattern: cabal-* + path: binaries - name: Create GitHub prerelease - uses: marvinpinto/action-automatic-releases@v1.2.1 + uses: softprops/action-gh-release@v2 with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - automatic_release_tag: cabal-head + tag_name: cabal-head prerelease: true - title: cabal-head - files: | - cabal-head-Windows-x86_64.tar.gz - cabal-head-Linux-x86_64.tar.gz - cabal-head-Linux-static-x86_64.tar.gz - cabal-head-macOS-aarch64.tar.gz + files: binaries/cabal-* prerelease-lts: name: Create a GitHub LTS prerelease with the binary artifacts @@ -498,39 +489,22 @@ jobs: steps: - uses: actions/download-artifact@v4 with: - name: cabal-Windows-x86_64 - - - uses: actions/download-artifact@v4 - with: - name: cabal-Linux-x86_64 - - - uses: actions/download-artifact@v4 - with: - name: cabal-Linux-static-x86_64 - - - uses: actions/download-artifact@v4 - with: - name: cabal-macOS-x86_64 + pattern: cabal-* + path: binaries - run: | # bash-ism, but we forced bash above mv cabal-{,lts-}head-Windows-x86_64.tar.gz mv cabal-{,lts-}head-Linux-x86_64.tar.gz mv cabal-{,lts-}head-Linux-static-x86_64.tar.gz - mv cabal-{,lts-}head-macOS-x86_64.tar.gz + mv cabal-{,lts-}head-macOS-aarch64.tar.gz - name: Create GitHub prerelease - uses: marvinpinto/action-automatic-releases@v1.2.1 + uses: softprops/action-gh-release@v2 with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - automatic_release_tag: cabal-lts-head + tag_name: cabal-lts-head prerelease: true - title: cabal-lts-head - files: | - cabal-lts-head-Windows-x86_64.tar.gz - cabal-lts-head-Linux-x86_64.tar.gz - cabal-lts-head-Linux-static-x86_64.tar.gz - cabal-lts-head-macOS-x86_64.tar.gz + files: binaries/cabal-* # We use this job as a summary of the workflow # It will fail if any of the previous jobs does diff --git a/.github/workflows/whitespace.yml b/.github/workflows/whitespace.yml index b6604798f1e..a6869a59e3f 100644 --- a/.github/workflows/whitespace.yml +++ b/.github/workflows/whitespace.yml @@ -7,10 +7,21 @@ on: jobs: whitespace: + defaults: + run: + shell: bash runs-on: ubuntu-latest steps: + - uses: actions/checkout@v4 - - uses: andreasabel/fix-whitespace-action@v1 - with: - verbose: true + + - run: | + # no longer using the action because apparently we're supposed to use the Makefile here + wget -q https://github.com/agda/fix-whitespace/releases/download/v0.1/fix-whitespace-0.1-linux.binary + mkdir -p "$HOME/.local/bin" + mv fix-whitespace-0.1-linux.binary "$HOME/.local/bin/fix-whitespace" + chmod +x "$HOME/.local/bin/fix-whitespace" + echo "$HOME/.local/bin" >> $GITHUB_PATH + + - run: make whitespace diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9a06d1c8329..93835320bb2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -196,7 +196,9 @@ the code base. We use automated whitespace convention checking. Violations can be fixed by running [fix-whitespace](https://hackage.haskell.org/package/fix-whitespace). If -you push a fix of a whitespace violation, please do so in a _separate commit_. +you push a fix of a whitespace violation, please do so in a _separate commit_. For convenience, +`make whitespace` will show violations and `make fix-whitespace` will fix them, if the +`fix-whitespace` utility is installed. ## Other Conventions @@ -346,13 +348,37 @@ description: { } ``` +Changelogs may also be written in "markdown-frontmatter" format. This is useful if your +description contains braces, which must be escaped with backslashes in `.cabal` file +format. The front matter is in YAML syntax, not `.cabal` file syntax, and the file +_must_ begin with a line containing only hyphens. + +```markdown +--- +synopsis: Add feature xyz +packages: [cabal-install] +prs: 0000 +issues: [0000, 0000] +significance: significant +--- + +- Detail number 1 +- Detail number 2 + +``` +The package list must be enclosed in square brackets and comma-separated, but this +isn't needed for `prs` or `issues`; those are free-form and any YAML syntax will +be accepted. Note that the number signs on PR and issue numbers are required in +`.cabal` file syntax, but won't work in markdown-frontmatter syntax because they +signify comments in YAML. + Only the `synopsis` and `prs` fields are required, but you should also set the others where applicable. | Field | Description | | ----- | ----------- | | `synopsis` | Brief description of the change. Often just the pr title. | | `description` | Longer description, with a list of sub-changes. Not needed for small/atomic changes. | -| `packages` | Packages affected by the change (`cabal-install`, `Cabal`...). Omit if it's an overarching or non-package change. | +| `packages` | Packages affected by the change (`cabal-install`, `Cabal`...). Omit if it's a non-package change. | | `prs` | Space-separated hash-prefixed pull request numbers containing the change (usually just one). | | `issues` | Space-separated hash-prefixed issue numbers that the change fixes/closes/affects. | | `significance` | Set to `significant` if the change is significant, that is if it warrants being put near the top of the changelog. | @@ -377,7 +403,7 @@ There are a few main venues of communication: * Many developers idle on `#hackage` on [`irc.libera.chat`](https://libera.chat). The `#ghc` channel is also a decently good bet. * You can join the channel using a web client, even anonymously: https://web.libera.chat/#hackage - * Alternatively you can join it using [matrix](https://matrix.org/): https://matrix.to/#/#hackage:libera.chat + * Alternatively you can join it using [matrix](https://matrix.org/): https://matrix.to/#/#hackage:matrix.org ## Releases diff --git a/Cabal-syntax/Cabal-syntax.cabal b/Cabal-syntax/Cabal-syntax.cabal index 0893f4f2588..42fb7f7d51e 100644 --- a/Cabal-syntax/Cabal-syntax.cabal +++ b/Cabal-syntax/Cabal-syntax.cabal @@ -135,6 +135,7 @@ library Distribution.Types.ConfVar Distribution.Types.Dependency Distribution.Types.DependencyMap + Distribution.Types.DependencySatisfaction Distribution.Types.ExeDependency Distribution.Types.Executable Distribution.Types.Executable.Lens @@ -158,6 +159,8 @@ library Distribution.Types.Library.Lens Distribution.Types.LibraryName Distribution.Types.LibraryVisibility + Distribution.Types.MissingDependency + Distribution.Types.MissingDependencyReason Distribution.Types.Mixin Distribution.Types.Module Distribution.Types.ModuleReexport diff --git a/Cabal-syntax/src/Distribution/Compat/Binary.hs b/Cabal-syntax/src/Distribution/Compat/Binary.hs index 8849fc13b10..4927ec1e69b 100644 --- a/Cabal-syntax/src/Distribution/Compat/Binary.hs +++ b/Cabal-syntax/src/Distribution/Compat/Binary.hs @@ -20,4 +20,4 @@ decodeOrFailIO :: Binary a => ByteString -> IO (Either String a) decodeOrFailIO bs = catch (evaluate (decode bs) >>= return . Right) handler where - handler (ErrorCallWithLocation str _) = return $ Left str + handler (ErrorCall str) = return $ Left str diff --git a/Cabal-syntax/src/Distribution/Compat/NonEmptySet.hs b/Cabal-syntax/src/Distribution/Compat/NonEmptySet.hs index 17e3811e9a4..9e227459e84 100644 --- a/Cabal-syntax/src/Distribution/Compat/NonEmptySet.hs +++ b/Cabal-syntax/src/Distribution/Compat/NonEmptySet.hs @@ -12,6 +12,7 @@ module Distribution.Compat.NonEmptySet -- * Deletion , delete + , filter -- * Conversions , toNonEmpty @@ -116,6 +117,9 @@ delete x (NES xs) where res = Set.delete x xs +filter :: (a -> Bool) -> NonEmptySet a -> Set.Set a +filter predicate (NES set) = Set.filter predicate set + ------------------------------------------------------------------------------- -- Conversions ------------------------------------------------------------------------------- diff --git a/Cabal-syntax/src/Distribution/PackageDescription/Configuration.hs b/Cabal-syntax/src/Distribution/PackageDescription/Configuration.hs index e811c361221..eebf760094d 100644 --- a/Cabal-syntax/src/Distribution/PackageDescription/Configuration.hs +++ b/Cabal-syntax/src/Distribution/PackageDescription/Configuration.hs @@ -56,12 +56,13 @@ import Distribution.System import Distribution.Types.Component import Distribution.Types.ComponentRequestedSpec import Distribution.Types.DependencyMap +import Distribution.Types.DependencySatisfaction (DependencySatisfaction (..)) +import Distribution.Types.MissingDependency (MissingDependency (..)) import Distribution.Types.PackageVersionConstraint import Distribution.Utils.Generic import Distribution.Utils.Path (sameDirectory) import Distribution.Version -import qualified Data.Map.Lazy as Map import Data.Tree (Tree (Node)) ------------------------------------------------------------------------------ @@ -144,15 +145,17 @@ parseCondition = condOr ------------------------------------------------------------------------------ --- | Result of dependency test. Isomorphic to @Maybe d@ but renamed for +-- | Result of dependency test. Isomorphic to @Maybe@ but renamed for -- clarity. -data DepTestRslt d = DepOk | MissingDeps d +data DepTestRslt + = DepOk + | MissingDeps [MissingDependency] -instance Semigroup d => Monoid (DepTestRslt d) where +instance Monoid DepTestRslt where mempty = DepOk mappend = (<>) -instance Semigroup d => Semigroup (DepTestRslt d) where +instance Semigroup DepTestRslt where DepOk <> x = x x <> DepOk = x (MissingDeps d) <> (MissingDeps d') = MissingDeps (d <> d') @@ -190,13 +193,13 @@ resolveWithFlags -> [PackageVersionConstraint] -- ^ Additional constraints -> [CondTree ConfVar [Dependency] PDTagged] - -> ([Dependency] -> DepTestRslt [Dependency]) + -> ([Dependency] -> DepTestRslt) -- ^ Dependency test function. - -> Either [Dependency] (TargetSet PDTagged, FlagAssignment) + -> Either [MissingDependency] (TargetSet PDTagged, FlagAssignment) -- ^ Either the missing dependencies (error case), or a pair of -- (set of build targets with dependencies, chosen flag assignments) resolveWithFlags dom enabled os arch impl constrs trees checkDeps = - either (Left . fromDepMapUnion) Right $ explore (build mempty dom) + explore (build mempty dom) where -- simplify trees by (partially) evaluating all conditions and converting -- dependencies to dependency maps. @@ -216,7 +219,7 @@ resolveWithFlags dom enabled os arch impl constrs trees checkDeps = -- computation overhead in the successful case. explore :: Tree FlagAssignment - -> Either DepMapUnion (TargetSet PDTagged, FlagAssignment) + -> Either [MissingDependency] (TargetSet PDTagged, FlagAssignment) explore (Node flags ts) = let targetSet = TargetSet $ @@ -229,7 +232,7 @@ resolveWithFlags dom enabled os arch impl constrs trees checkDeps = DepOk | null ts -> Right (targetSet, flags) | otherwise -> tryAll $ map explore ts - MissingDeps mds -> Left (toDepMapUnion mds) + MissingDeps mds -> Left mds -- Builds a tree of all possible flag assignments. Internal nodes -- have only partial assignments. @@ -238,18 +241,18 @@ resolveWithFlags dom enabled os arch impl constrs trees checkDeps = build assigned ((fn, vals) : unassigned) = Node assigned $ map (\v -> build (insertFlagAssignment fn v assigned) unassigned) vals - tryAll :: [Either DepMapUnion a] -> Either DepMapUnion a + tryAll :: Monoid a => [Either a b] -> Either a b tryAll = foldr mp mz -- special version of `mplus' for our local purposes - mp :: Either DepMapUnion a -> Either DepMapUnion a -> Either DepMapUnion a + mp :: Monoid a => Either a b -> Either a b -> Either a b mp m@(Right _) _ = m mp _ m@(Right _) = m mp (Left xs) (Left ys) = Left (xs <> ys) -- `mzero' - mz :: Either DepMapUnion a - mz = Left (DepMapUnion Map.empty) + mz :: Monoid a => Either a b + mz = Left mempty env :: FlagAssignment -> FlagName -> Either FlagName Bool env flags flag = (maybe (Left flag) Right . lookupFlagAssignment flag) flags @@ -323,27 +326,6 @@ extractConditions f gpkg = , extractCondition (f . benchmarkBuildInfo) . snd <$> condBenchmarks gpkg ] --- | A map of package constraints that combines version ranges using 'unionVersionRanges'. -newtype DepMapUnion = DepMapUnion {unDepMapUnion :: Map PackageName (VersionRange, NonEmptySet LibraryName)} - -instance Semigroup DepMapUnion where - DepMapUnion x <> DepMapUnion y = - DepMapUnion $ - Map.unionWith unionVersionRanges' x y - -unionVersionRanges' - :: (VersionRange, NonEmptySet LibraryName) - -> (VersionRange, NonEmptySet LibraryName) - -> (VersionRange, NonEmptySet LibraryName) -unionVersionRanges' (vr, cs) (vr', cs') = (unionVersionRanges vr vr', cs <> cs') - -toDepMapUnion :: [Dependency] -> DepMapUnion -toDepMapUnion ds = - DepMapUnion $ Map.fromListWith unionVersionRanges' [(p, (vr, cs)) | Dependency p vr cs <- ds] - -fromDepMapUnion :: DepMapUnion -> [Dependency] -fromDepMapUnion m = [Dependency p vr cs | (p, (vr, cs)) <- Map.toList (unDepMapUnion m)] - freeVars :: CondTree ConfVar c a -> [FlagName] freeVars t = [f | PackageFlag f <- freeVars' t] where @@ -453,7 +435,7 @@ finalizePD :: FlagAssignment -- ^ Explicitly specified flag assignments -> ComponentRequestedSpec - -> (Dependency -> Bool) + -> (Dependency -> DependencySatisfaction) -- ^ Is a given dependency satisfiable from the set of -- available packages? If this is unknown then use -- True. @@ -465,7 +447,7 @@ finalizePD -- ^ Additional constraints -> GenericPackageDescription -> Either - [Dependency] + [MissingDependency] (PackageDescription, FlagAssignment) -- ^ Either missing dependencies or the resolved package -- description along with the flag assignments chosen. @@ -526,7 +508,11 @@ finalizePD | otherwise -> [b, not b] -- flagDefaults = map (\(n,x:_) -> (n,x)) flagChoices check ds = - let missingDeps = filter (not . satisfyDep) ds + let missingDeps = + [ MissingDependency dependency reason + | (dependency, Unsatisfied reason) <- + map (\dependency -> (dependency, satisfyDep dependency)) ds + ] in if null missingDeps then DepOk else MissingDeps missingDeps diff --git a/Cabal-syntax/src/Distribution/Pretty.hs b/Cabal-syntax/src/Distribution/Pretty.hs index 3ddb806d81b..fcb0a7f0d0b 100644 --- a/Cabal-syntax/src/Distribution/Pretty.hs +++ b/Cabal-syntax/src/Distribution/Pretty.hs @@ -10,6 +10,8 @@ module Distribution.Pretty , showTokenStr , showFreeText , showFreeTextV3 + , commaSpaceSep + , commaSep -- * Deprecated , Separator @@ -118,3 +120,11 @@ lines_ s = in l : case s' of [] -> [] (_ : s'') -> lines_ s'' + +-- | Separate a list of documents by commas and spaces. +commaSpaceSep :: Pretty a => [a] -> PP.Doc +commaSpaceSep = PP.hsep . PP.punctuate PP.comma . map pretty + +-- | Separate a list of documents by commas. +commaSep :: Pretty a => [a] -> PP.Doc +commaSep = PP.hcat . PP.punctuate PP.comma . map pretty diff --git a/Cabal-syntax/src/Distribution/Types/Dependency.hs b/Cabal-syntax/src/Distribution/Types/Dependency.hs index 222a699a3f9..a152c9e3a68 100644 --- a/Cabal-syntax/src/Distribution/Types/Dependency.hs +++ b/Cabal-syntax/src/Distribution/Types/Dependency.hs @@ -78,31 +78,21 @@ instance NFData Dependency where rnf = genericRnf -- "pkg" -- -- >>> prettyShow $ Dependency (mkPackageName "pkg") anyVersion $ NES.insert (LSubLibName $ mkUnqualComponentName "sublib") mainLibSet --- "pkg:{pkg, sublib}" +-- "pkg:{pkg,sublib}" -- -- >>> prettyShow $ Dependency (mkPackageName "pkg") anyVersion $ NES.singleton (LSubLibName $ mkUnqualComponentName "sublib") -- "pkg:sublib" -- -- >>> prettyShow $ Dependency (mkPackageName "pkg") anyVersion $ NES.insert (LSubLibName $ mkUnqualComponentName "sublib-b") $ NES.singleton (LSubLibName $ mkUnqualComponentName "sublib-a") --- "pkg:{sublib-a, sublib-b}" +-- "pkg:{sublib-a,sublib-b}" instance Pretty Dependency where - pretty (Dependency name ver sublibs) = withSubLibs (pretty name) <+> pver + pretty (Dependency name ver sublibs) = prettyLibraryNames name (NES.toNonEmpty sublibs) <+> pver where -- TODO: change to isAnyVersion after #6736 pver | isAnyVersionLight ver = PP.empty | otherwise = pretty ver - withSubLibs doc = case NES.toList sublibs of - [LMainLibName] -> doc - [LSubLibName uq] -> doc <<>> PP.colon <<>> pretty uq - _ -> doc <<>> PP.colon <<>> PP.braces prettySublibs - - prettySublibs = PP.hsep $ PP.punctuate PP.comma $ prettySublib <$> NES.toList sublibs - - prettySublib LMainLibName = PP.text $ unPackageName name - prettySublib (LSubLibName un) = PP.text $ unUnqualComponentName un - -- | -- -- >>> simpleParsec "mylib:sub" :: Maybe Dependency diff --git a/Cabal-syntax/src/Distribution/Types/DependencySatisfaction.hs b/Cabal-syntax/src/Distribution/Types/DependencySatisfaction.hs new file mode 100644 index 00000000000..56ce74c1c45 --- /dev/null +++ b/Cabal-syntax/src/Distribution/Types/DependencySatisfaction.hs @@ -0,0 +1,14 @@ +module Distribution.Types.DependencySatisfaction + ( DependencySatisfaction (..) + ) where + +import Distribution.Types.MissingDependencyReason (MissingDependencyReason) + +-- | Whether or not a dependency constraint is satisfied. +data DependencySatisfaction + = -- | The dependency constraint is satisfied. + Satisfied + | -- | The dependency constraint is not satisfied. + -- + -- Includes a reason for explanation. + Unsatisfied MissingDependencyReason diff --git a/Cabal-syntax/src/Distribution/Types/LibraryName.hs b/Cabal-syntax/src/Distribution/Types/LibraryName.hs index 2b8f53f4f89..e31d1e82423 100644 --- a/Cabal-syntax/src/Distribution/Types/LibraryName.hs +++ b/Cabal-syntax/src/Distribution/Types/LibraryName.hs @@ -10,6 +10,7 @@ module Distribution.Types.LibraryName , libraryNameString -- * Pretty & Parse + , prettyLibraryNames , prettyLibraryNameComponent , parsecLibraryNameComponent ) where @@ -21,6 +22,7 @@ import Distribution.Parsec import Distribution.Pretty import Distribution.Types.UnqualComponentName +import qualified Data.List.NonEmpty as NEL import qualified Distribution.Compat.CharParsing as P import qualified Text.PrettyPrint as Disp @@ -42,6 +44,22 @@ prettyLibraryNameComponent :: LibraryName -> Disp.Doc prettyLibraryNameComponent LMainLibName = Disp.text "lib" prettyLibraryNameComponent (LSubLibName str) = Disp.text "lib:" <<>> pretty str +-- | Pretty print a 'LibraryName' after a package name. +-- +-- Produces output like @foo@, @foo:bar@, or @foo:{bar,baz}@ +prettyLibraryNames :: Pretty a => a -> NonEmpty LibraryName -> Disp.Doc +prettyLibraryNames package libraries = + let doc = pretty package + + prettyComponent LMainLibName = pretty package + prettyComponent (LSubLibName component) = Disp.text $ unUnqualComponentName component + + prettyComponents = commaSep $ prettyComponent <$> NEL.toList libraries + in case libraries of + LMainLibName :| [] -> doc + LSubLibName component :| [] -> doc <<>> Disp.colon <<>> pretty component + _ -> doc <<>> Disp.colon <<>> Disp.braces prettyComponents + parsecLibraryNameComponent :: CabalParsing m => m LibraryName parsecLibraryNameComponent = do _ <- P.string "lib" diff --git a/Cabal-syntax/src/Distribution/Types/MissingDependency.hs b/Cabal-syntax/src/Distribution/Types/MissingDependency.hs new file mode 100644 index 00000000000..57d90276d8c --- /dev/null +++ b/Cabal-syntax/src/Distribution/Types/MissingDependency.hs @@ -0,0 +1,34 @@ +module Distribution.Types.MissingDependency + ( MissingDependency (..) + ) where + +import Distribution.Compat.Prelude +import Distribution.Pretty +import Distribution.Types.Dependency + ( Dependency + , simplifyDependency + ) +import Distribution.Types.LibraryName + ( prettyLibraryNames + ) +import Distribution.Types.MissingDependencyReason + ( MissingDependencyReason (..) + ) + +import qualified Text.PrettyPrint as PP + +-- | A missing dependency and information on why it's missing. +data MissingDependency = MissingDependency Dependency MissingDependencyReason + deriving (Show) + +instance Pretty MissingDependency where + pretty (MissingDependency dependency reason) = + let prettyReason = + case reason of + MissingLibrary libraries -> + PP.text "missing" <+> prettyLibraryNames PP.empty libraries + MissingPackage -> PP.text "missing" + MissingComponent name -> PP.text "missing component" <+> pretty name + WrongVersion versions -> + PP.text "installed:" <+> commaSpaceSep versions + in pretty (simplifyDependency dependency) <+> PP.parens prettyReason diff --git a/Cabal-syntax/src/Distribution/Types/MissingDependencyReason.hs b/Cabal-syntax/src/Distribution/Types/MissingDependencyReason.hs new file mode 100644 index 00000000000..c1c37800f21 --- /dev/null +++ b/Cabal-syntax/src/Distribution/Types/MissingDependencyReason.hs @@ -0,0 +1,25 @@ +module Distribution.Types.MissingDependencyReason + ( MissingDependencyReason (..) + ) where + +import Data.List.NonEmpty (NonEmpty) +import Distribution.Types.LibraryName (LibraryName) +import Distribution.Types.PackageName (PackageName) +import Distribution.Types.Version (Version) + +-- | A reason for a depency failing to solve. +-- +-- This helps pinpoint dependencies that are installed with an incorrect +-- version vs. dependencies that are not installed at all. +data MissingDependencyReason + = -- | One or more libraries is missing. + MissingLibrary (NonEmpty LibraryName) + | -- | A package is not installed. + MissingPackage + | -- | A package is installed, but the versions don't match. + -- + -- Contains the available versions. + WrongVersion [Version] + | -- | A component is not installed. + MissingComponent PackageName + deriving (Show) diff --git a/Cabal-syntax/src/Distribution/Utils/Generic.hs b/Cabal-syntax/src/Distribution/Utils/Generic.hs index 997e0132f5a..ca48c8e820a 100644 --- a/Cabal-syntax/src/Distribution/Utils/Generic.hs +++ b/Cabal-syntax/src/Distribution/Utils/Generic.hs @@ -100,11 +100,13 @@ import qualified Data.Set as Set import qualified Control.Exception as Exception import System.Directory - ( removeFile + ( copyFile + , getTemporaryDirectory + , removeFile , renameFile ) import System.FilePath - ( splitFileName + ( takeFileName , (<.>) ) import System.IO @@ -153,8 +155,8 @@ wrapLine width = wrap 0 [] -- | Gets the contents of a file, but guarantee that it gets closed. -- --- The file is read lazily but if it is not fully consumed by the action then --- the remaining input is truncated and the file is closed. +-- The file is read lazily; if it is not fully consumed by the action then an +-- exception is thrown. withFileContents :: FilePath -> (String -> IO a) -> IO a withFileContents name action = withFile @@ -167,18 +169,38 @@ withFileContents name action = -- The file is either written successfully or an IO exception is raised and -- the original file is left unchanged. -- --- On windows it is not possible to delete a file that is open by a process. --- This case will give an IO exception but the atomic property is not affected. +-- On Unix: +-- +-- - If the temp directory (@$TMPDIR@) is in a filesystem different than the +-- destination path, the renaming will be emulated via 'copyFile' then +-- 'deleteFile'. +-- +-- On Windows: +-- +-- - This operation is not guaranteed to be atomic, see 'renameFile'. +-- +-- - It is not possible to delete a file that is open by a process. This case +-- will give an IO exception but the atomic property is not affected. +-- +-- - If the temp directory (@TMP@/@TEMP@/..., see haddocks on +-- 'getTemporaryDirectory') is in a different drive than the destination path, +-- the write will be emulated via 'copyFile', then 'deleteFile'. writeFileAtomic :: FilePath -> LBS.ByteString -> IO () writeFileAtomic targetPath content = do - let (targetDir, targetFile) = splitFileName targetPath + let targetFile = takeFileName targetPath + tmpDir <- getTemporaryDirectory Exception.bracketOnError - (openBinaryTempFileWithDefaultPermissions targetDir $ targetFile <.> "tmp") + (openBinaryTempFileWithDefaultPermissions tmpDir $ targetFile <.> "tmp") (\(tmpPath, handle) -> hClose handle >> removeFile tmpPath) ( \(tmpPath, handle) -> do LBS.hPut handle content hClose handle - renameFile tmpPath targetPath + Exception.catch + (renameFile tmpPath targetPath) + ( \(_ :: Exception.SomeException) -> do + copyFile tmpPath targetPath + removeFile tmpPath + ) ) -- ------------------------------------------------------------ diff --git a/Cabal-syntax/src/Distribution/Utils/Structured.hs b/Cabal-syntax/src/Distribution/Utils/Structured.hs index 83ae28995a8..ec8463bd6d3 100644 --- a/Cabal-syntax/src/Distribution/Utils/Structured.hs +++ b/Cabal-syntax/src/Distribution/Utils/Structured.hs @@ -277,7 +277,7 @@ structuredDecodeOrFailIO :: (Binary.Binary a, Structured a) => LBS.ByteString -> structuredDecodeOrFailIO bs = catch (evaluate (structuredDecode bs) >>= return . Right) handler where - handler (ErrorCallWithLocation str _) = return $ Left str + handler (ErrorCall str) = return $ Left str -- | Lazily reconstruct a value previously written to a file. structuredDecodeFileOrFail :: (Binary.Binary a, Structured a) => FilePath -> IO (Either String a) diff --git a/Cabal-tests/tests/ParserTests/regressions/issue-5846.format b/Cabal-tests/tests/ParserTests/regressions/issue-5846.format index 749a9c20524..93e53fc48bd 100644 --- a/Cabal-tests/tests/ParserTests/regressions/issue-5846.format +++ b/Cabal-tests/tests/ParserTests/regressions/issue-5846.format @@ -5,7 +5,7 @@ version: 5846 library default-language: Haskell2010 build-depends: - lib1:{a, b}, + lib1:{a,b}, lib2:c, lib3:d >=1, - lib4:{a, b} >=1 + lib4:{a,b} >=1 diff --git a/Cabal-tests/tests/UnitTests/Distribution/Simple/Utils.hs b/Cabal-tests/tests/UnitTests/Distribution/Simple/Utils.hs index 2e544c8c52d..48e8aae9c1d 100644 --- a/Cabal-tests/tests/UnitTests/Distribution/Simple/Utils.hs +++ b/Cabal-tests/tests/UnitTests/Distribution/Simple/Utils.hs @@ -23,16 +23,14 @@ import Test.Tasty.HUnit withTempFileTest :: Assertion withTempFileTest = do fileName <- newIORef "" - tempDir <- getTemporaryDirectory - withTempFile tempDir ".foo" $ \fileName' _handle -> do + withTempFile ".foo" $ \fileName' _handle -> do writeIORef fileName fileName' fileExists <- readIORef fileName >>= doesFileExist assertBool "Temporary file not deleted by 'withTempFile'!" (not fileExists) withTempFileRemovedTest :: Assertion withTempFileRemovedTest = do - tempDir <- getTemporaryDirectory - withTempFile tempDir ".foo" $ \fileName handle -> do + withTempFile ".foo" $ \fileName handle -> do hClose handle removeFile fileName @@ -58,9 +56,8 @@ rawSystemStdInOutTextDecodingTest ghcPath -- so skip the test if it's not. | show localeEncoding /= "UTF-8" = return () | otherwise = do - tempDir <- getTemporaryDirectory - res <- withTempFile tempDir ".hs" $ \filenameHs handleHs -> do - withTempFile tempDir ".exe" $ \filenameExe handleExe -> do + res <- withTempFile ".hs" $ \filenameHs handleHs -> do + withTempFile ".exe" $ \filenameExe handleExe -> do -- Small program printing not utf8 hPutStrLn handleHs "import Data.ByteString" hPutStrLn handleHs "main = Data.ByteString.putStr (Data.ByteString.pack [32, 32, 255])" diff --git a/Cabal/Cabal.cabal b/Cabal/Cabal.cabal index b9a7e0838ab..a9e108d1f7b 100644 --- a/Cabal/Cabal.cabal +++ b/Cabal/Cabal.cabal @@ -248,6 +248,7 @@ library Distribution.Types.ConfVar, Distribution.Types.Dependency, Distribution.Types.DependencyMap, + Distribution.Types.DependencySatisfaction, Distribution.Types.ExeDependency, Distribution.Types.Executable, Distribution.Types.Executable.Lens, @@ -271,6 +272,8 @@ library Distribution.Types.Library.Lens, Distribution.Types.LibraryName, Distribution.Types.LibraryVisibility, + Distribution.Types.MissingDependency, + Distribution.Types.MissingDependencyReason, Distribution.Types.Mixin, Distribution.Types.Module, Distribution.Types.ModuleReexport, diff --git a/Cabal/src/Distribution/Simple.hs b/Cabal/src/Distribution/Simple.hs index ada1d6c7aad..3533cf78336 100644 --- a/Cabal/src/Distribution/Simple.hs +++ b/Cabal/src/Distribution/Simple.hs @@ -1094,6 +1094,7 @@ defaultInstallHook_setupHooks inst_hooks pkg_descr localbuildinfo _ flags = do defaultRegisterFlags { regInPlace = installInPlace flags , regPackageDB = installPackageDB flags + , registerCommonFlags = installCommonFlags flags } when (hasLibs pkg_descr) $ register pkg_descr localbuildinfo registerFlags diff --git a/Cabal/src/Distribution/Simple/Configure.hs b/Cabal/src/Distribution/Simple/Configure.hs index 033f3c9de54..56d8517b3b9 100644 --- a/Cabal/src/Distribution/Simple/Configure.hs +++ b/Cabal/src/Distribution/Simple/Configure.hs @@ -106,9 +106,11 @@ import qualified Distribution.Simple.SetupHooks.Internal as SetupHooks import Distribution.Simple.Utils import Distribution.System import Distribution.Types.ComponentRequestedSpec +import Distribution.Types.DependencySatisfaction (DependencySatisfaction (..)) import Distribution.Types.GivenComponent import qualified Distribution.Types.LocalBuildConfig as LBC import Distribution.Types.LocalBuildInfo +import Distribution.Types.MissingDependencyReason (MissingDependencyReason (..)) import Distribution.Types.PackageVersionConstraint import Distribution.Utils.LogProgress import Distribution.Utils.NubList @@ -154,7 +156,6 @@ import System.Directory ( canonicalizePath , createDirectoryIfMissing , doesFileExist - , getTemporaryDirectory , removeFile ) import System.FilePath @@ -1480,7 +1481,7 @@ dependencySatisfiable -> Map (PackageName, ComponentName) PromisedComponent -> Map (PackageName, ComponentName) InstalledPackageInfo -- ^ required dependencies - -> (Dependency -> Bool) + -> (Dependency -> DependencySatisfaction) dependencySatisfiable use_external_internal_deps exact_config @@ -1506,16 +1507,14 @@ dependencySatisfiable internalDepSatisfiable else -- Backward compatibility for the old sublibrary syntax - ( sublibs == mainLibSet - && Map.member - ( pn - , CLibName $ - LSubLibName $ - packageNameToUnqualComponentName depName - ) - requiredDepsMap - ) - || all visible sublibs + let depComponentName = + CLibName $ LSubLibName $ packageNameToUnqualComponentName depName + invisibleLibraries = NES.filter (not . visible) sublibs + in if sublibs == mainLibSet && Map.member (pn, depComponentName) requiredDepsMap + then Satisfied + else case nonEmpty $ Set.toList invisibleLibraries of + Nothing -> Satisfied + Just invisibleLibraries' -> Unsatisfied $ MissingLibrary invisibleLibraries' | isInternalDep = if use_external_internal_deps then -- When we are doing per-component configure, we now need to @@ -1532,12 +1531,31 @@ dependencySatisfiable isInternalDep = pn == depName depSatisfiable = - not . null $ PackageIndex.lookupDependency installedPackageSet depName vr + let allVersions = PackageIndex.lookupPackageName installedPackageSet depName + eligibleVersions = + [ version + | (version, _infos) <- PackageIndex.eligibleDependencies allVersions + ] + in if null $ PackageIndex.matchingDependencies vr allVersions + then + if null eligibleVersions + then Unsatisfied $ MissingPackage + else Unsatisfied $ WrongVersion eligibleVersions + else Satisfied internalDepSatisfiable = - Set.isSubsetOf (NES.toSet sublibs) packageLibraries + let missingLibraries = (NES.toSet sublibs) `Set.difference` packageLibraries + in case nonEmpty $ Set.toList missingLibraries of + Nothing -> Satisfied + Just missingLibraries' -> Unsatisfied $ MissingLibrary missingLibraries' + internalDepSatisfiableExternally = - all (\ln -> not $ null $ PackageIndex.lookupInternalDependency installedPackageSet pn vr ln) sublibs + -- TODO: Might need to propagate information on which versions _are_ available, if any... + let missingLibraries = + NES.filter (null . PackageIndex.lookupInternalDependency installedPackageSet pn vr) sublibs + in case nonEmpty $ Set.toList missingLibraries of + Nothing -> Satisfied + Just missingLibraries' -> Unsatisfied $ MissingLibrary missingLibraries' -- Check whether a library exists and is visible. -- We don't disambiguate between dependency on non-existent or private @@ -1572,7 +1590,7 @@ configureFinalizedPackage -> ConfigFlags -> ComponentRequestedSpec -> [PackageVersionConstraint] - -> (Dependency -> Bool) + -> (Dependency -> DependencySatisfaction) -- ^ tests if a dependency is satisfiable. -- Might say it's satisfiable even when not. -> Compiler @@ -2674,10 +2692,9 @@ checkForeignDeps pkg lbi verbosity = builds :: String -> [ProgArg] -> IO Bool builds program args = - do - tempDir <- makeSymbolicPath <$> getTemporaryDirectory - withTempFileCwd mbWorkDir tempDir ".c" $ \cName cHnd -> - withTempFileCwd mbWorkDir tempDir "" $ \oNname oHnd -> do + withTempFileCwd ".c" $ \cName cHnd -> + withTempFileCwd "" $ \oNname oHnd -> + do hPutStrLn cHnd program hClose cHnd hClose oHnd @@ -2689,8 +2706,8 @@ checkForeignDeps pkg lbi verbosity = (withPrograms lbi) (getSymbolicPath cName : "-o" : getSymbolicPath oNname : args) return True - `catchIO` (\_ -> return False) - `catchExit` (\_ -> return False) + `catchIO` (\_ -> return False) + `catchExit` (\_ -> return False) explainErrors Nothing [] = return () -- should be impossible! explainErrors _ _ diff --git a/Cabal/src/Distribution/Simple/Errors.hs b/Cabal/src/Distribution/Simple/Errors.hs index c1cc75b5ad1..253b2f0dfbe 100644 --- a/Cabal/src/Distribution/Simple/Errors.hs +++ b/Cabal/src/Distribution/Simple/Errors.hs @@ -31,6 +31,7 @@ import Distribution.Simple.InstallDirs import Distribution.Simple.PreProcess.Types (Suffix) import Distribution.Simple.SetupHooks.Errors import Distribution.System (OS) +import Distribution.Types.MissingDependency (MissingDependency) import Distribution.Types.VersionRange.Internal () import Distribution.Version import Text.PrettyPrint @@ -126,7 +127,7 @@ data CabalException | CantFindForeignLibraries [String] | ExpectedAbsoluteDirectory FilePath | FlagsNotSpecified [FlagName] - | EncounteredMissingDependency [Dependency] + | EncounteredMissingDependency [MissingDependency] | CompilerDoesn'tSupportThinning | CompilerDoesn'tSupportReexports | CompilerDoesn'tSupportBackpack @@ -552,7 +553,7 @@ exceptionMessage e = case e of . nest 4 . sep . punctuate comma - . map (pretty . simplifyDependency) + . map pretty $ missing ) CompilerDoesn'tSupportThinning -> diff --git a/Cabal/src/Distribution/Simple/GHC/Build.hs b/Cabal/src/Distribution/Simple/GHC/Build.hs index 1972e9d903f..c12fb7b2427 100644 --- a/Cabal/src/Distribution/Simple/GHC/Build.hs +++ b/Cabal/src/Distribution/Simple/GHC/Build.hs @@ -12,7 +12,7 @@ import Distribution.Simple.Flag (Flag) import Distribution.Simple.GHC.Build.ExtraSources import Distribution.Simple.GHC.Build.Link import Distribution.Simple.GHC.Build.Modules -import Distribution.Simple.GHC.Build.Utils (isHaskell) +import Distribution.Simple.GHC.Build.Utils (compilerBuildWay, isHaskell) import Distribution.Simple.LocalBuildInfo import Distribution.Simple.Program.Builtin (ghcProgram) import Distribution.Simple.Program.Db (requireProgram) @@ -73,6 +73,7 @@ build numJobs pkg_descr pbci = do verbosity = buildVerbosity pbci isLib = buildIsLib pbci lbi = localBuildInfo pbci + bi = buildBI pbci clbi = buildCLBI pbci isIndef = componentIsIndefinite clbi mbWorkDir = mbWorkDirLBI lbi @@ -111,9 +112,22 @@ build numJobs pkg_descr pbci = do (ghcProg, _) <- liftIO $ requireProgram verbosity ghcProgram (withPrograms lbi) + -- Ways which are wanted from configuration flags let wantedWays@(wantedLibWays, _, wantedExeWay) = buildWays lbi - liftIO $ info verbosity ("Wanted build ways(" ++ show isLib ++ "): " ++ show (if isLib then wantedLibWays isIndef else [wantedExeWay])) + -- Ways which are needed due to the compiler configuration + let doingTH = usesTemplateHaskellOrQQ bi + defaultGhcWay = compilerBuildWay (buildCompiler pbci) + wantedLibBuildWays = + if isLib + then wantedLibWays isIndef + else [wantedExeWay] + finalLibBuildWays = + wantedLibBuildWays + ++ [defaultGhcWay | doingTH && defaultGhcWay `notElem` wantedLibBuildWays] + + liftIO $ info verbosity ("Wanted build ways(" ++ show isLib ++ "): " ++ show wantedLibBuildWays) + liftIO $ info verbosity ("Final lib build ways(" ++ show isLib ++ "): " ++ show finalLibBuildWays) -- We need a separate build and link phase, and C sources must be compiled -- after Haskell modules, because C sources may depend on stub headers -- generated from compiling Haskell modules (#842, #3294). @@ -127,7 +141,7 @@ build numJobs pkg_descr pbci = do | otherwise -> (Nothing, Just mainFile) Nothing -> (Nothing, Nothing) - buildOpts <- buildHaskellModules numJobs ghcProg hsMainFile inputModules buildTargetDir (wantedLibWays isIndef) pbci + buildOpts <- buildHaskellModules numJobs ghcProg hsMainFile inputModules buildTargetDir finalLibBuildWays pbci extraSources <- buildAllExtraSources nonHsMainFile ghcProg buildTargetDir wantedWays pbci linkOrLoadComponent ghcProg diff --git a/Cabal/src/Distribution/Simple/GHC/Build/Link.hs b/Cabal/src/Distribution/Simple/GHC/Build/Link.hs index 3f9f00c9d28..ef9f33d79c9 100644 --- a/Cabal/src/Distribution/Simple/GHC/Build/Link.hs +++ b/Cabal/src/Distribution/Simple/GHC/Build/Link.hs @@ -735,7 +735,9 @@ runReplOrWriteFlags ghcProg lbi rflags ghcOpts pkg_name target = Flag out_dir -> do let uid = componentUnitId clbi this_unit = prettyShow uid - reexported_modules = [mn | LibComponentLocalBuildInfo{} <- [clbi], IPI.ExposedModule mn (Just{}) <- componentExposedModules clbi] + reexported_modules = + [ mn | LibComponentLocalBuildInfo{componentExposedModules = exposed_mods} <- [clbi], IPI.ExposedModule mn (Just{}) <- exposed_mods + ] hidden_modules = otherModules bi extra_opts = concat $ diff --git a/Cabal/src/Distribution/Simple/GHC/Internal.hs b/Cabal/src/Distribution/Simple/GHC/Internal.hs index 0686f30ba1b..6e27b41bc83 100644 --- a/Cabal/src/Distribution/Simple/GHC/Internal.hs +++ b/Cabal/src/Distribution/Simple/GHC/Internal.hs @@ -85,7 +85,7 @@ import Distribution.Utils.Path import Distribution.Verbosity import Distribution.Version (Version) import Language.Haskell.Extension -import System.Directory (getDirectoryContents, getTemporaryDirectory) +import System.Directory (getDirectoryContents) import System.Environment (getEnv) import System.FilePath ( takeDirectory @@ -221,9 +221,8 @@ configureToolchain _implInfo ghcProg ghcInfo = -- we need to find out if ld supports the -x flag configureLd' :: Verbosity -> ConfiguredProgram -> IO ConfiguredProgram configureLd' verbosity ldProg = do - tempDir <- getTemporaryDirectory - ldx <- withTempFile tempDir ".c" $ \testcfile testchnd -> - withTempFile tempDir ".o" $ \testofile testohnd -> do + ldx <- withTempFile ".c" $ \testcfile testchnd -> + withTempFile ".o" $ \testofile testohnd -> do hPutStrLn testchnd "int foo() { return 0; }" hClose testchnd hClose testohnd @@ -236,7 +235,7 @@ configureToolchain _implInfo ghcProg ghcInfo = , "-o" , testofile ] - withTempFile tempDir ".o" $ \testofile' testohnd' -> + withTempFile ".o" $ \testofile' testohnd' -> do hClose testohnd' _ <- diff --git a/Cabal/src/Distribution/Simple/Haddock.hs b/Cabal/src/Distribution/Simple/Haddock.hs index ba025a85549..ec4e60ff685 100644 --- a/Cabal/src/Distribution/Simple/Haddock.hs +++ b/Cabal/src/Distribution/Simple/Haddock.hs @@ -1133,8 +1133,6 @@ renderArgs verbosity mbWorkDir tmpFileOpts version comp platform args k = do withResponseFile verbosity tmpFileOpts - mbWorkDir - outputDir "haddock-response.txt" (if haddockSupportsUTF8 then Just utf8 else Nothing) renderedArgs @@ -1144,7 +1142,7 @@ renderArgs verbosity mbWorkDir tmpFileOpts version comp platform args k = do (Flag pfile, _) -> withPrologueArgs ["--prologue=" ++ pfile] (_, Flag prologueText) -> - withTempFileEx tmpFileOpts mbWorkDir outputDir "haddock-prologue.txt" $ + withTempFileEx tmpFileOpts "haddock-prologue.txt" $ \prologueFileName h -> do when haddockSupportsUTF8 (hSetEncoding h utf8) hPutStrLn h prologueText diff --git a/Cabal/src/Distribution/Simple/PackageIndex.hs b/Cabal/src/Distribution/Simple/PackageIndex.hs index 927e10ae878..e6944430755 100644 --- a/Cabal/src/Distribution/Simple/PackageIndex.hs +++ b/Cabal/src/Distribution/Simple/PackageIndex.hs @@ -68,6 +68,7 @@ module Distribution.Simple.PackageIndex , lookupSourcePackageId , lookupPackageId , lookupPackageName + , lookupInternalPackageName , lookupDependency , lookupInternalDependency @@ -93,6 +94,10 @@ module Distribution.Simple.PackageIndex , dependencyCycles , dependencyGraph , moduleNameIndex + + -- ** Filters on lookup results + , eligibleDependencies + , matchingDependencies ) where import qualified Data.Map.Strict as Map @@ -474,7 +479,18 @@ lookupPackageName -> [(Version, [a])] lookupPackageName index name = -- Do not match internal libraries - case Map.lookup (name, LMainLibName) (packageIdIndex index) of + lookupInternalPackageName index name LMainLibName + +-- | Does a lookup by source package name and library name. +-- +-- Also looks up internal packages. +lookupInternalPackageName + :: PackageIndex a + -> PackageName + -> LibraryName + -> [(Version, [a])] +lookupInternalPackageName index name library = + case Map.lookup (name, library) (packageIdIndex index) of Nothing -> [] Just pvers -> Map.toList pvers @@ -509,23 +525,46 @@ lookupInternalDependency -> LibraryName -> [(Version, [IPI.InstalledPackageInfo])] lookupInternalDependency index name versionRange libn = - case Map.lookup (name, libn) (packageIdIndex index) of - Nothing -> [] - Just pvers -> - [ (ver, pkgs') - | (ver, pkgs) <- Map.toList pvers - , ver `withinRange` versionRange - , let pkgs' = filter eligible pkgs - , -- Enforce the invariant - not (null pkgs') - ] + matchingDependencies versionRange $ + lookupInternalPackageName index name libn + +-- | Filter a set of installed packages to ones eligible as dependencies. +-- +-- When we select for dependencies, we ONLY want to pick up indefinite +-- packages, or packages with no instantiations. We'll do mix-in linking to +-- improve any such package into an instantiated one later. +-- +-- INVARIANT: List of eligible 'IPI.InstalledPackageInfo' is non-empty. +eligibleDependencies + :: [(Version, [IPI.InstalledPackageInfo])] + -> [(Version, [IPI.InstalledPackageInfo])] +eligibleDependencies versions = + [ (ver, pkgs') + | (ver, pkgs) <- versions + , let pkgs' = filter eligible pkgs + , -- Enforce the invariant + not (null pkgs') + ] where - -- When we select for dependencies, we ONLY want to pick up indefinite - -- packages, or packages with no instantiations. We'll do mix-in - -- linking to improve any such package into an instantiated one - -- later. eligible pkg = IPI.indefinite pkg || null (IPI.instantiatedWith pkg) +-- | Get eligible dependencies from a list of versions. +-- +-- This can be used to filter the output of 'lookupPackageName' or +-- 'lookupInternalPackageName'. +-- +-- INVARIANT: List of eligible 'IPI.InstalledPackageInfo' is non-empty. +matchingDependencies + :: VersionRange + -> [(Version, [IPI.InstalledPackageInfo])] + -> [(Version, [IPI.InstalledPackageInfo])] +matchingDependencies versionRange versions = + let eligibleVersions = eligibleDependencies versions + in [ (ver, pkgs) + | (ver, pkgs) <- eligibleVersions + , ver `withinRange` versionRange + ] + -- -- * Case insensitive name lookups diff --git a/Cabal/src/Distribution/Simple/PreProcess.hs b/Cabal/src/Distribution/Simple/PreProcess.hs index 61dd8163733..e56627893c1 100644 --- a/Cabal/src/Distribution/Simple/PreProcess.hs +++ b/Cabal/src/Distribution/Simple/PreProcess.hs @@ -511,8 +511,6 @@ ppHsc2hs bi lbi clbi = withResponseFile verbosity defaultTempFileOptions - mbWorkDir - (makeSymbolicPath $ takeDirectory outFile) "hsc2hs-response.txt" Nothing pureArgs diff --git a/Cabal/src/Distribution/Simple/Program/Ar.hs b/Cabal/src/Distribution/Simple/Program/Ar.hs index 004b02cca1a..2e9b432385f 100644 --- a/Cabal/src/Distribution/Simple/Program/Ar.hs +++ b/Cabal/src/Distribution/Simple/Program/Ar.hs @@ -154,7 +154,7 @@ createArLibArchive verbosity lbi targetPath files = do (initial, middle, final) (map getSymbolicPath files) ] - else withResponseFile verbosity defaultTempFileOptions mbWorkDir tmpDir "ar.rsp" Nothing (map getSymbolicPath files) $ + else withResponseFile verbosity defaultTempFileOptions "ar.rsp" Nothing (map getSymbolicPath files) $ \path -> runProgramInvocation verbosity $ invokeWithResponseFile path unless diff --git a/Cabal/src/Distribution/Simple/Program/Ld.hs b/Cabal/src/Distribution/Simple/Program/Ld.hs index 5c2a33809ae..00ed5d182d7 100644 --- a/Cabal/src/Distribution/Simple/Program/Ld.hs +++ b/Cabal/src/Distribution/Simple/Program/Ld.hs @@ -83,8 +83,6 @@ combineObjectFiles verbosity lbi ldProg target files = do middle = ld middleArgs final = ld finalArgs - targetDir = takeDirectorySymbolicPath target - invokeWithResponseFile :: FilePath -> ProgramInvocation invokeWithResponseFile atFile = ld $ simpleArgs ++ ['@' : atFile] @@ -106,7 +104,7 @@ combineObjectFiles verbosity lbi ldProg target files = do if oldVersionManualOverride || responseArgumentsNotSupported then run $ multiStageProgramInvocation simple (initial, middle, final) (map getSymbolicPath files) - else withResponseFile verbosity defaultTempFileOptions mbWorkDir targetDir "ld.rsp" Nothing (map getSymbolicPath files) $ + else withResponseFile verbosity defaultTempFileOptions "ld.rsp" Nothing (map getSymbolicPath files) $ \path -> runProgramInvocation verbosity $ invokeWithResponseFile path where tmpfile = target <.> "tmp" -- perhaps should use a proper temp file diff --git a/Cabal/src/Distribution/Simple/Program/ResponseFile.hs b/Cabal/src/Distribution/Simple/Program/ResponseFile.hs index ee8271545f1..dec6cb0ae50 100644 --- a/Cabal/src/Distribution/Simple/Program/ResponseFile.hs +++ b/Cabal/src/Distribution/Simple/Program/ResponseFile.hs @@ -27,10 +27,6 @@ import Distribution.Verbosity withResponseFile :: Verbosity -> TempFileOptions - -> Maybe (SymbolicPath CWD (Dir Pkg)) - -- ^ Working directory - -> SymbolicPath Pkg (Dir Response) - -- ^ Directory to create response file in. -> String -- ^ Template for response file name. -> Maybe TextEncoding @@ -39,8 +35,8 @@ withResponseFile -- ^ Arguments to put into response file. -> (FilePath -> IO a) -> IO a -withResponseFile verbosity tmpFileOpts mbWorkDir responseDir fileNameTemplate encoding arguments f = - withTempFileEx tmpFileOpts mbWorkDir responseDir fileNameTemplate $ \responsePath hf -> do +withResponseFile verbosity tmpFileOpts fileNameTemplate encoding arguments f = + withTempFileEx tmpFileOpts fileNameTemplate $ \responsePath hf -> do let responseFileName = getSymbolicPath responsePath traverse_ (hSetEncoding hf) encoding let responseContents = diff --git a/Cabal/src/Distribution/Simple/Utils.hs b/Cabal/src/Distribution/Simple/Utils.hs index 3688f602759..d51601e5c27 100644 --- a/Cabal/src/Distribution/Simple/Utils.hs +++ b/Cabal/src/Distribution/Simple/Utils.hs @@ -250,6 +250,7 @@ import System.Directory , getDirectoryContents , getModificationTime , getPermissions + , getTemporaryDirectory , removeDirectoryRecursive , removeFile ) @@ -1733,23 +1734,17 @@ defaultTempFileOptions = TempFileOptions{optKeepTempFiles = False} -- | Use a temporary filename that doesn't already exist withTempFile - :: FilePath - -- ^ Temp dir to create the file in - -> String + :: String -- ^ File name template. See 'openTempFile'. -> (FilePath -> Handle -> IO a) -> IO a -withTempFile tmpDir template f = withFrozenCallStack $ - withTempFileCwd Nothing (makeSymbolicPath tmpDir) template $ +withTempFile template f = withFrozenCallStack $ + withTempFileCwd template $ \fp h -> f (getSymbolicPath fp) h -- | Use a temporary filename that doesn't already exist. withTempFileCwd - :: Maybe (SymbolicPath CWD (Dir Pkg)) - -- ^ Working directory - -> SymbolicPath Pkg (Dir tmpDir) - -- ^ Temp dir to create the file in - -> String + :: String -- ^ File name template. See 'openTempFile'. -> (SymbolicPath Pkg File -> Handle -> IO a) -> IO a @@ -1758,20 +1753,17 @@ withTempFileCwd = withFrozenCallStack $ withTempFileEx defaultTempFileOptions -- | A version of 'withTempFile' that additionally takes a 'TempFileOptions' -- argument. withTempFileEx - :: forall a tmpDir + :: forall a . TempFileOptions - -> Maybe (SymbolicPath CWD (Dir Pkg)) - -- ^ Working directory - -> SymbolicPath Pkg (Dir tmpDir) - -- ^ Temp dir to create the file in -> String -- ^ File name template. See 'openTempFile'. -> (SymbolicPath Pkg File -> Handle -> IO a) -> IO a -withTempFileEx opts mbWorkDir tmpDir template action = +withTempFileEx opts template action = do + tmp <- getTemporaryDirectory withFrozenCallStack $ Exception.bracket - (openTempFile (i tmpDir) template) + (openTempFile tmp template) ( \(name, handle) -> do hClose handle unless (optKeepTempFiles opts) $ @@ -1779,12 +1771,11 @@ withTempFileEx opts mbWorkDir tmpDir template action = removeFile $ name ) - (withLexicalCallStack (\(fn, h) -> action (mkRelToPkg fn) h)) + (withLexicalCallStack (\(fn, h) -> action (mkRelToPkg tmp fn) h)) where - i = interpretSymbolicPath mbWorkDir -- See Note [Symbolic paths] in Distribution.Utils.Path - mkRelToPkg :: FilePath -> SymbolicPath Pkg File - mkRelToPkg fp = - tmpDir makeRelativePathEx (takeFileName fp) + mkRelToPkg :: FilePath -> FilePath -> SymbolicPath Pkg File + mkRelToPkg tmp fp = + makeSymbolicPath tmp makeRelativePathEx (takeFileName fp) -- 'openTempFile' returns a path of the form @i tmpDir fn@, but we -- want 'withTempFileEx' to return @tmpDir fn@. So we split off diff --git a/Makefile b/Makefile index 7b272c55e09..12d38557de6 100644 --- a/Makefile +++ b/Makefile @@ -29,18 +29,26 @@ init: ## Set up git hooks and ignored revisions .PHONY: style style: ## Run the code styler - @fourmolu -q -i Cabal Cabal-syntax cabal-install + @fourmolu -q -i Cabal Cabal-syntax cabal-install cabal-validate .PHONY: style-modified style-modified: ## Run the code styler on modified files - @git ls-files --modified Cabal Cabal-syntax cabal-install \ + @git ls-files --modified Cabal Cabal-syntax cabal-install cabal-validate \ | grep '.hs$$' | xargs -P $(PROCS) -I {} fourmolu -q -i {} .PHONY: style-commit style-commit: ## Run the code styler on the previous commit - @git diff --name-only HEAD $(COMMIT) Cabal Cabal-syntax cabal-install \ + @git diff --name-only HEAD $(COMMIT) Cabal Cabal-syntax cabal-install cabal-validate \ | grep '.hs$$' | xargs -P $(PROCS) -I {} fourmolu -q -i {} +.PHONY: whitespace +whitespace: ## Run fix-whitespace in check mode + fix-whitespace --check --verbose + +.PHONY: fix-whitespace +fix-whitespace: ## Run fix-whitespace in fix mode + fix-whitespace --verbose + # source generation: SPDX SPDX_LICENSE_HS:=Cabal-syntax/src/Distribution/SPDX/LicenseId.hs diff --git a/cabal-install/src/Distribution/Client/CmdOutdated.hs b/cabal-install/src/Distribution/Client/CmdOutdated.hs index ed40a1a85e6..7674e67286f 100644 --- a/cabal-install/src/Distribution/Client/CmdOutdated.hs +++ b/cabal-install/src/Distribution/Client/CmdOutdated.hs @@ -129,6 +129,9 @@ import Distribution.Types.ComponentRequestedSpec import Distribution.Types.Dependency ( Dependency (..) ) +import Distribution.Types.DependencySatisfaction + ( DependencySatisfaction (..) + ) import Distribution.Types.PackageVersionConstraint ( PackageVersionConstraint (..) , simplifyPackageVersionConstraint @@ -443,7 +446,7 @@ depsFromPkgDesc verbosity comp platform = do finalizePD mempty (ComponentRequestedSpec True True) - (const True) + (const Satisfied) platform cinfo [] diff --git a/cabal-install/src/Distribution/Client/Config.hs b/cabal-install/src/Distribution/Client/Config.hs index 2faf9e1756d..f7b01512ca4 100644 --- a/cabal-install/src/Distribution/Client/Config.hs +++ b/cabal-install/src/Distribution/Client/Config.hs @@ -160,6 +160,7 @@ import qualified Distribution.Deprecated.ParseUtils as ParseUtils import Distribution.Parsec (ParsecParser, parsecFilePath, parsecOptCommaList, parsecToken) import Distribution.Simple.Command ( CommandUI (commandOptions) + , OptionField , ShowOrParseArgs (..) , commandDefaultFlags ) @@ -1314,6 +1315,19 @@ configFieldDescriptions src = ParseArgs ] where + toSavedConfig + :: (FieldDescr a -> FieldDescr SavedConfig) + -- Lifting function. + -> [OptionField a] + -- Option fields. + -> [String] + -- Fields to exclude, by name. + -> [FieldDescr a] + -- Field replacements. + -- + -- If an option is found with the same name as one of these replacement + -- fields, the replacement field is used instead of the option. + -> [FieldDescr SavedConfig] toSavedConfig lift options exclusions replacements = [ lift (fromMaybe field replacement) | opt <- options diff --git a/cabal-install/src/Distribution/Client/Configure.hs b/cabal-install/src/Distribution/Client/Configure.hs index 6634f874071..5f82329eb52 100644 --- a/cabal-install/src/Distribution/Client/Configure.hs +++ b/cabal-install/src/Distribution/Client/Configure.hs @@ -116,6 +116,9 @@ import Distribution.Simple.Utils as Utils import Distribution.System ( Platform ) +import Distribution.Types.DependencySatisfaction + ( DependencySatisfaction (..) + ) import Distribution.Types.GivenComponent ( GivenComponent (..) ) @@ -555,7 +558,7 @@ configurePackage pkg = case finalizePD flags (enableStanzas stanzas) - (const True) + (const Satisfied) platform comp [] diff --git a/cabal-install/src/Distribution/Client/Dependency.hs b/cabal-install/src/Distribution/Client/Dependency.hs index ae731cdc64b..d59bc611c44 100644 --- a/cabal-install/src/Distribution/Client/Dependency.hs +++ b/cabal-install/src/Distribution/Client/Dependency.hs @@ -127,6 +127,9 @@ import Distribution.System ( Platform ) import Distribution.Types.Dependency +import Distribution.Types.DependencySatisfaction + ( DependencySatisfaction (..) + ) import Distribution.Verbosity ( normal ) @@ -1092,7 +1095,7 @@ configuredPackageProblems case finalizePD specifiedFlags compSpec - (const True) + (const Satisfied) platform cinfo [] diff --git a/cabal-install/src/Distribution/Client/GenBounds.hs b/cabal-install/src/Distribution/Client/GenBounds.hs index 935db05fa43..2603a75d302 100644 --- a/cabal-install/src/Distribution/Client/GenBounds.hs +++ b/cabal-install/src/Distribution/Client/GenBounds.hs @@ -63,6 +63,9 @@ import Distribution.Types.ComponentRequestedSpec ( defaultComponentRequestedSpec ) import Distribution.Types.Dependency +import Distribution.Types.DependencySatisfaction + ( DependencySatisfaction (..) + ) import Distribution.Utils.Path (relativeSymbolicPath) import Distribution.Version ( LowerBound (..) @@ -134,7 +137,7 @@ genBounds verbosity packageDBs repoCtxt comp platform progdb globalFlags freezeF finalizePD mempty defaultComponentRequestedSpec - (const True) + (const Satisfied) platform cinfo [] diff --git a/cabal-install/src/Distribution/Client/HttpUtils.hs b/cabal-install/src/Distribution/Client/HttpUtils.hs index 956241ab307..3cdadf9304c 100644 --- a/cabal-install/src/Distribution/Client/HttpUtils.hs +++ b/cabal-install/src/Distribution/Client/HttpUtils.hs @@ -467,7 +467,6 @@ curlTransport prog = where gethttp verbosity uri etag destPath reqHeaders = do withTempFile - (takeDirectory destPath) "curl-headers.txt" $ \tmpFile tmpHandle -> do hClose tmpHandle @@ -675,10 +674,9 @@ wgetTransport prog = posthttpfile verbosity uri path auth = withTempFile - (takeDirectory path) (takeFileName path) $ \tmpFile tmpHandle -> - withTempFile (takeDirectory path) "response" $ + withTempFile "response" $ \responseFile responseHandle -> do hClose responseHandle (body, boundary) <- generateMultipartBody path @@ -702,7 +700,7 @@ wgetTransport prog = evaluate $ force (code, resp) puthttpfile verbosity uri path auth headers = - withTempFile (takeDirectory path) "response" $ + withTempFile "response" $ \responseFile responseHandle -> do hClose responseHandle let args = @@ -824,7 +822,6 @@ powershellTransport prog = posthttpfile verbosity uri path auth = withTempFile - (takeDirectory path) (takeFileName path) $ \tmpFile tmpHandle -> do (body, boundary) <- generateMultipartBody path diff --git a/cabal-install/src/Distribution/Client/Install.hs b/cabal-install/src/Distribution/Client/Install.hs index 84a590e3c74..b6a8198ae5c 100644 --- a/cabal-install/src/Distribution/Client/Install.hs +++ b/cabal-install/src/Distribution/Client/Install.hs @@ -244,6 +244,9 @@ import Distribution.System , buildOS , buildPlatform ) +import Distribution.Types.DependencySatisfaction + ( DependencySatisfaction (..) + ) import Distribution.Types.Flag ( FlagAssignment , PackageFlag (..) @@ -1676,7 +1679,7 @@ installReadyPackage pkg = case finalizePD flags (enableStanzas stanzas) - (const True) + (const Satisfied) platform cinfo [] diff --git a/cabal-install/src/Distribution/Client/InstallSymlink.hs b/cabal-install/src/Distribution/Client/InstallSymlink.hs index 7a470843779..46e1edaebef 100644 --- a/cabal-install/src/Distribution/Client/InstallSymlink.hs +++ b/cabal-install/src/Distribution/Client/InstallSymlink.hs @@ -74,6 +74,9 @@ import Distribution.Simple.Utils (info, withTempDirectory) import Distribution.System ( Platform ) +import Distribution.Types.DependencySatisfaction + ( DependencySatisfaction (..) + ) import Distribution.Types.UnqualComponentName import System.Directory @@ -205,7 +208,7 @@ symlinkBinaries case finalizePD flags (enableStanzas stanzas) - (const True) + (const Satisfied) platform cinfo [] diff --git a/cabal-install/src/Distribution/Client/ProjectBuilding/UnpackedPackage.hs b/cabal-install/src/Distribution/Client/ProjectBuilding/UnpackedPackage.hs index f1486388b8c..9e3b91d1753 100644 --- a/cabal-install/src/Distribution/Client/ProjectBuilding/UnpackedPackage.hs +++ b/cabal-install/src/Distribution/Client/ProjectBuilding/UnpackedPackage.hs @@ -44,10 +44,14 @@ import Distribution.Client.FileMonitor import Distribution.Client.JobControl import Distribution.Client.Setup ( CommonSetupFlags - , filterCommonFlags + , filterBenchmarkFlags + , filterBuildFlags , filterConfigureFlags + , filterCopyFlags , filterHaddockArgs , filterHaddockFlags + , filterRegisterFlags + , filterReplFlags , filterTestFlags ) import Distribution.Client.SetupWrapper @@ -272,9 +276,7 @@ buildAndRegisterUnpackedPackage | otherwise = return () mbWorkDir = useWorkingDir scriptOptions - commonFlags v = - flip filterCommonFlags v $ - setupHsCommonFlags verbosity mbWorkDir builddir + commonFlags = setupHsCommonFlags verbosity mbWorkDir builddir configureCommand = Cabal.configureCommand defaultProgramDb configureFlags v = @@ -284,19 +286,26 @@ buildAndRegisterUnpackedPackage plan rpkg pkgshared - (commonFlags v) + commonFlags configureArgs _ = setupHsConfigureArgs pkg buildCommand = Cabal.buildCommand defaultProgramDb - buildFlags v = setupHsBuildFlags comp_par_strat pkg pkgshared $ commonFlags v + buildFlags v = + flip filterBuildFlags v $ + setupHsBuildFlags + comp_par_strat + pkg + pkgshared + commonFlags buildArgs _ = setupHsBuildArgs pkg copyFlags destdir v = - setupHsCopyFlags - pkg - pkgshared - (commonFlags v) - destdir + flip filterCopyFlags v $ + setupHsCopyFlags + pkg + pkgshared + commonFlags + destdir -- In theory, we could want to copy less things than those that were -- built, but instead, we simply copy the targets that were built. copyArgs = buildArgs @@ -306,23 +315,25 @@ buildAndRegisterUnpackedPackage flip filterTestFlags v $ setupHsTestFlags pkg - (commonFlags v) + commonFlags testArgs _ = setupHsTestArgs pkg benchCommand = Cabal.benchmarkCommand benchFlags v = - setupHsBenchFlags - pkg - pkgshared - (commonFlags v) + flip filterBenchmarkFlags v $ + setupHsBenchFlags + pkg + pkgshared + commonFlags benchArgs _ = setupHsBenchArgs pkg replCommand = Cabal.replCommand defaultProgramDb replFlags v = - setupHsReplFlags - pkg - pkgshared - (commonFlags v) + flip filterReplFlags v $ + setupHsReplFlags + pkg + pkgshared + commonFlags replArgs _ = setupHsReplArgs pkg haddockCommand = Cabal.haddockCommand @@ -332,7 +343,7 @@ buildAndRegisterUnpackedPackage pkg pkgshared buildTimeSettings - (commonFlags v) + commonFlags haddockArgs v = flip filterHaddockArgs v $ setupHsHaddockArgs pkg @@ -394,11 +405,12 @@ buildAndRegisterUnpackedPackage distTempDirectory $ \pkgConfDest -> do let registerFlags v = - setupHsRegisterFlags - pkg - pkgshared - (commonFlags v) - pkgConfDest + flip filterRegisterFlags v $ + setupHsRegisterFlags + pkg + pkgshared + commonFlags + pkgConfDest setup (Cabal.registerCommand) Cabal.registerCommonFlags (\v -> return (registerFlags v)) (const []) withLogging :: (Maybe Handle -> IO r) -> IO r diff --git a/cabal-install/src/Distribution/Client/ProjectPlanning.hs b/cabal-install/src/Distribution/Client/ProjectPlanning.hs index 50423b2d1df..93baa8bf78f 100644 --- a/cabal-install/src/Distribution/Client/ProjectPlanning.hs +++ b/cabal-install/src/Distribution/Client/ProjectPlanning.hs @@ -176,6 +176,9 @@ import Distribution.System import Distribution.Types.AnnotatedId import Distribution.Types.ComponentInclude import Distribution.Types.ComponentName +import Distribution.Types.DependencySatisfaction + ( DependencySatisfaction (..) + ) import Distribution.Types.DumpBuildInfo import Distribution.Types.GivenComponent import Distribution.Types.LibraryName @@ -2130,7 +2133,7 @@ elaborateInstallPlan elabPkgDescription = case PD.finalizePD flags elabEnabledSpec - (const True) + (const Satisfied) platform (compilerInfo compiler) [] diff --git a/cabal-install/src/Distribution/Client/Setup.hs b/cabal-install/src/Distribution/Client/Setup.hs index 78e864d1a65..aebba9462c0 100644 --- a/cabal-install/src/Distribution/Client/Setup.hs +++ b/cabal-install/src/Distribution/Client/Setup.hs @@ -35,12 +35,15 @@ module Distribution.Client.Setup , defaultConfigExFlags , buildCommand , BuildFlags (..) + , filterBuildFlags , filterTestFlags , replCommand + , filterReplFlags , testCommand , benchmarkCommand , testOptions , benchmarkOptions + , filterBenchmarkFlags , configureExOptions , reconfigureCommand , installCommand @@ -87,7 +90,9 @@ module Distribution.Client.Setup , haddockCommand , cleanCommand , copyCommand + , filterCopyFlags , registerCommand + , filterRegisterFlags , liftOptions , yesNoOpt ) where @@ -183,7 +188,7 @@ import Distribution.Simple.InstallDirs ) import Distribution.Simple.Program (ProgramDb, defaultProgramDb) import Distribution.Simple.Setup - ( BenchmarkFlags + ( BenchmarkFlags (benchmarkCommonFlags) , BooleanFlag (..) , BuildFlags (..) , CleanFlags (..) @@ -192,7 +197,7 @@ import Distribution.Simple.Setup , CopyFlags (..) , HaddockFlags (..) , RegisterFlags (..) - , ReplFlags + , ReplFlags (..) , TestFlags , boolOpt , boolOpt' @@ -1144,6 +1149,21 @@ buildCommand = where parent = Cabal.buildCommand defaultProgramDb +-- | Given some 'BuildFlags' for the version of @Cabal@ that +-- @cabal-install@ was built with, and a target older 'Version' of +-- @Cabal@ that we want to pass these flags to, convert the +-- flags into a form that will be accepted by the older +-- @Setup@ script. Generally speaking, this just means filtering +-- out flags that the old @Cabal@ library doesn't understand, but +-- in some cases it may also mean "emulating" a feature using +-- some more legacy flags. +filterBuildFlags :: BuildFlags -> Version -> BuildFlags +filterBuildFlags flags cabalLibVersion = + flags + { buildCommonFlags = + filterCommonFlags (buildCommonFlags flags) cabalLibVersion + } + -- ------------------------------------------------------------ -- * Test flags @@ -1236,6 +1256,21 @@ replCommand = where parent = Cabal.replCommand defaultProgramDb +-- | Given some 'ReplFlags' for the version of @Cabal@ that +-- @cabal-install@ was built with, and a target older 'Version' of +-- @Cabal@ that we want to pass these flags to, convert the +-- flags into a form that will be accepted by the older +-- @Setup@ script. Generally speaking, this just means filtering +-- out flags that the old @Cabal@ library doesn't understand, but +-- in some cases it may also mean "emulating" a feature using +-- some more legacy flags. +filterReplFlags :: ReplFlags -> Version -> ReplFlags +filterReplFlags flags cabalLibVersion = + flags + { replCommonFlags = + filterCommonFlags (replCommonFlags flags) cabalLibVersion + } + -- ------------------------------------------------------------ -- * Test command @@ -1331,6 +1366,21 @@ benchmarkCommand = parent = Cabal.benchmarkCommand progDb = defaultProgramDb +-- | Given some 'BenchmarkFlags' for the version of @Cabal@ that +-- @cabal-install@ was built with, and a target older 'Version' of +-- @Cabal@ that we want to pass these flags to, convert the +-- flags into a form that will be accepted by the older +-- @Setup@ script. Generally speaking, this just means filtering +-- out flags that the old @Cabal@ library doesn't understand, but +-- in some cases it may also mean "emulating" a feature using +-- some more legacy flags. +filterBenchmarkFlags :: BenchmarkFlags -> Version -> BenchmarkFlags +filterBenchmarkFlags flags cabalLibVersion = + flags + { benchmarkCommonFlags = + filterCommonFlags (benchmarkCommonFlags flags) cabalLibVersion + } + -- ------------------------------------------------------------ -- * Fetch command @@ -2404,21 +2454,25 @@ filterHaddockArgs args cabalLibVersion -- Cabal < 2.3 doesn't know about per-component haddock args_2_3_0 = [] +-- | Given some 'HaddockFlags' for the version of @Cabal@ that +-- @cabal-install@ was built with, and a target older 'Version' of +-- @Cabal@ that we want to pass these flags to, convert the +-- flags into a form that will be accepted by the older +-- @Setup@ script. Generally speaking, this just means filtering +-- out flags that the old @Cabal@ library doesn't understand, but +-- in some cases it may also mean "emulating" a feature using +-- some more legacy flags. filterHaddockFlags :: HaddockFlags -> Version -> HaddockFlags -filterHaddockFlags flags cabalLibVersion = - let flags' = filterHaddockFlags' flags cabalLibVersion - in flags' - { haddockCommonFlags = - filterCommonFlags (haddockCommonFlags flags') cabalLibVersion - } - -filterHaddockFlags' :: HaddockFlags -> Version -> HaddockFlags -filterHaddockFlags' flags cabalLibVersion +filterHaddockFlags flags cabalLibVersion | cabalLibVersion >= mkVersion [2, 3, 0] = flags_latest | cabalLibVersion < mkVersion [2, 3, 0] = flags_2_3_0 | otherwise = flags_latest where - flags_latest = flags + flags_latest = + flags + { haddockCommonFlags = + filterCommonFlags (haddockCommonFlags flags) cabalLibVersion + } flags_2_3_0 = flags_latest @@ -2490,6 +2544,9 @@ testOptions showOrParseArgs = | "test-" `isPrefixOf` name = name | otherwise = "test-" ++ name +-- | Options for the @bench@ command. +-- +-- Not to be confused with the @benchmarkOptions@ field of the `BenchmarkFlags` record! benchmarkOptions :: ShowOrParseArgs -> [OptionField BenchmarkFlags] benchmarkOptions showOrParseArgs = [ opt @@ -3317,6 +3374,35 @@ registerCommand = { commandUsage = \pname -> "Usage: " ++ pname ++ " v1-register [FLAGS]\n" } +-- | Given some 'RegisterFlags' for the version of @Cabal@ that +-- @cabal-install@ was built with, and a target older 'Version' of +-- @Cabal@ that we want to pass these flags to, convert the +-- flags into a form that will be accepted by the older +-- @Setup@ script. Generally speaking, this just means filtering +-- out flags that the old @Cabal@ library doesn't understand, but +-- in some cases it may also mean "emulating" a feature using +-- some more legacy flags. +filterRegisterFlags :: RegisterFlags -> Version -> RegisterFlags +filterRegisterFlags flags cabalLibVersion = + flags + { registerCommonFlags = + filterCommonFlags (registerCommonFlags flags) cabalLibVersion + } + +-- | Given some 'CopyFlags' for the version of @Cabal@ that +-- @cabal-install@ was built with, and a target older 'Version' of +-- @Cabal@ that we want to pass these flags to, convert the +-- flags into a form that will be accepted by the older +-- @Setup@ script. Generally speaking, this just means filtering +-- out flags that the old @Cabal@ library doesn't understand, but +-- in some cases it may also mean "emulating" a feature using +-- some more legacy flags. +filterCopyFlags :: CopyFlags -> Version -> CopyFlags +filterCopyFlags flags cabalLibVersion = + flags + { copyCommonFlags = filterCommonFlags (copyCommonFlags flags) cabalLibVersion + } + -- ------------------------------------------------------------ -- * ActAsSetup flags diff --git a/cabal-install/tests/IntegrationTests2.hs b/cabal-install/tests/IntegrationTests2.hs index e6373cd18b8..22c7da0c37f 100644 --- a/cabal-install/tests/IntegrationTests2.hs +++ b/cabal-install/tests/IntegrationTests2.hs @@ -103,7 +103,9 @@ main = do removeDirectoryRecursive configDir <|> return () createDirectoryIfMissing True configDir -- sigh - callProcess "cabal" ["user-config", "init", "-f"] + -- NOTE: This is running the `cabal` from the user environment, which is + -- generally not the `cabal` being tested! + callProcess "cabal" ["-v0", "user-config", "init", "-f"] callProcess "cabal" ["update"] defaultMainWithIngredients (defaultIngredients ++ [includingOptions projectConfigOptionDescriptions]) @@ -1982,230 +1984,272 @@ testNixFlags = do -- Tests whether config options are commented or not testConfigOptionComments :: Assertion testConfigOptionComments = do - _ <- createDefaultConfigFile verbosity [] (basedir "config" "default-config") - defaultConfigFile <- readFile (basedir "config" "default-config") - - " url" @=? findLineWith False "url" defaultConfigFile - " -- secure" @=? findLineWith True "secure" defaultConfigFile - " -- root-keys" @=? findLineWith True "root-keys" defaultConfigFile - " -- key-threshold" @=? findLineWith True "key-threshold" defaultConfigFile - - "-- ignore-expiry" @=? findLineWith True "ignore-expiry" defaultConfigFile - "-- http-transport" @=? findLineWith True "http-transport" defaultConfigFile - "-- nix" @=? findLineWith True "nix" defaultConfigFile - "-- store-dir" @=? findLineWith True "store-dir" defaultConfigFile - "-- active-repositories" @=? findLineWith True "active-repositories" defaultConfigFile - "-- local-no-index-repo" @=? findLineWith True "local-no-index-repo" defaultConfigFile - "remote-repo-cache" @=? findLineWith False "remote-repo-cache" defaultConfigFile - "-- logs-dir" @=? findLineWith True "logs-dir" defaultConfigFile - "-- default-user-config" @=? findLineWith True "default-user-config" defaultConfigFile - "-- verbose" @=? findLineWith True "verbose" defaultConfigFile - "-- compiler" @=? findLineWith True "compiler" defaultConfigFile - "-- cabal-file" @=? findLineWith True "cabal-file" defaultConfigFile - "-- with-compiler" @=? findLineWith True "with-compiler" defaultConfigFile - "-- with-hc-pkg" @=? findLineWith True "with-hc-pkg" defaultConfigFile - "-- program-prefix" @=? findLineWith True "program-prefix" defaultConfigFile - "-- program-suffix" @=? findLineWith True "program-suffix" defaultConfigFile - "-- library-vanilla" @=? findLineWith True "library-vanilla" defaultConfigFile - "-- library-profiling" @=? findLineWith True "library-profiling" defaultConfigFile - "-- shared" @=? findLineWith True "shared" defaultConfigFile - "-- static" @=? findLineWith True "static" defaultConfigFile - "-- executable-dynamic" @=? findLineWith True "executable-dynamic" defaultConfigFile - "-- executable-static" @=? findLineWith True "executable-static" defaultConfigFile - "-- profiling" @=? findLineWith True "profiling" defaultConfigFile - "-- executable-profiling" @=? findLineWith True "executable-profiling" defaultConfigFile - "-- profiling-detail" @=? findLineWith True "profiling-detail" defaultConfigFile - "-- library-profiling-detail" @=? findLineWith True "library-profiling-detail" defaultConfigFile - "-- optimization" @=? findLineWith True "optimization" defaultConfigFile - "-- debug-info" @=? findLineWith True "debug-info" defaultConfigFile - "-- build-info" @=? findLineWith True "build-info" defaultConfigFile - "-- library-for-ghci" @=? findLineWith True "library-for-ghci" defaultConfigFile - "-- split-sections" @=? findLineWith True "split-sections" defaultConfigFile - "-- split-objs" @=? findLineWith True "split-objs" defaultConfigFile - "-- executable-stripping" @=? findLineWith True "executable-stripping" defaultConfigFile - "-- library-stripping" @=? findLineWith True "library-stripping" defaultConfigFile - "-- configure-option" @=? findLineWith True "configure-option" defaultConfigFile - "-- user-install" @=? findLineWith True "user-install" defaultConfigFile - "-- package-db" @=? findLineWith True "package-db" defaultConfigFile - "-- flags" @=? findLineWith True "flags" defaultConfigFile - "-- extra-include-dirs" @=? findLineWith True "extra-include-dirs" defaultConfigFile - "-- deterministic" @=? findLineWith True "deterministic" defaultConfigFile - "-- cid" @=? findLineWith True "cid" defaultConfigFile - "-- extra-lib-dirs" @=? findLineWith True "extra-lib-dirs" defaultConfigFile - "-- extra-lib-dirs-static" @=? findLineWith True "extra-lib-dirs-static" defaultConfigFile - "-- extra-framework-dirs" @=? findLineWith True "extra-framework-dirs" defaultConfigFile - "-- extra-prog-path" @=? findLineWith False "extra-prog-path" defaultConfigFile - "-- instantiate-with" @=? findLineWith True "instantiate-with" defaultConfigFile - "-- tests" @=? findLineWith True "tests" defaultConfigFile - "-- coverage" @=? findLineWith True "coverage" defaultConfigFile - "-- library-coverage" @=? findLineWith True "library-coverage" defaultConfigFile - "-- exact-configuration" @=? findLineWith True "exact-configuration" defaultConfigFile - "-- benchmarks" @=? findLineWith True "benchmarks" defaultConfigFile - "-- relocatable" @=? findLineWith True "relocatable" defaultConfigFile - "-- response-files" @=? findLineWith True "response-files" defaultConfigFile - "-- allow-depending-on-private-libs" @=? findLineWith True "allow-depending-on-private-libs" defaultConfigFile - "-- cabal-lib-version" @=? findLineWith True "cabal-lib-version" defaultConfigFile - "-- append" @=? findLineWith True "append" defaultConfigFile - "-- backup" @=? findLineWith True "backup" defaultConfigFile - "-- constraint" @=? findLineWith True "constraint" defaultConfigFile - "-- preference" @=? findLineWith True "preference" defaultConfigFile - "-- solver" @=? findLineWith True "solver" defaultConfigFile - "-- allow-older" @=? findLineWith True "allow-older" defaultConfigFile - "-- allow-newer" @=? findLineWith True "allow-newer" defaultConfigFile - "-- write-ghc-environment-files" @=? findLineWith True "write-ghc-environment-files" defaultConfigFile - "-- documentation" @=? findLineWith True "documentation" defaultConfigFile - "-- doc-index-file" @=? findLineWith True "doc-index-file" defaultConfigFile - "-- only-download" @=? findLineWith True "only-download" defaultConfigFile - "-- target-package-db" @=? findLineWith True "target-package-db" defaultConfigFile - "-- max-backjumps" @=? findLineWith True "max-backjumps" defaultConfigFile - "-- reorder-goals" @=? findLineWith True "reorder-goals" defaultConfigFile - "-- count-conflicts" @=? findLineWith True "count-conflicts" defaultConfigFile - "-- fine-grained-conflicts" @=? findLineWith True "fine-grained-conflicts" defaultConfigFile - "-- minimize-conflict-set" @=? findLineWith True "minimize-conflict-set" defaultConfigFile - "-- independent-goals" @=? findLineWith True "independent-goals" defaultConfigFile - "-- prefer-oldest" @=? findLineWith True "prefer-oldest" defaultConfigFile - "-- shadow-installed-packages" @=? findLineWith True "shadow-installed-packages" defaultConfigFile - "-- strong-flags" @=? findLineWith True "strong-flags" defaultConfigFile - "-- allow-boot-library-installs" @=? findLineWith True "allow-boot-library-installs" defaultConfigFile - "-- reject-unconstrained-dependencies" @=? findLineWith True "reject-unconstrained-dependencies" defaultConfigFile - "-- reinstall" @=? findLineWith True "reinstall" defaultConfigFile - "-- avoid-reinstalls" @=? findLineWith True "avoid-reinstalls" defaultConfigFile - "-- force-reinstalls" @=? findLineWith True "force-reinstalls" defaultConfigFile - "-- upgrade-dependencies" @=? findLineWith True "upgrade-dependencies" defaultConfigFile - "-- index-state" @=? findLineWith True "index-state" defaultConfigFile - "-- root-cmd" @=? findLineWith True "root-cmd" defaultConfigFile - "-- symlink-bindir" @=? findLineWith True "symlink-bindir" defaultConfigFile - "build-summary" @=? findLineWith False "build-summary" defaultConfigFile - "-- build-log" @=? findLineWith True "build-log" defaultConfigFile - "remote-build-reporting" @=? findLineWith False "remote-build-reporting" defaultConfigFile - "-- report-planning-failure" @=? findLineWith True "report-planning-failure" defaultConfigFile - "-- per-component" @=? findLineWith True "per-component" defaultConfigFile - "-- run-tests" @=? findLineWith True "run-tests" defaultConfigFile - "jobs" @=? findLineWith False "jobs" defaultConfigFile - "-- keep-going" @=? findLineWith True "keep-going" defaultConfigFile - "-- offline" @=? findLineWith True "offline" defaultConfigFile - "-- lib" @=? findLineWith True "lib" defaultConfigFile - "-- package-env" @=? findLineWith True "package-env" defaultConfigFile - "-- overwrite-policy" @=? findLineWith True "overwrite-policy" defaultConfigFile - "-- install-method" @=? findLineWith True "install-method" defaultConfigFile - "installdir" @=? findLineWith False "installdir" defaultConfigFile - "-- token" @=? findLineWith True "token" defaultConfigFile - "-- username" @=? findLineWith True "username" defaultConfigFile - "-- password" @=? findLineWith True "password" defaultConfigFile - "-- password-command" @=? findLineWith True "password-command" defaultConfigFile - "-- builddir" @=? findLineWith True "builddir" defaultConfigFile - - " -- keep-temp-files" @=? findLineWith True "keep-temp-files" defaultConfigFile - " -- hoogle" @=? findLineWith True "hoogle" defaultConfigFile - " -- html" @=? findLineWith True "html" defaultConfigFile - " -- html-location" @=? findLineWith True "html-location" defaultConfigFile - " -- executables" @=? findLineWith True "executables" defaultConfigFile - " -- foreign-libraries" @=? findLineWith True "foreign-libraries" defaultConfigFile - " -- all" @=? findLineWith True "all" defaultConfigFile - " -- internal" @=? findLineWith True "internal" defaultConfigFile - " -- css" @=? findLineWith True "css" defaultConfigFile - " -- hyperlink-source" @=? findLineWith True "hyperlink-source" defaultConfigFile - " -- quickjump" @=? findLineWith True "quickjump" defaultConfigFile - " -- hscolour-css" @=? findLineWith True "hscolour-css" defaultConfigFile - " -- contents-location" @=? findLineWith True "contents-location" defaultConfigFile - " -- index-location" @=? findLineWith True "index-location" defaultConfigFile - " -- base-url" @=? findLineWith True "base-url" defaultConfigFile - " -- resources-dir" @=? findLineWith True "resources-dir" defaultConfigFile - " -- output-dir" @=? findLineWith True "output-dir" defaultConfigFile - " -- use-unicode" @=? findLineWith True "use-unicode" defaultConfigFile - - " -- interactive" @=? findLineWith True "interactive" defaultConfigFile - " -- quiet" @=? findLineWith True "quiet" defaultConfigFile - " -- no-comments" @=? findLineWith True "no-comments" defaultConfigFile - " -- minimal" @=? findLineWith True "minimal" defaultConfigFile - " -- cabal-version" @=? findLineWith True "cabal-version" defaultConfigFile - " -- license" @=? findLineWith True "license" defaultConfigFile - " -- extra-doc-file" @=? findLineWith True "extra-doc-file" defaultConfigFile - " -- test-dir" @=? findLineWith True "test-dir" defaultConfigFile - " -- simple" @=? findLineWith True "simple" defaultConfigFile - " -- language" @=? findLineWith True "language" defaultConfigFile - " -- application-dir" @=? findLineWith True "application-dir" defaultConfigFile - " -- source-dir" @=? findLineWith True "source-dir" defaultConfigFile - - " -- prefix" @=? findLineWith True "prefix" defaultConfigFile - " -- bindir"@=? findLineWith True "bindir" defaultConfigFile - " -- libdir" @=? findLineWith True "libdir" defaultConfigFile - " -- libsubdir" @=? findLineWith True "libsubdir" defaultConfigFile - " -- dynlibdir" @=? findLineWith True "dynlibdir" defaultConfigFile - " -- libexecdir" @=? findLineWith True "libexecdir" defaultConfigFile - " -- libexecsubdir" @=? findLineWith True "libexecsubdir" defaultConfigFile - " -- datadir" @=? findLineWith True "datadir" defaultConfigFile - " -- datasubdir" @=? findLineWith True "datasubdir" defaultConfigFile - " -- docdir" @=? findLineWith True "docdir" defaultConfigFile - " -- htmldir" @=? findLineWith True "htmldir" defaultConfigFile - " -- haddockdir" @=? findLineWith True "haddockdir" defaultConfigFile - " -- sysconfdir" @=? findLineWith True "sysconfdir" defaultConfigFile - - " -- alex-location" @=? findLineWith True "alex-location" defaultConfigFile - " -- ar-location" @=? findLineWith True "ar-location" defaultConfigFile - " -- c2hs-location" @=? findLineWith True "c2hs-location" defaultConfigFile - " -- cpphs-location" @=? findLineWith True "cpphs-location" defaultConfigFile - " -- doctest-location" @=? findLineWith True "doctest-location" defaultConfigFile - " -- gcc-location" @=? findLineWith True "gcc-location" defaultConfigFile - " -- ghc-location" @=? findLineWith True "ghc-location" defaultConfigFile - " -- ghc-pkg-location" @=? findLineWith True "ghc-pkg-location" defaultConfigFile - " -- ghcjs-location" @=? findLineWith True "ghcjs-location" defaultConfigFile - " -- ghcjs-pkg-location" @=? findLineWith True "ghcjs-pkg-location" defaultConfigFile - " -- greencard-location" @=? findLineWith True "greencard-location" defaultConfigFile - " -- haddock-location" @=? findLineWith True "haddock-location" defaultConfigFile - " -- happy-location" @=? findLineWith True "happy-location" defaultConfigFile - " -- haskell-suite-location" @=? findLineWith True "haskell-suite-location" defaultConfigFile - " -- haskell-suite-pkg-location" @=? findLineWith True "haskell-suite-pkg-location" defaultConfigFile - " -- hmake-location" @=? findLineWith True "hmake-location" defaultConfigFile - " -- hpc-location" @=? findLineWith True "hpc-location" defaultConfigFile - " -- hscolour-location" @=? findLineWith True "hscolour-location" defaultConfigFile - " -- jhc-location" @=? findLineWith True "jhc-location" defaultConfigFile - " -- ld-location" @=? findLineWith True "ld-location" defaultConfigFile - " -- pkg-config-location" @=? findLineWith True "pkg-config-location" defaultConfigFile - " -- runghc-location" @=? findLineWith True "runghc-location" defaultConfigFile - " -- strip-location" @=? findLineWith True "strip-location" defaultConfigFile - " -- tar-location" @=? findLineWith True "tar-location" defaultConfigFile - " -- uhc-location" @=? findLineWith True "uhc-location" defaultConfigFile - - " -- alex-options" @=? findLineWith True "alex-options" defaultConfigFile - " -- ar-options" @=? findLineWith True "ar-options" defaultConfigFile - " -- c2hs-options" @=? findLineWith True "c2hs-options" defaultConfigFile - " -- cpphs-options" @=? findLineWith True "cpphs-options" defaultConfigFile - " -- doctest-options" @=? findLineWith True "doctest-options" defaultConfigFile - " -- gcc-options" @=? findLineWith True "gcc-options" defaultConfigFile - " -- ghc-options" @=? findLineWith True "ghc-options" defaultConfigFile - " -- ghc-pkg-options" @=? findLineWith True "ghc-pkg-options" defaultConfigFile - " -- ghcjs-options" @=? findLineWith True "ghcjs-options" defaultConfigFile - " -- ghcjs-pkg-options" @=? findLineWith True "ghcjs-pkg-options" defaultConfigFile - " -- greencard-options" @=? findLineWith True "greencard-options" defaultConfigFile - " -- haddock-options" @=? findLineWith True "haddock-options" defaultConfigFile - " -- happy-options" @=? findLineWith True "happy-options" defaultConfigFile - " -- haskell-suite-options" @=? findLineWith True "haskell-suite-options" defaultConfigFile - " -- haskell-suite-pkg-options" @=? findLineWith True "haskell-suite-pkg-options" defaultConfigFile - " -- hmake-options" @=? findLineWith True "hmake-options" defaultConfigFile - " -- hpc-options" @=? findLineWith True "hpc-options" defaultConfigFile - " -- hsc2hs-options" @=? findLineWith True "hsc2hs-options" defaultConfigFile - " -- hscolour-options" @=? findLineWith True "hscolour-options" defaultConfigFile - " -- jhc-options" @=? findLineWith True "jhc-options" defaultConfigFile - " -- ld-options" @=? findLineWith True "ld-options" defaultConfigFile - " -- pkg-config-options" @=? findLineWith True "pkg-config-options" defaultConfigFile - " -- runghc-options" @=? findLineWith True "runghc-options" defaultConfigFile - " -- strip-options" @=? findLineWith True "strip-options" defaultConfigFile - " -- tar-options" @=? findLineWith True "tar-options" defaultConfigFile - " -- uhc-options" @=? findLineWith True "uhc-options" defaultConfigFile - where - -- | Find lines containing a target string. + let + -- | Find the first line containing a target setting name. + -- + -- If `isComment` is set, only comment lines will be found. findLineWith :: Bool -> String -> String -> String findLineWith isComment target text = case findLinesWith isComment target text of [] -> text - (l : _) -> removeCommentValue l + (l : _) -> removeColonAndAfter l + + -- | Find lines containing a target setting name. findLinesWith :: Bool -> String -> String -> [String] findLinesWith isComment target - | isComment = filter (isInfixOf (" " ++ target ++ ":")) . lines + | isComment = filter (isInfixOf ("-- " ++ target ++ ":")) . lines | otherwise = filter (isInfixOf (target ++ ":")) . lines - removeCommentValue :: String -> String - removeCommentValue = takeWhile (/= ':') + + -- | Transform @-- puppy: doggy@ into @-- puppy@. + removeColonAndAfter :: String -> String + removeColonAndAfter = takeWhile (/= ':') + + cwd <- getCurrentDirectory + let configFile = cwd basedir "config" "default-config" + _ <- createDefaultConfigFile verbosity [] configFile + defaultConfigFile <- readFile configFile + + let + -- TODO: These assertions are fairly weak. Potential improvements: + -- + -- - Include the section name in the assertion, so that (e.g.) a + -- `keep-temp-files` setting in the `haddock` section won't be confused + -- with a `keep-temp-files` setting in the `init` section. + -- + -- - Check all matching lines to confirm that settings are not listed + -- multiple times. For example, `cabal-file` is listed twice right now, + -- once under the `haddock` settings! + -- + -- - Consume the file as we go, ensuring that the settings are in a given + -- order. + -- + -- - Check the generated config file into Git (replacing e.g. `$HOME` with + -- a sentinel value) so changes show up in PR diffs. + assertHasLine' :: Bool -> String -> String -> Assertion + assertHasLine' isComment expected settingName = + let actual = findLineWith isComment settingName defaultConfigFile + messagePrefix = + "Did not find expected line for setting " + <> show settingName + <> " in configuration file " + <> configFile + in assertEqual messagePrefix expected actual + + assertHasLine :: String -> String -> Assertion + assertHasLine = assertHasLine' False + + assertHasCommentLine :: String -> String -> Assertion + assertHasCommentLine = assertHasLine' True + + + " url" `assertHasLine` "url" + " -- secure" `assertHasCommentLine` "secure" + " -- root-keys" `assertHasCommentLine` "root-keys" + " -- key-threshold" `assertHasCommentLine` "key-threshold" + + "-- ignore-expiry" `assertHasCommentLine` "ignore-expiry" + "-- http-transport" `assertHasCommentLine` "http-transport" + "-- nix" `assertHasCommentLine` "nix" + "-- store-dir" `assertHasCommentLine` "store-dir" + "-- active-repositories" `assertHasCommentLine` "active-repositories" + "-- local-no-index-repo" `assertHasCommentLine` "local-no-index-repo" + "remote-repo-cache" `assertHasLine` "remote-repo-cache" + "-- logs-dir" `assertHasCommentLine` "logs-dir" + "-- default-user-config" `assertHasCommentLine` "default-user-config" + "-- verbose" `assertHasCommentLine` "verbose" + "-- compiler" `assertHasCommentLine` "compiler" + "-- cabal-file" `assertHasCommentLine` "cabal-file" + "-- with-compiler" `assertHasCommentLine` "with-compiler" + "-- with-hc-pkg" `assertHasCommentLine` "with-hc-pkg" + "-- program-prefix" `assertHasCommentLine` "program-prefix" + "-- program-suffix" `assertHasCommentLine` "program-suffix" + "-- library-vanilla" `assertHasCommentLine` "library-vanilla" + "-- library-profiling" `assertHasCommentLine` "library-profiling" + "-- shared" `assertHasCommentLine` "shared" + "-- static" `assertHasCommentLine` "static" + "-- executable-dynamic" `assertHasCommentLine` "executable-dynamic" + "-- executable-static" `assertHasCommentLine` "executable-static" + "-- profiling" `assertHasCommentLine` "profiling" + "-- executable-profiling" `assertHasCommentLine` "executable-profiling" + "-- profiling-detail" `assertHasCommentLine` "profiling-detail" + "-- library-profiling-detail" `assertHasCommentLine` "library-profiling-detail" + "-- optimization" `assertHasCommentLine` "optimization" + "-- debug-info" `assertHasCommentLine` "debug-info" + "-- build-info" `assertHasCommentLine` "build-info" + "-- library-for-ghci" `assertHasCommentLine` "library-for-ghci" + "-- split-sections" `assertHasCommentLine` "split-sections" + "-- split-objs" `assertHasCommentLine` "split-objs" + "-- executable-stripping" `assertHasCommentLine` "executable-stripping" + "-- library-stripping" `assertHasCommentLine` "library-stripping" + "-- configure-option" `assertHasCommentLine` "configure-option" + "-- user-install" `assertHasCommentLine` "user-install" + "-- package-db" `assertHasCommentLine` "package-db" + "-- flags" `assertHasCommentLine` "flags" + "-- extra-include-dirs" `assertHasCommentLine` "extra-include-dirs" + "-- deterministic" `assertHasCommentLine` "deterministic" + "-- cid" `assertHasCommentLine` "cid" + "-- extra-lib-dirs" `assertHasCommentLine` "extra-lib-dirs" + "-- extra-lib-dirs-static" `assertHasCommentLine` "extra-lib-dirs-static" + "-- extra-framework-dirs" `assertHasCommentLine` "extra-framework-dirs" + "-- extra-prog-path" `assertHasLine` "extra-prog-path" + "-- instantiate-with" `assertHasCommentLine` "instantiate-with" + "-- tests" `assertHasCommentLine` "tests" + "-- coverage" `assertHasCommentLine` "coverage" + "-- library-coverage" `assertHasCommentLine` "library-coverage" + "-- exact-configuration" `assertHasCommentLine` "exact-configuration" + "-- benchmarks" `assertHasCommentLine` "benchmarks" + "-- relocatable" `assertHasCommentLine` "relocatable" + "-- response-files" `assertHasCommentLine` "response-files" + "-- allow-depending-on-private-libs" `assertHasCommentLine` "allow-depending-on-private-libs" + "-- cabal-lib-version" `assertHasCommentLine` "cabal-lib-version" + "-- append" `assertHasCommentLine` "append" + "-- backup" `assertHasCommentLine` "backup" + "-- constraint" `assertHasCommentLine` "constraint" + "-- preference" `assertHasCommentLine` "preference" + "-- solver" `assertHasCommentLine` "solver" + "-- allow-older" `assertHasCommentLine` "allow-older" + "-- allow-newer" `assertHasCommentLine` "allow-newer" + "-- write-ghc-environment-files" `assertHasCommentLine` "write-ghc-environment-files" + "-- documentation" `assertHasCommentLine` "documentation" + "-- doc-index-file" `assertHasCommentLine` "doc-index-file" + "-- only-download" `assertHasCommentLine` "only-download" + "-- target-package-db" `assertHasCommentLine` "target-package-db" + "-- max-backjumps" `assertHasCommentLine` "max-backjumps" + "-- reorder-goals" `assertHasCommentLine` "reorder-goals" + "-- count-conflicts" `assertHasCommentLine` "count-conflicts" + "-- fine-grained-conflicts" `assertHasCommentLine` "fine-grained-conflicts" + "-- minimize-conflict-set" `assertHasCommentLine` "minimize-conflict-set" + "-- independent-goals" `assertHasCommentLine` "independent-goals" + "-- prefer-oldest" `assertHasCommentLine` "prefer-oldest" + "-- shadow-installed-packages" `assertHasCommentLine` "shadow-installed-packages" + "-- strong-flags" `assertHasCommentLine` "strong-flags" + "-- allow-boot-library-installs" `assertHasCommentLine` "allow-boot-library-installs" + "-- reject-unconstrained-dependencies" `assertHasCommentLine` "reject-unconstrained-dependencies" + "-- reinstall" `assertHasCommentLine` "reinstall" + "-- avoid-reinstalls" `assertHasCommentLine` "avoid-reinstalls" + "-- force-reinstalls" `assertHasCommentLine` "force-reinstalls" + "-- upgrade-dependencies" `assertHasCommentLine` "upgrade-dependencies" + "-- index-state" `assertHasCommentLine` "index-state" + "-- root-cmd" `assertHasCommentLine` "root-cmd" + "-- symlink-bindir" `assertHasCommentLine` "symlink-bindir" + "build-summary" `assertHasLine` "build-summary" + "-- build-log" `assertHasCommentLine` "build-log" + "remote-build-reporting" `assertHasLine` "remote-build-reporting" + "-- report-planning-failure" `assertHasCommentLine` "report-planning-failure" + "-- per-component" `assertHasCommentLine` "per-component" + "-- run-tests" `assertHasCommentLine` "run-tests" + "jobs" `assertHasLine` "jobs" + "-- keep-going" `assertHasCommentLine` "keep-going" + "-- offline" `assertHasCommentLine` "offline" + "-- lib" `assertHasCommentLine` "lib" + "-- package-env" `assertHasCommentLine` "package-env" + "-- overwrite-policy" `assertHasCommentLine` "overwrite-policy" + "-- install-method" `assertHasCommentLine` "install-method" + "installdir" `assertHasLine` "installdir" + "-- token" `assertHasCommentLine` "token" + "-- username" `assertHasCommentLine` "username" + "-- password" `assertHasCommentLine` "password" + "-- password-command" `assertHasCommentLine` "password-command" + "-- builddir" `assertHasCommentLine` "builddir" + + " -- keep-temp-files" `assertHasCommentLine` "keep-temp-files" + " -- hoogle" `assertHasCommentLine` "hoogle" + " -- html" `assertHasCommentLine` "html" + " -- html-location" `assertHasCommentLine` "html-location" + " -- executables" `assertHasCommentLine` "executables" + " -- foreign-libraries" `assertHasCommentLine` "foreign-libraries" + " -- all" `assertHasCommentLine` "all" + " -- internal" `assertHasCommentLine` "internal" + " -- css" `assertHasCommentLine` "css" + " -- hyperlink-source" `assertHasCommentLine` "hyperlink-source" + " -- quickjump" `assertHasCommentLine` "quickjump" + " -- hscolour-css" `assertHasCommentLine` "hscolour-css" + " -- contents-location" `assertHasCommentLine` "contents-location" + " -- index-location" `assertHasCommentLine` "index-location" + " -- base-url" `assertHasCommentLine` "base-url" + " -- resources-dir" `assertHasCommentLine` "resources-dir" + " -- output-dir" `assertHasCommentLine` "output-dir" + " -- use-unicode" `assertHasCommentLine` "use-unicode" + + " -- interactive" `assertHasCommentLine` "interactive" + " -- quiet" `assertHasCommentLine` "quiet" + " -- no-comments" `assertHasCommentLine` "no-comments" + " -- minimal" `assertHasCommentLine` "minimal" + " -- cabal-version" `assertHasCommentLine` "cabal-version" + " -- license" `assertHasCommentLine` "license" + " -- extra-doc-file" `assertHasCommentLine` "extra-doc-file" + " -- test-dir" `assertHasCommentLine` "test-dir" + " -- simple" `assertHasCommentLine` "simple" + " -- language" `assertHasCommentLine` "language" + " -- application-dir" `assertHasCommentLine` "application-dir" + " -- source-dir" `assertHasCommentLine` "source-dir" + + " -- prefix" `assertHasCommentLine` "prefix" + " -- bindir" `assertHasCommentLine` "bindir" + " -- libdir" `assertHasCommentLine` "libdir" + " -- libsubdir" `assertHasCommentLine` "libsubdir" + " -- dynlibdir" `assertHasCommentLine` "dynlibdir" + " -- libexecdir" `assertHasCommentLine` "libexecdir" + " -- libexecsubdir" `assertHasCommentLine` "libexecsubdir" + " -- datadir" `assertHasCommentLine` "datadir" + " -- datasubdir" `assertHasCommentLine` "datasubdir" + " -- docdir" `assertHasCommentLine` "docdir" + " -- htmldir" `assertHasCommentLine` "htmldir" + " -- haddockdir" `assertHasCommentLine` "haddockdir" + " -- sysconfdir" `assertHasCommentLine` "sysconfdir" + + " -- alex-location" `assertHasCommentLine` "alex-location" + " -- ar-location" `assertHasCommentLine` "ar-location" + " -- c2hs-location" `assertHasCommentLine` "c2hs-location" + " -- cpphs-location" `assertHasCommentLine` "cpphs-location" + " -- doctest-location" `assertHasCommentLine` "doctest-location" + " -- gcc-location" `assertHasCommentLine` "gcc-location" + " -- ghc-location" `assertHasCommentLine` "ghc-location" + " -- ghc-pkg-location" `assertHasCommentLine` "ghc-pkg-location" + " -- ghcjs-location" `assertHasCommentLine` "ghcjs-location" + " -- ghcjs-pkg-location" `assertHasCommentLine` "ghcjs-pkg-location" + " -- greencard-location" `assertHasCommentLine` "greencard-location" + " -- haddock-location" `assertHasCommentLine` "haddock-location" + " -- happy-location" `assertHasCommentLine` "happy-location" + " -- haskell-suite-location" `assertHasCommentLine` "haskell-suite-location" + " -- haskell-suite-pkg-location" `assertHasCommentLine` "haskell-suite-pkg-location" + " -- hmake-location" `assertHasCommentLine` "hmake-location" + " -- hpc-location" `assertHasCommentLine` "hpc-location" + " -- hscolour-location" `assertHasCommentLine` "hscolour-location" + " -- jhc-location" `assertHasCommentLine` "jhc-location" + " -- ld-location" `assertHasCommentLine` "ld-location" + " -- pkg-config-location" `assertHasCommentLine` "pkg-config-location" + " -- runghc-location" `assertHasCommentLine` "runghc-location" + " -- strip-location" `assertHasCommentLine` "strip-location" + " -- tar-location" `assertHasCommentLine` "tar-location" + " -- uhc-location" `assertHasCommentLine` "uhc-location" + + " -- alex-options" `assertHasCommentLine` "alex-options" + " -- ar-options" `assertHasCommentLine` "ar-options" + " -- c2hs-options" `assertHasCommentLine` "c2hs-options" + " -- cpphs-options" `assertHasCommentLine` "cpphs-options" + " -- doctest-options" `assertHasCommentLine` "doctest-options" + " -- gcc-options" `assertHasCommentLine` "gcc-options" + " -- ghc-options" `assertHasCommentLine` "ghc-options" + " -- ghc-pkg-options" `assertHasCommentLine` "ghc-pkg-options" + " -- ghcjs-options" `assertHasCommentLine` "ghcjs-options" + " -- ghcjs-pkg-options" `assertHasCommentLine` "ghcjs-pkg-options" + " -- greencard-options" `assertHasCommentLine` "greencard-options" + " -- haddock-options" `assertHasCommentLine` "haddock-options" + " -- happy-options" `assertHasCommentLine` "happy-options" + " -- haskell-suite-options" `assertHasCommentLine` "haskell-suite-options" + " -- haskell-suite-pkg-options" `assertHasCommentLine` "haskell-suite-pkg-options" + " -- hmake-options" `assertHasCommentLine` "hmake-options" + " -- hpc-options" `assertHasCommentLine` "hpc-options" + " -- hsc2hs-options" `assertHasCommentLine` "hsc2hs-options" + " -- hscolour-options" `assertHasCommentLine` "hscolour-options" + " -- jhc-options" `assertHasCommentLine` "jhc-options" + " -- ld-options" `assertHasCommentLine` "ld-options" + " -- pkg-config-options" `assertHasCommentLine` "pkg-config-options" + " -- runghc-options" `assertHasCommentLine` "runghc-options" + " -- strip-options" `assertHasCommentLine` "strip-options" + " -- tar-options" `assertHasCommentLine` "tar-options" + " -- uhc-options" `assertHasCommentLine` "uhc-options" testIgnoreProjectFlag :: Assertion testIgnoreProjectFlag = do diff --git a/cabal-testsuite/PackageTests/Backpack/Includes2/cabal-internal.test.hs b/cabal-testsuite/PackageTests/Backpack/Includes2/cabal-internal.test.hs index e2c42fe43fd..1413f66dcdc 100644 --- a/cabal-testsuite/PackageTests/Backpack/Includes2/cabal-internal.test.hs +++ b/cabal-testsuite/PackageTests/Backpack/Includes2/cabal-internal.test.hs @@ -2,7 +2,7 @@ import Test.Cabal.Prelude main = cabalTest $ do skipUnlessGhcVersion ">= 8.1" - expectBrokenIfWindowsCI 10191 $ withProjectFile "cabal.internal.project" $ do + withProjectFile "cabal.internal.project" $ do cabal "v2-build" ["exe"] withPlan $ do r <- runPlanExe' "I" "exe" [] diff --git a/cabal-testsuite/PackageTests/Backpack/Includes3/cabal-external.test.hs b/cabal-testsuite/PackageTests/Backpack/Includes3/cabal-external.test.hs index a2431cdf389..a974254fc98 100644 --- a/cabal-testsuite/PackageTests/Backpack/Includes3/cabal-external.test.hs +++ b/cabal-testsuite/PackageTests/Backpack/Includes3/cabal-external.test.hs @@ -2,8 +2,6 @@ import Test.Cabal.Prelude main = cabalTest $ do skipUnlessGhcVersion ">= 8.1" - ghcVer <- isGhcVersion ">= 9.10" - skipIf "Windows + 9.10.1 (#10191)" (isWindows && ghcVer) withProjectFile "cabal.external.project" $ do cabal "v2-build" ["exe"] withPlan $ do diff --git a/cabal-testsuite/PackageTests/Backpack/Includes3/cabal-internal.test.hs b/cabal-testsuite/PackageTests/Backpack/Includes3/cabal-internal.test.hs index b0d3e21688f..81f3fcb0027 100644 --- a/cabal-testsuite/PackageTests/Backpack/Includes3/cabal-internal.test.hs +++ b/cabal-testsuite/PackageTests/Backpack/Includes3/cabal-internal.test.hs @@ -2,7 +2,7 @@ import Test.Cabal.Prelude main = cabalTest $ do skipUnlessGhcVersion ">= 8.1" - expectBrokenIfWindowsCI 10191 $ withProjectFile "cabal.internal.project" $ do + withProjectFile "cabal.internal.project" $ do cabal "v2-build" ["exe"] withPlan $ do r <- runPlanExe' "I" "exe" [] diff --git a/cabal-testsuite/PackageTests/Backpack/T6385/cabal.test.hs b/cabal-testsuite/PackageTests/Backpack/T6385/cabal.test.hs index d05327839da..5048e09d56b 100644 --- a/cabal-testsuite/PackageTests/Backpack/T6385/cabal.test.hs +++ b/cabal-testsuite/PackageTests/Backpack/T6385/cabal.test.hs @@ -1,6 +1,6 @@ import Test.Cabal.Prelude main = - cabalTest $ expectBrokenIfWindows 10191 $ withShorterPathForNewBuildStore $ do + cabalTest $ withShorterPathForNewBuildStore $ do skipUnlessGhcVersion ">= 8.1" withRepo "repo" $ do cabal "v2-build" ["T6385"] diff --git a/cabal-testsuite/PackageTests/BuildWays/p/CHANGELOG.md b/cabal-testsuite/PackageTests/BuildWays/p/CHANGELOG.md new file mode 100644 index 00000000000..9ede8b27d4f --- /dev/null +++ b/cabal-testsuite/PackageTests/BuildWays/p/CHANGELOG.md @@ -0,0 +1,5 @@ +# Revision history for p + +## 0.1.0.0 -- YYYY-mm-dd + +* First version. Released on an unsuspecting world. diff --git a/cabal-testsuite/PackageTests/BuildWays/p/p.cabal b/cabal-testsuite/PackageTests/BuildWays/p/p.cabal new file mode 100644 index 00000000000..687cf16bc0c --- /dev/null +++ b/cabal-testsuite/PackageTests/BuildWays/p/p.cabal @@ -0,0 +1,18 @@ +cabal-version: 3.12 +name: p +version: 0.1.0.0 +license: NONE +author: Matthew Pickering +maintainer: matthewtpickering@gmail.com +build-type: Simple +extra-doc-files: CHANGELOG.md + +common warnings + ghc-options: -Wall + +library + import: warnings + exposed-modules: MyLib + build-depends: base + hs-source-dirs: src + default-language: Haskell2010 diff --git a/cabal-testsuite/PackageTests/BuildWays/p/src/MyLib.hs b/cabal-testsuite/PackageTests/BuildWays/p/src/MyLib.hs new file mode 100644 index 00000000000..e657c4403f6 --- /dev/null +++ b/cabal-testsuite/PackageTests/BuildWays/p/src/MyLib.hs @@ -0,0 +1,4 @@ +module MyLib (someFunc) where + +someFunc :: IO () +someFunc = putStrLn "someFunc" diff --git a/cabal-testsuite/PackageTests/BuildWays/q/CHANGELOG.md b/cabal-testsuite/PackageTests/BuildWays/q/CHANGELOG.md new file mode 100644 index 00000000000..62632c53766 --- /dev/null +++ b/cabal-testsuite/PackageTests/BuildWays/q/CHANGELOG.md @@ -0,0 +1,5 @@ +# Revision history for q + +## 0.1.0.0 -- YYYY-mm-dd + +* First version. Released on an unsuspecting world. diff --git a/cabal-testsuite/PackageTests/BuildWays/q/app/Main.hs b/cabal-testsuite/PackageTests/BuildWays/q/app/Main.hs new file mode 100644 index 00000000000..642b418a547 --- /dev/null +++ b/cabal-testsuite/PackageTests/BuildWays/q/app/Main.hs @@ -0,0 +1,7 @@ +{-# LANGUAGE TemplateHaskell #-} +module Main where + +import MyLib + +main :: IO () +main = someFunc diff --git a/cabal-testsuite/PackageTests/BuildWays/q/q.cabal b/cabal-testsuite/PackageTests/BuildWays/q/q.cabal new file mode 100644 index 00000000000..a4f2a1d65d7 --- /dev/null +++ b/cabal-testsuite/PackageTests/BuildWays/q/q.cabal @@ -0,0 +1,19 @@ +cabal-version: 3.12 +name: q +version: 0.1.0.0 +license: NONE +author: Matthew Pickering +maintainer: matthewtpickering@gmail.com +build-type: Simple +extra-doc-files: CHANGELOG.md + +common warnings + ghc-options: -Wall + +executable q + import: warnings + main-is: Main.hs + build-depends: p, base + hs-source-dirs: app + ghc-options: -dynamic-too + default-language: Haskell2010 diff --git a/cabal-testsuite/PackageTests/BuildWays/setup.test.hs b/cabal-testsuite/PackageTests/BuildWays/setup.test.hs new file mode 100644 index 00000000000..dd4531599e4 --- /dev/null +++ b/cabal-testsuite/PackageTests/BuildWays/setup.test.hs @@ -0,0 +1,10 @@ +import Test.Cabal.Prelude + +opts = ["--enable-shared", "--enable-library-vanilla", "--enable-library-profiling"] + +-- See #10418 +main = setupTest $ recordMode DoNotRecord $ withPackageDb $ do + skipIfNoSharedLibraries + skipIfNoProfiledLibraries + withDirectory "p" $ setup_install opts + withDirectory "q" $ setup_install opts diff --git a/cabal-testsuite/PackageTests/ConfigureComponent/MissingOrPrivate/Lib.cabal b/cabal-testsuite/PackageTests/ConfigureComponent/MissingOrPrivate/Lib.cabal new file mode 100644 index 00000000000..284513bd8b1 --- /dev/null +++ b/cabal-testsuite/PackageTests/ConfigureComponent/MissingOrPrivate/Lib.cabal @@ -0,0 +1,20 @@ +cabal-version: 3.0 +name: Lib +version: 0.1.0.0 +license: BSD-3-Clause +author: Edward Z. Yang +maintainer: ezyang@cs.stanford.edu +build-type: Simple + +library foo-internal + build-depends: base + exposed-modules: Lib + default-language: Haskell2010 + +executable exe + main-is: Exe.hs + build-depends: base <=1.0, + package-that-does-not-exist, + Lib:{foo-internal, bar-internal}, + hs-source-dirs: exe + default-language: Haskell2010 diff --git a/cabal-testsuite/PackageTests/ConfigureComponent/MissingOrPrivate/Lib.hs b/cabal-testsuite/PackageTests/ConfigureComponent/MissingOrPrivate/Lib.hs new file mode 100644 index 00000000000..1d7d07d5cba --- /dev/null +++ b/cabal-testsuite/PackageTests/ConfigureComponent/MissingOrPrivate/Lib.hs @@ -0,0 +1,2 @@ +module Lib where +lib = "OK" diff --git a/cabal-testsuite/PackageTests/ConfigureComponent/MissingOrPrivate/exe/Exe.hs b/cabal-testsuite/PackageTests/ConfigureComponent/MissingOrPrivate/exe/Exe.hs new file mode 100644 index 00000000000..6ee3fb933aa --- /dev/null +++ b/cabal-testsuite/PackageTests/ConfigureComponent/MissingOrPrivate/exe/Exe.hs @@ -0,0 +1,2 @@ +import Lib +main = putStrLn lib diff --git a/cabal-testsuite/PackageTests/ConfigureComponent/MissingOrPrivate/setup-fail.out b/cabal-testsuite/PackageTests/ConfigureComponent/MissingOrPrivate/setup-fail.out new file mode 100644 index 00000000000..2ea38e253cd --- /dev/null +++ b/cabal-testsuite/PackageTests/ConfigureComponent/MissingOrPrivate/setup-fail.out @@ -0,0 +1,16 @@ +# Setup configure +Configuring library 'foo-internal' for Lib-0.1.0.0... +# Setup build +Preprocessing library 'foo-internal' for Lib-0.1.0.0... +Building library 'foo-internal' for Lib-0.1.0.0... +# Setup copy +Installing internal library foo-internal in +# Setup register +Registering library 'foo-internal' for Lib-0.1.0.0... +# Setup configure +Configuring executable 'exe' for Lib-0.1.0.0... +Error: [Cabal-8010] +Encountered missing or private dependencies: + Lib:{bar-internal,foo-internal} (missing :bar-internal), + base <=1.0 (installed: ), + package-that-does-not-exist (missing) diff --git a/cabal-testsuite/PackageTests/ConfigureComponent/MissingOrPrivate/setup-fail.test.hs b/cabal-testsuite/PackageTests/ConfigureComponent/MissingOrPrivate/setup-fail.test.hs new file mode 100644 index 00000000000..5d8123ee88c --- /dev/null +++ b/cabal-testsuite/PackageTests/ConfigureComponent/MissingOrPrivate/setup-fail.test.hs @@ -0,0 +1,8 @@ + +import Test.Cabal.Prelude +main = setupTest $ do + withPackageDb $ do + base_id <- getIPID "base" + setup_install ["foo-internal", "--cid", "foo-internal-0.1-abc"] + r <- fails $ setup' "configure" [ "exe" ] + assertOutputContains "Lib" r diff --git a/cabal-testsuite/PackageTests/ConfigureComponent/SubLib/setup-explicit-fail.out b/cabal-testsuite/PackageTests/ConfigureComponent/SubLib/setup-explicit-fail.out index fec347864e5..ee7258799b1 100644 --- a/cabal-testsuite/PackageTests/ConfigureComponent/SubLib/setup-explicit-fail.out +++ b/cabal-testsuite/PackageTests/ConfigureComponent/SubLib/setup-explicit-fail.out @@ -11,4 +11,4 @@ Registering library 'sublib' for Lib-0.1.0.0... Configuring executable 'exe' for Lib-0.1.0.0... Error: [Cabal-8010] Encountered missing or private dependencies: - Lib:sublib + Lib:sublib (missing :sublib) diff --git a/cabal-testsuite/PackageTests/HaddockKeepTmpsCustom/Setup.hs b/cabal-testsuite/PackageTests/HaddockKeepTmpsCustom/Setup.hs new file mode 100644 index 00000000000..e8ef27dbba9 --- /dev/null +++ b/cabal-testsuite/PackageTests/HaddockKeepTmpsCustom/Setup.hs @@ -0,0 +1,3 @@ +import Distribution.Simple + +main = defaultMain diff --git a/cabal-testsuite/PackageTests/HaddockKeepTmpsCustom/Simple.hs b/cabal-testsuite/PackageTests/HaddockKeepTmpsCustom/Simple.hs new file mode 100644 index 00000000000..df38c448c5e --- /dev/null +++ b/cabal-testsuite/PackageTests/HaddockKeepTmpsCustom/Simple.hs @@ -0,0 +1,4 @@ +module Simple where + +-- | For hiding needles. +data Haystack = Haystack diff --git a/cabal-testsuite/PackageTests/HaddockKeepTmpsCustom/cabal.project b/cabal-testsuite/PackageTests/HaddockKeepTmpsCustom/cabal.project new file mode 100644 index 00000000000..f45d5a19d3c --- /dev/null +++ b/cabal-testsuite/PackageTests/HaddockKeepTmpsCustom/cabal.project @@ -0,0 +1,3 @@ +packages: . + +haddock-keep-temp-files: true diff --git a/cabal-testsuite/PackageTests/HaddockKeepTmpsCustom/cabal.test.hs b/cabal-testsuite/PackageTests/HaddockKeepTmpsCustom/cabal.test.hs new file mode 100644 index 00000000000..e2d819e44d6 --- /dev/null +++ b/cabal-testsuite/PackageTests/HaddockKeepTmpsCustom/cabal.test.hs @@ -0,0 +1,22 @@ +import Test.Cabal.Prelude + +-- Test that "cabal haddock" preserves temporary files +-- We use haddock-keep-temp-file: True in the cabal.project. +main = cabalTest $ recordMode DoNotRecord $ withProjectFile "cabal.project" $ do + cabal "haddock" [] + + -- From the docs for `System.IO.openTempFile`: + -- + -- On Windows, the template prefix may be truncated to 3 chars, e.g. + -- "foobar.ext" will be "fooXXX.ext". + let glob = + if isWindows + then "had*.txt" + else "haddock-response*.txt" + + -- Check that there is a response file. + responseFiles <- assertGlobMatchesTestDir testTmpDir glob + + -- Check that the matched response file is not empty, and is indeed a Haddock + -- response file. + assertAnyFileContains responseFiles "--package-name" diff --git a/cabal-testsuite/PackageTests/HaddockKeepTmpsCustom/my.cabal b/cabal-testsuite/PackageTests/HaddockKeepTmpsCustom/my.cabal new file mode 100644 index 00000000000..a4a24980c12 --- /dev/null +++ b/cabal-testsuite/PackageTests/HaddockKeepTmpsCustom/my.cabal @@ -0,0 +1,16 @@ +cabal-version: 3.0 +name: HaddockKeepsTmpsCustom +version: 0.1 +license: BSD-3-Clause +author: Rodrigo Mesquita +stability: stable +category: PackageTests +build-type: Custom + +custom-setup + setup-depends: Cabal, base + +library + default-language: Haskell2010 + exposed-modules: Simple + build-depends: base diff --git a/cabal-testsuite/PackageTests/HaddockKeepsTmps/cabal.test.hs b/cabal-testsuite/PackageTests/HaddockKeepsTmps/cabal.test.hs index a4db6795625..3a76caadf6c 100644 --- a/cabal-testsuite/PackageTests/HaddockKeepsTmps/cabal.test.hs +++ b/cabal-testsuite/PackageTests/HaddockKeepsTmps/cabal.test.hs @@ -1,21 +1,29 @@ -{-# LANGUAGE LambdaCase #-} -import Test.Cabal.Prelude -import Data.List (sort) -import Distribution.Verbosity +import Data.List (isPrefixOf, sort) import Distribution.Simple.Glob import Distribution.Simple.Glob.Internal import Distribution.Simple.Utils +import Distribution.Verbosity +import System.Directory +import Test.Cabal.Prelude -- Test that "cabal haddock" preserves temporary files -- We use haddock-keep-temp-file: True in the cabal.project. -main = cabalTest $ recordMode DoNotRecord $ withProjectFile "cabal.project" $ do +main = + cabalTest $ recordMode DoNotRecord $ withProjectFile "cabal.project" $ do cabal "haddock" [] - cwd <- fmap testCurrentDir getTestEnv + -- From the docs for `System.IO.openTempFile`: + -- + -- On Windows, the template prefix may be truncated to 3 chars, e.g. + -- "foobar.ext" will be "fooXXX.ext". + let glob = + if isWindows + then "had*.txt" + else "haddock-response*.txt" + + -- Check that there is a response file. + responseFiles <- assertGlobMatchesTestDir testTmpDir glob - -- Windows has multiple response files, and only the last one (alphabetically) is the important one. - (safeLast . sort . globMatches <$> liftIO (runDirFileGlob silent Nothing cwd (GlobDirRecursive [WildCard, Literal "txt"]))) >>= \case - Nothing -> error "Expecting a response file to exist" - Just m -> do - -- Assert the matched response file is not empty, and indeed a haddock rsp - assertFileDoesContain (cwd m) "--package-name" + -- Check that the matched response file is not empty, and is indeed a Haddock + -- response file. + assertAnyFileContains responseFiles "--package-name" diff --git a/cabal-testsuite/PackageTests/Install/DistPrefInstall/CHANGELOG.md b/cabal-testsuite/PackageTests/Install/DistPrefInstall/CHANGELOG.md new file mode 100644 index 00000000000..3d5552b0ba5 --- /dev/null +++ b/cabal-testsuite/PackageTests/Install/DistPrefInstall/CHANGELOG.md @@ -0,0 +1,5 @@ +# Revision history for DistPrefInstall + +## 0.1.0.0 -- YYYY-mm-dd + +* First version. Released on an unsuspecting world. diff --git a/cabal-testsuite/PackageTests/Install/DistPrefInstall/DistPrefInstall.cabal b/cabal-testsuite/PackageTests/Install/DistPrefInstall/DistPrefInstall.cabal new file mode 100644 index 00000000000..536d26b3648 --- /dev/null +++ b/cabal-testsuite/PackageTests/Install/DistPrefInstall/DistPrefInstall.cabal @@ -0,0 +1,18 @@ +cabal-version: 3.12 +name: DistPrefInstall +version: 0.1.0.0 +license: NONE +author: Matthew Pickering +maintainer: matthewtpickering@gmail.com +build-type: Simple +extra-doc-files: CHANGELOG.md + +common warnings + ghc-options: -Wall + +library + import: warnings + exposed-modules: MyLib + build-depends: base + hs-source-dirs: src + default-language: Haskell2010 diff --git a/cabal-testsuite/PackageTests/Install/DistPrefInstall/Setup.hs b/cabal-testsuite/PackageTests/Install/DistPrefInstall/Setup.hs new file mode 100644 index 00000000000..9a994af677b --- /dev/null +++ b/cabal-testsuite/PackageTests/Install/DistPrefInstall/Setup.hs @@ -0,0 +1,2 @@ +import Distribution.Simple +main = defaultMain diff --git a/cabal-testsuite/PackageTests/Install/DistPrefInstall/setup.test.hs b/cabal-testsuite/PackageTests/Install/DistPrefInstall/setup.test.hs new file mode 100644 index 00000000000..27b709e7070 --- /dev/null +++ b/cabal-testsuite/PackageTests/Install/DistPrefInstall/setup.test.hs @@ -0,0 +1,8 @@ +import Test.Cabal.Prelude + +main = setupTest $ recordMode DoNotRecord $ withPackageDb $ do + setup "configure" [] + setup "build" [] + setup "copy" [] + setup "install" [] + setup "sdist" [] diff --git a/cabal-testsuite/PackageTests/Install/DistPrefInstall/src/MyLib.hs b/cabal-testsuite/PackageTests/Install/DistPrefInstall/src/MyLib.hs new file mode 100644 index 00000000000..e657c4403f6 --- /dev/null +++ b/cabal-testsuite/PackageTests/Install/DistPrefInstall/src/MyLib.hs @@ -0,0 +1,4 @@ +module MyLib (someFunc) where + +someFunc :: IO () +someFunc = putStrLn "someFunc" diff --git a/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/cabal.no.out b/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/cabal.no.out new file mode 100644 index 00000000000..073e87feea2 --- /dev/null +++ b/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/cabal.no.out @@ -0,0 +1,11 @@ +# cabal clean +# cabal v2-repl +Resolving dependencies... +Build profile: -w ghc- -O1 +In order, the following will be built: + - pkg-a-1 (interactive) (first run) + - pkg-b-0 (interactive) (first run) +Configuring pkg-a-1... +Preprocessing library for pkg-a-1... +Configuring pkg-b-0... +Preprocessing library for pkg-b-0... diff --git a/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/cabal.out b/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/cabal.out new file mode 100644 index 00000000000..9c141d7ac42 --- /dev/null +++ b/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/cabal.out @@ -0,0 +1,10 @@ +# cabal v2-repl +Resolving dependencies... +Build profile: -w ghc- -O1 +In order, the following will be built: + - pkg-a-1 (interactive) (lib) (first run) + - pkg-b-0 (interactive) (lib) (first run) +Configuring library for pkg-a-1... +Preprocessing library for pkg-a-1... +Configuring library for pkg-b-0... +Preprocessing library for pkg-b-0... diff --git a/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/cabal.project b/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/cabal.project new file mode 100644 index 00000000000..bf8292adeb5 --- /dev/null +++ b/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/cabal.project @@ -0,0 +1,2 @@ +packages: pkg-a/*.cabal +packages: pkg-b/*.cabal diff --git a/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/cabal.test.hs b/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/cabal.test.hs new file mode 100644 index 00000000000..ea0851f1f2c --- /dev/null +++ b/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/cabal.test.hs @@ -0,0 +1,33 @@ +import Test.Cabal.Prelude + +main = do + cabalTest' "yes" $ do + skipUnlessAnyCabalVersion ">= 3.11" + skipUnlessGhcVersion ">= 9.4" + cabal' "clean" [] + res <- + cabalWithStdin + "v2-repl" + [ "--keep-temp-files" + , "--enable-multi-repl" + , "pkg-b" + , "pkg-a" + ] + "Bar.bar" + assertOutputContains "foo is 42" res + void $ assertGlobMatchesTestDir testDistDir "multi-out*/" + + cabalTest' "no" $ do + skipUnlessAnyCabalVersion ">= 3.11" + skipUnlessGhcVersion ">= 9.4" + cabal' "clean" [] + res <- + cabalWithStdin + "v2-repl" + [ "--enable-multi-repl" + , "pkg-b" + , "pkg-a" + ] + "Bar.bar" + assertOutputContains "foo is 42" res + void $ assertGlobDoesNotMatchTestDir testDistDir "multi-out*/" diff --git a/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/cabal.yes.out b/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/cabal.yes.out new file mode 100644 index 00000000000..073e87feea2 --- /dev/null +++ b/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/cabal.yes.out @@ -0,0 +1,11 @@ +# cabal clean +# cabal v2-repl +Resolving dependencies... +Build profile: -w ghc- -O1 +In order, the following will be built: + - pkg-a-1 (interactive) (first run) + - pkg-b-0 (interactive) (first run) +Configuring pkg-a-1... +Preprocessing library for pkg-a-1... +Configuring pkg-b-0... +Preprocessing library for pkg-b-0... diff --git a/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/pkg-a/Foo.hs b/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/pkg-a/Foo.hs new file mode 100644 index 00000000000..208f04764de --- /dev/null +++ b/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/pkg-a/Foo.hs @@ -0,0 +1,4 @@ +module Foo where + +foo :: Int +foo = 42 diff --git a/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/pkg-a/Setup.hs b/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/pkg-a/Setup.hs new file mode 100644 index 00000000000..00bfe1fe441 --- /dev/null +++ b/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/pkg-a/Setup.hs @@ -0,0 +1,4 @@ +import Distribution.Simple + +main :: IO () +main = defaultMain diff --git a/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/pkg-a/pkg-a.cabal b/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/pkg-a/pkg-a.cabal new file mode 100644 index 00000000000..3c3bbde21a2 --- /dev/null +++ b/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/pkg-a/pkg-a.cabal @@ -0,0 +1,12 @@ +cabal-version: 2.2 +name: pkg-a +version: 1 +build-type: Custom + +custom-setup + setup-depends: Cabal, base + +library + default-language: Haskell2010 + build-depends: base + exposed-modules: Foo diff --git a/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/pkg-b/Bar.hs b/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/pkg-b/Bar.hs new file mode 100644 index 00000000000..114eedd9306 --- /dev/null +++ b/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/pkg-b/Bar.hs @@ -0,0 +1,6 @@ +module Bar (foo, bar) where + +import Foo (foo) + +bar :: String +bar = "foo is " <> show foo diff --git a/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/pkg-b/Setup.hs b/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/pkg-b/Setup.hs new file mode 100644 index 00000000000..00bfe1fe441 --- /dev/null +++ b/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/pkg-b/Setup.hs @@ -0,0 +1,4 @@ +import Distribution.Simple + +main :: IO () +main = defaultMain diff --git a/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/pkg-b/pkg-b.cabal b/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/pkg-b/pkg-b.cabal new file mode 100644 index 00000000000..a354090771d --- /dev/null +++ b/cabal-testsuite/PackageTests/MultiRepl/CustomSetupKeepTempFiles/pkg-b/pkg-b.cabal @@ -0,0 +1,12 @@ +cabal-version: 2.2 +name: pkg-b +version: 0 +build-type: Custom + +custom-setup + setup-depends: Cabal, base + +library + default-language: Haskell2010 + build-depends: base, pkg-a + exposed-modules: Bar diff --git a/cabal-testsuite/PackageTests/MultiRepl/KeepTempFiles/cabal.no.out b/cabal-testsuite/PackageTests/MultiRepl/KeepTempFiles/cabal.no.out new file mode 100644 index 00000000000..348fea760af --- /dev/null +++ b/cabal-testsuite/PackageTests/MultiRepl/KeepTempFiles/cabal.no.out @@ -0,0 +1,11 @@ +# cabal clean +# cabal v2-repl +Resolving dependencies... +Build profile: -w ghc- -O1 +In order, the following will be built: + - pkg-a-1 (interactive) (lib) (first run) + - pkg-b-0 (interactive) (lib) (first run) +Configuring library for pkg-a-1... +Preprocessing library for pkg-a-1... +Configuring library for pkg-b-0... +Preprocessing library for pkg-b-0... diff --git a/cabal-testsuite/PackageTests/MultiRepl/KeepTempFiles/cabal.project b/cabal-testsuite/PackageTests/MultiRepl/KeepTempFiles/cabal.project new file mode 100644 index 00000000000..bf8292adeb5 --- /dev/null +++ b/cabal-testsuite/PackageTests/MultiRepl/KeepTempFiles/cabal.project @@ -0,0 +1,2 @@ +packages: pkg-a/*.cabal +packages: pkg-b/*.cabal diff --git a/cabal-testsuite/PackageTests/MultiRepl/KeepTempFiles/cabal.test.hs b/cabal-testsuite/PackageTests/MultiRepl/KeepTempFiles/cabal.test.hs new file mode 100644 index 00000000000..26c90db7ab1 --- /dev/null +++ b/cabal-testsuite/PackageTests/MultiRepl/KeepTempFiles/cabal.test.hs @@ -0,0 +1,31 @@ +import Test.Cabal.Prelude + +main = do + cabalTest' "yes" $ do + skipUnlessGhcVersion ">= 9.4" + cabal' "clean" [] + res <- + cabalWithStdin + "v2-repl" + [ "--keep-temp-files" + , "--enable-multi-repl" + , "pkg-b" + , "pkg-a" + ] + "Bar.bar" + assertOutputContains "foo is 42" res + void $ assertGlobMatchesTestDir testDistDir "multi-out*/" + + cabalTest' "no" $ do + skipUnlessGhcVersion ">= 9.4" + cabal' "clean" [] + res <- + cabalWithStdin + "v2-repl" + [ "--enable-multi-repl" + , "pkg-b" + , "pkg-a" + ] + "Bar.bar" + assertOutputContains "foo is 42" res + void $ assertGlobDoesNotMatchTestDir testDistDir "multi-out*/" diff --git a/cabal-testsuite/PackageTests/MultiRepl/KeepTempFiles/cabal.yes.out b/cabal-testsuite/PackageTests/MultiRepl/KeepTempFiles/cabal.yes.out new file mode 100644 index 00000000000..348fea760af --- /dev/null +++ b/cabal-testsuite/PackageTests/MultiRepl/KeepTempFiles/cabal.yes.out @@ -0,0 +1,11 @@ +# cabal clean +# cabal v2-repl +Resolving dependencies... +Build profile: -w ghc- -O1 +In order, the following will be built: + - pkg-a-1 (interactive) (lib) (first run) + - pkg-b-0 (interactive) (lib) (first run) +Configuring library for pkg-a-1... +Preprocessing library for pkg-a-1... +Configuring library for pkg-b-0... +Preprocessing library for pkg-b-0... diff --git a/cabal-testsuite/PackageTests/MultiRepl/KeepTempFiles/pkg-a/Foo.hs b/cabal-testsuite/PackageTests/MultiRepl/KeepTempFiles/pkg-a/Foo.hs new file mode 100644 index 00000000000..208f04764de --- /dev/null +++ b/cabal-testsuite/PackageTests/MultiRepl/KeepTempFiles/pkg-a/Foo.hs @@ -0,0 +1,4 @@ +module Foo where + +foo :: Int +foo = 42 diff --git a/cabal-testsuite/PackageTests/MultiRepl/KeepTempFiles/pkg-a/pkg-a.cabal b/cabal-testsuite/PackageTests/MultiRepl/KeepTempFiles/pkg-a/pkg-a.cabal new file mode 100644 index 00000000000..7e4a3e9ef70 --- /dev/null +++ b/cabal-testsuite/PackageTests/MultiRepl/KeepTempFiles/pkg-a/pkg-a.cabal @@ -0,0 +1,8 @@ +cabal-version: 2.2 +name: pkg-a +version: 1 + +library + default-language: Haskell2010 + build-depends: base + exposed-modules: Foo diff --git a/cabal-testsuite/PackageTests/MultiRepl/KeepTempFiles/pkg-b/Bar.hs b/cabal-testsuite/PackageTests/MultiRepl/KeepTempFiles/pkg-b/Bar.hs new file mode 100644 index 00000000000..114eedd9306 --- /dev/null +++ b/cabal-testsuite/PackageTests/MultiRepl/KeepTempFiles/pkg-b/Bar.hs @@ -0,0 +1,6 @@ +module Bar (foo, bar) where + +import Foo (foo) + +bar :: String +bar = "foo is " <> show foo diff --git a/cabal-testsuite/PackageTests/MultiRepl/KeepTempFiles/pkg-b/pkg-b.cabal b/cabal-testsuite/PackageTests/MultiRepl/KeepTempFiles/pkg-b/pkg-b.cabal new file mode 100644 index 00000000000..8e1a273f0c4 --- /dev/null +++ b/cabal-testsuite/PackageTests/MultiRepl/KeepTempFiles/pkg-b/pkg-b.cabal @@ -0,0 +1,8 @@ +cabal-version: 2.2 +name: pkg-b +version: 0 + +library + default-language: Haskell2010 + build-depends: base, pkg-a + exposed-modules: Bar diff --git a/cabal-testsuite/cabal-testsuite.cabal b/cabal-testsuite/cabal-testsuite.cabal index 4c114082d43..f0dce35e60e 100644 --- a/cabal-testsuite/cabal-testsuite.cabal +++ b/cabal-testsuite/cabal-testsuite.cabal @@ -70,6 +70,7 @@ library , directory ^>= 1.2.0.1 || ^>= 1.3.0.0 , exceptions ^>= 0.10.0 , filepath ^>= 1.3.0.1 || ^>= 1.4.0.0 || ^>= 1.5.0.0 + , Glob ^>= 0.10.2 , network-wait ^>= 0.1.2.0 || ^>= 0.2.0.0 , optparse-applicative ^>= 0.14.3.0 || ^>=0.15.1.0 || ^>=0.16.0.0 || ^>= 0.17.0.0 || ^>= 0.18.1.0 , process ^>= 1.2.1.0 || ^>= 1.4.2.0 || ^>= 1.6.1.0 diff --git a/cabal-testsuite/src/Test/Cabal/Monad.hs b/cabal-testsuite/src/Test/Cabal/Monad.hs index abb9fba5def..31e1e07bf52 100644 --- a/cabal-testsuite/src/Test/Cabal/Monad.hs +++ b/cabal-testsuite/src/Test/Cabal/Monad.hs @@ -402,14 +402,18 @@ runTestM mode m = testSkipSetupTests = argSkipSetupTests (testCommonArgs args), testHaveCabalShared = runnerWithSharedLib senv, testEnvironment = - -- Try to avoid Unicode output - [ ("LC_ALL", Just "C") + -- Use UTF-8 output on all platforms. + [ ("LC_ALL", Just "en_US.UTF-8") -- Hermetic builds (knot-tied) , ("HOME", Just (testHomeDir env)) -- Set CABAL_DIR in addition to HOME, since HOME has no -- effect on Windows. , ("CABAL_DIR", Just (testCabalDir env)) , ("CABAL_CONFIG", Just (testUserCabalConfigFile env)) + -- Set `TMPDIR` so that temporary files aren't created in the global `TMPDIR`. + , ("TMPDIR", Just tmp_dir) + -- Windows uses `TMP` for the `TMPDIR`. + , ("TMP", Just tmp_dir) ], testShouldFail = False, testRelativeCurrentDir = ".", diff --git a/cabal-testsuite/src/Test/Cabal/OutputNormalizer.hs b/cabal-testsuite/src/Test/Cabal/OutputNormalizer.hs index 33e1522526b..fb2840be9e6 100644 --- a/cabal-testsuite/src/Test/Cabal/OutputNormalizer.hs +++ b/cabal-testsuite/src/Test/Cabal/OutputNormalizer.hs @@ -80,6 +80,7 @@ normalizeOutput nenv = . maybe id normalizePathCmdOutput (normalizerCabalInstallVersion nenv) -- hackage-security locks occur non-deterministically . resub "(Released|Acquired|Waiting) .*hackage-security-lock\n" "" + . resub "installed: [0-9]+(\\.[0-9]+)*" "installed: " where sameDir = "(\\.((\\\\)+|\\/))*" packageIdRegex pid = diff --git a/cabal-testsuite/src/Test/Cabal/Prelude.hs b/cabal-testsuite/src/Test/Cabal/Prelude.hs index 50f9395d74a..d8cee954d83 100644 --- a/cabal-testsuite/src/Test/Cabal/Prelude.hs +++ b/cabal-testsuite/src/Test/Cabal/Prelude.hs @@ -53,9 +53,9 @@ import Text.Regex.TDFA ((=~)) import Control.Concurrent.Async (withAsync) import qualified Data.Aeson as JSON import qualified Data.ByteString.Lazy as BSL -import Control.Monad (unless, when, void, forM_, liftM2, liftM4) +import Control.Monad (unless, when, void, forM_, foldM, liftM2, liftM4) import Control.Monad.Catch ( bracket_ ) -import Control.Monad.Trans.Reader (withReaderT, runReaderT) +import Control.Monad.Trans.Reader (asks, withReaderT, runReaderT) import Control.Monad.IO.Class (MonadIO (..)) import qualified Crypto.Hash.SHA256 as SHA256 import qualified Data.ByteString.Base16 as Base16 @@ -70,6 +70,7 @@ import System.Directory import Control.Retry (exponentialBackoff, limitRetriesByCumulativeDelay) import Network.Wait (waitTcpVerbose) import System.Environment +import qualified System.FilePath.Glob as Glob (globDir1, compile) import System.Process import System.IO @@ -726,7 +727,7 @@ recordHeader args = do ------------------------------------------------------------------------ -- * Subprocess run results -assertFailure :: WithCallStack (String -> m ()) +assertFailure :: WithCallStack (String -> m a) assertFailure msg = withFrozenCallStack $ error msg assertExitCode :: MonadIO m => WithCallStack (ExitCode -> Result -> m ()) @@ -835,6 +836,31 @@ assertFileDoesNotContain path needle = (assertFailure ("expected: " ++ needle ++ "\n" ++ " in file: " ++ path))) +-- | Assert that at least one of the given paths contains the given search string. +assertAnyFileContains :: MonadIO m => WithCallStack ([FilePath] -> String -> m ()) +assertAnyFileContains paths needle = do + let findOne found path = + if found + then pure found + else withFileContents path $ \contents -> + pure $! needle `isInfixOf` contents + foundNeedle <- liftIO $ foldM findOne False paths + withFrozenCallStack $ + unless foundNeedle $ + assertFailure $ + "expected: " <> + needle <> + "\nin one of:\n" <> + unlines (map ("* " <>) paths) + +-- | Assert that none of the given paths contains the given search string. +assertNoFileContains :: MonadIO m => WithCallStack ([FilePath] -> String -> m ()) +assertNoFileContains paths needle = + liftIO $ + forM_ paths $ + \path -> + assertFileDoesNotContain path needle + -- | Replace line breaks with spaces, correctly handling "\r\n". concatOutput :: String -> String concatOutput = unwords . lines . filter ((/=) '\r') @@ -847,6 +873,58 @@ getScriptCacheDirectory script = do let hash = C.unpack . Base16.encode . C.take 26 . SHA256.hash . C.pack $ hashinput return $ cabalDir "script-builds" hash +------------------------------------------------------------------------ +-- * Globs + +-- | Match a glob from a root directory and return the results. +matchGlob :: MonadIO m => FilePath -> String -> m [FilePath] +matchGlob root glob = do + liftIO $ Glob.globDir1 (Glob.compile glob) root + +-- | Assert that a glob matches at least one path in the given root directory. +-- +-- The matched paths are returned for further validation. +assertGlobMatches :: MonadIO m => WithCallStack (FilePath -> String -> m [FilePath]) +assertGlobMatches root glob = do + results <- matchGlob root glob + withFrozenCallStack $ + when (null results) $ + assertFailure $ + "Expected glob " <> show glob <> " to match in " <> show root + pure results + +-- | Assert that a glob matches no paths in the given root directory. +assertGlobDoesNotMatch :: MonadIO m => WithCallStack (FilePath -> String -> m ()) +assertGlobDoesNotMatch root glob = do + results <- matchGlob root glob + withFrozenCallStack $ + unless (null results) $ + assertFailure $ + "Expected glob " + <> show glob + <> " to not match any paths in " + <> show root + <> ", but the following matches were found:" + <> unlines (map ("* " <>) results) + +-- | Assert that a glob matches a path in the given root directory. +-- +-- The root directory is determined from the `TestEnv` with a function like `testDistDir`. +-- +-- The matched paths are returned for further validation. +assertGlobMatchesTestDir :: WithCallStack ((TestEnv -> FilePath) -> String -> TestM [FilePath]) +assertGlobMatchesTestDir rootSelector glob = do + root <- asks rootSelector + assertGlobMatches root glob + +-- | Assert that a glob matches a path in the given root directory. +-- +-- The root directory is determined from the `TestEnv` with a function like `testDistDir`. +assertGlobDoesNotMatchTestDir :: WithCallStack ((TestEnv -> FilePath) -> String -> TestM ()) +assertGlobDoesNotMatchTestDir rootSelector glob = do + root <- asks rootSelector + assertGlobDoesNotMatch root glob + ------------------------------------------------------------------------ -- * Skipping tests diff --git a/cabal-testsuite/src/Test/Cabal/Run.hs b/cabal-testsuite/src/Test/Cabal/Run.hs index 37b27e9edf3..498c14ded23 100644 --- a/cabal-testsuite/src/Test/Cabal/Run.hs +++ b/cabal-testsuite/src/Test/Cabal/Run.hs @@ -54,6 +54,29 @@ runAction _verbosity mb_cwd env_overrides path0 args input action = do mb_env <- getEffectiveEnvironment env_overrides putStrLn $ "+ " ++ showCommandForUser path args (readh, writeh) <- createPipe + + -- `System.Process.createPipe` calls (through many intermediaries) + -- `GHC.IO.Handle.FD.fdToHandle`, whose documentation says: + -- + -- > Makes a binary Handle. This is for historical reasons; it should + -- > probably be a text Handle with the default encoding and newline + -- > translation instead. + -- + -- The documentation for `System.IO.hSetBinaryMode` says: + -- + -- > This has the same effect as calling `hSetEncoding` with `char8`, together + -- > with `hSetNewlineMode` with `noNewlineTranslation`. + -- + -- But this is a lie, and Unicode written to or read from binary handles is + -- always encoded or decoded as Latin-1, which is always the wrong choice. + -- + -- Therefore, we explicitly set the output to UTF-8 to keep it consistent + -- between platforms and correct on all modern computers. + -- + -- See: https://gitlab.haskell.org/ghc/ghc/-/issues/25307 + hSetEncoding readh utf8 + hSetEncoding writeh utf8 + hSetBuffering readh LineBuffering hSetBuffering writeh LineBuffering let drain = do diff --git a/cabal-validate/README.md b/cabal-validate/README.md new file mode 100644 index 00000000000..5f40e9d28f1 --- /dev/null +++ b/cabal-validate/README.md @@ -0,0 +1,23 @@ +# cabal-validate + +`cabal-validate` is a script that builds and tests `Cabal` and `cabal-install`. +`cabal-validate` can be run with `validate.sh` in the repository root; +arguments passed to `validate.sh` will be forwarded to `cabal-validate`. + +Notable arguments include: + +- `-v`/`--verbose` to display build and test output in real-time, instead of + only if commands fail. +- `-s`/`--step` to run a specific step (e.g. `-s build -s lib-tests` will only + run the `build` and `lib-tests` steps). +- `-p`/`--pattern` to filter tests by a pattern. + +## Hacking on cabal-validate + +Overview of important modules: + +- `Main.hs` encodes all the commands that are run for each step. +- `Cli.hs` parses the CLI arguments and resolves default values from the + environment, like determining which steps are run by default or the `--jobs` + argument to pass to test suites. +- `Step.hs` lists the available steps. diff --git a/cabal-validate/cabal-validate.cabal b/cabal-validate/cabal-validate.cabal new file mode 100644 index 00000000000..582cf67434a --- /dev/null +++ b/cabal-validate/cabal-validate.cabal @@ -0,0 +1,47 @@ +cabal-version: 3.0 +name: cabal-validate +version: 1.0.0 +copyright: 2024-2024, Cabal Development Team (see AUTHORS file) +license: BSD-3-Clause +author: Cabal Development Team +synopsis: An internal tool for building and testing the Cabal package manager +build-type: Simple + +common common + ghc-options: -Wall + + if impl(ghc <9.6) + -- Pattern exhaustiveness checker is not as good, misses a case. + ghc-options: -Wno-incomplete-patterns + + default-language: Haskell2010 + default-extensions: + OverloadedStrings + , TypeApplications + +executable cabal-validate + import: common + ghc-options: -O -threaded -rtsopts -with-rtsopts=-N + + main-is: Main.hs + hs-source-dirs: src + + other-modules: + , ANSI + , Cli + , ClockUtil + , OutputUtil + , ProcessUtil + , Step + + build-depends: + , base >=4 && <5 + , bytestring >=0.11 && <1 + , containers >=0.6 && <1 + , directory >=1.0 && <2 + , filepath >=1 && <2 + , optparse-applicative >=0.18 && <1 + , terminal-size >=0.3 && <1 + , text >=2 && <3 + , time >=1 && <2 + , typed-process >=0.2 && <1 diff --git a/cabal-validate/src/ANSI.hs b/cabal-validate/src/ANSI.hs new file mode 100644 index 00000000000..a0d9111d957 --- /dev/null +++ b/cabal-validate/src/ANSI.hs @@ -0,0 +1,105 @@ +-- | ANSI escape sequences. +-- +-- This is a stripped-down version of the parts of the @ansi-terminal@ package +-- we use. +-- +-- See: +module ANSI + ( SGR (..) + , setSGR + ) where + +-- | Render a single numeric SGR sequence. +rawSGR :: Int -> String +rawSGR code = "\x1b[" <> show code <> "m" + +-- | Render a series of `SGR` escape sequences. +setSGR :: [SGR] -> String +setSGR = concat . map renderSGR + +-- | All of the SGR sequences we want to use. +data SGR + = Reset + | Bold + | Dim + | Italic + | Underline + | Black + | Red + | Green + | Yellow + | Blue + | Magenta + | Cyan + | White + | Default + | OnBlack + | OnRed + | OnGreen + | OnYellow + | OnBlue + | OnMagenta + | OnCyan + | OnWhite + | OnDefault + | BrightBlack + | BrightRed + | BrightGreen + | BrightYellow + | BrightBlue + | BrightMagenta + | BrightCyan + | BrightWhite + | OnBrightBlack + | OnBrightRed + | OnBrightGreen + | OnBrightYellow + | OnBrightBlue + | OnBrightMagenta + | OnBrightCyan + | OnBrightWhite + deriving (Show) + +-- Render a single `SGR` sequence. +renderSGR :: SGR -> String +renderSGR code = + case code of + Reset -> rawSGR 0 + Bold -> rawSGR 1 + Dim -> rawSGR 2 + Italic -> rawSGR 3 + Underline -> rawSGR 4 + Black -> rawSGR 30 + Red -> rawSGR 31 + Green -> rawSGR 32 + Yellow -> rawSGR 33 + Blue -> rawSGR 34 + Magenta -> rawSGR 35 + Cyan -> rawSGR 36 + White -> rawSGR 37 + Default -> rawSGR 39 + OnBlack -> rawSGR 40 + OnRed -> rawSGR 41 + OnGreen -> rawSGR 42 + OnYellow -> rawSGR 43 + OnBlue -> rawSGR 44 + OnMagenta -> rawSGR 45 + OnCyan -> rawSGR 46 + OnWhite -> rawSGR 47 + OnDefault -> rawSGR 49 + BrightBlack -> rawSGR 90 + BrightRed -> rawSGR 91 + BrightGreen -> rawSGR 92 + BrightYellow -> rawSGR 93 + BrightBlue -> rawSGR 94 + BrightMagenta -> rawSGR 95 + BrightCyan -> rawSGR 96 + BrightWhite -> rawSGR 97 + OnBrightBlack -> rawSGR 100 + OnBrightRed -> rawSGR 101 + OnBrightGreen -> rawSGR 102 + OnBrightYellow -> rawSGR 103 + OnBrightBlue -> rawSGR 104 + OnBrightMagenta -> rawSGR 105 + OnBrightCyan -> rawSGR 106 + OnBrightWhite -> rawSGR 107 diff --git a/cabal-validate/src/Cli.hs b/cabal-validate/src/Cli.hs new file mode 100644 index 00000000000..ef01d907594 --- /dev/null +++ b/cabal-validate/src/Cli.hs @@ -0,0 +1,437 @@ +-- | Parse CLI arguments and resolve defaults from the environment. +module Cli + ( Opts (..) + , parseOpts + , HackageTests (..) + , Compiler (..) + , VersionParseException (..) + ) +where + +import Control.Applicative (Alternative (many, (<|>)), (<**>)) +import Control.Exception (Exception (displayException), throw) +import Control.Monad (forM_, when) +import Data.Data (Typeable) +import Data.Maybe (listToMaybe) +import qualified Data.Text as T +import qualified Data.Text.Lazy as T (toStrict) +import qualified Data.Text.Lazy.Encoding as T (decodeUtf8) +import Data.Version (Version, parseVersion) +import GHC.Conc (getNumCapabilities) +import Options.Applicative + ( FlagFields + , Mod + , Parser + , ParserInfo + , auto + , execParser + , flag + , flag' + , fullDesc + , help + , helper + , hidden + , info + , long + , maybeReader + , option + , progDesc + , short + , strOption + , switch + , value + ) +import qualified Options.Applicative as Opt +import System.Directory (getCurrentDirectory) +import System.Exit (exitSuccess) +import System.Info (arch, os) +import System.Process.Typed (proc, readProcessStdout_) +import Text.ParserCombinators.ReadP (readP_to_S) + +import ClockUtil (AbsoluteTime, getAbsoluteTime) +import Step (Step (..), displayStep, parseStep) + +-- | Command-line options, resolved with context from the environment. +data Opts = Opts + { verbose :: Bool + -- ^ Whether to display build and test output. + , jobs :: Int + -- ^ How many jobs to use when running tests. + -- + -- Defaults to the number of physical cores. + , cwd :: FilePath + -- ^ Current working directory when @cabal-validate@ was started. + , startTime :: AbsoluteTime + -- ^ System time when @cabal-validate@ was started. + -- + -- Used to determine the total test duration so far. + , compiler :: Compiler + -- ^ Compiler to build Cabal with. + -- + -- Defaults to @ghc@. + , extraCompilers :: [FilePath] + -- ^ Extra compilers to run @cabal-testsuite@ with. + , cabal :: FilePath + -- ^ @cabal-install@ to build Cabal with. + -- + -- Defaults to @cabal@. + , hackageTests :: HackageTests + -- ^ Whether to run tests on Hackage data, and if so how much. + -- + -- Defaults to `NoHackageTests`. + , archPath :: FilePath + -- ^ The path for this system's architecture within the build directory. + -- + -- Like @x86_64-windows@ or @aarch64-osx@ or @arm-linux@. + , projectFile :: FilePath + -- ^ Path to the @cabal.project@ file to use for running tests. + , tastyArgs :: [String] + -- ^ Extra arguments to pass to @tasty@ test suites. + -- + -- This defaults to @--hide-successes@ (which cannot yet be changed) and + -- includes the @--pattern@ argument if one is given. + , targets :: [String] + -- ^ Targets to build. + , steps :: [Step] + -- ^ Steps to run. + } + deriving (Show) + +-- | Whether to run tests on Hackage data, and if so how much. +data HackageTests + = -- | Run tests on complete Hackage data. + CompleteHackageTests + | -- | Run tests on partial Hackage data. + PartialHackageTests + | -- | Do not run tests on Hackage data. + NoHackageTests + deriving (Show) + +-- | A compiler executable and version number. +data Compiler = Compiler + { compilerExecutable :: FilePath + -- ^ The compiler's executable. + , compilerVersion :: Version + -- ^ The compiler's version number. + } + deriving (Show) + +-- | An `Exception` thrown when parsing @--numeric-version@ output from a compiler. +data VersionParseException = VersionParseException + { versionInput :: String + -- ^ The string we attempted to parse. + , versionExecutable :: FilePath + -- ^ The compiler which produced the string. + } + deriving (Typeable, Show) + +instance Exception VersionParseException where + displayException exception = + "Failed to parse `" + <> versionExecutable exception + <> " --numeric-version` output: " + <> show (versionInput exception) + +-- | Runs @ghc --numeric-version@ for the given executable to construct a +-- `Compiler`. +makeCompiler :: FilePath -> IO Compiler +makeCompiler executable = do + stdout <- + readProcessStdout_ $ + proc executable ["--numeric-version"] + let version = T.unpack $ T.strip $ T.toStrict $ T.decodeUtf8 stdout + parsedVersions = readP_to_S parseVersion version + -- Who needs error messages? Those aren't in the API. + maybeParsedVersion = + listToMaybe + [ parsed + | (parsed, []) <- parsedVersions + ] + parsedVersion = case maybeParsedVersion of + Just parsedVersion' -> parsedVersion' + Nothing -> + throw + VersionParseException + { versionInput = version + , versionExecutable = executable + } + + pure + Compiler + { compilerExecutable = executable + , compilerVersion = parsedVersion + } + +-- | Resolve options and default values from the environment. +-- +-- This makes the `Opts` type much nicer to deal with than `RawOpts`. +resolveOpts :: RawOpts -> IO Opts +resolveOpts opts = do + let optionals :: Bool -> [a] -> [a] + optionals True items = items + optionals False _ = [] + + optional :: Bool -> a -> [a] + optional keep item = optionals keep [item] + + steps' = + if not (null (rawSteps opts)) + then rawSteps opts + else + concat + [ + [ PrintConfig + , PrintToolVersions + , Build + ] + , optional (rawDoctest opts) Doctest + , optional (rawRunLibTests opts) LibTests + , optional (rawRunLibSuite opts) LibSuite + , optional (rawRunLibSuite opts && not (null (rawExtraCompilers opts))) LibSuiteExtras + , optional (rawRunCliTests opts && not (rawLibOnly opts)) CliTests + , optional (rawRunCliSuite opts && not (rawLibOnly opts)) CliSuite + , optionals (rawSolverBenchmarks opts) [SolverBenchmarksTests, SolverBenchmarksRun] + , [TimeSummary] + ] + + targets' = + concat + [ + [ "Cabal" + , "Cabal-hooks" + , "cabal-testsuite" + , "Cabal-tests" + , "Cabal-QuickCheck" + , "Cabal-tree-diff" + , "Cabal-described" + ] + , optionals + (not (rawLibOnly opts)) + [ "cabal-install" + , "cabal-install-solver" + , "cabal-benchmarks" + ] + , optionals + (rawSolverBenchmarks opts) + [ "solver-benchmarks" + , "solver-benchmarks:tests" + ] + ] + + archPath' = + let osPath = + case os of + "darwin" -> "osx" + "linux" -> "linux" + "mingw32" -> "windows" + _ -> os -- TODO: Warning? + in arch <> "-" <> osPath + + projectFile' = + if rawLibOnly opts + then "cabal.validate-libonly.project" + else "cabal.validate.project" + + tastyArgs' = + "--hide-successes" + : case rawTastyPattern opts of + Just tastyPattern -> ["--pattern", tastyPattern] + Nothing -> [] + + when (rawListSteps opts) $ do + -- TODO: This should probably list _all_ available steps, not just the selected ones! + putStrLn "Targets:" + forM_ targets' $ \target -> do + putStrLn $ " " <> target + putStrLn "Steps:" + forM_ steps' $ \step -> do + putStrLn $ " " <> displayStep step + exitSuccess + + startTime' <- getAbsoluteTime + jobs' <- maybe getNumCapabilities pure (rawJobs opts) + cwd' <- getCurrentDirectory + compiler' <- makeCompiler (rawCompiler opts) + + pure + Opts + { verbose = rawVerbose opts + , jobs = jobs' + , cwd = cwd' + , startTime = startTime' + , compiler = compiler' + , extraCompilers = rawExtraCompilers opts + , cabal = rawCabal opts + , archPath = archPath' + , projectFile = projectFile' + , hackageTests = rawHackageTests opts + , tastyArgs = tastyArgs' + , targets = targets' + , steps = steps' + } + +-- | Literate command-line options as supplied by the user, before resolving +-- defaults and other values from the environment. +data RawOpts = RawOpts + { rawVerbose :: Bool + , rawJobs :: Maybe Int + , rawCompiler :: FilePath + , rawCabal :: FilePath + , rawExtraCompilers :: [FilePath] + , rawTastyPattern :: Maybe String + , rawDoctest :: Bool + , rawSteps :: [Step] + , rawListSteps :: Bool + , rawLibOnly :: Bool + , rawRunLibTests :: Bool + , rawRunCliTests :: Bool + , rawRunLibSuite :: Bool + , rawRunCliSuite :: Bool + , rawSolverBenchmarks :: Bool + , rawHackageTests :: HackageTests + } + deriving (Show) + +-- | `Parser` for `RawOpts`. +-- +-- See: `fullRawOptsParser` +rawOptsParser :: Parser RawOpts +rawOptsParser = + RawOpts + <$> ( flag' + True + ( short 'v' + <> long "verbose" + <> help "Always display build and test output" + ) + <|> flag + False + False + ( short 'q' + <> long "quiet" + <> help "Silence build and test output" + ) + ) + <*> option + (Just <$> auto) + ( short 'j' + <> long "jobs" + <> help "Passed to `cabal build --jobs`" + <> value Nothing + ) + <*> strOption + ( short 'w' + <> long "with-compiler" + <> help "Build Cabal with the given compiler instead of `ghc`" + <> value "ghc" + ) + <*> strOption + ( long "with-cabal" + <> help "Test the given `cabal-install` (the `cabal` on your `$PATH` is used for builds)" + <> value "cabal" + ) + <*> many + ( strOption + ( long "extra-hc" + <> help "Extra compilers to run the test suites against" + ) + ) + <*> option + (Just <$> Opt.str) + ( short 'p' + <> long "pattern" + <> help "Pattern to filter tests by" + <> value Nothing + ) + <*> boolOption + False + "doctest" + ( help "Run doctest on the `Cabal` library" + ) + <*> many + ( option + (maybeReader parseStep) + ( short 's' + <> long "step" + <> help "Run only a specific step (can be specified multiple times)" + ) + ) + <*> switch + ( long "list-steps" + <> help "List the available steps and exit" + ) + <*> ( flag' + True + ( long "lib-only" + <> help "Test only `Cabal` (the library)" + ) + <|> flag + False + False + ( long "cli" + <> help "Test `cabal-install` (the executable) in addition to `Cabal` (the library)" + ) + ) + <*> boolOption + True + "run-lib-tests" + ( help "Run tests for the `Cabal` library" + ) + <*> boolOption + True + "run-cli-tests" + ( help "Run client tests for the `cabal-install` executable" + ) + <*> boolOption + False + "run-lib-suite" + ( help "Run `cabal-testsuite` with the `Cabal` library" + ) + <*> boolOption + False + "run-cli-suite" + ( help "Run `cabal-testsuite` with the `cabal-install` executable" + ) + <*> boolOption + False + "solver-benchmarks" + ( help "Build and trial run `solver-benchmarks`" + ) + <*> ( flag' + CompleteHackageTests + ( long "complete-hackage-tests" + <> help "Run `hackage-tests` on complete Hackage data" + ) + <|> flag + NoHackageTests + PartialHackageTests + ( long "partial-hackage-tests" + <> help "Run `hackage-tests` on parts of Hackage data" + ) + ) + +-- | Parse a boolean switch with separate names for the true and false options. +boolOption' :: Bool -> String -> String -> Mod FlagFields Bool -> Parser Bool +boolOption' defaultValue trueName falseName modifiers = + flag' True (modifiers <> long trueName) + <|> flag defaultValue False (modifiers <> hidden <> long falseName) + +-- | Parse a boolean switch with a @--no-*@ flag for setting the option to false. +boolOption :: Bool -> String -> Mod FlagFields Bool -> Parser Bool +boolOption defaultValue trueName = + boolOption' defaultValue trueName ("no-" <> trueName) + +-- | Full `Parser` for `RawOpts`, which includes a @--help@ argument and +-- information about the program. +fullRawOptsParser :: ParserInfo RawOpts +fullRawOptsParser = + info + (rawOptsParser <**> helper) + ( fullDesc + <> progDesc "Test suite runner for `Cabal` and `cabal-install` developers" + ) + +-- | Parse command-line arguments and resolve defaults from the environment, +-- producing `Opts`. +parseOpts :: IO Opts +parseOpts = execParser fullRawOptsParser >>= resolveOpts diff --git a/cabal-validate/src/ClockUtil.hs b/cabal-validate/src/ClockUtil.hs new file mode 100644 index 00000000000..2df7cdd9866 --- /dev/null +++ b/cabal-validate/src/ClockUtil.hs @@ -0,0 +1,33 @@ +-- | Utilities for dealing with times and durations. +module ClockUtil + ( DiffTime + , AbsoluteTime + , diffAbsoluteTime + , getAbsoluteTime + , formatDiffTime + ) where + +import Data.Time.Clock (DiffTime, secondsToDiffTime) +import Data.Time.Clock.System (getSystemTime, systemToTAITime) +import Data.Time.Clock.TAI (AbsoluteTime, diffAbsoluteTime) +import Data.Time.Format (defaultTimeLocale, formatTime) + +-- | Get the current time as an `AbsoluteTime`. +getAbsoluteTime :: IO AbsoluteTime +getAbsoluteTime = systemToTAITime <$> getSystemTime + +-- | Format a `DiffTime` nicely. +-- +-- Short durations are formatted like @16.34s@, durations longer than a minute +-- are formatted like @22:34.68@, durations longer than an hour are formatted +-- like @1:32:04.68@. +formatDiffTime :: DiffTime -> String +formatDiffTime delta = + let minute = secondsToDiffTime 60 + hour = 60 * minute + in if delta >= hour + then formatTime defaultTimeLocale "%h:%02M:%02ES" delta + else + if delta >= minute + then formatTime defaultTimeLocale "%m:%2ES" delta + else formatTime defaultTimeLocale "%2Ess" delta diff --git a/cabal-validate/src/Main.hs b/cabal-validate/src/Main.hs new file mode 100644 index 00000000000..428a8a7358d --- /dev/null +++ b/cabal-validate/src/Main.hs @@ -0,0 +1,425 @@ +-- | Entry-point to the @cabal-validate@ script. +-- +-- This module encodes all the commands that are run for each step in +-- `runStep`. +module Main + ( main + , runStep + ) where + +import Control.Monad (forM_) +import qualified Data.Text as T +import qualified Data.Text.IO as T +import qualified Data.Text.Lazy as T (toStrict) +import qualified Data.Text.Lazy.Encoding as T (decodeUtf8) +import Data.Version (makeVersion, showVersion) +import System.FilePath (()) +import System.Process.Typed (proc, readProcessStdout_) + +import ANSI (SGR (Bold, BrightCyan, Reset), setSGR) +import Cli (Compiler (..), HackageTests (..), Opts (..), parseOpts) +import ClockUtil (diffAbsoluteTime, formatDiffTime, getAbsoluteTime) +import OutputUtil (printHeader, withTiming) +import ProcessUtil (timed, timedWithCwd) +import Step (Step (..), displayStep) + +-- | Entry-point for @cabal-validate@. +main :: IO () +main = do + opts <- parseOpts + forM_ (steps opts) $ \step -> do + runStep opts step + +-- | Run a given `Step` with the given `Opts`. +runStep :: Opts -> Step -> IO () +runStep opts step = do + let title = displayStep step + printHeader title + let action = case step of + PrintConfig -> printConfig opts + PrintToolVersions -> printToolVersions opts + Build -> build opts + Doctest -> doctest opts + LibTests -> libTests opts + LibSuite -> libSuite opts + LibSuiteExtras -> libSuiteExtras opts + CliSuite -> cliSuite opts + CliTests -> cliTests opts + SolverBenchmarksTests -> solverBenchmarksTests opts + SolverBenchmarksRun -> solverBenchmarksRun opts + TimeSummary -> timeSummary opts + withTiming (startTime opts) title action + T.putStrLn "" + +-- | Compiler with version number like @ghc-9.6.6@. +baseHc :: Opts -> FilePath +baseHc opts = "ghc-" <> showVersion (compilerVersion $ compiler opts) + +-- | Base build directory for @cabal-validate@. +baseBuildDir :: Opts -> FilePath +baseBuildDir opts = "dist-newstyle-validate-" <> baseHc opts + +-- | Absolute path to the build directory for this architecture. +-- +-- This is a path nested fairly deeply under `baseBuildDir`. +buildDir :: Opts -> FilePath +buildDir opts = + cwd opts + baseBuildDir opts + "build" + archPath opts + baseHc opts + +-- | @--num-threads@ argument for test suites. +-- +-- This isn't always used because some test suites are finicky and only accept +-- @-j@. +jobsArgs :: Opts -> [String] +jobsArgs opts = ["--num-threads", show $ jobs opts] + +-- | Default arguments for invoking @cabal@. +cabalArgs :: Opts -> [String] +cabalArgs opts = + [ "--jobs=" <> show (jobs opts) + , "--with-compiler=" <> compilerExecutable (compiler opts) + , "--builddir=" <> baseBuildDir opts + , "--project-file=" <> projectFile opts + ] + +-- | The `buildDir` for @cabal-testsuite-3@. +cabalTestsuiteBuildDir :: Opts -> FilePath +cabalTestsuiteBuildDir opts = + buildDir opts + "cabal-testsuite-3" + +-- | Arguments for @cabal build@. +cabalNewBuildArgs :: Opts -> [String] +cabalNewBuildArgs opts = "build" : cabalArgs opts + +-- | Arguments for @cabal list-bin@. +-- +-- This is used to find the binaries for various test suites. +cabalListBinArgs :: Opts -> [String] +cabalListBinArgs opts = "list-bin" : cabalArgs opts + +-- | Get the binary for a given @cabal@ target by running @cabal list-bin@. +cabalListBin :: Opts -> String -> IO FilePath +cabalListBin opts target = do + let args = cabalListBinArgs opts ++ [target] + stdout <- + readProcessStdout_ $ + proc (cabal opts) args + + pure (T.unpack $ T.strip $ T.toStrict $ T.decodeUtf8 stdout) + +-- | Get the RTS arguments for invoking test suites. +-- +-- These seem to only be used for some of the test suites, I'm not sure why. +rtsArgs :: Opts -> [String] +rtsArgs opts = + case archPath opts of + "x86_64-windows" -> + -- See: https://github.com/haskell/cabal/issues/9571 + if compilerVersion (compiler opts) > makeVersion [9, 0, 2] + then ["+RTS", "--io-manager=native", "-RTS"] + else [] + _ -> [] + +-- | Run a binary built by @cabal@ and output timing information. +-- +-- This is used to run many of the test suites. +timedCabalBin :: Opts -> String -> String -> [String] -> IO () +timedCabalBin opts package component args = do + command <- cabalListBin opts (package <> ":" <> component) + timedWithCwd + opts + package + command + args + +-- | Print the configuration for CI logs. +printConfig :: Opts -> IO () +printConfig opts = do + putStr $ + unlines + [ "compiler: " + <> compilerExecutable (compiler opts) + , "cabal-install: " + <> cabal opts + , "jobs: " + <> show (jobs opts) + , "steps: " + <> unwords (map displayStep (steps opts)) + , "Hackage tests: " + <> show (hackageTests opts) + , "verbose: " + <> show (verbose opts) + , "extra compilers: " + <> unwords (extraCompilers opts) + , "extra RTS options: " + <> unwords (rtsArgs opts) + ] + +-- | Print the versions of tools being used. +printToolVersions :: Opts -> IO () +printToolVersions opts = do + timed opts (compilerExecutable (compiler opts)) ["--version"] + timed opts (cabal opts) ["--version"] + + forM_ (extraCompilers opts) $ \compiler' -> do + timed opts compiler' ["--version"] + +-- | Run the build step. +build :: Opts -> IO () +build opts = do + printHeader "build (dry run)" + timed + opts + (cabal opts) + ( cabalNewBuildArgs opts + ++ targets opts + ++ ["--dry-run"] + ) + + printHeader "build (full build plan; cached and to-be-built dependencies)" + timed + opts + "jq" + [ "-r" + , -- TODO: Maybe use `cabal-plan`? It's a heavy dependency though... + ".\"install-plan\" | map(.\"pkg-name\" + \"-\" + .\"pkg-version\" + \" \" + .\"component-name\") | join(\"\n\")" + , baseBuildDir opts "cache" "plan.json" + ] + + printHeader "build (actual build)" + timed + opts + (cabal opts) + (cabalNewBuildArgs opts ++ targets opts) + +-- | Run doctests. +-- +-- This doesn't work on my machine, maybe @cabal.nix@ needs some love to +-- support @cabal-env@? +doctest :: Opts -> IO () +doctest opts = do + timed + opts + "cabal-env" + [ "--name" + , "doctest-cabal" + , "--transitive" + , "QuickCheck" + ] + + timed + opts + "cabal-env" + [ "--name" + , "doctest-cabal" + , "array" + , "bytestring" + , "containers" + , "deepseq" + , "directory" + , "filepath" + , "pretty" + , "process" + , "time" + , "binary" + , "unix" + , "text" + , "parsec" + , "mtl" + ] + + timed + opts + "doctest" + [ "-package-env=doctest-Cabal" + , "--fast" + , "Cabal/Distribution" + , "Cabal/Language" + ] + +-- | Run tests for the @Cabal@ library, and also `runHackageTests` if those are +-- enabled. +libTests :: Opts -> IO () +libTests opts = do + let runCabalTests' suite extraArgs = + timedCabalBin + opts + "Cabal-tests" + ("test:" <> suite) + ( tastyArgs opts + ++ jobsArgs opts + ++ extraArgs + ) + + runCabalTests suite = runCabalTests' suite [] + + runCabalTests' "unit-tests" ["--with-ghc=" <> compilerExecutable (compiler opts)] + runCabalTests "check-tests" + runCabalTests "parser-tests" + runCabalTests "rpmvercmp" + runCabalTests "no-thunks-test" + + runHackageTests opts + +-- | Run Hackage tests, if enabled. +runHackageTests :: Opts -> IO () +runHackageTests opts + | NoHackageTests <- hackageTests opts = pure () + | otherwise = do + command <- cabalListBin opts "Cabal-tests:test:hackage-tests" + + let + -- See #10284 for why this value is pinned. + hackageTestsIndexState = "--index-state=2024-08-25" + + hackageTest args = + timedWithCwd + opts + "Cabal-tests" + command + (args ++ [hackageTestsIndexState]) + + hackageTest ["read-fields"] + + case hackageTests opts of + CompleteHackageTests -> do + hackageTest ["parsec"] + hackageTest ["roundtrip"] + PartialHackageTests -> do + hackageTest ["parsec", "d"] + hackageTest ["roundtrip", "k"] + +-- | Run @cabal-testsuite@ with the @Cabal@ library with a non-default GHC. +libSuiteWith :: Opts -> FilePath -> [String] -> IO () +libSuiteWith opts ghc extraArgs = + timedCabalBin + opts + "cabal-testsuite" + "exe:cabal-tests" + ( [ "--builddir=" <> cabalTestsuiteBuildDir opts + , "--with-ghc=" <> ghc + , -- This test suite doesn't support `--jobs` _or_ `--num-threads`! + "-j" <> show (jobs opts) + ] + ++ tastyArgs opts + ++ extraArgs + ) + +-- | Run @cabal-testsuite@ with the @Cabal@ library with the default GHC. +libSuite :: Opts -> IO () +libSuite opts = libSuiteWith opts (compilerExecutable (compiler opts)) (rtsArgs opts) + +-- | Run @cabal-testsuite@ with the @Cabal@ library with all extra GHCs. +libSuiteExtras :: Opts -> IO () +libSuiteExtras opts = forM_ (extraCompilers opts) $ \compiler' -> + libSuiteWith opts compiler' [] + +-- | Test the @cabal-install@ executable. +-- +-- These tests mostly run sequentially, so they're pretty slow as a result. +cliTests :: Opts -> IO () +cliTests opts = do + -- These are sorted in asc time used, quicker tests first. + timedCabalBin + opts + "cabal-install" + "test:long-tests" + ( jobsArgs opts + ++ tastyArgs opts + ) + + -- This doesn't work in parallel either. + timedCabalBin + opts + "cabal-install" + "test:unit-tests" + ( ["--num-threads", "1"] + ++ tastyArgs opts + ) + + -- Only single job, otherwise we fail with "Heap exhausted" + timedCabalBin + opts + "cabal-install" + "test:mem-use-tests" + ( ["--num-threads", "1"] + ++ tastyArgs opts + ) + + -- This test-suite doesn't like concurrency + timedCabalBin + opts + "cabal-install" + "test:integration-tests2" + ( [ "--num-threads" + , "1" + , "--with-ghc=" <> compilerExecutable (compiler opts) + ] + ++ tastyArgs opts + ) + +-- | Run @cabal-testsuite@ with the @cabal-install@ executable. +cliSuite :: Opts -> IO () +cliSuite opts = do + cabal' <- cabalListBin opts "cabal-install:exe:cabal" + + timedCabalBin + opts + "cabal-testsuite" + "exe:cabal-tests" + ( [ "--builddir=" <> cabalTestsuiteBuildDir opts + , "--with-cabal=" <> cabal' + , "--with-ghc=" <> compilerExecutable (compiler opts) + , "--intree-cabal-lib=" <> cwd opts + , "--test-tmp=" <> cwd opts "testdb" + , -- This test suite doesn't support `--jobs` _or_ `--num-threads`! + "-j" + , show (jobs opts) + ] + ++ tastyArgs opts + ++ rtsArgs opts + ) + +-- | Run the @solver-benchmarks@ unit tests. +solverBenchmarksTests :: Opts -> IO () +solverBenchmarksTests opts = do + command <- cabalListBin opts "solver-benchmarks:test:unit-tests" + + timedWithCwd + opts + "Cabal" + command + [] + +-- | Run the @solver-benchmarks@. +solverBenchmarksRun :: Opts -> IO () +solverBenchmarksRun opts = do + command <- cabalListBin opts "solver-benchmarks:exe:hackage-benchmark" + cabal' <- cabalListBin opts "cabal-install:exe:cabal" + + timedWithCwd + opts + "Cabal" + command + [ "--cabal1=" <> cabal opts + , "--cabal2=" <> cabal' + , "--trials=5" + , "--packages=Chart-diagrams" + , "--print-trials" + ] + +-- | Print the total time taken so far. +timeSummary :: Opts -> IO () +timeSummary opts = do + endTime <- getAbsoluteTime + let totalDuration = diffAbsoluteTime endTime (startTime opts) + putStrLn $ + setSGR [Bold, BrightCyan] + <> "!!! Validation completed in " + <> formatDiffTime totalDuration + <> setSGR [Reset] diff --git a/cabal-validate/src/OutputUtil.hs b/cabal-validate/src/OutputUtil.hs new file mode 100644 index 00000000000..576c6180433 --- /dev/null +++ b/cabal-validate/src/OutputUtil.hs @@ -0,0 +1,86 @@ +-- | Utilities for printing terminal output. +module OutputUtil + ( printHeader + , withTiming + ) where + +import Control.Exception (catch) +import qualified System.Console.Terminal.Size as Terminal +import System.Process.Typed (ExitCodeException) + +import ANSI (SGR (Bold, BrightCyan, BrightGreen, BrightRed, Reset), setSGR) +import ClockUtil (AbsoluteTime, diffAbsoluteTime, formatDiffTime, getAbsoluteTime) +import System.Exit (exitFailure) + +-- | Get the width of the current terminal, or 80 if no width can be determined. +getTerminalWidth :: IO Int +getTerminalWidth = maybe 80 Terminal.width <$> Terminal.size @Int + +-- | Print a header for a given step. +-- +-- This is colorful and hard to miss in the output. +printHeader + :: String + -- ^ Title to print. + -> IO () +printHeader title = do + columns <- getTerminalWidth + let left = 3 + right = columns - length title - left - 2 + header = + setSGR [Bold, BrightCyan] + <> replicate left '═' + <> " " + <> title + <> " " + <> replicate right '═' + <> setSGR [Reset] + putStrLn header + +-- | Run an `IO` action and print duration information after it finishes. +withTiming + :: AbsoluteTime + -- ^ Start time for the whole @cabal-validate@ run. + -> String + -- ^ Name for describing the action. + -- + -- Used in a sentence like "@title@ finished after 16.34s". + -> IO a + -- ^ Action to time. + -> IO a +withTiming startTime title action = do + startTime' <- getAbsoluteTime + + result <- + (Right <$> action) + `catch` (\exception -> pure (Left (exception :: ExitCodeException))) + + endTime <- getAbsoluteTime + + let duration = diffAbsoluteTime endTime startTime' + totalDuration = diffAbsoluteTime endTime startTime + + case result of + Right inner -> do + putStrLn $ + setSGR [Bold, BrightGreen] + <> title + <> " finished after " + <> formatDiffTime duration + <> "\nTotal time so far: " + <> formatDiffTime totalDuration + <> setSGR [Reset] + + pure inner + Left _procFailed -> do + putStrLn $ + setSGR [Bold, BrightRed] + <> title + <> " failed after " + <> formatDiffTime duration + <> "\nTotal time so far: " + <> formatDiffTime totalDuration + <> setSGR [Reset] + + -- TODO: `--keep-going` mode. + exitFailure diff --git a/cabal-validate/src/ProcessUtil.hs b/cabal-validate/src/ProcessUtil.hs new file mode 100644 index 00000000000..3e27f5517a1 --- /dev/null +++ b/cabal-validate/src/ProcessUtil.hs @@ -0,0 +1,137 @@ +-- | Utilities for running processes and timing them. +module ProcessUtil + ( timed + , timedWithCwd + ) where + +import Control.Exception (throwIO) +import Control.Monad (unless) +import Data.ByteString.Lazy (ByteString) +import qualified Data.ByteString.Lazy as ByteString +import Data.Text (Text) +import qualified Data.Text as T +import qualified Data.Text.IO as T +import qualified Data.Text.Lazy as T (toStrict) +import qualified Data.Text.Lazy.Encoding as T (decodeUtf8) +import System.Directory (withCurrentDirectory) +import System.Exit (ExitCode (ExitFailure, ExitSuccess)) +import System.Process.Typed (ExitCodeException (..), proc, readProcess, runProcess) + +import ANSI (SGR (BrightBlue, BrightGreen, BrightRed, Reset), setSGR) +import Cli (Opts (..)) +import ClockUtil (diffAbsoluteTime, formatDiffTime, getAbsoluteTime) + +-- | Like `timed`, but runs the command in a given directory. +timedWithCwd + :: Opts + -- ^ @cabal-validate@ options. + -> FilePath + -- ^ Path to run the command in. + -> FilePath + -- ^ The command to run. + -> [String] + -- ^ Arguments to pass to the command. + -> IO () +timedWithCwd opts cdPath command args = + withCurrentDirectory cdPath (timed opts command args) + +-- | Run a command, displaying timing information after it finishes. +-- +-- This prints out the command to be executed before it's run, handles hiding +-- or showing output (according to the value of `verbose`), and throws an +-- `ExitCodeException` if the command fails. +timed + :: Opts + -- ^ @cabal-validate@ options. + -> FilePath + -- ^ The command to run. + -> [String] + -- ^ Arguments to pass to the command. + -> IO () +timed opts command args = do + let prettyCommand = displayCommand command args + process = proc command args + + startTime' <- getAbsoluteTime + + -- TODO: Replace `$HOME` or `opts.cwd` for brevity? + putStrLn $ + setSGR [BrightBlue] + <> "$ " + <> prettyCommand + <> setSGR [Reset] + + (exitCode, rawStdout, rawStderr) <- + if verbose opts + then do + exitCode <- runProcess process + pure (exitCode, ByteString.empty, ByteString.empty) + else readProcess process + + endTime <- getAbsoluteTime + + let duration = diffAbsoluteTime endTime startTime' + totalDuration = diffAbsoluteTime endTime (startTime opts) + + output = decodeStrip rawStdout <> "\n" <> decodeStrip rawStderr + linesLimit = 50 + outputLines = T.lines output + hiddenLines = length outputLines - linesLimit + tailLines = drop hiddenLines outputLines + + case exitCode of + ExitSuccess -> do + unless (verbose opts) $ do + if hiddenLines <= 0 + then T.putStrLn output + else + T.putStrLn $ + "(" + <> T.pack (show hiddenLines) + <> " lines hidden, use `--verbose` to show)\n" + <> "...\n" + <> T.unlines tailLines + + putStrLn $ + setSGR [BrightGreen] + <> "Finished after " + <> formatDiffTime duration + <> ": " + <> prettyCommand + <> "\nTotal time so far: " + <> formatDiffTime totalDuration + <> setSGR [Reset] + ExitFailure exitCode' -> do + unless (verbose opts) $ do + T.putStrLn output + + putStrLn $ + setSGR [BrightRed] + <> "Failed with exit code " + <> show exitCode' + <> " after " + <> formatDiffTime duration + <> ": " + <> prettyCommand + <> "\nTotal time so far: " + <> formatDiffTime totalDuration + <> setSGR [Reset] + + throwIO + ExitCodeException + { eceExitCode = exitCode + , eceProcessConfig = process + , eceStdout = rawStdout + , eceStderr = rawStderr + } + +-- | Decode `ByteString` output from a command and strip whitespace at the +-- start and end. +decodeStrip :: ByteString -> Text +decodeStrip = T.strip . T.toStrict . T.decodeUtf8 + +-- | Escape a shell command to display it to a user. +-- +-- TODO: Shell escaping +displayCommand :: String -> [String] -> String +displayCommand command args = command <> " " <> unwords args diff --git a/cabal-validate/src/Step.hs b/cabal-validate/src/Step.hs new file mode 100644 index 00000000000..2636f483a79 --- /dev/null +++ b/cabal-validate/src/Step.hs @@ -0,0 +1,62 @@ +-- | The steps that can be run by @cabal-validate@. +module Step + ( Step (..) + , displayStep + , nameToStep + , parseStep + ) where + +import Data.Map.Strict (Map) +import qualified Data.Map.Strict as Map + +-- | A step to be run by @cabal-validate@. +data Step + = PrintConfig + | PrintToolVersions + | Build + | Doctest + | LibTests + | LibSuite + | LibSuiteExtras + | CliTests + | CliSuite + | SolverBenchmarksTests + | SolverBenchmarksRun + | TimeSummary + deriving (Eq, Enum, Bounded, Show) + +-- | Get the display identifier for a given `Step`. +-- +-- This is used to parse the @--step@ command-line argument. +-- +-- Note that these names are just kebab-case variants of the `Step` constructor +-- names; they do not attempt to describe the steps. +displayStep :: Step -> String +displayStep step = + case step of + PrintConfig -> "print-config" + PrintToolVersions -> "print-tool-versions" + Build -> "build" + Doctest -> "doctest" + LibTests -> "lib-tests" + LibSuite -> "lib-suite" + LibSuiteExtras -> "lib-suite-extras" + CliTests -> "cli-tests" + CliSuite -> "cli-suite" + SolverBenchmarksTests -> "solver-benchmarks-tests" + SolverBenchmarksRun -> "solver-benchmarks-run" + TimeSummary -> "time-summary" + +-- | A map from step names to `Steps`. +-- +-- This is an inverse of `displayStep`. +nameToStep :: Map String Step +nameToStep = + Map.fromList + [ (displayStep step, step) + | step <- [minBound .. maxBound] + ] + +-- | Parse a string as a `Step`. +parseStep :: String -> Maybe Step +parseStep step = Map.lookup step nameToStep diff --git a/changelog.d/i10418 b/changelog.d/i10418 new file mode 100644 index 00000000000..9a96e47a1e9 --- /dev/null +++ b/changelog.d/i10418 @@ -0,0 +1,13 @@ +synopsis: Fix build ways for modules in executables +packages: Cabal +prs: #10419 +issues: #10418 +significance: significant + +description: { + +- Modules belonging to executables were being built in too many ways. For instance, if you +had configured to build profiled library files then your executable modules would also +be built profiled. Which was a regression in behaviour since `Cabal-3.12`. + +} diff --git a/changelog.d/pr-10273 b/changelog.d/pr-10273 new file mode 100644 index 00000000000..343c871377b --- /dev/null +++ b/changelog.d/pr-10273 @@ -0,0 +1,19 @@ +--- +synopsis: "Show why `cabal act-as-setup configure` failed" +packages: [Cabal] +prs: 10273 +--- + +When `cabal act-as-setup configure` fails, it prints a list of "missing or +private dependencies". + +Now, it will show you if each failing dependency is missing, private, or an +incompatible version: + +``` +Error: [Cabal-8010] +Encountered missing or private dependencies: + Lib:{bar-internal,foo-internal} (missing :bar-internal), + base <=1.0 (installed: 4.18.2.1), + package-that-does-not-exist (missing) +``` diff --git a/changelog.d/t10416 b/changelog.d/t10416 new file mode 100644 index 00000000000..071b9b1ad95 --- /dev/null +++ b/changelog.d/t10416 @@ -0,0 +1,11 @@ +synopsis: Fix ./setup install command +packages: Cabal +prs: #10417 +issues: #10416 +significance: significant + +description: { + +- `./setup install` was failing with a `fromFlag NoFlag` error. It is now fixed. + +} diff --git a/project-cabal/pkgs/tests.config b/project-cabal/pkgs/tests.config index a9cec9c596f..75fe4af5ad7 100644 --- a/project-cabal/pkgs/tests.config +++ b/project-cabal/pkgs/tests.config @@ -2,3 +2,4 @@ packages: Cabal-QuickCheck , Cabal-tests , Cabal-tree-diff + , cabal-validate diff --git a/validate.sh b/validate.sh index b22e033f86e..b887b724e8f 100755 --- a/validate.sh +++ b/validate.sh @@ -1,554 +1,3 @@ -#!/usr/bin/env bash -# shellcheck disable=SC2086 +#!/usr/bin/env sh -# default config -####################################################################### - -# We use the default ghc in PATH as default -# Use the ghc-x.y.z trigger several errors in windows: -# * It triggers the max path length issue: -# See https://github.com/haskell/cabal/issues/6271#issuecomment-1065102255 -# * It triggers a `createProcess: does not exist` error in units tests -# See https://github.com/haskell/cabal/issues/8049 -HC=ghc -CABAL=cabal -JOBS="" -LIBTESTS=true -CLITESTS=true -CABALSUITETESTS=true -LIBONLY=false -DEPSONLY=false -DOCTEST=false -BENCHMARKS=false -VERBOSE=false -HACKAGETESTSALL=false - -TARGETS="" -STEPS="" -EXTRAHCS="" - -LISTSTEPS=false - -# Help -####################################################################### - -show_usage() { -cat <&1 - else - "$@" > "$OUTPUT" 2>&1 - fi - # echo "MOCK" > "$OUTPUT" - RET=$? - - end_time=$(date +%s) - duration=$((end_time - start_time)) - tduration=$((end_time - JOB_START_TIME)) - - if [ $RET -eq 0 ]; then - if ! $VERBOSE; then - # if output is relatively short, show everything - if [ "$(wc -l < "$OUTPUT")" -le 50 ]; then - cat "$OUTPUT" - else - echo "..." - tail -n 20 "$OUTPUT" - fi - - rm -f "$OUTPUT" - fi - - green "<<< $PRETTYCMD" "($duration/$tduration sec)" - - # bottom-margin - echo "" - else - if ! $VERBOSE; then - cat "$OUTPUT" - fi - - red "<<< $PRETTYCMD" "($duration/$tduration sec, $RET)" - red "<<< $*" "($duration/$tduration sec, $RET)" - rm -f "$OUTPUT" - exit 1 - fi -} - -print_header() { - TITLE=$1 - TITLEPAT="$(echo "$TITLE"|sed 's:.:=:g')" - cyan "===X========================================================================== $(date +%T) ===" \ - | sed "s#X$TITLEPAT=# $TITLE #" - -} - -# getopt -####################################################################### - -while [ $# -gt 0 ]; do - arg=$1 - case $arg in - --help) - show_usage - exit - ;; - -j|--jobs) - JOBS="$2" - shift - shift - ;; - --lib-only) - LIBONLY=true - shift - ;; - --cli) - LIBONLY=false - shift - ;; - --run-lib-tests) - LIBTESTS=true - shift - ;; - --no-run-lib-tests) - LIBTESTS=false - shift - ;; - --run-cli-tests) - CLITESTS=true - shift - ;; - --no-run-cli-tests) - CLITESTS=false - shift - ;; - --run-lib-suite) - LIBSUITE=true - shift - ;; - --no-run-lib-suite) - LIBSUITE=false - shift - ;; - --run-cli-suite) - CLISUITE=true - shift - ;; - --no-run-cli-suite) - CLISUITE=false - shift - ;; - -w|--with-compiler) - HC=$2 - shift - shift - ;; - --with-cabal) - CABAL=$2 - shift - shift - ;; - --extra-hc) - EXTRAHCS="$EXTRAHCS $2" - shift - shift - ;; - --doctest) - DOCTEST=true - shift - ;; - --no-doctest) - DOCTEST=false - shift - ;; - --solver-benchmarks) - BENCHMARKS=true - shift - ;; - --no-solver-benchmarks) - BENCHMARKS=false - shift - ;; - --complete-hackage-tests) - HACKAGETESTSALL=true - shift - ;; - --partial-hackage-tests) - HACKAGETESTSALL=false - shift - ;; - -v|--verbose) - VERBOSE=true - shift - ;; - -q|--quiet) - VERBOSE=false - shift - ;; - -s|--step) - STEPS="$STEPS $2" - shift - shift - ;; - --list-steps) - LISTSTEPS=true - shift - ;; - *) - echo "Unknown option $arg" - exit 1 - esac -done - -# calculate steps and build targets -####################################################################### - -# If there are no explicit steps given calculate them -if $LIBONLY; then - CLITESTS=false - CLISUITE=false - BENCHMARKS=false -fi - -if [ -z "$STEPS" ]; then - STEPS="print-config print-tool-versions" - STEPS="$STEPS build" - if $DOCTEST; then STEPS="$STEPS doctest"; fi - if $LIBTESTS; then STEPS="$STEPS lib-tests"; fi - if $LIBSUITE; then STEPS="$STEPS lib-suite"; fi - if $LIBSUITE && [ -n "$EXTRAHCS" ]; - then STEPS="$STEPS lib-suite-extras"; fi - if $CLITESTS; then STEPS="$STEPS cli-tests"; fi - if $CLISUITE; then STEPS="$STEPS cli-suite"; fi - if $BENCHMARKS; then STEPS="$STEPS solver-benchmarks-tests solver-benchmarks-run"; fi - STEPS="$STEPS time-summary" -fi - -TARGETS="Cabal Cabal-hooks cabal-testsuite Cabal-tests Cabal-QuickCheck Cabal-tree-diff Cabal-described" -if ! $LIBONLY; then TARGETS="$TARGETS cabal-install cabal-install-solver cabal-benchmarks"; fi -if $BENCHMARKS; then TARGETS="$TARGETS solver-benchmarks"; fi - -if $LISTSTEPS; then - echo "Targets: $TARGETS" - echo "Steps: $STEPS" - exit -fi - -# Adjust runtime configuration -####################################################################### - -if [ -z "$JOBS" ]; then - if command -v nproc >/dev/null; then - JOBS=$(nproc) - else - echo "Warning: \`nproc\` not found, setting \`--jobs\` to default of 4." - JOBS=4 - fi -fi - -TESTSUITEJOBS="-j$JOBS" -JOBS="-j$JOBS" - -# assume compiler is GHC -RUNHASKELL=$(echo "$HC" | sed -E 's/ghc(-[0-9.]*)$/runghc\1/') - -ARCH=$(uname -m) - -case "$ARCH" in - arm64) - ARCH=aarch64 - ;; - x86_64) - ARCH=x86_64 - ;; - *) - echo "Warning: Unknown architecture '$ARCH'" - ;; -esac - -OS=$(uname) - -case "$OS" in - MINGW64*) - ARCH="$ARCH-windows" - ;; - Linux) - ARCH="$ARCH-linux" - ;; - Darwin) - ARCH="$ARCH-osx" - ;; - *) - echo "Warning: Unknown operating system '$OS'" - ARCH="$ARCH-$OS" - ;; -esac - -if $LIBONLY; then - PROJECTFILE=cabal.validate-libonly.project -else - PROJECTFILE=cabal.validate.project -fi - -BASEHC=ghc-$($HC --numeric-version) -BUILDDIR=dist-newstyle-validate-$BASEHC -CABAL_TESTSUITE_BDIR="$(pwd)/$BUILDDIR/build/$ARCH/$BASEHC/cabal-testsuite-3" - -CABALNEWBUILD="${CABAL} build $JOBS -w $HC --builddir=$BUILDDIR --project-file=$PROJECTFILE" -CABALLISTBIN="${CABAL} list-bin --builddir=$BUILDDIR --project-file=$PROJECTFILE" - -# See https://github.com/haskell/cabal/issues/9571 for why we set this for Windows -RTSOPTS="$([ $ARCH = "x86_64-windows" ] && [ "$($HC --numeric-version)" != "9.0.2" ] && [ "$(echo -e "$(ghc --numeric-version)\n9.0.2" | sort -V | head -n1)" = "9.0.2" ] && echo "+RTS --io-manager=native" || echo "")" - -# header -####################################################################### - -step_print_config() { -print_header print-config - -cat <