hackport 0.4.7 → 0.5
raw patch · 409 files changed
+37590/−11314 lines, 409 filesdep +base64-bytestringdep +cryptohashdep +ed25519dep ~basesetup-changed
Dependencies added: base64-bytestring, cryptohash, ed25519, ghc-prim, hashable, mtl, split, template-haskell, transformers
Dependency ranges changed: base
Files
- .gitmodules +3/−0
- Cabal2Ebuild.hs +11/−6
- CacheFile.hs +0/−12
- Diff.hs +0/−188
- DistroMap.hs +0/−158
- HackPort/GlobalFlags.hs +47/−0
- Hackage.hs +0/−35
- Main-GuessGHC.hs +0/−27
- Main.hs +55/−204
- Merge.hs +10/−20
- Merge/Dependencies.hs +15/−7
- Overlays.hs +4/−6
- Portage/EBuild.hs +3/−29
- Portage/GHCCore.hs +1/−2
- Portage/Host.hs +2/−8
- Portage/PackageId.hs +1/−6
- Progress.hs +0/−61
- README.rst +1/−0
- Setup.hs +1/−1
- Status.hs +12/−14
- Util.hs +0/−8
- cabal/.arcconfig +4/−0
- cabal/.gitignore +11/−0
- cabal/.travis.yml +17/−50
- cabal/Cabal/Cabal.cabal +127/−57
- cabal/Cabal/Distribution/Compat/Binary/Class.hs +1/−13
- cabal/Cabal/Distribution/Compat/CopyFile.hs +6/−9
- cabal/Cabal/Distribution/Compat/Internal/TempFile.hs +124/−0
- cabal/Cabal/Distribution/Compat/MonadFail.hs +36/−0
- cabal/Cabal/Distribution/Compat/ReadP.hs +23/−19
- cabal/Cabal/Distribution/Compat/Semigroup.hs +171/−0
- cabal/Cabal/Distribution/Compat/TempFile.hs +0/−128
- cabal/Cabal/Distribution/Compiler.hs +4/−4
- cabal/Cabal/Distribution/InstalledPackageInfo.hs +41/−88
- cabal/Cabal/Distribution/Lex.hs +5/−5
- cabal/Cabal/Distribution/License.hs +4/−4
- cabal/Cabal/Distribution/Make.hs +4/−7
- cabal/Cabal/Distribution/ModuleName.hs +6/−3
- cabal/Cabal/Distribution/Package.hs +90/−192
- cabal/Cabal/Distribution/PackageDescription.hs +206/−151
- cabal/Cabal/Distribution/PackageDescription/Check.hs +77/−82
- cabal/Cabal/Distribution/PackageDescription/Configuration.hs +250/−108
- cabal/Cabal/Distribution/PackageDescription/Parse.hs +111/−122
- cabal/Cabal/Distribution/PackageDescription/PrettyPrint.hs +17/−26
- cabal/Cabal/Distribution/ParseUtils.hs +11/−16
- cabal/Cabal/Distribution/Simple.hs +50/−77
- cabal/Cabal/Distribution/Simple/Bench.hs +11/−16
- cabal/Cabal/Distribution/Simple/Build.hs +103/−168
- cabal/Cabal/Distribution/Simple/Build/Macros.hs +24/−26
- cabal/Cabal/Distribution/Simple/Build/PathsModule.hs +9/−17
- cabal/Cabal/Distribution/Simple/BuildPaths.hs +14/−19
- cabal/Cabal/Distribution/Simple/BuildTarget.hs +99/−49
- cabal/Cabal/Distribution/Simple/CCompiler.hs +11/−13
- cabal/Cabal/Distribution/Simple/Command.hs +25/−12
- cabal/Cabal/Distribution/Simple/Compiler.hs +26/−6
- cabal/Cabal/Distribution/Simple/Configure.hs +2131/−1752
- cabal/Cabal/Distribution/Simple/GHC.hs +249/−152
- cabal/Cabal/Distribution/Simple/GHC/IPI641.hs +0/−106
- cabal/Cabal/Distribution/Simple/GHC/IPI642.hs +7/−47
- cabal/Cabal/Distribution/Simple/GHC/IPIConvert.hs +50/−0
- cabal/Cabal/Distribution/Simple/GHC/ImplInfo.hs +4/−36
- cabal/Cabal/Distribution/Simple/GHC/Internal.hs +72/−164
- cabal/Cabal/Distribution/Simple/GHCJS.hs +65/−88
- cabal/Cabal/Distribution/Simple/Haddock.hs +101/−134
- cabal/Cabal/Distribution/Simple/HaskellSuite.hs +10/−16
- cabal/Cabal/Distribution/Simple/Install.hs +105/−71
- cabal/Cabal/Distribution/Simple/InstallDirs.hs +22/−56
- cabal/Cabal/Distribution/Simple/JHC.hs +19/−27
- cabal/Cabal/Distribution/Simple/LHC.hs +36/−64
- cabal/Cabal/Distribution/Simple/LocalBuildInfo.hs +267/−148
- cabal/Cabal/Distribution/Simple/PackageIndex.hs +135/−80
- cabal/Cabal/Distribution/Simple/PreProcess.hs +42/−59
- cabal/Cabal/Distribution/Simple/Program.hs +8/−6
- cabal/Cabal/Distribution/Simple/Program/Ar.hs +3/−1
- cabal/Cabal/Distribution/Simple/Program/Builtin.hs +5/−16
- cabal/Cabal/Distribution/Simple/Program/Db.hs +38/−34
- cabal/Cabal/Distribution/Simple/Program/Find.hs +103/−44
- cabal/Cabal/Distribution/Simple/Program/GHC.hs +61/−168
- cabal/Cabal/Distribution/Simple/Program/HcPkg.hs +167/−77
- cabal/Cabal/Distribution/Simple/Program/Hpc.hs +6/−7
- cabal/Cabal/Distribution/Simple/Program/Run.hs +1/−6
- cabal/Cabal/Distribution/Simple/Program/Script.hs +0/−2
- cabal/Cabal/Distribution/Simple/Program/Strip.hs +7/−9
- cabal/Cabal/Distribution/Simple/Program/Types.hs +20/−9
- cabal/Cabal/Distribution/Simple/Register.hs +119/−112
- cabal/Cabal/Distribution/Simple/Setup.hs +240/−382
- cabal/Cabal/Distribution/Simple/SrcDist.hs +23/−37
- cabal/Cabal/Distribution/Simple/Test.hs +7/−13
- cabal/Cabal/Distribution/Simple/Test/ExeV10.hs +25/−26
- cabal/Cabal/Distribution/Simple/Test/LibV09.hs +31/−33
- cabal/Cabal/Distribution/Simple/Test/Log.hs +10/−12
- cabal/Cabal/Distribution/Simple/UHC.hs +41/−30
- cabal/Cabal/Distribution/Simple/UserHooks.hs +5/−10
- cabal/Cabal/Distribution/Simple/Utils.hs +115/−57
- cabal/Cabal/Distribution/System.hs +33/−11
- cabal/Cabal/Distribution/Text.hs +18/−14
- cabal/Cabal/Distribution/Utils/NubList.hs +10/−8
- cabal/Cabal/Distribution/Verbosity.hs +3/−2
- cabal/Cabal/Distribution/Version.hs +66/−33
- cabal/Cabal/Language/Haskell/Extension.hs +54/−2
- cabal/Cabal/Makefile +10/−4
- cabal/Cabal/Setup.hs +3/−0
- cabal/Cabal/changelog +36/−3
- cabal/Cabal/doc/developing-packages.markdown +92/−28
- cabal/Cabal/doc/index.markdown +1/−0
- cabal/Cabal/doc/installing-packages.markdown +167/−2
- cabal/Cabal/misc/gen-extra-source-files.sh +20/−3
- cabal/Cabal/misc/travis-diff-files.sh +3/−0
- cabal/HACKING.md +15/−3
- cabal/Paths_Cabal.hs +1/−1
- cabal/Paths_cabal_install.hs +1/−1
- cabal/README.md +1/−1
- cabal/appveyor.yml +28/−0
- cabal/cabal-install/Distribution/Client/BuildReports/Anonymous.hs +0/−1
- cabal/cabal-install/Distribution/Client/BuildReports/Storage.hs +5/−3
- cabal/cabal-install/Distribution/Client/BuildReports/Types.hs +7/−1
- cabal/cabal-install/Distribution/Client/BuildReports/Upload.hs +12/−10
- cabal/cabal-install/Distribution/Client/Compat/Time.hs +102/−77
- cabal/cabal-install/Distribution/Client/ComponentDeps.hs +45/−25
- cabal/cabal-install/Distribution/Client/Config.hs +158/−76
- cabal/cabal-install/Distribution/Client/Configure.hs +42/−39
- cabal/cabal-install/Distribution/Client/Dependency.hs +118/−116
- cabal/cabal-install/Distribution/Client/Dependency/Modular.hs +5/−8
- cabal/cabal-install/Distribution/Client/Dependency/Modular/Assignment.hs +34/−43
- cabal/cabal-install/Distribution/Client/Dependency/Modular/Builder.hs +18/−3
- cabal/cabal-install/Distribution/Client/Dependency/Modular/Configured.hs +3/−1
- cabal/cabal-install/Distribution/Client/Dependency/Modular/ConfiguredConversion.hs +17/−8
- cabal/cabal-install/Distribution/Client/Dependency/Modular/Cycles.hs +73/−0
- cabal/cabal-install/Distribution/Client/Dependency/Modular/Dependency.hs +74/−91
- cabal/cabal-install/Distribution/Client/Dependency/Modular/Explore.hs +55/−116
- cabal/cabal-install/Distribution/Client/Dependency/Modular/Flag.hs +15/−5
- cabal/cabal-install/Distribution/Client/Dependency/Modular/Index.hs +6/−1
- cabal/cabal-install/Distribution/Client/Dependency/Modular/IndexConversion.hs +153/−56
- cabal/cabal-install/Distribution/Client/Dependency/Modular/Linking.hs +6/−0
- cabal/cabal-install/Distribution/Client/Dependency/Modular/Log.hs +72/−90
- cabal/cabal-install/Distribution/Client/Dependency/Modular/Message.hs +59/−23
- cabal/cabal-install/Distribution/Client/Dependency/Modular/PSQ.hs +135/−19
- cabal/cabal-install/Distribution/Client/Dependency/Modular/Package.hs +90/−40
- cabal/cabal-install/Distribution/Client/Dependency/Modular/Preference.hs +93/−76
- cabal/cabal-install/Distribution/Client/Dependency/Modular/Solver.hs +49/−14
- cabal/cabal-install/Distribution/Client/Dependency/Modular/Tree.hs +36/−18
- cabal/cabal-install/Distribution/Client/Dependency/Modular/Validate.hs +35/−9
- cabal/cabal-install/Distribution/Client/Dependency/Modular/Version.hs +16/−5
- cabal/cabal-install/Distribution/Client/Dependency/TopDown.hs +30/−24
- cabal/cabal-install/Distribution/Client/Dependency/TopDown/Types.hs +13/−12
- cabal/cabal-install/Distribution/Client/Dependency/Types.hs +46/−47
- cabal/cabal-install/Distribution/Client/DistDirLayout.hs +134/−0
- cabal/cabal-install/Distribution/Client/Exec.hs +8/−13
- cabal/cabal-install/Distribution/Client/Fetch.hs +21/−21
- cabal/cabal-install/Distribution/Client/FetchUtils.hs +34/−18
- cabal/cabal-install/Distribution/Client/FileMonitor.hs +1101/−0
- cabal/cabal-install/Distribution/Client/Freeze.hs +57/−41
- cabal/cabal-install/Distribution/Client/GenBounds.hs +159/−0
- cabal/cabal-install/Distribution/Client/Get.hs +12/−16
- cabal/cabal-install/Distribution/Client/Glob.hs +276/−0
- cabal/cabal-install/Distribution/Client/GlobalFlags.hs +260/−0
- cabal/cabal-install/Distribution/Client/Haddock.hs +2/−3
- cabal/cabal-install/Distribution/Client/HttpUtils.hs +200/−96
- cabal/cabal-install/Distribution/Client/IndexUtils.hs +241/−153
- cabal/cabal-install/Distribution/Client/Init.hs +19/−10
- cabal/cabal-install/Distribution/Client/Init/Types.hs +10/−62
- cabal/cabal-install/Distribution/Client/Install.hs +206/−170
- cabal/cabal-install/Distribution/Client/InstallPlan.hs +278/−104
- cabal/cabal-install/Distribution/Client/InstallSymlink.hs +16/−22
- cabal/cabal-install/Distribution/Client/List.hs +28/−29
- cabal/cabal-install/Distribution/Client/Manpage.hs +171/−0
- cabal/cabal-install/Distribution/Client/PackageHash.hs +226/−0
- cabal/cabal-install/Distribution/Client/PackageIndex.hs +12/−2
- cabal/cabal-install/Distribution/Client/PackageUtils.hs +7/−5
- cabal/cabal-install/Distribution/Client/ParseUtils.hs +229/−12
- cabal/cabal-install/Distribution/Client/PkgConfigDb.hs +103/−0
- cabal/cabal-install/Distribution/Client/PlanIndex.hs +65/−115
- cabal/cabal-install/Distribution/Client/ProjectBuilding.hs +1284/−0
- cabal/cabal-install/Distribution/Client/ProjectConfig.hs +676/−0
- cabal/cabal-install/Distribution/Client/ProjectConfig/Legacy.hs +1251/−0
- cabal/cabal-install/Distribution/Client/ProjectConfig/Types.hs +333/−0
- cabal/cabal-install/Distribution/Client/ProjectPlanning.hs +2333/−0
- cabal/cabal-install/Distribution/Client/RebuildMonad.hs +135/−0
- cabal/cabal-install/Distribution/Client/Sandbox.hs +119/−26
- cabal/cabal-install/Distribution/Client/Sandbox/Index.hs +87/−48
- cabal/cabal-install/Distribution/Client/Sandbox/PackageEnvironment.hs +26/−30
- cabal/cabal-install/Distribution/Client/Sandbox/Timestamp.hs +48/−19
- cabal/cabal-install/Distribution/Client/Sandbox/Types.hs +10/−7
- cabal/cabal-install/Distribution/Client/Security/HTTP.hs +174/−0
- cabal/cabal-install/Distribution/Client/Setup.hs +210/−339
- cabal/cabal-install/Distribution/Client/SetupWrapper.hs +102/−45
- cabal/cabal-install/Distribution/Client/SrcDist.hs +3/−4
- cabal/cabal-install/Distribution/Client/Tar.hs +44/−885
- cabal/cabal-install/Distribution/Client/Targets.hs +78/−48
- cabal/cabal-install/Distribution/Client/Types.hs +168/−82
- cabal/cabal-install/Distribution/Client/Update.hs +43/−22
- cabal/cabal-install/Distribution/Client/Upload.hs +107/−58
- cabal/cabal-install/Distribution/Client/Utils.hs +48/−8
- cabal/cabal-install/Main.hs +282/−172
- cabal/cabal-install/Setup.hs +63/−2
- cabal/cabal-install/bash-completion/cabal +19/−0
- cabal/cabal-install/bootstrap.sh +81/−32
- cabal/cabal-install/cabal-install.cabal +200/−45
- cabal/cabal-install/cbits/getnumcores.c +0/−46
- cabal/cabal-install/changelog +36/−2
- cabal/cabal-install/tests/IntegrationTests.hs +319/−0
- cabal/cabal-install/tests/IntegrationTests/common.sh +12/−0
- cabal/cabal-install/tests/IntegrationTests/custom/plain.err +2/−0
- cabal/cabal-install/tests/IntegrationTests/custom/plain.sh +4/−0
- cabal/cabal-install/tests/IntegrationTests/custom/plain/A.hs +1/−0
- cabal/cabal-install/tests/IntegrationTests/custom/plain/Setup.hs +3/−0
- cabal/cabal-install/tests/IntegrationTests/custom/plain/plain.cabal +12/−0
- cabal/cabal-install/tests/IntegrationTests/exec/Foo.hs +4/−0
- cabal/cabal-install/tests/IntegrationTests/exec/My.hs +5/−0
- cabal/cabal-install/tests/IntegrationTests/exec/adds_sandbox_bin_directory_to_path.out +1/−0
- cabal/cabal-install/tests/IntegrationTests/exec/adds_sandbox_bin_directory_to_path.sh +10/−0
- cabal/cabal-install/tests/IntegrationTests/exec/auto_configures_on_exec.out +4/−0
- cabal/cabal-install/tests/IntegrationTests/exec/auto_configures_on_exec.sh +2/−0
- cabal/cabal-install/tests/IntegrationTests/exec/can_run_executables_installed_in_sandbox.out +1/−0
- cabal/cabal-install/tests/IntegrationTests/exec/can_run_executables_installed_in_sandbox.sh +9/−0
- cabal/cabal-install/tests/IntegrationTests/exec/configures_cabal_to_use_sandbox.sh +14/−0
- cabal/cabal-install/tests/IntegrationTests/exec/configures_ghc_to_use_sandbox.sh +13/−0
- cabal/cabal-install/tests/IntegrationTests/exec/exit_with_failure_without_args.err +1/−0
- cabal/cabal-install/tests/IntegrationTests/exec/exit_with_failure_without_args.sh +3/−0
- cabal/cabal-install/tests/IntegrationTests/exec/my.cabal +14/−0
- cabal/cabal-install/tests/IntegrationTests/exec/runs_given_command.out +1/−0
- cabal/cabal-install/tests/IntegrationTests/exec/runs_given_command.sh +3/−0
- cabal/cabal-install/tests/IntegrationTests/exec/subdir/.gitkeep +0/−0
- cabal/cabal-install/tests/IntegrationTests/freeze/disable_benchmarks_freezes_bench_deps.sh +3/−0
- cabal/cabal-install/tests/IntegrationTests/freeze/disable_tests_freezes_test_deps.sh +3/−0
- cabal/cabal-install/tests/IntegrationTests/freeze/does_not_freeze_nondeps.sh +5/−0
- cabal/cabal-install/tests/IntegrationTests/freeze/does_not_freeze_self.sh +3/−0
- cabal/cabal-install/tests/IntegrationTests/freeze/dry_run_does_not_create_config.sh +3/−0
- cabal/cabal-install/tests/IntegrationTests/freeze/enable_benchmarks_freezes_bench_deps.sh +4/−0
- cabal/cabal-install/tests/IntegrationTests/freeze/enable_tests_freezes_test_deps.sh +3/−0
- cabal/cabal-install/tests/IntegrationTests/freeze/freezes_direct_dependencies.sh +3/−0
- cabal/cabal-install/tests/IntegrationTests/freeze/freezes_transitive_dependencies.sh +3/−0
- cabal/cabal-install/tests/IntegrationTests/freeze/my.cabal +21/−0
- cabal/cabal-install/tests/IntegrationTests/freeze/runs_without_error.sh +2/−0
- cabal/cabal-install/tests/IntegrationTests/internal-libs/internal_lib_basic.sh +5/−0
- cabal/cabal-install/tests/IntegrationTests/internal-libs/internal_lib_shadow.sh +6/−0
- cabal/cabal-install/tests/IntegrationTests/internal-libs/p/Foo.hs +2/−0
- cabal/cabal-install/tests/IntegrationTests/internal-libs/p/p.cabal +23/−0
- cabal/cabal-install/tests/IntegrationTests/internal-libs/p/p/P.hs +2/−0
- cabal/cabal-install/tests/IntegrationTests/internal-libs/p/q/Q.hs +2/−0
- cabal/cabal-install/tests/IntegrationTests/internal-libs/q/Q.hs +2/−0
- cabal/cabal-install/tests/IntegrationTests/internal-libs/q/q.cabal +12/−0
- cabal/cabal-install/tests/IntegrationTests/manpage/outputs_manpage.sh +11/−0
- cabal/cabal-install/tests/IntegrationTests/multiple-source/finds_second_source_of_multiple_source.sh +11/−0
- cabal/cabal-install/tests/IntegrationTests/multiple-source/p/LICENSE +0/−0
- cabal/cabal-install/tests/IntegrationTests/multiple-source/p/Setup.hs +2/−0
- cabal/cabal-install/tests/IntegrationTests/multiple-source/p/p.cabal +11/−0
- cabal/cabal-install/tests/IntegrationTests/multiple-source/q/LICENSE +0/−0
- cabal/cabal-install/tests/IntegrationTests/multiple-source/q/Setup.hs +2/−0
- cabal/cabal-install/tests/IntegrationTests/multiple-source/q/q.cabal +11/−0
- cabal/cabal-install/tests/IntegrationTests/sandbox-sources/fail_removing_source_thats_not_registered.err +3/−0
- cabal/cabal-install/tests/IntegrationTests/sandbox-sources/fail_removing_source_thats_not_registered.sh +10/−0
- cabal/cabal-install/tests/IntegrationTests/sandbox-sources/p/LICENSE +0/−0
- cabal/cabal-install/tests/IntegrationTests/sandbox-sources/p/Setup.hs +2/−0
- cabal/cabal-install/tests/IntegrationTests/sandbox-sources/p/p.cabal +11/−0
- cabal/cabal-install/tests/IntegrationTests/sandbox-sources/q/LICENSE +0/−0
- cabal/cabal-install/tests/IntegrationTests/sandbox-sources/q/Setup.hs +2/−0
- cabal/cabal-install/tests/IntegrationTests/sandbox-sources/q/q.cabal +11/−0
- cabal/cabal-install/tests/IntegrationTests/sandbox-sources/remove_nonexistent_source.sh +22/−0
- cabal/cabal-install/tests/IntegrationTests/sandbox-sources/report_success_removing_source.out +6/−0
- cabal/cabal-install/tests/IntegrationTests/sandbox-sources/report_success_removing_source.sh +11/−0
- cabal/cabal-install/tests/IntegrationTests/user-config/cabal-config +182/−0
- cabal/cabal-install/tests/IntegrationTests/user-config/common.sh +9/−0
- cabal/cabal-install/tests/IntegrationTests/user-config/doesnt_overwrite_without_f.err +1/−0
- cabal/cabal-install/tests/IntegrationTests/user-config/doesnt_overwrite_without_f.sh +5/−0
- cabal/cabal-install/tests/IntegrationTests/user-config/overwrites_with_f.out +2/−0
- cabal/cabal-install/tests/IntegrationTests/user-config/overwrites_with_f.sh +9/−0
- cabal/cabal-install/tests/IntegrationTests/user-config/runs_without_error.out +1/−0
- cabal/cabal-install/tests/IntegrationTests/user-config/runs_without_error.sh +7/−0
- cabal/cabal-install/tests/IntegrationTests/user-config/uses_CABAL_CONFIG.out +1/−0
- cabal/cabal-install/tests/IntegrationTests/user-config/uses_CABAL_CONFIG.sh +5/−0
- cabal/cabal-install/tests/PackageTests.hs +0/−95
- cabal/cabal-install/tests/PackageTests/Exec/Check.hs +0/−145
- cabal/cabal-install/tests/PackageTests/Exec/Foo.hs +0/−4
- cabal/cabal-install/tests/PackageTests/Exec/My.hs +0/−5
- cabal/cabal-install/tests/PackageTests/Exec/my.cabal +0/−14
- cabal/cabal-install/tests/PackageTests/Exec/subdir/.gitkeep +0/−0
- cabal/cabal-install/tests/PackageTests/Freeze/Check.hs +0/−116
- cabal/cabal-install/tests/PackageTests/Freeze/my.cabal +0/−21
- cabal/cabal-install/tests/PackageTests/MultipleSource/Check.hs +0/−28
- cabal/cabal-install/tests/PackageTests/MultipleSource/p/LICENSE +0/−0
- cabal/cabal-install/tests/PackageTests/MultipleSource/p/Setup.hs +0/−2
- cabal/cabal-install/tests/PackageTests/MultipleSource/p/p.cabal +0/−11
- cabal/cabal-install/tests/PackageTests/MultipleSource/q/LICENSE +0/−0
- cabal/cabal-install/tests/PackageTests/MultipleSource/q/Setup.hs +0/−2
- cabal/cabal-install/tests/PackageTests/MultipleSource/q/q.cabal +0/−11
- cabal/cabal-install/tests/PackageTests/PackageTester.hs +0/−232
- cabal/cabal-install/tests/README +0/−1
- cabal/cabal-install/tests/README.md +27/−0
- cabal/cabal-install/tests/SolverQuickCheck.hs +16/−0
- cabal/cabal-install/tests/UnitTests.hs +92/−25
- cabal/cabal-install/tests/UnitTests/Distribution/Client/ArbitraryInstances.hs +172/−0
- cabal/cabal-install/tests/UnitTests/Distribution/Client/Compat/Time.hs +49/−0
- cabal/cabal-install/tests/UnitTests/Distribution/Client/Dependency/Modular/DSL.hs +203/−70
- cabal/cabal-install/tests/UnitTests/Distribution/Client/Dependency/Modular/QuickCheck.hs +291/−0
- cabal/cabal-install/tests/UnitTests/Distribution/Client/Dependency/Modular/Solver.hs +277/−49
- cabal/cabal-install/tests/UnitTests/Distribution/Client/FileMonitor.hs +769/−0
- cabal/cabal-install/tests/UnitTests/Distribution/Client/Glob.hs +202/−0
- cabal/cabal-install/tests/UnitTests/Distribution/Client/ProjectConfig.hs +610/−0
- cabal/cabal-install/tests/UnitTests/Distribution/Client/Sandbox/Timestamp.hs +63/−0
- cabal/cabal-install/tests/UnitTests/Distribution/Client/Tar.hs +75/−0
- cabal/cabal-install/tests/UnitTests/Distribution/Client/UserConfig.hs +44/−35
- cabal/cabal-install/tests/UnitTests/Options.hs +41/−0
- cabal/setup-dev.sh +25/−39
- cabal/travis-script.sh +91/−0
- hackage-security/.git +1/−0
- hackage-security/.gitignore +5/−0
- hackage-security/.travis.yml +125/−0
- hackage-security/README.md +694/−0
- hackage-security/example-client/LICENSE +30/−0
- hackage-security/example-client/example-client.cabal +48/−0
- hackage-security/example-client/src/ExampleClient/Options.hs +171/−0
- hackage-security/example-client/src/Main.hs +182/−0
- hackage-security/example-client/src/Prelude.hs +27/−0
- hackage-security/hackage-repo-tool/ChangeLog.md +13/−0
- hackage-security/hackage-repo-tool/LICENSE +30/−0
- hackage-security/hackage-repo-tool/Setup.hs +2/−0
- hackage-security/hackage-repo-tool/hackage-repo-tool.cabal +69/−0
- hackage-security/hackage-repo-tool/src/Hackage/Security/RepoTool/Layout.hs +89/−0
- hackage-security/hackage-repo-tool/src/Hackage/Security/RepoTool/Layout/Keys.hs +44/−0
- hackage-security/hackage-repo-tool/src/Hackage/Security/RepoTool/Options.hs +198/−0
- hackage-security/hackage-repo-tool/src/Hackage/Security/RepoTool/Paths.hs +33/−0
- hackage-security/hackage-repo-tool/src/Hackage/Security/RepoTool/Util/IO.hs +112/−0
- hackage-security/hackage-repo-tool/src/Main.hs +673/−0
- hackage-security/hackage-repo-tool/src/Prelude.hs +27/−0
- hackage-security/hackage-root-tool/ChangeLog.md +4/−0
- hackage-security/hackage-root-tool/LICENSE +30/−0
- hackage-security/hackage-root-tool/Main.hs +159/−0
- hackage-security/hackage-root-tool/Setup.hs +2/−0
- hackage-security/hackage-root-tool/hackage-root-tool.cabal +34/−0
- hackage-security/hackage-security-HTTP/ChangeLog.md +17/−0
- hackage-security/hackage-security-HTTP/LICENSE +30/−0
- hackage-security/hackage-security-HTTP/Setup.hs +2/−0
- hackage-security/hackage-security-HTTP/hackage-security-HTTP.cabal +55/−0
- hackage-security/hackage-security-HTTP/src/Hackage/Security/Client/Repository/HttpLib/HTTP.hs +274/−0
- hackage-security/hackage-security-curl/LICENSE +30/−0
- hackage-security/hackage-security-curl/Setup.hs +2/−0
- hackage-security/hackage-security-curl/hackage-security-curl.cabal +33/−0
- hackage-security/hackage-security-curl/src/Hackage/Security/Client/Repository/HttpLib/Curl.hs +70/−0
- hackage-security/hackage-security-http-client/ChangeLog.md +7/−0
- hackage-security/hackage-security-http-client/LICENSE +30/−0
- hackage-security/hackage-security-http-client/Setup.hs +2/−0
- hackage-security/hackage-security-http-client/hackage-security-http-client.cabal +39/−0
- hackage-security/hackage-security-http-client/src/Hackage/Security/Client/Repository/HttpLib/HttpClient.hs +164/−0
- hackage-security/hackage-security/ChangeLog.md +58/−0
- hackage-security/hackage-security/LICENSE +30/−0
- hackage-security/hackage-security/Setup.hs +2/−0
- hackage-security/hackage-security/hackage-security.cabal +232/−0
- hackage-security/hackage-security/src/Hackage/Security/Client.hs +1031/−0
- hackage-security/hackage-security/src/Hackage/Security/Client/Formats.hs +116/−0
- hackage-security/hackage-security/src/Hackage/Security/Client/Repository.hs +462/−0
- hackage-security/hackage-security/src/Hackage/Security/Client/Repository/Cache.hs +204/−0
- hackage-security/hackage-security/src/Hackage/Security/Client/Repository/HttpLib.hs +142/−0
- hackage-security/hackage-security/src/Hackage/Security/Client/Repository/Local.hs +99/−0
- hackage-security/hackage-security/src/Hackage/Security/Client/Repository/Remote.hs +717/−0
- hackage-security/hackage-security/src/Hackage/Security/Client/Verify.hs +103/−0
- hackage-security/hackage-security/src/Hackage/Security/JSON.hs +323/−0
- hackage-security/hackage-security/src/Hackage/Security/Key.hs +298/−0
- hackage-security/hackage-security/src/Hackage/Security/Key/Env.hs +90/−0
- hackage-security/hackage-security/src/Hackage/Security/Server.hs +29/−0
- hackage-security/hackage-security/src/Hackage/Security/TUF.hs +41/−0
- hackage-security/hackage-security/src/Hackage/Security/TUF/Common.hs +53/−0
- hackage-security/hackage-security/src/Hackage/Security/TUF/FileInfo.hs +103/−0
- hackage-security/hackage-security/src/Hackage/Security/TUF/FileMap.hs +134/−0
- hackage-security/hackage-security/src/Hackage/Security/TUF/Header.hs +119/−0
- hackage-security/hackage-security/src/Hackage/Security/TUF/Layout/Cache.hs +64/−0
- hackage-security/hackage-security/src/Hackage/Security/TUF/Layout/Index.hs +116/−0
- hackage-security/hackage-security/src/Hackage/Security/TUF/Layout/Repo.hs +79/−0
- hackage-security/hackage-security/src/Hackage/Security/TUF/Mirrors.hs +103/−0
- hackage-security/hackage-security/src/Hackage/Security/TUF/Paths.hs +72/−0
- hackage-security/hackage-security/src/Hackage/Security/TUF/Patterns.hs +346/−0
- hackage-security/hackage-security/src/Hackage/Security/TUF/Root.hs +117/−0
- hackage-security/hackage-security/src/Hackage/Security/TUF/Signed.hs +238/−0
- hackage-security/hackage-security/src/Hackage/Security/TUF/Snapshot.hs +100/−0
- hackage-security/hackage-security/src/Hackage/Security/TUF/Targets.hs +130/−0
- hackage-security/hackage-security/src/Hackage/Security/TUF/Timestamp.hs +74/−0
- hackage-security/hackage-security/src/Hackage/Security/Trusted.hs +58/−0
- hackage-security/hackage-security/src/Hackage/Security/Trusted/TCB.hs +320/−0
- hackage-security/hackage-security/src/Hackage/Security/Util/Base64.hs +31/−0
- hackage-security/hackage-security/src/Hackage/Security/Util/Checked.hs +102/−0
- hackage-security/hackage-security/src/Hackage/Security/Util/IO.hs +61/−0
- hackage-security/hackage-security/src/Hackage/Security/Util/JSON.hs +212/−0
- hackage-security/hackage-security/src/Hackage/Security/Util/Lens.hs +48/−0
- hackage-security/hackage-security/src/Hackage/Security/Util/Path.hs +459/−0
- hackage-security/hackage-security/src/Hackage/Security/Util/Pretty.hs +8/−0
- hackage-security/hackage-security/src/Hackage/Security/Util/Some.hs +102/−0
- hackage-security/hackage-security/src/Hackage/Security/Util/Stack.hs +8/−0
- hackage-security/hackage-security/src/Hackage/Security/Util/TypedEmbedded.hs +38/−0
- hackage-security/hackage-security/src/Prelude.hs +32/−0
- hackage-security/hackage-security/src/Text/JSON/Canonical.hs +272/−0
- hackage-security/hackage-security/tests/TestSuite.hs +434/−0
- hackage-security/hackage-security/tests/TestSuite/HttpMem.hs +70/−0
- hackage-security/hackage-security/tests/TestSuite/InMemCache.hs +149/−0
- hackage-security/hackage-security/tests/TestSuite/InMemRepo.hs +262/−0
- hackage-security/hackage-security/tests/TestSuite/InMemRepository.hs +63/−0
- hackage-security/hackage-security/tests/TestSuite/PrivateKeys.hs +69/−0
- hackage-security/hackage-security/tests/TestSuite/Util/StrictMVar.hs +27/−0
- hackage-security/precompute-fileinfo/LICENSE +30/−0
- hackage-security/precompute-fileinfo/Setup.hs +2/−0
- hackage-security/precompute-fileinfo/precompute-fileinfo.cabal +27/−0
- hackage-security/precompute-fileinfo/src/Main.hs +196/−0
- hackage-security/stack.yaml +13/−0
- hackage-security/testscripts/squid.conf +4/−0
- hackage-security/testscripts/test-outdated-index.log +440/−0
- hackage-security/testscripts/test-outdated-index.sh +67/−0
- hackage-security/testscripts/test-outdated-timestamp.log +280/−0
- hackage-security/testscripts/test-outdated-timestamp.sh +60/−0
- hackage-security/testscripts/test-range-request.sh +13/−0
- hackport.cabal +58/−54
.gitmodules view
@@ -1,3 +1,6 @@ [submodule "cabal"] path = cabal url = git://github.com/gentoo-haskell/cabal.git+[submodule "hackage-security"]+ path = hackage-security+ url = git://github.com/well-typed/hackage-security.git
Cabal2Ebuild.hs view
@@ -51,17 +51,22 @@ else Cabal.description pkg, E.homepage = thisHomepage, E.license = Portage.convertLicense $ Cabal.license pkg,- E.slot = (E.slot E.ebuildTemplate) ++ maybe [] (const "/${PV}") (Cabal.library pkg),+ E.slot = (E.slot E.ebuildTemplate) ++ (if hasLibs then "/${PV}" else ""), E.my_pn = if any isUpper cabalPkgName then Just cabalPkgName else Nothing, E.features = E.features E.ebuildTemplate- ++ (if hasExe then ["bin"] else [])- ++ maybe [] (const (["lib","profile","haddock","hoogle"]- ++ if cabalPkgName == "hscolour" then [] else ["hscolour"])- ) (Cabal.library pkg) -- hscolour can't colour its own sources- ++ (if hasTests then ["test-suite"] else [])+ ++ (if hasExe then ["bin"]+ else [])+ ++ (if hasLibs then (["lib","profile","haddock","hoogle"]+ ++ if cabalPkgName == "hscolour"+ then []+ else ["hscolour"])+ else [])+ ++ (if hasTests then ["test-suite"]+ else []) } where cabal_pn = Cabal.pkgName $ Cabal.package pkg cabalPkgName = display cabal_pn+ hasLibs = Cabal.libraries pkg /= [] hasExe = (not . null) (Cabal.executables pkg) hasTests = (not . null) (Cabal.testSuites pkg) thisHomepage = if (null $ Cabal.homepage pkg)
− CacheFile.hs
@@ -1,12 +0,0 @@-module CacheFile where--import System.FilePath--indexFile :: String-indexFile = "00-index.tar.gz"--hackportDir :: String-hackportDir = ".hackport"--cacheFile :: FilePath -> FilePath-cacheFile tree = tree </> hackportDir </> indexFile
− Diff.hs
@@ -1,188 +0,0 @@-module Diff- ( runDiff- , DiffMode(..)- ) where--import Control.Monad ( mplus )-import Control.Exception ( assert )-import Data.Maybe ( fromJust, listToMaybe )-import Data.List ( sortBy, groupBy )-import Data.Ord ( comparing )--import qualified Portage.Overlay as Portage-import qualified Portage.Cabal as Portage-import qualified Portage.PackageId as Portage--import qualified Data.Version as Cabal---- cabal-import Distribution.Verbosity-import Distribution.Text(display)-import qualified Distribution.Package as Cabal-import qualified Distribution.Client.PackageIndex as Index-import Distribution.Simple.Utils (equating)---- cabal-install-import qualified Distribution.Client.IndexUtils as Index (getSourcePackages)-import qualified Distribution.Client.Types as Cabal-import Distribution.Client.Utils (mergeBy, MergeResult(..))--data DiffMode- = ShowAll- | ShowMissing- | ShowAdditions- | ShowNewer- | ShowCommon- | ShowPackages [String]- deriving Eq---{--type DiffState a = MergeResult a a-tabs :: String -> String-tabs str = let len = length str in str++(if len < 3*8- then replicate (3*8-len) ' '- else "")----- TODO: is the new showPackageCompareInfo showing the packages in the same--- way as showDiffState did?--showDiffState :: Cabal.PackageName -> DiffState Portage.Version -> String-showDiffState pkg st = (tabs (display pkg)) ++ " [" ++ (case st of- InBoth x y -> display x ++ (case compare x y of- EQ -> "="- GT -> ">"- LT -> "<") ++ display y- OnlyInLeft x -> display x ++ ">none"- OnlyInRight y -> "none<" ++ display y) ++ "]"--}--runDiff :: Verbosity -> FilePath -> DiffMode -> Cabal.Repo -> IO ()-runDiff verbosity overlayPath dm repo = do- -- get package list from hackage- pkgDB <- Index.getSourcePackages verbosity [ repo ]- let (Cabal.SourcePackageDb hackageIndex _) = pkgDB-- -- get package list from the overlay- overlay0 <- (Portage.loadLazy overlayPath)- let overlayIndex = Portage.fromOverlay (Portage.reduceOverlay overlay0)-- let (subHackage, subOverlay)- = case dm of- ShowPackages pkgs ->- (concatMap (concatMap snd . Index.searchByNameSubstring hackageIndex) pkgs- ,concatMap (concatMap snd . Index.searchByNameSubstring overlayIndex) pkgs)- _ ->- (Index.allPackages hackageIndex- ,Index.allPackages overlayIndex)- diff subHackage subOverlay dm--data PackageCompareInfo = PackageCompareInfo {- name :: Cabal.PackageName,--- hackageVersions :: [ Cabal.Version ],--- overlayVersions :: [ Cabal.Version ]- hackageVersion :: Maybe Cabal.Version,- overlayVersion :: Maybe Cabal.Version- } deriving Show--showPackageCompareInfo :: PackageCompareInfo -> String-showPackageCompareInfo pkgCmpInfo =- display (name pkgCmpInfo) ++ " ["- ++ hackageS ++ sign ++ overlayS ++ "]" - where- overlay = overlayVersion pkgCmpInfo- hackage = hackageVersion pkgCmpInfo- hackageS = maybe "none" display hackage- overlayS = maybe "none" display overlay- sign = case compare hackage overlay of- EQ -> "="- GT -> ">"- LT -> "<"--diff :: [Cabal.SourcePackage]- -> [Portage.ExistingEbuild]- -> DiffMode- -> IO ()-diff hackage overlay dm = do- mapM_ (putStrLn . showPackageCompareInfo) pkgCmpInfos- where- merged = mergePackages (map (Portage.normalizeCabalPackageId . Cabal.packageId) hackage)- (map Portage.ebuildCabalId overlay)- pkgCmpInfos = filter pkgFilter (map (uncurry mergePackageInfo) merged)- pkgFilter :: PackageCompareInfo -> Bool- pkgFilter pkgCmpInfo =- let om = overlayVersion pkgCmpInfo- hm = hackageVersion pkgCmpInfo- st = case (om,hm) of- (Just ov, Just hv) -> InBoth ov hv- (Nothing, Just hv) -> OnlyInRight hv- (Just ov, Nothing) -> OnlyInLeft ov- _ -> error "impossible"- in- case dm of- ShowAll -> True- ShowPackages _ -> True -- already filtered- ShowNewer -> case st of- InBoth o h -> h>o- _ -> False- ShowMissing -> case st of- OnlyInLeft _ -> False- InBoth x y -> x < y- OnlyInRight _ -> True- ShowAdditions -> case st of- OnlyInLeft _ -> True- InBoth x y -> x > y- OnlyInRight _ -> False- ShowCommon -> case st of- OnlyInLeft _ -> False- InBoth x y -> x == y- OnlyInRight _ -> False- --- | We get the 'PackageCompareInfo' by combining the info for the overlay--- and hackage versions of a package.------ * We're building info about a various versions of a single named package so--- the input package info records are all supposed to refer to the same--- package name.----mergePackageInfo :: [Cabal.PackageIdentifier]- -> [Cabal.PackageIdentifier]- -> PackageCompareInfo-mergePackageInfo hackage overlay =- assert (length overlay + length hackage > 0) $- PackageCompareInfo {- name = combine Cabal.pkgName latestHackage- Cabal.pkgName latestOverlay,--- hackageVersions = map Cabal.pkgVersion hackage,--- overlayVersions = map Cabal.pkgVersion overlay- hackageVersion = fmap Cabal.pkgVersion latestHackage,- overlayVersion = fmap Cabal.pkgVersion latestOverlay- }- where- combine f x g y = fromJust (fmap f x `mplus` fmap g y)- latestHackage = latestOf hackage- latestOverlay = latestOf overlay- latestOf :: [Cabal.PackageIdentifier] -> Maybe Cabal.PackageIdentifier- latestOf = listToMaybe . reverse . sortBy (comparing Cabal.pkgVersion)---- | Rearrange installed and available packages into groups referring to the--- same package by name. In the result pairs, the lists are guaranteed to not--- both be empty.----mergePackages :: [Cabal.PackageIdentifier] -> [Cabal.PackageIdentifier]- -> [([Cabal.PackageIdentifier], [Cabal.PackageIdentifier])]-mergePackages hackage overlay =- map collect- $ mergeBy (\i a -> fst i `compare` fst a)- (groupOn Cabal.pkgName hackage)- (groupOn Cabal.pkgName overlay)- where- collect (OnlyInLeft (_,is) ) = (is, [])- collect ( InBoth (_,is) (_,as)) = (is, as)- collect (OnlyInRight (_,as)) = ([], as)--groupOn :: Ord key => (a -> key) -> [a] -> [(key,[a])]-groupOn key = map (\xs -> (key (head xs), xs))- . groupBy (equating key)- . sortBy (comparing key)
− DistroMap.hs
@@ -1,158 +0,0 @@-{-# OPTIONS -XPatternGuards #-}-{--Generate a distromap, like these:-http://hackage.haskell.org/packages/archive/00-distromap/-Format:--("xmobar","0.8",Just "http://packages.gentoo.org/package/x11-misc/xmobar")-("xmobar","0.9",Just "http://packages.gentoo.org/package/x11-misc/xmobar")-("xmobar","0.9.2",Just "http://packages.gentoo.org/package/x11-misc/xmobar")-("xmonad","0.5",Just "http://packages.gentoo.org/package/x11-wm/xmonad")-("xmonad","0.6",Just "http://packages.gentoo.org/package/x11-wm/xmonad")-("xmonad","0.7",Just "http://packages.gentoo.org/package/x11-wm/xmonad")-("xmonad","0.8",Just "http://packages.gentoo.org/package/x11-wm/xmonad")-("xmonad","0.8.1",Just "http://packages.gentoo.org/package/x11-wm/xmonad")-("xmonad","0.9",Just "http://packages.gentoo.org/package/x11-wm/xmonad")-("xmonad","0.9.1",Just "http://en.gentoo-wiki.com/wiki/Haskell/overlay")--Multiple entries for each package is allowed, given that there are different versions.---Setup:- Join all packages from portage and the overlay into a big map;- From Portage.PackageId: PackageName = category/package- PVULine = (packagename, versionstring, url)- Create such a map: Map PackageName DistroLine- Only one PVULine per version, and prefer portage over the overlay.--Algorithm;- 1. Take a package from hackage- 2. Look for it in the map- a. For each version:- find a match in the list of versions:- yield the PVULine--}--module DistroMap- ( distroMap ) where--import Control.Applicative-import qualified Data.List as List ( nub )-import qualified Data.Map as Map-import Data.Map ( Map )-import System.FilePath ( (</>) )-import Debug.Trace ( trace )-import Data.Maybe ( fromJust )--import Distribution.Verbosity-import Distribution.Text ( display )-import Distribution.Client.Types ( Repo, SourcePackageDb(..), SourcePackage(..) )-import Distribution.Simple.Utils ( info )--import qualified Data.Version as Cabal-import qualified Distribution.Package as Cabal-import qualified Distribution.Client.PackageIndex as CabalInstall-import qualified Distribution.Client.IndexUtils as CabalInstall--import Portage.Overlay ( readOverlayByPackage, getDirectoryTree )-import qualified Portage.PackageId as Portage-import qualified Portage.Version as Portage--type PVU = (Cabal.PackageName, Cabal.Version, Maybe String)-type PVU_Map = Map Portage.PackageName [(Cabal.Version, Maybe String)]--distroMap :: Verbosity -> Repo -> FilePath -> FilePath -> [String] -> IO ()-distroMap verbosity repo portagePath overlayPath args = do- info verbosity "distro map called"- info verbosity ("verbosity: " ++ show verbosity)- info verbosity ("portage: " ++ portagePath)- info verbosity ("overlay: " ++ overlayPath)- info verbosity ("args: " ++ show args)-- portage <- readOverlayByPackage <$> getDirectoryTree portagePath- overlay <- readOverlayByPackage <$> getDirectoryTree overlayPath-- info verbosity ("portage packages: " ++ show (length portage))- info verbosity ("overlay packages: " ++ show (length overlay))-- let portageMap = buildPortageMap portage- overlayMap = buildOverlayMap overlay- completeMap = unionMap portageMap overlayMap-- info verbosity ("portage map: " ++ show (Map.size portageMap))- info verbosity ("overlay map: " ++ show (Map.size overlayMap))- info verbosity ("complete map: " ++ show (Map.size completeMap))-- SourcePackageDb { packageIndex = packageIndex } <-- CabalInstall.getSourcePackages verbosity [repo]-- let pkgs0 = map (map packageInfoId) (CabalInstall.allPackagesByName packageIndex)- hackagePkgs = [ (Cabal.pkgName (head p), map Cabal.pkgVersion p) | p <- pkgs0 ]-- info verbosity ("cabal packages: " ++ show (length hackagePkgs))-- let pvus = concat $ map (\(p,vs) -> lookupPVU completeMap p vs) hackagePkgs- info verbosity ("found pvus: " ++ show (length pvus))-- mapM_ (putStrLn . showPVU) pvus- return ()---showPVU :: PVU -> String-showPVU (p,v,u) = show $ (display p, display v, u)---- building the PVU_Map--reduceVersion :: Portage.Version -> Portage.Version-reduceVersion (Portage.Version ns _ _ _) = Portage.Version ns Nothing [] 0--reduceVersions :: [Portage.Version] -> [Portage.Version]-reduceVersions = List.nub . map reduceVersion--buildMap :: [(Portage.PackageName, [Portage.Version])]- -> (Portage.PackageName -> Portage.Version -> Maybe String)- -> PVU_Map-buildMap pvs f = Map.mapWithKey (\p vs -> [ (fromJust $ Portage.toCabalVersion v, f p v)- | v <- reduceVersions vs ])- (Map.fromList pvs)--buildPortageMap :: [(Portage.PackageName, [Portage.Version])] -> PVU_Map-buildPortageMap lst = buildMap lst $ \ (Portage.PackageName c p) _v ->- Just $ "http://packages.gentoo.org/package" </> display c </> display p--buildOverlayMap :: [(Portage.PackageName, [Portage.Version])] -> PVU_Map-buildOverlayMap lst = buildMap lst $ \_ _ -> Just "http://en.gentoo-wiki.com/wiki/Haskell/overlay"--unionMap :: PVU_Map -> PVU_Map -> PVU_Map-unionMap = Map.unionWith f- where- f :: [(Cabal.Version, Maybe String)]- -> [(Cabal.Version, Maybe String)]- -> [(Cabal.Version, Maybe String)]- f vas vbs = Map.toList (Map.union (Map.fromList vas) (Map.fromList vbs))----- resolving Cabal.PackageName to Portage.PackageName--lookupPVU :: PVU_Map -> Cabal.PackageName -> [Cabal.Version] -> [PVU]-lookupPVU pvu_map pn cvs =- case findItems (Portage.normalizeCabalPackageName pn) of- [] -> []- [item] -> ret item- items | [item] <- preferableItem items -> ret item- | otherwise -> trace (noDefaultText items) []- where- noDefaultText is = unlines $ ("no default for package: " ++ display pn)- : [ " * " ++ (display cat)- | (Portage.PackageName cat _, _) <- is]-- ret (_, vs) = [ (pn, v, u) | (v, u) <- vs, v `elem` cvs ]- preferableItem items =- [ item- | item@(Portage.PackageName cat _pn, _vs) <- items- , cat == Portage.Category "dev-haskell"]- findItems cpn = Map.toList $ Map.filterWithKey f pvu_map- where- f (Portage.PackageName _cat _pn) _vs = cpn == pn--
+ HackPort/GlobalFlags.hs view
@@ -0,0 +1,47 @@+module HackPort.GlobalFlags+ ( GlobalFlags(..)+ , defaultGlobalFlags+ , withHackPortContext+ ) where++import qualified Distribution.Verbosity as DV+import qualified Distribution.Simple.Setup as DSS+import qualified Distribution.Client.GlobalFlags as DCG+import qualified Distribution.Client.Types as DCT+import qualified Distribution.Utils.NubList as DUN++import qualified Network.URI as NU++import System.FilePath ((</>))++import qualified Overlays++data GlobalFlags =+ GlobalFlags { globalVersion :: DSS.Flag Bool+ , globalNumericVersion :: DSS.Flag Bool+ , globalPathToOverlay :: DSS.Flag (Maybe FilePath)+ , globalPathToPortage :: DSS.Flag (Maybe FilePath)+ }++defaultGlobalFlags :: GlobalFlags+defaultGlobalFlags =+ GlobalFlags { globalVersion = DSS.Flag False+ , globalNumericVersion = DSS.Flag False+ , globalPathToOverlay = DSS.Flag Nothing+ , globalPathToPortage = DSS.Flag Nothing+ }++defaultRemoteRepo :: DCT.RemoteRepo+defaultRemoteRepo = (DCT.emptyRemoteRepo name) { DCT.remoteRepoURI = uri }+ where+ Just uri = NU.parseURI "https://hackage.haskell.org/"+ name = "hackage.haskell.org"++withHackPortContext :: DV.Verbosity -> GlobalFlags -> (DCG.RepoContext -> IO a) -> IO a+withHackPortContext verbosity global_flags callback = do+ overlayPath <- Overlays.getOverlayPath verbosity (DSS.fromFlag $ globalPathToOverlay global_flags)+ let flags = DCG.defaultGlobalFlags {+ DCG.globalRemoteRepos = DUN.toNubList [defaultRemoteRepo]+ , DCG.globalCacheDir = DSS.Flag $ overlayPath </> ".hackport"+ }+ DCG.withRepoContext verbosity flags callback
− Hackage.hs
@@ -1,35 +0,0 @@-{-|- Author : Sergei Trofimovich <slyfox@gentoo.org>- Stability : experimental- Portability : haskell98-- Utilities to work with hackage-alike repositories--}-module Hackage- ( defaultRepo- , defaultRepoURI- ) where--import Distribution.Client.Types (Repo(..), RemoteRepo(..))-import Network.URI (URI(..), URIAuth(..))-import System.FilePath--defaultRepo :: FilePath -> Repo-defaultRepo overlayPath =- Repo {- repoKind = Left defaultRemoteRepo,- repoLocalDir = overlayPath </> ".hackport"- }---- A copy from cabal-install/Distribution.Client.Config-defaultRemoteRepo :: RemoteRepo-defaultRemoteRepo = RemoteRepo name uri () False- where- name = "hackage.haskell.org"- uri = URI "http:" (Just (URIAuth "" name "")) "/" "" ""--defaultRepoURI :: FilePath -> URI-defaultRepoURI overlayPath =- case repoKind (defaultRepo overlayPath) of- Left (RemoteRepo { remoteRepoURI = uri }) -> uri- Right _ -> error $ "defaultRepoURI: unable to get URI for " ++ overlayPath
− Main-GuessGHC.hs
@@ -1,27 +0,0 @@-module Main where--import System.Environment--import Distribution.PackageDescription-import Distribution.PackageDescription.Parse--import Distribution.Text-import Distribution.Verbosity--import Portage.GHCCore--main :: IO ()-main = do- args <- getArgs- gpds <- mapM (readPackageDescription silent) args- mapM_ guess gpds--guess :: GenericPackageDescription -> IO ()-guess gpd = do- let pkg = package . packageDescription $ gpd- let mghc = minimumGHCVersionToBuildPackage gpd- putStr (display pkg)- putStr "\t\t"- putStrLn $ case mghc of- Nothing -> "Unknown"- Just (compiler, _pkgs) -> display compiler
Main.hs view
@@ -1,4 +1,4 @@-module Main where+module Main (main) where import Control.Applicative import Control.Monad@@ -11,10 +11,9 @@ import Distribution.Simple.Setup ( Flag(..), fromFlag , trueArg- , flagToList , optionVerbosity )-import Distribution.ReadE ( succeedReadE )+ import Distribution.Simple.Command -- commandsRun import Distribution.Simple.Utils ( die, cabalVersion, warn ) import qualified Distribution.PackageDescription.Parse as Cabal@@ -24,29 +23,24 @@ import Distribution.Client.Types import Distribution.Client.Update-import qualified Distribution.Client.HttpUtils as DCH- import qualified Distribution.Client.PackageIndex as Index import qualified Distribution.Client.IndexUtils as Index -import Hackage (defaultRepo, defaultRepoURI)- import Portage.Overlay as Overlay ( loadLazy, inOverlay ) import Portage.Host as Host ( getInfo, portage_dir ) import Portage.PackageId ( normalizeCabalPackageId ) -import Network.URI ( URI(..), parseURI ) import System.Environment ( getArgs, getProgName ) import System.Directory ( doesDirectoryExist ) import System.Exit ( exitFailure ) import System.FilePath ( (</>) ) -import Diff+import qualified HackPort.GlobalFlags as H+ import Error import Status import Overlays import Merge-import DistroMap ( distroMap ) import qualified Paths_cabal_install import qualified Paths_hackport@@ -57,37 +51,30 @@ data ListFlags = ListFlags { listVerbosity :: Flag Verbosity- -- , listOverlayPath :: Flag FilePath- -- , listServerURI :: Flag String } instance Monoid ListFlags where mempty = ListFlags { listVerbosity = mempty- -- , listOverlayPath = mempty- -- , listServerURI = mempty } mappend a b = ListFlags { listVerbosity = combine listVerbosity- -- , listOverlayPath = combine listOverlayPath- -- , listServerURI = combine listServerURI } where combine field = field a `mappend` field b defaultListFlags :: ListFlags defaultListFlags = ListFlags { listVerbosity = Flag normal- -- , listOverlayPath = NoFlag- -- , listServerURI = Flag defaultHackageServerURI } listCommand :: CommandUI ListFlags listCommand = CommandUI { commandName = "list",- commandSynopsis = "List packages",- commandDescription = Just $ \_pname ->- "TODO: this is the commandDescription for listCommand\n",+ commandSynopsis = "List package versions matching pattern", commandUsage = usagePackages "list",+ commandDescription = Nothing,+ commandNotes = Nothing,+ commandDefaultFlags = defaultListFlags, commandOptions = \_showOrParseArgs -> [ optionVerbosity listVerbosity (\v flags -> flags { listVerbosity = v })@@ -100,12 +87,12 @@ ] } -listAction :: ListFlags -> [String] -> GlobalFlags -> IO ()+listAction :: ListFlags -> [String] -> H.GlobalFlags -> IO () listAction flags extraArgs globalFlags = do- let verbosity = fromFlag (listVerbosity flags)- overlayPath <- getOverlayPath verbosity (fromFlag $ globalPathToOverlay globalFlags)- let repo = defaultRepo overlayPath- index <- fmap packageIndex (Index.getSourcePackages verbosity [ repo ])+ let verbosity = fromFlag (listVerbosity flags)+ H.withHackPortContext verbosity globalFlags $ \repoContext -> do+ overlayPath <- getOverlayPath verbosity (fromFlag $ H.globalPathToOverlay globalFlags)+ index <- fmap packageIndex (Index.getSourcePackages verbosity repoContext) overlay <- Overlay.loadLazy overlayPath let pkgs | null extraArgs = Index.allPackages index | otherwise = concatMap (concatMap snd . Index.searchByNameSubstring index) extraArgs@@ -146,7 +133,7 @@ , makeEbuildCabalFlags = Flag Nothing } -makeEbuildAction :: MakeEbuildFlags -> [String] -> GlobalFlags -> IO ()+makeEbuildAction :: MakeEbuildFlags -> [String] -> H.GlobalFlags -> IO () makeEbuildAction flags args globalFlags = do (catstr,cabals) <- case args of (category:cabal1:cabaln) -> return (category, cabal1:cabaln)@@ -155,7 +142,7 @@ Just c -> return c Nothing -> throwEx (ArgumentError ("could not parse category: " ++ catstr)) let verbosity = fromFlag (makeEbuildVerbosity flags)- overlayPath <- getOverlayPath verbosity (fromFlag $ globalPathToOverlay globalFlags)+ overlayPath <- getOverlayPath verbosity (fromFlag $ H.globalPathToOverlay globalFlags) forM_ cabals $ \cabalFileName -> do pkg <- Cabal.readPackageDescription normal cabalFileName mergeGenericPackageDescription verbosity overlayPath cat pkg False (fromFlag $ makeEbuildCabalFlags flags)@@ -164,9 +151,10 @@ makeEbuildCommand = CommandUI { commandName = "make-ebuild", commandSynopsis = "Make an ebuild from a .cabal file",- commandDescription = Just $ \_pname ->- "TODO: this is the commandDescription for makeEbuildCommand\n", commandUsage = \_ -> [],+ commandDescription = Nothing,+ commandNotes = Nothing,+ commandDefaultFlags = defaultMakeEbuildFlags, commandOptions = \_showOrParseArgs -> [ optionVerbosity makeEbuildVerbosity (\v flags -> flags { makeEbuildVerbosity = v })@@ -182,106 +170,35 @@ } -------------------------------------------------------------------------- Diff--------------------------------------------------------------------------data DiffFlags = DiffFlags {- -- diffMode :: Flag String, -- DiffMode,- diffVerbosity :: Flag Verbosity- -- , diffServerURI :: Flag String- }--instance Monoid DiffFlags where- mempty = DiffFlags {- -- diffMode = mempty,- diffVerbosity = mempty- -- , diffServerURI = mempty- }- mappend a b = DiffFlags {- -- diffMode = combine diffMode,- diffVerbosity = combine diffVerbosity- -- , diffServerURI = combine diffServerURI- }- where combine field = field a `mappend` field b--defaultDiffFlags :: DiffFlags-defaultDiffFlags = DiffFlags {- -- diffMode = Flag "all",- diffVerbosity = Flag normal- -- , diffServerURI = Flag defaultHackageServerURI- }--diffCommand :: CommandUI DiffFlags-diffCommand = CommandUI {- commandName = "diff",- commandSynopsis = "Run diff",- commandDescription = Just $ \_pname ->- "TODO: this is the commandDescription for diffCommand\n",- commandUsage = usagePackages "diff",- commandDefaultFlags = defaultDiffFlags,- commandOptions = \_showOrParseArgs ->- [ optionVerbosity diffVerbosity (\v flags -> flags { diffVerbosity = v })- {-- , option [] ["mode"]- "Diff mode, one of: all, newer, missing, additions, common"- diffMode (\v flags -> flags { diffMode = v })- (reqArgFlag "MODE") -- I don't know how to map it strictly to DiffMode- -}- ]- }--diffAction :: DiffFlags -> [String] -> GlobalFlags -> IO ()-diffAction flags args globalFlags = do- let verbosity = fromFlag (diffVerbosity flags)- -- dm0 = fromFlag (diffMode flags)- dm <- case args of- [] -> return ShowAll- ["all"] -> return ShowAll- ["missing"] -> return ShowMissing- ["additions"] -> return ShowAdditions- ["newer"] -> return ShowNewer- ["common"] -> return ShowCommon- ("package": pkgs) -> return (ShowPackages pkgs)- -- TODO: ["package",packagePattern] ->- -- return ShowPackagePattern packagePattern- _ -> die $ "Unknown mode: " ++ unwords args- overlayPath <- getOverlayPath verbosity (fromFlag $ globalPathToOverlay globalFlags)- let repo = defaultRepo overlayPath- runDiff verbosity overlayPath dm repo------------------------------------------------------------------------- -- Update ----------------------------------------------------------------------- data UpdateFlags = UpdateFlags { updateVerbosity :: Flag Verbosity- -- , updateServerURI :: Flag String } instance Monoid UpdateFlags where mempty = UpdateFlags { updateVerbosity = mempty- -- , updateServerURI = mempty } mappend a b = UpdateFlags { updateVerbosity = combine updateVerbosity- -- , updateServerURI = combine updateServerURI } where combine field = field a `mappend` field b defaultUpdateFlags :: UpdateFlags defaultUpdateFlags = UpdateFlags { updateVerbosity = Flag normal- -- , updateServerURI = Flag defaultHackageServerURI } updateCommand :: CommandUI UpdateFlags updateCommand = CommandUI { commandName = "update",- commandSynopsis = "Update the local cache",- commandDescription = Just $ \_pname ->- "TODO: this is the commandDescription for updateCommand\n",+ commandSynopsis = "Update the local package database", commandUsage = usageFlags "update",+ commandDescription = Nothing,+ commandNotes = Nothing,+ commandDefaultFlags = defaultUpdateFlags, commandOptions = \_ -> [ optionVerbosity updateVerbosity (\v flags -> flags { updateVerbosity = v })@@ -295,16 +212,15 @@ ] } -updateAction :: UpdateFlags -> [String] -> GlobalFlags -> IO ()+updateAction :: UpdateFlags -> [String] -> H.GlobalFlags -> IO () updateAction flags extraArgs globalFlags = do unless (null extraArgs) $ die $ "'update' doesn't take any extra arguments: " ++ unwords extraArgs let verbosity = fromFlag (updateVerbosity flags)- overlayPath <- getOverlayPath verbosity (fromFlag $ globalPathToOverlay globalFlags)- http_transport <- DCH.configureTransport verbosity Nothing- update http_transport verbosity [ defaultRepo overlayPath ]- + H.withHackPortContext verbosity globalFlags $ \repoContext ->+ update verbosity repoContext+ ----------------------------------------------------------------------- -- Status -----------------------------------------------------------------------@@ -323,10 +239,11 @@ statusCommand :: CommandUI StatusFlags statusCommand = CommandUI { commandName = "status",- commandSynopsis = "Show status(??)",- commandDescription = Just $ \_pname ->- "TODO: this is the commandDescription for statusCommand\n",+ commandSynopsis = "Show up-to-date status against other repos (hackage, ::gentoo)", commandUsage = usagePackages "status",+ commandDescription = Nothing,+ commandNotes = Nothing,+ commandDefaultFlags = defaultStatusFlags, commandOptions = \_ -> [ optionVerbosity statusVerbosity (\v flags -> flags { statusVerbosity = v })@@ -341,14 +258,16 @@ ] } -statusAction :: StatusFlags -> [String] -> GlobalFlags -> IO ()+statusAction :: StatusFlags -> [String] -> H.GlobalFlags -> IO () statusAction flags args globalFlags = do let verbosity = fromFlag (statusVerbosity flags) direction = fromFlag (statusDirection flags) portagePath <- getPortageDir verbosity globalFlags- overlayPath <- getOverlayPath verbosity (fromFlag $ globalPathToOverlay globalFlags)- runStatus verbosity portagePath overlayPath direction args+ overlayPath <- getOverlayPath verbosity (fromFlag $ H.globalPathToOverlay globalFlags) + H.withHackPortContext verbosity globalFlags $ \repoContext ->+ runStatus verbosity portagePath overlayPath direction args repoContext+ ----------------------------------------------------------------------- -- Merge -----------------------------------------------------------------------@@ -379,9 +298,10 @@ mergeCommand = CommandUI { commandName = "merge", commandSynopsis = "Make an ebuild out of hackage package",- commandDescription = Just $ \_pname ->- "TODO: this is the commandDescription for mergeCommand\n", commandUsage = usagePackages "merge",+ commandDescription = Nothing,+ commandNotes = Nothing,+ commandDefaultFlags = defaultMergeFlags, commandOptions = \_showOrParseArgs -> [ optionVerbosity mergeVerbosity (\v flags -> flags { mergeVerbosity = v })@@ -396,71 +316,18 @@ ] } -mergeAction :: MergeFlags -> [String] -> GlobalFlags -> IO ()+mergeAction :: MergeFlags -> [String] -> H.GlobalFlags -> IO () mergeAction flags extraArgs globalFlags = do let verbosity = fromFlag (mergeVerbosity flags)- overlayPath <- getOverlayPath verbosity (fromFlag $ globalPathToOverlay globalFlags)- let repo = defaultRepo overlayPath- merge verbosity repo (defaultRepoURI overlayPath) extraArgs overlayPath (fromFlag $ mergeCabalFlags flags)---------------------------------------------------------------------------- DistroMap--------------------------------------------------------------------------data DistroMapFlags = DistroMapFlags {- distroMapVerbosity :: Flag Verbosity- }--instance Monoid DistroMapFlags where- mempty = DistroMapFlags {- distroMapVerbosity = mempty- -- , mergeServerURI = mempty- }- mappend a b = DistroMapFlags {- distroMapVerbosity = combine distroMapVerbosity- }- where combine field = field a `mappend` field b--defaultDistroMapFlags :: DistroMapFlags-defaultDistroMapFlags = DistroMapFlags {- distroMapVerbosity = Flag normal- }--distroMapCommand :: CommandUI DistroMapFlags-distroMapCommand = CommandUI {- commandName = "distromap",- commandSynopsis = "Build a distromap file",- commandDescription = Just $ \_pname ->- "TODO: this is the commandDescription for distroMapCommand\n",- commandUsage = usagePackages "distromap",- commandDefaultFlags = defaultDistroMapFlags,- commandOptions = \_showOrParseArgs ->- [ optionVerbosity distroMapVerbosity (\v flags -> flags { distroMapVerbosity = v })- ]- }+ overlayPath <- getOverlayPath verbosity (fromFlag $ H.globalPathToOverlay globalFlags) -distroMapAction :: DistroMapFlags-> [String] -> GlobalFlags -> IO ()-distroMapAction flags extraArgs globalFlags = do- let verbosity = fromFlag (distroMapVerbosity flags)- overlayPath <- getOverlayPath verbosity (fromFlag $ globalPathToOverlay globalFlags)- let repo = defaultRepo overlayPath- portagePath <- getPortageDir verbosity globalFlags- distroMap verbosity repo portagePath overlayPath extraArgs+ H.withHackPortContext verbosity globalFlags $ \repoContext ->+ merge verbosity repoContext extraArgs overlayPath (fromFlag $ mergeCabalFlags flags) ----------------------------------------------------------------------- -- Utils ----------------------------------------------------------------------- -getServerURI :: String -> IO URI-getServerURI str =- case parseURI str of- Just uri -> return uri- Nothing -> throwEx (InvalidServer str)--reqArgFlag :: ArgPlaceHolder -> SFlags -> LFlags -> Description ->- (b -> Flag String) -> (Flag String -> b -> b) -> OptDescr b-reqArgFlag ad = reqArg ad (succeedReadE Flag) flagToList- usagePackages :: String -> String -> String usagePackages op_name pname = "Usage: " ++ pname ++ " " ++ op_name ++ " [FLAGS] [PACKAGE]\n\n"@@ -471,9 +338,9 @@ "Usage: " ++ pname ++ " " ++ flag_name ++ " [FLAGS]\n\n" ++ "Flags for " ++ flag_name ++ ":" -getPortageDir :: Verbosity -> GlobalFlags -> IO FilePath+getPortageDir :: Verbosity -> H.GlobalFlags -> IO FilePath getPortageDir verbosity globalFlags = do- let portagePathM = fromFlag (globalPathToPortage globalFlags)+ let portagePathM = fromFlag (H.globalPathToPortage globalFlags) portagePath <- case portagePathM of Nothing -> Host.portage_dir <$> Host.getInfo Just path -> return path@@ -486,45 +353,31 @@ -- Main ----------------------------------------------------------------------- -data GlobalFlags =- GlobalFlags { globalVersion :: Flag Bool- , globalNumericVersion :: Flag Bool- , globalPathToOverlay :: Flag (Maybe FilePath)- , globalPathToPortage :: Flag (Maybe FilePath)- }--defaultGlobalFlags :: GlobalFlags-defaultGlobalFlags =- GlobalFlags { globalVersion = Flag False- , globalNumericVersion = Flag False- , globalPathToOverlay = Flag Nothing- , globalPathToPortage = Flag Nothing- }--globalCommand :: CommandUI GlobalFlags+globalCommand :: CommandUI H.GlobalFlags globalCommand = CommandUI { commandName = "", commandSynopsis = "",- commandDescription = Just $ \_pname ->- "TODO: this is the commandDescription for globalCommand\n",+ commandDescription = Nothing,+ commandNotes = Nothing, commandUsage = \_ -> [],- commandDefaultFlags = defaultGlobalFlags,++ commandDefaultFlags = H.defaultGlobalFlags, commandOptions = \_showOrParseArgs -> [ option ['V'] ["version"] "Print version information"- globalVersion (\v flags -> flags { globalVersion = v })+ H.globalVersion (\v flags -> flags { H.globalVersion = v }) trueArg , option [] ["numeric-version"] "Print just the version number"- globalNumericVersion (\v flags -> flags { globalNumericVersion = v })+ H.globalNumericVersion (\v flags -> flags { H.globalNumericVersion = v }) trueArg , option ['p'] ["overlay-path"] "Override search path list where .hackport/ lives (default list: ['.', paludis-ovls or emerge-ovls])"- globalPathToOverlay (\ovrl_path flags -> flags { globalPathToOverlay = ovrl_path })+ H.globalPathToOverlay (\ovrl_path flags -> flags { H.globalPathToOverlay = ovrl_path }) (reqArg' "PATH" (Flag . Just) (\(Flag ms) -> catMaybes [ms])) , option [] ["portage-path"] "Override path to your portage tree"- globalPathToPortage (\port_path flags -> flags { globalPathToPortage = port_path })+ H.globalPathToPortage (\port_path flags -> flags { H.globalPathToPortage = port_path }) (reqArg' "PATH" (Flag . Just) (\(Flag ms) -> catMaybes [ms])) ] }@@ -537,8 +390,8 @@ CommandErrors errs -> printErrors errs CommandReadyToGo (globalflags, commandParse) -> do case commandParse of- _ | fromFlag (globalVersion globalflags) -> printVersion- | fromFlag (globalNumericVersion globalflags) -> printNumericVersion+ _ | fromFlag (H.globalVersion globalflags) -> printVersion+ | fromFlag (H.globalNumericVersion globalflags) -> printNumericVersion CommandHelp help -> printHelp help CommandList opts -> printOptionsList opts CommandErrors errs -> printErrors errs@@ -563,10 +416,8 @@ [ listCommand `commandAddAction` listAction , makeEbuildCommand `commandAddAction` makeEbuildAction , statusCommand `commandAddAction` statusAction- , diffCommand `commandAddAction` diffAction , updateCommand `commandAddAction` updateAction , mergeCommand `commandAddAction` mergeAction- , distroMapCommand `commandAddAction` distroMapAction ] main :: IO ()
Merge.hs view
@@ -31,10 +31,12 @@ -- cabal-install import Distribution.Client.IndexUtils ( getSourcePackages )+import qualified Distribution.Client.GlobalFlags as CabalInstall import qualified Distribution.Client.PackageIndex as Index import Distribution.Client.Types -- others+import qualified Data.List.Split as DLS import System.Directory ( getCurrentDirectory , setCurrentDirectory , createDirectoryIfMissing@@ -49,8 +51,6 @@ import qualified Portage.EMeta as EM import Error as E -import Network.URI- import qualified Portage.Cabal as Portage import qualified Portage.PackageId as Portage import qualified Portage.Version as Portage@@ -64,9 +64,6 @@ import qualified Merge.Dependencies as Merge -import qualified Util as U-- (<.>) :: String -> String -> String a <.> b = a ++ '.':b @@ -98,14 +95,14 @@ -- | Given a list of available packages, and maybe a preferred version, -- return the available package with that version. Latest version is chosen -- if no preference.-resolveVersion :: [SourcePackage] -> Maybe Cabal.Version -> Maybe SourcePackage+resolveVersion :: [UnresolvedSourcePackage] -> Maybe Cabal.Version -> Maybe UnresolvedSourcePackage resolveVersion avails Nothing = Just $ L.maximumBy (comparing (Cabal.pkgVersion . packageInfoId)) avails resolveVersion avails (Just ver) = listToMaybe (filter match avails) where match avail = ver == Cabal.pkgVersion (packageInfoId avail) -merge :: Verbosity -> Repo -> URI -> [String] -> FilePath -> Maybe String -> IO ()-merge verbosity repo _serverURI args overlayPath users_cabal_flags = do+merge :: Verbosity -> CabalInstall.RepoContext -> [String] -> FilePath -> Maybe String -> IO ()+merge verbosity repoContext args overlayPath users_cabal_flags = do (m_category, user_pName, m_version) <- case readPackageString args of Left err -> throwEx err@@ -125,7 +122,7 @@ overlay <- Overlay.loadLazy overlayPath -- portage_path <- Host.portage_dir `fmap` Host.getInfo -- portage <- Overlay.loadLazy portage_path- index <- fmap packageIndex $ getSourcePackages verbosity [ repo ]+ index <- fmap packageIndex $ getSourcePackages verbosity repoContext -- find all packages that maches the user specified package name availablePkgs <-@@ -186,7 +183,7 @@ user_renames = [ (cfn, ein) | ((Cabal.FlagName cfn, ein), Nothing) <- cn_in_mb ]- cn_in_mb = map read_fa $ U.split (== ',') user_fas_s+ cn_in_mb = map read_fa $ DLS.splitOn "," user_fas_s read_fa :: String -> ((Cabal.FlagName, String), Maybe Bool) read_fa [] = error $ "read_fas: empty flag?" read_fa (op:flag) =@@ -196,7 +193,7 @@ _ -> (get_rename (op:flag), Nothing) where get_rename :: String -> (Cabal.FlagName, String) get_rename s =- case U.split (== ':') s of+ case DLS.splitOn ":" s of [cabal_flag_name] -> (Cabal.FlagName cabal_flag_name, cabal_flag_name) [cabal_flag_name, iuse_name] -> (Cabal.FlagName cabal_flag_name, iuse_name) _ -> error $ "get_rename: too many components" ++ show (s)@@ -356,14 +353,6 @@ icalate _s [x] = [x] icalate s (x:xs) = (x ++ s) : icalate s xs - gamesFlags :: [String]- gamesFlags = ["--prefix=\"${GAMES_PREFIX}\""]-- addGamesFlags :: [String] -> [String]- addGamesFlags xs- | Portage.is_games_cat cat = xs ++ gamesFlags- | otherwise = xs- build_configure_call :: [String] -> [String] build_configure_call [] = [] build_configure_call conf_args = icalate " \\" $@@ -386,7 +375,8 @@ . (\e -> e { E.depend_extra = S.toList $ Merge.dep_e tdeps } ) . (\e -> e { E.rdepend = Merge.rdep tdeps} ) . (\e -> e { E.rdepend_extra = S.toList $ Merge.rdep_e tdeps } )- . (\e -> e { E.src_configure = build_configure_call $ addGamesFlags $ selected_flags (active_flags, user_specified_fas) } )+ . (\e -> e { E.src_configure = build_configure_call $+ selected_flags (active_flags, user_specified_fas) } ) . (\e -> e { E.iuse = E.iuse e ++ map to_iuse active_flag_descs }) . ( case requested_cabal_flags of Nothing -> id
Merge/Dependencies.hs view
@@ -114,7 +114,7 @@ where -- hasBuildableExes p = any (buildable . buildInfo) . executables $ p treatAsLibrary :: Bool- treatAsLibrary = isJust (Cabal.library pkg)+ treatAsLibrary = Cabal.libraries pkg /= [] -- without slot business raw_haskell_deps :: Portage.Dependency raw_haskell_deps = PN.normalize_depend $ Portage.DependAllOf $ haskellDependencies overlay (buildDepends pkg)@@ -230,14 +230,14 @@ --------------------------------------------------------------- findCLibs :: PackageDescription -> [Portage.Dependency]-findCLibs (PackageDescription { library = lib, executables = exes }) =+findCLibs (PackageDescription { libraries = libs, executables = exes }) = [ trace ("WARNING: This package depends on a C library we don't know the portage name for: " ++ p ++ ". Check the generated ebuild.") (any_c_p "unknown-c-lib" p) | p <- notFound ] ++ found where- libE = maybe [] (extraLibs.libBuildInfo) lib+ libE = concatMap (extraLibs . libBuildInfo) libs exeE = concatMap extraLibs (filter buildable (map buildInfo exes)) allE = libE ++ exeE @@ -357,6 +357,7 @@ , ("SDL_gfx", any_c_p "media-libs" "sdl-gfx") , ("SDL_image", any_c_p "media-libs" "sdl-image") , ("SDL_ttf", any_c_p "media-libs" "sdl-ttf")+ , ("odbc", any_c_p "dev-db" "unixODBC") ] ---------------------------------------------------------------@@ -364,7 +365,7 @@ --------------------------------------------------------------- buildToolsDependencies :: PackageDescription -> [Portage.Dependency]-buildToolsDependencies (PackageDescription { library = lib, executables = exes }) = nub $+buildToolsDependencies (PackageDescription { libraries = libs, executables = exes }) = nub $ [ case pkg of Just p -> p Nothing -> trace ("WARNING: Unknown build tool '" ++ pn ++ "'. Check the generated ebuild.")@@ -374,7 +375,7 @@ ] where cabalDeps = filter notProvided $ depL ++ depE- depL = maybe [] (buildTools.libBuildInfo) lib+ depL = concatMap (buildTools . libBuildInfo) libs depE = concatMap buildTools (filter buildable (map buildInfo exes)) notProvided (Cabal.Dependency (Cabal.PackageName pn) _range) = pn `notElem` buildToolsProvided @@ -405,10 +406,10 @@ --------------------------------------------------------------- pkgConfigDependencies :: Portage.Overlay -> PackageDescription -> [Portage.Dependency]-pkgConfigDependencies overlay (PackageDescription { library = lib, executables = exes }) = nub $ resolvePkgConfigs overlay cabalDeps+pkgConfigDependencies overlay (PackageDescription { libraries = libs, executables = exes }) = nub $ resolvePkgConfigs overlay cabalDeps where cabalDeps = depL ++ depE- depL = maybe [] (pkgconfigDepends.libBuildInfo) lib+ depL = concatMap (pkgconfigDepends . libBuildInfo) libs depE = concatMap pkgconfigDepends (filter buildable (map buildInfo exes)) resolvePkgConfigs :: Portage.Overlay -> [Cabal.Dependency] -> [Portage.Dependency]@@ -517,4 +518,11 @@ ,("libpq", ("dev-db", "postgresql", Portage.AnySlot)) ,("poppler-glib", ("app-text", "poppler", Portage.AnySlot)) ,("gsl", ("sci-libs", "gsl", Portage.AnySlot))+ ,("libvirt", ("app-emulation", "libvirt", Portage.AnySlot))++ ,("Qt5Core", ("dev-qt", "qtcore", Portage.GivenSlot "5"))+ ,("Qt5Gui", ("dev-qt", "qtgui", Portage.GivenSlot "5"))+ ,("Qt5Qml", ("dev-qt", "qtdeclarative", Portage.GivenSlot "5"))+ ,("Qt5Quick", ("dev-qt", "qtdeclarative", Portage.GivenSlot "5"))+ ,("Qt5Widgets", ("dev-qt", "qtwidgets", Portage.GivenSlot "5")) ]
Overlays.hs view
@@ -5,11 +5,10 @@ import Control.Monad import Data.List (nub, inits) import Data.Maybe (maybeToList, listToMaybe, isJust, fromJust)-import System.Directory+import qualified System.Directory as SD import System.FilePath ((</>), splitPath, joinPath) import Error-import CacheFile import Portage.Host -- cabal@@ -32,7 +31,7 @@ let loop [] = throwEx (MultipleOverlays mul) loop (x:xs) = do info verbosity $ "Checking '" ++ x ++ "'..."- found <- doesFileExist (cacheFile x)+ found <- SD.doesDirectoryExist (x </> ".hackport") if found then do info verbosity "OK!"@@ -62,10 +61,9 @@ getLocalOverlay :: IO (Maybe FilePath) getLocalOverlay = do- curDir <- getCurrentDirectory+ curDir <- SD.getCurrentDirectory let lookIn = map joinPath . reverse . inits . splitPath $ curDir fmap listToMaybe (filterM probe lookIn) where- probe dir = doesDirectoryExist (dir </> "dev-haskell")-+ probe dir = SD.doesDirectoryExist (dir </> "dev-haskell")
Portage/EBuild.hs view
@@ -7,7 +7,6 @@ ) where import Portage.Dependency-import qualified Portage.PackageId as PI import qualified Portage.Dependency.Normalize as PN import Data.String.Utils@@ -18,9 +17,7 @@ import Data.Version(Version(..)) import qualified Paths_hackport(version) -#if MIN_VERSION_time(1,5,0)-import qualified System.Locale as SL-#else+#if ! MIN_VERSION_time(1,5,0) import qualified System.Locale as TC #endif @@ -96,13 +93,13 @@ ss "# Distributed under the terms of the GNU General Public License v2". nl. ss "# $Id$". nl. nl.- ss "EAPI=5". nl.+ ss "EAPI=6". nl. nl. ss ("# ebuild generated by hackport " ++ hackportVersion ebuild). nl. sconcat (map (\(k, v) -> ss "#hackport: " . ss k . ss ": " . ss v . nl) $ used_options ebuild). nl. ss "CABAL_FEATURES=". quote' (sepBy " " $ features ebuild). nl.- ss "inherit haskell-cabal". if_games (ss " games") . nl.+ ss "inherit haskell-cabal". nl. nl. (case my_pn ebuild of Nothing -> id@@ -125,11 +122,6 @@ Nothing -> id Just _ -> nl. ss "S=". quote ("${WORKDIR}/${MY_P}"). nl). - if_games (nl . ss "pkg_setup() {" . nl.- ss (tabify_line " games_pkg_setup") . nl.- ss (tabify_line " haskell-cabal_pkg_setup") . nl.- ss "}" . nl).- verbatim (nl . ss "src_prepare() {" . nl) (src_prepare ebuild) (ss "}" . nl).@@ -138,20 +130,6 @@ (src_configure ebuild) (ss "}" . nl). - if_games (nl . ss "src_compile() {" . nl.- ss (tabify_line " haskell-cabal_src_compile") . nl.- ss "}" . nl).-- if_games (nl . ss "src_install() {" . nl.- ss (tabify_line " haskell-cabal_src_install") . nl.- ss (tabify_line " prepgamesdirs") . nl.- ss "}" . nl).-- if_games (nl . ss "pkg_postinst() {" . nl.- ss (tabify_line " haskell-cabal_pkg_postinst") . nl.- ss (tabify_line " games_pkg_postinst") . nl.- ss "}" . nl).- id $ [] where expandVars = replaceMultiVars [ ( name ebuild, "${PN}")@@ -162,10 +140,6 @@ toHttps = replace "http://github.com/" "https://github.com/" this_year :: String this_year = TC.formatTime TC.defaultTimeLocale "%Y" now- if_games :: DString -> DString- if_games ds = if PI.is_games_cat (PI.Category (category ebuild))- then ds- else id -- "+a" -> "a" -- "b" -> "b"
Portage/GHCCore.hs view
@@ -95,8 +95,7 @@ mkIndex :: [PackageIdentifier] -> InstalledPackageIndex mkIndex pids = fromList [ emptyInstalledPackageInfo- { IPI.installedPackageId = InstalledPackageId $ display name ++ "-" ++ display version- , sourcePackageId = pindex+ { sourcePackageId = pindex , exposed = True } | pindex@(PackageIdentifier name version) <- pids ]
Portage/Host.hs view
@@ -4,6 +4,7 @@ ) where import Util (run_cmd)+import qualified Data.List.Split as DLS import Data.Maybe (fromJust, isJust, catMaybes) import Control.Applicative ( (<$>) ) @@ -73,7 +74,7 @@ parsePaludisInfo :: String -> LocalInfo parsePaludisInfo text =- let chunks = splitBy (=="") . lines $ text+ let chunks = DLS.splitOn [""] . lines $ text repositories = catMaybes (map parseRepository chunks) in fromJust (mkLocalInfo repositories) where@@ -97,13 +98,6 @@ , portage_dir = gentooLocation , overlay_list = overlays })--splitBy :: (a -> Bool) -> [a] -> [[a]]-splitBy _ [] = []-splitBy c lst =- let (x,xs) = break c lst- (_,xs') = span c xs- in x : splitBy c xs' --------- -- Emerge
Portage/PackageId.hs view
@@ -12,12 +12,10 @@ normalizeCabalPackageName, normalizeCabalPackageId, packageIdToFilePath,- cabal_pn_to_PN,- is_games_cat+ cabal_pn_to_PN ) where import Data.Char-import qualified Data.List as L import qualified Distribution.Package as Cabal import Distribution.Text (Text(..))@@ -131,6 +129,3 @@ cabal_pn_to_PN :: Cabal.PackageName -> String cabal_pn_to_PN = map toLower . display--is_games_cat :: Category -> Bool-is_games_cat = L.isPrefixOf "games" . unCategory
− Progress.hs
@@ -1,61 +0,0 @@--------------------------------------------------------------------------------- |--- Module : Progress--- Copyright : (c) Duncan Coutts 2008--- License : BSD-like------ Portability : portable------ Common types for dependency resolution.-------------------------------------------------------------------------------module Progress (- Progress(..),- fold, unfold, fromList,- ) where--import Prelude hiding (fail)---- | A type to represent the unfolding of an expensive long running--- calculation that may fail. We may get intermediate steps before the final--- retult which may be used to indicate progress and\/or logging messages.----data Progress step fail done = Step step (Progress step fail done)- | Fail fail- | Done done---- | Consume a 'Progress' calculation. Much like 'foldr' for lists but with--- two base cases, one for a final result and one for failure.------ Eg to convert into a simple 'Either' result use:------ > foldProgress (flip const) Left Right----fold :: (step -> a -> a) -> (fail -> a) -> (done -> a)- -> Progress step fail done -> a-fold step fail done = go- where- go (Step s p) = step s (go p)- go (Fail f) = fail f- go (Done r) = done r--unfold :: (s -> Either (Either fail done) (step, s))- -> s -> Progress step fail done-unfold f = go- where- go s = case f s of- Left (Left fail) -> Fail fail- Left (Right done) -> Done done- Right (step, s') -> Step step (go s')--fromList :: [a] -> Progress () b [a]-fromList xs0 = unfold next xs0- where- next [] = Left (Right xs0)- next (_:xs) = Right ((), xs)--instance Functor (Progress step fail) where- fmap f = fold Step Fail (Done . f)--instance Monad (Progress step fail) where- return a = Done a- p >>= f = fold Step Fail f p
README.rst view
@@ -23,6 +23,7 @@ $ cd ~/overlays $ git clone git://github.com/gentoo-haskell/gentoo-haskell.git $ cd gentoo-haskell+ $ mkdir .hackport $ hackport update $ ls -1 .hackport/ 00-index.tar
Setup.hs view
@@ -1,5 +1,5 @@ #!/usr/bin/runhaskell-module Main where+module Main (main) where import Distribution.Simple
Status.hs view
@@ -28,16 +28,15 @@ import Control.Monad -- cabal-import Distribution.Client.Types ( Repo, SourcePackageDb(..), SourcePackage(..) )+import Distribution.Client.Types ( SourcePackageDb(..), SourcePackage(..) ) import Distribution.Verbosity import Distribution.Package (pkgName) import Distribution.Simple.Utils (comparing, die, equating) import Distribution.Text ( display, simpleParse ) -import qualified Distribution.Client.PackageIndex as CabalInstall+import qualified Distribution.Client.GlobalFlags as CabalInstall import qualified Distribution.Client.IndexUtils as CabalInstall--import Hackage (defaultRepo)+import qualified Distribution.Client.PackageIndex as CabalInstall data StatusDirection = PortagePlusOverlay@@ -76,9 +75,9 @@ -loadHackage :: Verbosity -> Distribution.Client.Types.Repo -> Overlay -> IO [[PackageId]]-loadHackage verbosity repo overlay = do- SourcePackageDb { packageIndex = pindex } <- CabalInstall.getSourcePackages verbosity [repo]+loadHackage :: Verbosity -> CabalInstall.RepoContext -> Overlay -> IO [[PackageId]]+loadHackage verbosity repoContext overlay = do+ SourcePackageDb { packageIndex = pindex } <- CabalInstall.getSourcePackages verbosity repoContext let get_cat cabal_pkg = case resolveCategories overlay (pkgName cabal_pkg) of [] -> Category "dev-haskell" [cat] -> cat@@ -89,11 +88,10 @@ (CabalInstall.allPackagesByName pindex) return pkg_infos -status :: Verbosity -> FilePath -> FilePath -> IO (Map PackageName [FileStatus ExistingEbuild])-status verbosity portdir overlaydir = do- let repo = defaultRepo overlaydir+status :: Verbosity -> FilePath -> FilePath -> CabalInstall.RepoContext -> IO (Map PackageName [FileStatus ExistingEbuild])+status verbosity portdir overlaydir repoContext = do overlay <- loadLazy overlaydir- hackage <- loadHackage verbosity repo overlay+ hackage <- loadHackage verbosity repoContext overlay portage <- filterByEmail ("haskell@gentoo.org" `elem`) <$> loadLazy portdir let (over, both, port) = portageDiff (overlayMap overlay) (overlayMap portage) @@ -132,8 +130,8 @@ ebuilds <- Map.lookup (packageId pkgid) overlay List.find (\e -> ebuildId e == pkgid) ebuilds -runStatus :: Verbosity -> FilePath -> FilePath -> StatusDirection -> [String] -> IO ()-runStatus verbosity portdir overlaydir direction pkgs = do+runStatus :: Verbosity -> FilePath -> FilePath -> StatusDirection -> [String] -> CabalInstall.RepoContext -> IO ()+runStatus verbosity portdir overlaydir direction pkgs repoContext = do let pkgFilter = case direction of OverlayToPortage -> toPortageFilter PortagePlusOverlay -> id@@ -142,7 +140,7 @@ case simpleParse p of Nothing -> die ("Could not parse package name: " ++ p ++ ". Format cat/pkg") Just pn -> return pn- tree0 <- status verbosity portdir overlaydir+ tree0 <- status verbosity portdir overlaydir repoContext let tree = pkgFilter tree0 if (null pkgs') then statusPrinter tree
Util.hs view
@@ -8,7 +8,6 @@ module Util ( run_cmd -- :: String -> IO (Maybe String)- , split -- :: (a -> Bool) -> [a] -> [[a]] ) where import System.IO@@ -30,10 +29,3 @@ return $ if (output == "" || exitCode /= ExitSuccess) then Nothing else Just output--split :: Eq a => (a -> Bool) -> [a] -> [[a]]-split _ [] = []-split p xs =- case break p xs of- (l, []) -> [l]- (l, _:r) -> l: split p r
+ cabal/.arcconfig view
@@ -0,0 +1,4 @@+{+ "repository.callsign" : "CABAL",+ "phabricator.uri" : "https://phabricator.haskell.org"+}
cabal/.gitignore view
@@ -15,9 +15,11 @@ /Cabal/dist/ /Cabal/tests/Setup /Cabal/Setup+/Cabal/source-file-list /cabal-install/dist/ /cabal-install/Setup+/cabal-install/source-file-list # editor temp files@@ -26,6 +28,7 @@ .#* *~ .*.swp+*.bak # GHC build @@ -41,3 +44,11 @@ # stack artifacts /.stack-work/++# Shake artifacts+.shake*+progress.txt++# test files+dist-test+register.sh
cabal/.travis.yml view
@@ -1,70 +1,37 @@ # NB: don't set `language: haskell` here -# The following enables several GHC versions to be tested; often it's enough to test only against the last release in a major GHC version. Feel free to omit lines listings versions you don't need/want testing for.+# The following enables several GHC versions to be tested; often it's enough to+# test only against the last release in a major GHC version. Feel free to omit+# lines listings versions you don't need/want testing for. env: - GHCVER=7.4.2 - GHCVER=7.6.3 - GHCVER=7.8.4- - GHCVER=7.10.1+ - GHCVER=7.10.3+ - GHCVER=8.0.1 TEST_OLDER=YES+ # TODO add PARSEC_BUNDLED=YES when it's so - GHCVER=head # Note: the distinction between `before_install` and `install` is not important. before_install: - travis_retry sudo add-apt-repository -y ppa:hvr/ghc - travis_retry sudo apt-get update- - travis_retry sudo apt-get install cabal-install-1.22 ghc-$GHCVER-prof ghc-$GHCVER-dyn happy- - export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/1.22/bin:$PATH+ - travis_retry sudo apt-get install cabal-install-1.24 ghc-$GHCVER-prof ghc-$GHCVER-dyn happy+ - if [ "$TEST_OLDER" == "YES" ]; then travis_retry sudo apt-get install ghc-7.0.4-prof ghc-7.0.4-dyn ghc-7.2.2-prof ghc-7.2.2-dyn; fi+ - export PATH=$HOME/.cabal/bin:/opt/ghc/$GHCVER/bin:/opt/cabal/1.24/bin:$PATH+ - git version install: - cabal update-# We intentionally do not install anything before trying to build Cabal because-# it should build with each supported GHC version out-of-the-box.+ # We intentionally do not install anything before trying to build Cabal because+ # it should build with each supported GHC version out-of-the-box. -# Here starts the actual work to be performed for the package under test; any command which exits with a non-zero exit code causes the build to fail.-# Using ./dist/setup/setup here instead of cabal-install to avoid breakage-# when the build config format changed+# Here starts the actual work to be performed for the package under test; any+# command which exits with a non-zero exit code causes the build to fail. Using+# ./dist/setup/setup here instead of cabal-install to avoid breakage when the+# build config format changed. script:- - cd Cabal- - mkdir -p ./dist/setup- - cp Setup.hs ./dist/setup/setup.hs-# Should be able to build setup without extra dependencies- - /opt/ghc/$GHCVER/bin/ghc --make -odir ./dist/setup -hidir ./dist/setup -i -i. ./dist/setup/setup.hs -o ./dist/setup/setup -Wall -Werror -threaded # the command cabal-install would use to build setup--# Need extra dependencies for test suite- - cabal install --only-dependencies --enable-tests- - sudo /opt/ghc/$GHCVER/bin/ghc-pkg recache- - /opt/ghc/$GHCVER/bin/ghc-pkg recache --user-- - ./dist/setup/setup configure --user --enable-tests --enable-benchmarks --ghc-option=-Werror -v2 # -v2 provides useful information for debugging- - ./dist/setup/setup build # this builds all libraries and executables (including tests/benchmarks)- - ./dist/setup/setup haddock # see #2198- - ./dist/setup/setup test --show-details=streaming- - cabal check- - cabal sdist # tests that a source-distribution can be generated--# The following scriptlet checks that the resulting source distribution can be built & installed- - function install_from_tarball {- export SRC_TGZ=$(cabal info . | awk '{print $2 ".tar.gz";exit}') ;- if [ -f "dist/$SRC_TGZ" ]; then- cabal install "dist/$SRC_TGZ" -v2;- else- echo "expected 'dist/$SRC_TGZ' not found";- exit 1;- fi- }- - install_from_tarball--# Also build cabal-install.- - cd ../cabal-install- - cabal sandbox init- - cabal sandbox add-source ../Cabal- - cabal install --dependencies-only --enable-tests- - cabal configure --enable-tests --ghc-option=-Werror- - cabal build- - cabal test- - cabal check- - cabal sdist- - install_from_tarball+ - ./travis-script.sh matrix: allow_failures:
cabal/Cabal/Cabal.cabal view
@@ -1,5 +1,5 @@ name: Cabal-version: 1.23.0.0+version: 1.25.0.0 copyright: 2003-2006, Isaac Jones 2005-2011, Duncan Coutts license: BSD3@@ -19,9 +19,9 @@ organizing, and cataloging Haskell libraries and tools. category: Distribution cabal-version: >=1.10-build-type: Custom--- Even though we do use the default Setup.lhs it's vital to bootstrapping--- that we build Setup.lhs using our own local Cabal source code.+build-type: Simple+-- If we use a new Cabal feature, this needs to be changed to Custom so+-- we can bootstrap. extra-source-files: README.md tests/README.md changelog@@ -29,7 +29,13 @@ doc/installing-packages.markdown doc/misc.markdown - -- Generated with 'misc/gen-extra-source-files.sh' & 'M-x sort-lines':+ -- Generated with 'misc/gen-extra-source-files.sh'+ -- Do NOT edit this section manually; instead, run the script.+ -- BEGIN gen-extra-source-files+ tests/PackageTests/AllowNewer/AllowNewer.cabal+ tests/PackageTests/AllowNewer/benchmarks/Bench.hs+ tests/PackageTests/AllowNewer/src/Foo.hs+ tests/PackageTests/AllowNewer/tests/Test.hs tests/PackageTests/BenchmarkExeV10/Foo.hs tests/PackageTests/BenchmarkExeV10/benchmarks/bench-Foo.hs tests/PackageTests/BenchmarkExeV10/my.cabal@@ -74,18 +80,78 @@ tests/PackageTests/BuildDeps/TargetSpecificDeps3/MyLibrary.hs tests/PackageTests/BuildDeps/TargetSpecificDeps3/lemon.hs tests/PackageTests/BuildDeps/TargetSpecificDeps3/my.cabal+ tests/PackageTests/BuildTestSuiteDetailedV09/Dummy2.hs+ tests/PackageTests/BuildableField/BuildableField.cabal+ tests/PackageTests/BuildableField/Main.hs tests/PackageTests/CMain/Bar.hs- tests/PackageTests/CMain/Setup.hs tests/PackageTests/CMain/foo.c tests/PackageTests/CMain/my.cabal+ tests/PackageTests/Configure/A.hs+ tests/PackageTests/Configure/Setup.hs+ tests/PackageTests/Configure/X11.cabal+ tests/PackageTests/CopyComponent/Exe/Main.hs+ tests/PackageTests/CopyComponent/Exe/Main2.hs+ tests/PackageTests/CopyComponent/Exe/myprog.cabal+ tests/PackageTests/CopyComponent/Lib/Main.hs+ tests/PackageTests/CopyComponent/Lib/p.cabal+ tests/PackageTests/CopyComponent/Lib/src/P.hs+ tests/PackageTests/CustomPreProcess/Hello.hs+ tests/PackageTests/CustomPreProcess/MyCustomPreprocessor.hs+ tests/PackageTests/CustomPreProcess/Setup.hs+ tests/PackageTests/CustomPreProcess/internal-preprocessor-test.cabal tests/PackageTests/DeterministicAr/Lib.hs tests/PackageTests/DeterministicAr/my.cabal+ tests/PackageTests/DuplicateModuleName/DuplicateModuleName.cabal+ tests/PackageTests/DuplicateModuleName/src/Foo.hs+ tests/PackageTests/DuplicateModuleName/tests/Foo.hs+ tests/PackageTests/DuplicateModuleName/tests2/Foo.hs tests/PackageTests/EmptyLib/empty/empty.cabal+ tests/PackageTests/GhcPkgGuess/SameDirectory/SameDirectory.cabal+ tests/PackageTests/GhcPkgGuess/SameDirectory/ghc+ tests/PackageTests/GhcPkgGuess/SameDirectory/ghc-pkg+ tests/PackageTests/GhcPkgGuess/SameDirectoryGhcVersion/SameDirectory.cabal+ tests/PackageTests/GhcPkgGuess/SameDirectoryGhcVersion/ghc-7.10+ tests/PackageTests/GhcPkgGuess/SameDirectoryGhcVersion/ghc-pkg-ghc-7.10+ tests/PackageTests/GhcPkgGuess/SameDirectoryVersion/SameDirectory.cabal+ tests/PackageTests/GhcPkgGuess/SameDirectoryVersion/ghc-7.10+ tests/PackageTests/GhcPkgGuess/SameDirectoryVersion/ghc-pkg-7.10+ tests/PackageTests/GhcPkgGuess/Symlink/SameDirectory.cabal+ tests/PackageTests/GhcPkgGuess/Symlink/bin/ghc+ tests/PackageTests/GhcPkgGuess/Symlink/bin/ghc-pkg+ tests/PackageTests/GhcPkgGuess/SymlinkGhcVersion/SameDirectory.cabal+ tests/PackageTests/GhcPkgGuess/SymlinkGhcVersion/bin/ghc-7.10+ tests/PackageTests/GhcPkgGuess/SymlinkGhcVersion/bin/ghc-pkg-7.10+ tests/PackageTests/GhcPkgGuess/SymlinkVersion/SameDirectory.cabal+ tests/PackageTests/GhcPkgGuess/SymlinkVersion/bin/ghc-7.10+ tests/PackageTests/GhcPkgGuess/SymlinkVersion/bin/ghc-pkg-ghc-7.10 tests/PackageTests/Haddock/CPP.hs tests/PackageTests/Haddock/Literate.lhs tests/PackageTests/Haddock/NoCPP.hs tests/PackageTests/Haddock/Simple.hs tests/PackageTests/Haddock/my.cabal+ tests/PackageTests/HaddockNewline/A.hs+ tests/PackageTests/HaddockNewline/HaddockNewline.cabal+ tests/PackageTests/HaddockNewline/Setup.hs+ tests/PackageTests/InternalLibraries/Executable/exe/Main.hs+ tests/PackageTests/InternalLibraries/Executable/foo.cabal+ tests/PackageTests/InternalLibraries/Executable/src/Foo.hs+ tests/PackageTests/InternalLibraries/Library/fooexe/Main.hs+ tests/PackageTests/InternalLibraries/Library/fooexe/fooexe.cabal+ tests/PackageTests/InternalLibraries/Library/foolib/Foo.hs+ tests/PackageTests/InternalLibraries/Library/foolib/foolib.cabal+ tests/PackageTests/InternalLibraries/Library/foolib/private/Internal.hs+ tests/PackageTests/InternalLibraries/p/Foo.hs+ tests/PackageTests/InternalLibraries/p/p.cabal+ tests/PackageTests/InternalLibraries/p/p/P.hs+ tests/PackageTests/InternalLibraries/p/q/Q.hs+ tests/PackageTests/InternalLibraries/q/Q.hs+ tests/PackageTests/InternalLibraries/q/q.cabal+ tests/PackageTests/Macros/A.hs+ tests/PackageTests/Macros/B.hs+ tests/PackageTests/Macros/Main.hs+ tests/PackageTests/Macros/macros.cabal+ tests/PackageTests/Macros/src/C.hs+ tests/PackageTests/Options.hs tests/PackageTests/OrderFlags/Foo.hs tests/PackageTests/OrderFlags/my.cabal tests/PackageTests/PathsModule/Executable/Main.hs@@ -94,6 +160,9 @@ tests/PackageTests/PreProcess/Foo.hsc tests/PackageTests/PreProcess/Main.hs tests/PackageTests/PreProcess/my.cabal+ tests/PackageTests/PreProcessExtraSources/Foo.hsc+ tests/PackageTests/PreProcessExtraSources/Main.hs+ tests/PackageTests/PreProcessExtraSources/my.cabal tests/PackageTests/ReexportedModules/ReexportedModules.cabal tests/PackageTests/TemplateHaskell/dynamic/Exe.hs tests/PackageTests/TemplateHaskell/dynamic/Lib.hs@@ -107,20 +176,33 @@ tests/PackageTests/TemplateHaskell/vanilla/Lib.hs tests/PackageTests/TemplateHaskell/vanilla/TH.hs tests/PackageTests/TemplateHaskell/vanilla/my.cabal+ tests/PackageTests/TestNameCollision/child/Child.hs+ tests/PackageTests/TestNameCollision/child/child.cabal+ tests/PackageTests/TestNameCollision/child/tests/Test.hs+ tests/PackageTests/TestNameCollision/parent/Parent.hs+ tests/PackageTests/TestNameCollision/parent/parent.cabal tests/PackageTests/TestOptions/TestOptions.cabal tests/PackageTests/TestOptions/test-TestOptions.hs tests/PackageTests/TestStanza/my.cabal tests/PackageTests/TestSuiteTests/ExeV10/Foo.hs tests/PackageTests/TestSuiteTests/ExeV10/my.cabal tests/PackageTests/TestSuiteTests/ExeV10/tests/test-Foo.hs- tests/PackageTests/TestSuiteTests/LibV09/LibV09.cabal+ tests/PackageTests/TestSuiteTests/ExeV10/tests/test-Short.hs tests/PackageTests/TestSuiteTests/LibV09/Lib.hs+ tests/PackageTests/TestSuiteTests/LibV09/LibV09.cabal tests/PackageTests/TestSuiteTests/LibV09/tests/Deadlock.hs+ tests/PackageTests/Tests.hs+ tests/PackageTests/UniqueIPID/P1/M.hs+ tests/PackageTests/UniqueIPID/P1/my.cabal+ tests/PackageTests/UniqueIPID/P2/M.hs+ tests/PackageTests/UniqueIPID/P2/my.cabal+ tests/PackageTests/multInst/my.cabal tests/Setup.hs tests/hackage/check.sh tests/hackage/download.sh tests/hackage/unpack.sh tests/misc/ghc-supported-languages.hs+ -- END gen-extra-source-files source-repository head type: git@@ -132,21 +214,21 @@ library build-depends:- base >= 4.4 && < 5,- deepseq >= 1.3 && < 1.5,- filepath >= 1 && < 1.5,- directory >= 1 && < 1.3,- process >= 1.1.0.1 && < 1.4,- time >= 1.1 && < 1.6,- containers >= 0.1 && < 0.6, array >= 0.1 && < 0.6,- pretty >= 1 && < 1.2,- bytestring >= 0.9+ base >= 4.5 && < 5,+ bytestring >= 0.9 && < 1,+ containers >= 0.4 && < 0.6,+ deepseq >= 1.3 && < 1.5,+ directory >= 1.1 && < 1.3,+ filepath >= 1.3 && < 1.5,+ pretty >= 1.1 && < 1.2,+ process >= 1.1.0.1 && < 1.5,+ time >= 1.4 && < 1.7 if flag(bundled-binary-generic) build-depends: binary >= 0.5 && < 0.7 else- build-depends: binary >= 0.7 && < 0.8+ build-depends: binary >= 0.7 && < 0.9 -- Needed for GHC.Generics before GHC 7.6 if impl(ghc < 7.6)@@ -154,15 +236,24 @@ if !os(windows) build-depends:- unix >= 2.0 && < 2.8+ unix >= 2.5 && < 2.8 + if os(windows)+ build-depends:+ Win32 >= 2.2 && < 2.4+ ghc-options: -Wall -fno-ignore-asserts -fwarn-tabs+ if impl(ghc >= 8.0)+ ghc-options: -Wcompat -Wnoncanonical-monad-instances+ -Wnoncanonical-monadfail-instances exposed-modules: Distribution.Compat.CreatePipe Distribution.Compat.Environment Distribution.Compat.Exception+ Distribution.Compat.Internal.TempFile Distribution.Compat.ReadP+ Distribution.Compat.Semigroup Distribution.Compiler Distribution.InstalledPackageInfo Distribution.License@@ -232,16 +323,16 @@ Distribution.Verbosity Distribution.Version Language.Haskell.Extension+ Distribution.Compat.Binary other-modules:- Distribution.Compat.Binary Distribution.Compat.CopyFile- Distribution.Compat.TempFile+ Distribution.Compat.MonadFail Distribution.GetOpt Distribution.Lex Distribution.Simple.GHC.Internal- Distribution.Simple.GHC.IPI641 Distribution.Simple.GHC.IPI642+ Distribution.Simple.GHC.IPIConvert Distribution.Simple.GHC.ImplInfo Paths_Cabal @@ -251,25 +342,33 @@ Distribution.Compat.Binary.Generic default-language: Haskell98- default-extensions: CPP+ -- starting with GHC 7.0, rely on {-# LANGUAGE CPP #-} instead+ if !impl(ghc >= 7.0)+ default-extensions: CPP -- Small, fast running tests. test-suite unit-tests type: exitcode-stdio-1.0 hs-source-dirs: tests other-modules:+ Test.Laws+ Test.QuickCheck.Utils UnitTests.Distribution.Compat.CreatePipe UnitTests.Distribution.Compat.ReadP UnitTests.Distribution.Simple.Program.Internal+ UnitTests.Distribution.Simple.Utils+ UnitTests.Distribution.System UnitTests.Distribution.Utils.NubList+ UnitTests.Distribution.Version main-is: UnitTests.hs build-depends: base,+ directory, tasty, tasty-hunit, tasty-quickcheck, pretty,- QuickCheck < 2.9,+ QuickCheck >= 2.7 && < 2.9, Cabal ghc-options: -Wall default-language: Haskell98@@ -279,57 +378,28 @@ type: exitcode-stdio-1.0 main-is: PackageTests.hs other-modules:- PackageTests.BenchmarkExeV10.Check- PackageTests.BenchmarkOptions.Check PackageTests.BenchmarkStanza.Check- PackageTests.BuildDeps.GlobalBuildDepsNotAdditive1.Check- PackageTests.BuildDeps.GlobalBuildDepsNotAdditive2.Check- PackageTests.BuildDeps.InternalLibrary0.Check- PackageTests.BuildDeps.InternalLibrary1.Check- PackageTests.BuildDeps.InternalLibrary2.Check- PackageTests.BuildDeps.InternalLibrary3.Check- PackageTests.BuildDeps.InternalLibrary4.Check- PackageTests.BuildDeps.SameDepsAllRound.Check- PackageTests.BuildDeps.TargetSpecificDeps1.Check- PackageTests.BuildDeps.TargetSpecificDeps2.Check- PackageTests.BuildDeps.TargetSpecificDeps3.Check- PackageTests.CMain.Check- PackageTests.DeterministicAr.Check- PackageTests.EmptyLib.Check- PackageTests.Haddock.Check- PackageTests.OrderFlags.Check- PackageTests.PackageTester- PackageTests.PathsModule.Executable.Check- PackageTests.PathsModule.Library.Check- PackageTests.PreProcess.Check- PackageTests.PreProcessExtraSources.Check- PackageTests.ReexportedModules.Check- PackageTests.TemplateHaskell.Check- PackageTests.TestOptions.Check PackageTests.TestStanza.Check+ PackageTests.DeterministicAr.Check PackageTests.TestSuiteTests.ExeV10.Check- PackageTests.TestSuiteTests.LibV09.Check- Test.Distribution.Version- Test.Laws- Test.QuickCheck.Utils+ PackageTests.PackageTester hs-source-dirs: tests build-depends: base, containers,+ tagged, tasty,- tasty-quickcheck, tasty-hunit,- QuickCheck >= 2.1.0.1 && < 2.9,+ transformers, Cabal, process, directory, filepath,- extensible-exceptions, bytestring, regex-posix, old-time if !os(windows) build-depends: unix- ghc-options: -Wall+ ghc-options: -Wall -rtsopts default-extensions: CPP default-language: Haskell98
cabal/Cabal/Distribution/Compat/Binary/Class.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE CPP, FlexibleContexts #-}+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE Trustworthy #-} {-# LANGUAGE DefaultSignatures #-} -----------------------------------------------------------------------------@@ -53,13 +53,8 @@ import GHC.Generics ------ This isn't available in older Hugs or older GHC----#if __GLASGOW_HASKELL__ >= 606 import qualified Data.Sequence as Seq import qualified Data.Foldable as Fold-#endif ------------------------------------------------------------------------ @@ -467,11 +462,6 @@ ------------------------------------------------------------------------ -- Queues and Sequences -#if __GLASGOW_HASKELL__ >= 606------ This is valid Hugs, but you need the most recent Hugs---- instance (Binary e) => Binary (Seq.Seq e) where put s = put (Seq.length s) >> Fold.mapM_ put s get = do n <- get :: Get Int@@ -480,8 +470,6 @@ rep xs n g = xs `seq` n `seq` do x <- g rep (xs Seq.|> x) (n-1) g--#endif ------------------------------------------------------------------------ -- Floating point
cabal/Cabal/Distribution/Compat/CopyFile.hs view
@@ -11,24 +11,22 @@ setDirOrdinary, ) where +import Distribution.Compat.Exception+import Distribution.Compat.Internal.TempFile import Control.Monad ( when, unless ) import Control.Exception- ( bracket, bracketOnError, throwIO )+ ( bracketOnError, throwIO ) import qualified Data.ByteString.Lazy as BSL-import Distribution.Compat.Exception- ( catchIO ) import System.IO.Error ( ioeSetLocation ) import System.Directory ( doesFileExist, renameFile, removeFile )-import Distribution.Compat.TempFile- ( openBinaryTempFile ) import System.FilePath ( takeDirectory ) import System.IO- ( openBinaryFile, IOMode(ReadMode), hClose, hGetBuf, hPutBuf+ ( IOMode(ReadMode), hClose, hGetBuf, hPutBuf , withBinaryFile ) import Foreign ( allocaBytes )@@ -69,7 +67,7 @@ copyFile fromFPath toFPath = copy `catchIO` (\ioe -> throwIO (ioeSetLocation ioe "copyFile"))- where copy = bracket (openBinaryFile fromFPath ReadMode) hClose $ \hFrom ->+ where copy = withBinaryFile fromFPath ReadMode $ \hFrom -> bracketOnError openTmp cleanTmp $ \(tmpFPath, hTmp) -> do allocaBytes bufferSize $ copyContents hFrom hTmp hClose hTmp@@ -100,8 +98,7 @@ filesEqual f1 f2 = do ex1 <- doesFileExist f1 ex2 <- doesFileExist f2- if not (ex1 && ex2) then return False else do-+ if not (ex1 && ex2) then return False else withBinaryFile f1 ReadMode $ \h1 -> withBinaryFile f2 ReadMode $ \h2 -> do c1 <- BSL.hGetContents h1
+ cabal/Cabal/Distribution/Compat/Internal/TempFile.hs view
@@ -0,0 +1,124 @@+{-# LANGUAGE CPP #-}+{-# OPTIONS_HADDOCK hide #-}+module Distribution.Compat.Internal.TempFile (+ openTempFile,+ openBinaryTempFile,+ openNewBinaryFile,+ createTempDirectory,+ ) where++import Distribution.Compat.Exception++import System.FilePath ((</>))+import Foreign.C (CInt, eEXIST, getErrno, errnoToIOError)++import System.IO (Handle, openTempFile, openBinaryTempFile)+import Data.Bits ((.|.))+import System.Posix.Internals (c_open, c_close, o_CREAT, o_EXCL, o_RDWR,+ o_BINARY, o_NONBLOCK, o_NOCTTY,+ withFilePath, c_getpid)+import System.IO.Error (isAlreadyExistsError)+import GHC.IO.Handle.FD (fdToHandle)+import Control.Exception (onException)++#if defined(mingw32_HOST_OS) || defined(ghcjs_HOST_OS)+import System.Directory ( createDirectory )+#else+import qualified System.Posix+#endif++-- ------------------------------------------------------------+-- * temporary files+-- ------------------------------------------------------------++-- This is here for Haskell implementations that do not come with+-- System.IO.openTempFile. This includes nhc-1.20, hugs-2006.9.+-- TODO: Not sure about JHC+-- TODO: This file should probably be removed.++-- This is a copy/paste of the openBinaryTempFile definition, but+-- if uses 666 rather than 600 for the permissions. The base library+-- needs to be changed to make this better.+openNewBinaryFile :: FilePath -> String -> IO (FilePath, Handle)+openNewBinaryFile dir template = do+ pid <- c_getpid+ findTempName pid+ where+ -- We split off the last extension, so we can use .foo.ext files+ -- for temporary files (hidden on Unix OSes). Unfortunately we're+ -- below file path in the hierarchy here.+ (prefix,suffix) =+ case break (== '.') $ reverse template of+ -- First case: template contains no '.'s. Just re-reverse it.+ (rev_suffix, "") -> (reverse rev_suffix, "")+ -- Second case: template contains at least one '.'. Strip the+ -- dot from the prefix and prepend it to the suffix (if we don't+ -- do this, the unique number will get added after the '.' and+ -- thus be part of the extension, which is wrong.)+ (rev_suffix, '.':rest) -> (reverse rest, '.':reverse rev_suffix)+ -- Otherwise, something is wrong, because (break (== '.')) should+ -- always return a pair with either the empty string or a string+ -- beginning with '.' as the second component.+ _ -> error "bug in System.IO.openTempFile"++ oflags = rw_flags .|. o_EXCL .|. o_BINARY++ findTempName x = do+ fd <- withFilePath filepath $ \ f ->+ c_open f oflags 0o666+ if fd < 0+ then do+ errno <- getErrno+ if errno == eEXIST+ then findTempName (x+1)+ else ioError (errnoToIOError "openNewBinaryFile" errno Nothing (Just dir))+ else do+ -- TODO: We want to tell fdToHandle what the file path is,+ -- as any exceptions etc will only be able to report the+ -- FD currently+ h <- fdToHandle fd `onException` c_close fd+ return (filepath, h)+ where+ filename = prefix ++ show x ++ suffix+ filepath = dir `combine` filename++ -- FIXME: bits copied from System.FilePath+ combine a b+ | null b = a+ | null a = b+ | last a == pathSeparator = a ++ b+ | otherwise = a ++ [pathSeparator] ++ b++-- FIXME: Should use System.FilePath library+pathSeparator :: Char+#ifdef mingw32_HOST_OS+pathSeparator = '\\'+#else+pathSeparator = '/'+#endif++-- FIXME: Copied from GHC.Handle+std_flags, output_flags, rw_flags :: CInt+std_flags = o_NONBLOCK .|. o_NOCTTY+output_flags = std_flags .|. o_CREAT+rw_flags = output_flags .|. o_RDWR++createTempDirectory :: FilePath -> String -> IO FilePath+createTempDirectory dir template = do+ pid <- c_getpid+ findTempName pid+ where+ findTempName x = do+ let dirpath = dir </> template ++ "-" ++ show x+ r <- tryIO $ mkPrivateDir dirpath+ case r of+ Right _ -> return dirpath+ Left e | isAlreadyExistsError e -> findTempName (x+1)+ | otherwise -> ioError e++mkPrivateDir :: String -> IO ()+#if defined(mingw32_HOST_OS) || defined(ghcjs_HOST_OS)+mkPrivateDir s = createDirectory s+#else+mkPrivateDir s = System.Posix.createDirectory s 0o700+#endif
+ cabal/Cabal/Distribution/Compat/MonadFail.hs view
@@ -0,0 +1,36 @@+{-# LANGUAGE CPP #-}++-- | Compatibility layer for "Control.Monad.Fail"+module Distribution.Compat.MonadFail ( MonadFail(fail) ) where+#if __GLASGOW_HASKELL__ >= 800+-- provided by base-4.9.0.0 and later+import Control.Monad.Fail (MonadFail(fail))+#else+-- the following code corresponds to+-- http://hackage.haskell.org/package/fail-4.9.0.0+import qualified Prelude as P+import Prelude hiding (fail)++import Text.ParserCombinators.ReadP+import Text.ParserCombinators.ReadPrec++class Monad m => MonadFail m where+ fail :: String -> m a++-- instances provided by base-4.9++instance MonadFail Maybe where+ fail _ = Nothing++instance MonadFail [] where+ fail _ = []++instance MonadFail IO where+ fail = P.fail++instance MonadFail ReadPrec where+ fail = P.fail -- = P (\_ -> fail s)++instance MonadFail ReadP where+ fail = P.fail+#endif
cabal/Cabal/Distribution/Compat/ReadP.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE CPP #-} ----------------------------------------------------------------------------- -- | -- Module : Distribution.Compat.ReadP@@ -70,12 +69,11 @@ ) where -import Control.Monad( MonadPlus(..), liftM, liftM2, ap )+import qualified Distribution.Compat.MonadFail as Fail++import Control.Monad( MonadPlus(..), liftM, liftM2, replicateM, ap, (>=>) ) import Data.Char (isSpace)-#if __GLASGOW_HASKELL__ < 710-import Control.Applicative (Applicative(..))-#endif-import Control.Applicative (Alternative(empty, (<|>)))+import Control.Applicative as AP (Applicative(..), Alternative(empty, (<|>))) infixr 5 +++, <++ @@ -96,18 +94,21 @@ fmap = liftM instance Applicative (P s) where- pure = return+ pure x = Result x Fail (<*>) = ap instance Monad (P s) where- return x = Result x Fail+ return = AP.pure - (Get f) >>= k = Get (\c -> f c >>= k)- (Look f) >>= k = Look (\s -> f s >>= k)+ (Get f) >>= k = Get (f >=> k)+ (Look f) >>= k = Look (f >=> k) Fail >>= _ = Fail (Result x p) >>= k = k x `mplus` (p >>= k) (Final r) >>= k = final [ys' | (x,s) <- r, ys' <- run (k x) s] + fail = Fail.fail++instance Fail.MonadFail (P s) where fail _ = Fail instance Alternative (P s) where@@ -155,14 +156,17 @@ fmap h (R f) = R (\k -> f (k . h)) instance Applicative (Parser r s) where- pure = return+ pure x = R (\k -> k x) (<*>) = ap instance Monad (Parser r s) where- return x = R (\k -> k x)- fail _ = R (\_ -> Fail)+ return = AP.pure+ fail = Fail.fail R m >>= f = R (\k -> m (\a -> let R m' = f a in m' k)) +instance Fail.MonadFail (Parser r s) where+ fail _ = R (const Fail)+ --instance MonadPlus (Parser r s) where -- mzero = pfail -- mplus = (+++)@@ -197,7 +201,7 @@ pfail :: ReadP r a -- ^ Always fails.-pfail = R (\_ -> Fail)+pfail = R (const Fail) (+++) :: ReadP r a -> ReadP r a -> ReadP r a -- ^ Symmetric choice.@@ -230,7 +234,7 @@ where gath l (Get f) = Get (\c -> gath (l.(c:)) (f c)) gath _ Fail = Fail- gath l (Look f) = Look (\s -> gath l (f s))+ gath l (Look f) = Look (gath l . f) gath l (Result k p) = k (l []) `mplus` gath l p gath _ (Final _) = error "do not use readS_to_P in gather!" @@ -250,9 +254,9 @@ -- ^ Parses and returns the specified string. string this = do s <- look; scan this s where- scan [] _ = do return this- scan (x:xs) (y:ys) | x == y = do get >> scan xs ys- scan _ _ = do pfail+ scan [] _ = return this+ scan (x:xs) (y:ys) | x == y = get >> scan xs ys+ scan _ _ = pfail munch :: (Char -> Bool) -> ReadP r String -- ^ Parses the first zero or more characters satisfying the predicate.@@ -288,7 +292,7 @@ count :: Int -> ReadP r a -> ReadP r [a] -- ^ @ count n p @ parses @n@ occurrences of @p@ in sequence. A list of -- results is returned.-count n p = sequence (replicate n p)+count n p = replicateM n p between :: ReadP r open -> ReadP r close -> ReadP r a -> ReadP r a -- ^ @ between open close p @ parses @open@, followed by @p@ and finally
+ cabal/Cabal/Distribution/Compat/Semigroup.hs view
@@ -0,0 +1,171 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE TypeOperators #-}++-- | Compatibility layer for "Data.Semigroup"+module Distribution.Compat.Semigroup+ ( Semigroup((<>))+ , Mon.Monoid(..)+ , All(..)+ , Any(..)++ , Last'(..)++ , gmappend+ , gmempty+ ) where++import Distribution.Compat.Binary (Binary)++import Control.Applicative as App+import GHC.Generics+#if __GLASGOW_HASKELL__ >= 711+-- Data.Semigroup is available since GHC 8.0/base-4.9+import Data.Semigroup+import qualified Data.Monoid as Mon+#else+-- provide internal simplified non-exposed class for older GHCs+import Data.Monoid as Mon (Monoid(..), All(..), Any(..), Dual(..))+-- containers+import Data.Set (Set)+import Data.IntSet (IntSet)+import Data.Map (Map)+import Data.IntMap (IntMap)+++class Semigroup a where+ (<>) :: a -> a -> a++-- several primitive instances+instance Semigroup () where+ _ <> _ = ()++instance Semigroup [a] where+ (<>) = (++)++instance Semigroup a => Semigroup (Dual a) where+ Dual a <> Dual b = Dual (b <> a)++instance Semigroup a => Semigroup (Maybe a) where+ Nothing <> b = b+ a <> Nothing = a+ Just a <> Just b = Just (a <> b)++instance Semigroup (Either a b) where+ Left _ <> b = b+ a <> _ = a++instance Semigroup Ordering where+ LT <> _ = LT+ EQ <> y = y+ GT <> _ = GT++instance Semigroup b => Semigroup (a -> b) where+ f <> g = \a -> f a <> g a++instance Semigroup All where+ All a <> All b = All (a && b)++instance Semigroup Any where+ Any a <> Any b = Any (a || b)++instance (Semigroup a, Semigroup b) => Semigroup (a, b) where+ (a,b) <> (a',b') = (a<>a',b<>b')++instance (Semigroup a, Semigroup b, Semigroup c)+ => Semigroup (a, b, c) where+ (a,b,c) <> (a',b',c') = (a<>a',b<>b',c<>c')++instance (Semigroup a, Semigroup b, Semigroup c, Semigroup d)+ => Semigroup (a, b, c, d) where+ (a,b,c,d) <> (a',b',c',d') = (a<>a',b<>b',c<>c',d<>d')++instance (Semigroup a, Semigroup b, Semigroup c, Semigroup d, Semigroup e)+ => Semigroup (a, b, c, d, e) where+ (a,b,c,d,e) <> (a',b',c',d',e') = (a<>a',b<>b',c<>c',d<>d',e<>e')++-- containers instances+instance Semigroup IntSet where+ (<>) = mappend++instance Ord a => Semigroup (Set a) where+ (<>) = mappend++instance Semigroup (IntMap v) where+ (<>) = mappend++instance Ord k => Semigroup (Map k v) where+ (<>) = mappend+#endif++-- | Cabal's own 'Data.Monoid.Last' copy to avoid requiring an orphan+-- 'Binary' instance.+--+-- Once the oldest `binary` version we support provides a 'Binary'+-- instance for 'Data.Monoid.Last' we can remove this one here.+--+-- NB: 'Data.Semigroup.Last' is defined differently and not a 'Monoid'+newtype Last' a = Last' { getLast' :: Maybe a }+ deriving (Eq, Ord, Read, Show, Binary,+ Functor, App.Applicative, Generic)++instance Semigroup (Last' a) where+ x <> Last' Nothing = x+ _ <> x = x++instance Monoid (Last' a) where+ mempty = Last' Nothing+ mappend = (<>)++-------------------------------------------------------------------------------+-------------------------------------------------------------------------------+-- Stolen from Edward Kmett's BSD3-licensed `semigroups` package++-- | Generically generate a 'Semigroup' ('<>') operation for any type+-- implementing 'Generic'. This operation will append two values+-- by point-wise appending their component fields. It is only defined+-- for product types.+--+-- @+-- 'gmappend' a ('gmappend' b c) = 'gmappend' ('gmappend' a b) c+-- @+gmappend :: (Generic a, GSemigroup (Rep a)) => a -> a -> a+gmappend x y = to (gmappend' (from x) (from y))++class GSemigroup f where+ gmappend' :: f p -> f p -> f p++instance Semigroup a => GSemigroup (K1 i a) where+ gmappend' (K1 x) (K1 y) = K1 (x <> y)++instance GSemigroup f => GSemigroup (M1 i c f) where+ gmappend' (M1 x) (M1 y) = M1 (gmappend' x y)++instance (GSemigroup f, GSemigroup g) => GSemigroup (f :*: g) where+ gmappend' (x1 :*: x2) (y1 :*: y2) = gmappend' x1 y1 :*: gmappend' x2 y2++-- | Generically generate a 'Monoid' 'mempty' for any product-like type+-- implementing 'Generic'.+--+-- It is only defined for product types.+--+-- @+-- 'gmappend' 'gmempty' a = a = 'gmappend' a 'gmempty'+-- @++gmempty :: (Generic a, GMonoid (Rep a)) => a+gmempty = to gmempty'++class GSemigroup f => GMonoid f where+ gmempty' :: f p++instance (Semigroup a, Monoid a) => GMonoid (K1 i a) where+ gmempty' = K1 mempty++instance GMonoid f => GMonoid (M1 i c f) where+ gmempty' = M1 gmempty'++instance (GMonoid f, GMonoid g) => GMonoid (f :*: g) where+ gmempty' = gmempty' :*: gmempty'
− cabal/Cabal/Distribution/Compat/TempFile.hs
@@ -1,128 +0,0 @@-{-# LANGUAGE CPP #-}-{-# OPTIONS_HADDOCK hide #-}-module Distribution.Compat.TempFile (- openTempFile,- openBinaryTempFile,- openNewBinaryFile,- createTempDirectory,- ) where---import System.FilePath ((</>))-import Foreign.C (eEXIST)--import System.IO (Handle, openTempFile, openBinaryTempFile)-import Data.Bits ((.|.))-import System.Posix.Internals (c_open, c_close, o_CREAT, o_EXCL, o_RDWR,- o_BINARY, o_NONBLOCK, o_NOCTTY)-import System.IO.Error (isAlreadyExistsError)-import System.Posix.Internals (withFilePath)-import Foreign.C (CInt)-import GHC.IO.Handle.FD (fdToHandle)-import Distribution.Compat.Exception (tryIO)-import Control.Exception (onException)-import Foreign.C (getErrno, errnoToIOError)--import System.Posix.Internals (c_getpid)--#if defined(mingw32_HOST_OS) || defined(ghcjs_HOST_OS)-import System.Directory ( createDirectory )-#else-import qualified System.Posix-#endif---- --------------------------------------------------------------- * temporary files--- ---------------------------------------------------------------- This is here for Haskell implementations that do not come with--- System.IO.openTempFile. This includes nhc-1.20, hugs-2006.9.--- TODO: Not sure about JHC--- TODO: This file should probably be removed.---- This is a copy/paste of the openBinaryTempFile definition, but--- if uses 666 rather than 600 for the permissions. The base library--- needs to be changed to make this better.-openNewBinaryFile :: FilePath -> String -> IO (FilePath, Handle)-openNewBinaryFile dir template = do- pid <- c_getpid- findTempName pid- where- -- We split off the last extension, so we can use .foo.ext files- -- for temporary files (hidden on Unix OSes). Unfortunately we're- -- below file path in the hierarchy here.- (prefix,suffix) =- case break (== '.') $ reverse template of- -- First case: template contains no '.'s. Just re-reverse it.- (rev_suffix, "") -> (reverse rev_suffix, "")- -- Second case: template contains at least one '.'. Strip the- -- dot from the prefix and prepend it to the suffix (if we don't- -- do this, the unique number will get added after the '.' and- -- thus be part of the extension, which is wrong.)- (rev_suffix, '.':rest) -> (reverse rest, '.':reverse rev_suffix)- -- Otherwise, something is wrong, because (break (== '.')) should- -- always return a pair with either the empty string or a string- -- beginning with '.' as the second component.- _ -> error "bug in System.IO.openTempFile"-- oflags = rw_flags .|. o_EXCL .|. o_BINARY-- findTempName x = do- fd <- withFilePath filepath $ \ f ->- c_open f oflags 0o666- if fd < 0- then do- errno <- getErrno- if errno == eEXIST- then findTempName (x+1)- else ioError (errnoToIOError "openNewBinaryFile" errno Nothing (Just dir))- else do- -- TODO: We want to tell fdToHandle what the file path is,- -- as any exceptions etc will only be able to report the- -- FD currently- h <- fdToHandle fd `onException` c_close fd- return (filepath, h)- where- filename = prefix ++ show x ++ suffix- filepath = dir `combine` filename-- -- FIXME: bits copied from System.FilePath- combine a b- | null b = a- | null a = b- | last a == pathSeparator = a ++ b- | otherwise = a ++ [pathSeparator] ++ b---- FIXME: Should use System.FilePath library-pathSeparator :: Char-#ifdef mingw32_HOST_OS-pathSeparator = '\\'-#else-pathSeparator = '/'-#endif---- FIXME: Copied from GHC.Handle-std_flags, output_flags, rw_flags :: CInt-std_flags = o_NONBLOCK .|. o_NOCTTY-output_flags = std_flags .|. o_CREAT-rw_flags = output_flags .|. o_RDWR--createTempDirectory :: FilePath -> String -> IO FilePath-createTempDirectory dir template = do- pid <- c_getpid- findTempName pid- where- findTempName x = do- let dirpath = dir </> template ++ "-" ++ show x- r <- tryIO $ mkPrivateDir dirpath- case r of- Right _ -> return dirpath- Left e | isAlreadyExistsError e -> findTempName (x+1)- | otherwise -> ioError e--mkPrivateDir :: String -> IO ()-#if defined(mingw32_HOST_OS) || defined(ghcjs_HOST_OS)-mkPrivateDir s = createDirectory s-#else-mkPrivateDir s = System.Posix.createDirectory s 0o700-#endif
cabal/Cabal/Distribution/Compiler.hs view
@@ -42,15 +42,15 @@ AbiTag(..), abiTagString ) where -import Distribution.Compat.Binary (Binary)+import Distribution.Compat.Binary+import Language.Haskell.Extension+ import Data.Data (Data) import Data.Typeable (Typeable) import Data.Maybe (fromMaybe) import Distribution.Version (Version(..)) import GHC.Generics (Generic) -import Language.Haskell.Extension (Language, Extension)- import qualified System.Info (compilerName, compilerVersion) import Distribution.Text (Text(..), display) import qualified Distribution.Compat.ReadP as Parse@@ -180,7 +180,7 @@ data AbiTag = NoAbiTag | AbiTag String- deriving (Generic, Show, Read)+ deriving (Eq, Generic, Show, Read) instance Binary AbiTag
cabal/Cabal/Distribution/InstalledPackageInfo.hs view
@@ -28,8 +28,9 @@ module Distribution.InstalledPackageInfo ( InstalledPackageInfo(..),- libraryName,- OriginalModule(..), ExposedModule(..),+ installedComponentId,+ installedPackageId,+ ExposedModule(..), ParseResult(..), PError(..), PWarning, emptyInstalledPackageInfo, parseInstalledPackageInfo,@@ -40,43 +41,30 @@ ) where import Distribution.ParseUtils- ( FieldDescr(..), ParseResult(..), PError(..), PWarning- , simpleField, listField, parseLicenseQ- , showFields, showSingleNamedField, showSimpleSingleNamedField- , parseFieldsFlat- , parseFilePathQ, parseTokenQ, parseModuleNameQ, parsePackageNameQ- , showFilePath, showToken, boolField, parseOptVersion- , parseFreeText, showFreeText, parseOptCommaList )-import Distribution.License ( License(..) )-import Distribution.Package- ( PackageName(..), PackageIdentifier(..)- , PackageId, InstalledPackageId(..)- , packageName, packageVersion, PackageKey(..)- , LibraryName(..) )+import Distribution.License+import Distribution.Package hiding (installedUnitId, installedPackageId) import qualified Distribution.Package as Package import Distribution.ModuleName- ( ModuleName ) import Distribution.Version- ( Version(..) ) import Distribution.Text- ( Text(disp, parse) )-import Text.PrettyPrint as Disp import qualified Distribution.Compat.ReadP as Parse+import Distribution.Compat.Binary -import Distribution.Compat.Binary (Binary)+import Text.PrettyPrint as Disp import Data.Maybe (fromMaybe) import GHC.Generics (Generic) -- ----------------------------------------------------------------------------- -- The InstalledPackageInfo type -+-- For BC reasons, we continue to name this record an InstalledPackageInfo;+-- but it would more accurately be called an InstalledUnitInfo with Backpack data InstalledPackageInfo = InstalledPackageInfo { -- these parts are exactly the same as PackageDescription- installedPackageId :: InstalledPackageId,- sourcePackageId :: PackageId,- packageKey :: PackageKey,+ sourcePackageId :: PackageId,+ installedUnitId :: UnitId,+ compatPackageKey :: String, license :: License, copyright :: String, maintainer :: String,@@ -88,9 +76,9 @@ description :: String, category :: String, -- these parts are required by an installed package only:+ abiHash :: AbiHash, exposed :: Bool, exposedModules :: [ExposedModule],- instantiatedWith :: [(ModuleName, OriginalModule)], hiddenModules :: [ModuleName], trusted :: Bool, importDirs :: [FilePath],@@ -101,7 +89,7 @@ extraGHCiLibraries:: [String], -- overrides extraLibraries for GHCi includeDirs :: [FilePath], includes :: [String],- depends :: [InstalledPackageId],+ depends :: [UnitId], ccOptions :: [String], ldOptions :: [String], frameworkDirs :: [FilePath],@@ -110,18 +98,24 @@ haddockHTMLs :: [FilePath], pkgRoot :: Maybe FilePath }- deriving (Generic, Read, Show)+ deriving (Eq, Generic, Read, Show) -libraryName :: InstalledPackageInfo -> LibraryName-libraryName ipi = Package.packageKeyLibraryName (sourcePackageId ipi) (packageKey ipi)+installedComponentId :: InstalledPackageInfo -> ComponentId+installedComponentId ipi = case installedUnitId ipi of+ SimpleUnitId cid -> cid +{-# DEPRECATED installedPackageId "Use installedUnitId instead" #-}+-- | Backwards compatibility with Cabal pre-1.24.+installedPackageId :: InstalledPackageInfo -> UnitId+installedPackageId = installedUnitId+ instance Binary InstalledPackageInfo instance Package.Package InstalledPackageInfo where packageId = sourcePackageId -instance Package.HasInstalledPackageId InstalledPackageInfo where- installedPackageId = installedPackageId+instance Package.HasUnitId InstalledPackageInfo where+ installedUnitId = installedUnitId instance Package.PackageInstalled InstalledPackageInfo where installedDepends = depends@@ -129,10 +123,9 @@ emptyInstalledPackageInfo :: InstalledPackageInfo emptyInstalledPackageInfo = InstalledPackageInfo {- installedPackageId = InstalledPackageId "",- sourcePackageId = PackageIdentifier (PackageName "") noVersion,- packageKey = OldPackageKey (PackageIdentifier- (PackageName "") noVersion),+ sourcePackageId = PackageIdentifier (PackageName "") (Version [] []),+ installedUnitId = mkUnitId "",+ compatPackageKey = "", license = UnspecifiedLicense, copyright = "", maintainer = "",@@ -143,10 +136,10 @@ synopsis = "", description = "", category = "",+ abiHash = AbiHash "", exposed = False, exposedModules = [], hiddenModules = [],- instantiatedWith = [], trusted = False, importDirs = [], libraryDirs = [],@@ -166,45 +159,22 @@ pkgRoot = Nothing } -noVersion :: Version-noVersion = Version [] []- -- ----------------------------------------------------------------------------- -- Exposed modules -data OriginalModule- = OriginalModule {- originalPackageId :: InstalledPackageId,- originalModuleName :: ModuleName- }- deriving (Generic, Eq, Read, Show)- data ExposedModule = ExposedModule { exposedName :: ModuleName,- exposedReexport :: Maybe OriginalModule,- exposedSignature :: Maybe OriginalModule -- This field is unused for now.+ exposedReexport :: Maybe Module }- deriving (Generic, Read, Show)--instance Text OriginalModule where- disp (OriginalModule ipi m) =- disp ipi <> Disp.char ':' <> disp m- parse = do- ipi <- parse- _ <- Parse.char ':'- m <- parse- return (OriginalModule ipi m)+ deriving (Eq, Generic, Read, Show) instance Text ExposedModule where- disp (ExposedModule m reexport signature) =+ disp (ExposedModule m reexport) = Disp.sep [ disp m , case reexport of Just m' -> Disp.sep [Disp.text "from", disp m'] Nothing -> Disp.empty- , case signature of- Just m' -> Disp.sep [Disp.text "is", disp m']- Nothing -> Disp.empty ] parse = do m <- parseModuleNameQ@@ -213,16 +183,9 @@ _ <- Parse.string "from" Parse.skipSpaces fmap Just parse- Parse.skipSpaces- signature <- Parse.option Nothing $ do- _ <- Parse.string "is"- Parse.skipSpaces- fmap Just parse- return (ExposedModule m reexport signature)+ return (ExposedModule m reexport) -instance Binary OriginalModule- instance Binary ExposedModule -- To maintain backwards-compatibility, we accept both comma/non-comma@@ -234,7 +197,7 @@ showExposedModules xs | all isExposedModule xs = fsep (map disp xs) | otherwise = fsep (Disp.punctuate comma (map disp xs))- where isExposedModule (ExposedModule _ Nothing Nothing) = True+ where isExposedModule (ExposedModule _ Nothing) = True isExposedModule _ = False parseExposedModules :: Parse.ReadP r [ExposedModule]@@ -248,14 +211,6 @@ parseFieldsFlat (fieldsInstalledPackageInfo ++ deprecatedFieldDescrs) emptyInstalledPackageInfo -parseInstantiatedWith :: Parse.ReadP r (ModuleName, OriginalModule)-parseInstantiatedWith = do k <- parse- _ <- Parse.char '='- n <- parse- _ <- Parse.char '@'- p <- parse- return (k, OriginalModule p n)- -- ----------------------------------------------------------------------------- -- Pretty-printing @@ -268,9 +223,6 @@ showSimpleInstalledPackageInfoField :: String -> Maybe (InstalledPackageInfo -> String) showSimpleInstalledPackageInfoField = showSimpleSingleNamedField fieldsInstalledPackageInfo -showInstantiatedWith :: (ModuleName, OriginalModule) -> Doc-showInstantiatedWith (k, OriginalModule p m) = disp k <> text "=" <> disp m <> text "@" <> disp p- -- ----------------------------------------------------------------------------- -- Description of the fields, for parsing/printing @@ -287,10 +239,11 @@ packageVersion (\ver pkg -> pkg{sourcePackageId=(sourcePackageId pkg){pkgVersion=ver}}) , simpleField "id" disp parse- installedPackageId (\ipid pkg -> pkg{installedPackageId=ipid})+ installedUnitId (\pk pkg -> pkg{installedUnitId=pk})+ -- NB: parse these as component IDs , simpleField "key"- disp parse- packageKey (\pk pkg -> pkg{packageKey=pk})+ (disp . ComponentId) (fmap (\(ComponentId s) -> s) parse)+ compatPackageKey (\pk pkg -> pkg{compatPackageKey=pk}) , simpleField "license" disp parseLicenseQ license (\l pkg -> pkg{license=l})@@ -333,9 +286,9 @@ , listField "hidden-modules" disp parseModuleNameQ hiddenModules (\xs pkg -> pkg{hiddenModules=xs})- , listField "instantiated-with"- showInstantiatedWith parseInstantiatedWith- instantiatedWith (\xs pkg -> pkg{instantiatedWith=xs})+ , simpleField "abi"+ disp parse+ abiHash (\abi pkg -> pkg{abiHash=abi}) , boolField "trusted" trusted (\val pkg -> pkg{trusted=val}) , listField "import-dirs"
cabal/Cabal/Distribution/Lex.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE CPP #-} {-# LANGUAGE PatternGuards #-} ----------------------------------------------------------------------------- -- |@@ -15,9 +14,7 @@ ) where import Data.Char (isSpace)-#if __GLASGOW_HASKELL__ < 710-import Data.Monoid-#endif+import Distribution.Compat.Semigroup as Semi newtype DList a = DList ([a] -> [a]) @@ -29,7 +26,10 @@ instance Monoid (DList a) where mempty = DList id- DList a `mappend` DList b = DList (a . b)+ mappend = (Semi.<>)++instance Semigroup (DList a) where+ DList a <> DList b = DList (a . b) tokenizeQuotedWords :: String -> [String] tokenizeQuotedWords = filter (not . null) . go False mempty
cabal/Cabal/Distribution/License.hs view
@@ -47,13 +47,13 @@ knownLicenses, ) where -import Distribution.Version (Version(Version))--import Distribution.Text (Text(..), display)+import Distribution.Version+import Distribution.Text import qualified Distribution.Compat.ReadP as Parse+import Distribution.Compat.Binary+ import qualified Text.PrettyPrint as Disp import Text.PrettyPrint ((<>))-import Distribution.Compat.Binary (Binary) import qualified Data.Char as Char (isAlphaNum) import Data.Data (Data) import Data.Typeable (Typeable)
cabal/Cabal/Distribution/Make.hs view
@@ -62,22 +62,19 @@ -- local import Distribution.Compat.Exception-import Distribution.Package --must not specify imports, since we're exporting moule.-import Distribution.Simple.Program(defaultProgramConfiguration)+import Distribution.Package+import Distribution.Simple.Program import Distribution.PackageDescription import Distribution.Simple.Setup import Distribution.Simple.Command -import Distribution.Simple.Utils (rawSystemExit, cabalVersion)+import Distribution.Simple.Utils -import Distribution.License (License(..))+import Distribution.License import Distribution.Version- ( Version(..) ) import Distribution.Text- ( display ) import System.Environment (getArgs, getProgName)-import Data.List (intercalate) import System.Exit defaultMain :: IO ()
cabal/Cabal/Distribution/ModuleName.hs view
@@ -22,14 +22,14 @@ ) where import Distribution.Text- ( Text(..) )+import Distribution.Compat.Binary+import qualified Distribution.Compat.ReadP as Parse -import Distribution.Compat.Binary (Binary) import qualified Data.Char as Char ( isAlphaNum, isUpper )+import Control.DeepSeq import Data.Data (Data) import Data.Typeable (Typeable)-import qualified Distribution.Compat.ReadP as Parse import qualified Text.PrettyPrint as Disp import Data.List ( intercalate, intersperse )@@ -43,6 +43,9 @@ deriving (Eq, Generic, Ord, Read, Show, Typeable, Data) instance Binary ModuleName++instance NFData ModuleName where+ rnf (ModuleName ms) = rnf ms instance Text ModuleName where disp (ModuleName ms) =
cabal/Cabal/Distribution/Package.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-} ----------------------------------------------------------------------------- -- |@@ -21,19 +22,19 @@ PackageIdentifier(..), PackageId, - -- * Installed package identifiers- InstalledPackageId(..),+ -- * Package keys/installed package IDs (used for linker symbols)+ ComponentId(..),+ UnitId(..),+ mkUnitId,+ mkLegacyUnitId,+ getHSLibraryName,+ InstalledPackageId, -- backwards compat - -- * Package keys (used for linker symbols)- PackageKey(..),- mkPackageKey,- packageKeyHash,- packageKeyLibraryName,+ -- * Modules+ Module(..), - -- * Library name (used for install path, package key)- LibraryName(..),- emptyLibraryName,- getHSLibraryName,+ -- * ABI hash+ AbiHash(..), -- * Package source dependencies Dependency(..),@@ -43,7 +44,8 @@ -- * Package classes Package(..), packageName, packageVersion,- HasInstalledPackageId(..),+ HasUnitId(..),+ installedPackageId, PackageInstalled(..), ) where @@ -51,22 +53,20 @@ ( Version(..), VersionRange, anyVersion, thisVersion , notThisVersion, simplifyVersionRange ) -import Distribution.Text (Text(..), display) import qualified Distribution.Compat.ReadP as Parse-import Distribution.Compat.ReadP ((<++)) import qualified Text.PrettyPrint as Disp+import Distribution.Compat.ReadP+import Distribution.Compat.Binary+import Distribution.Text+import Distribution.ModuleName import Control.DeepSeq (NFData(..))-import Distribution.Compat.Binary (Binary) import qualified Data.Char as Char- ( isDigit, isAlphaNum, isUpper, isLower, ord, chr )+ ( isDigit, isAlphaNum, ) import Data.Data ( Data )-import Data.List ( intercalate, foldl', sort )+import Data.List ( intercalate ) import Data.Typeable ( Typeable )-import Data.Word ( Word64 )-import GHC.Fingerprint ( Fingerprint(..), fingerprintString ) import GHC.Generics (Generic)-import Numeric ( showIntAtBase ) import Text.PrettyPrint ((<>), (<+>), text) newtype PackageName = PackageName { unPackageName :: String }@@ -115,190 +115,72 @@ instance NFData PackageIdentifier where rnf (PackageIdentifier name version) = rnf name `seq` rnf version --- --------------------------------------------------------------- * Installed Package Ids--- ---------------------------------------------------------------- | An InstalledPackageId uniquely identifies an instance of an installed--- package. There can be at most one package with a given 'InstalledPackageId'--- in a package database, or overlay of databases.----newtype InstalledPackageId = InstalledPackageId String- deriving (Generic, Read,Show,Eq,Ord,Typeable,Data)--instance Binary InstalledPackageId+-- | A module identity uniquely identifies a Haskell module by+-- qualifying a 'ModuleName' with the 'UnitId' which defined+-- it. This type distinguishes between two packages+-- which provide a module with the same name, or a module+-- from the same package compiled with different dependencies.+-- There are a few cases where Cabal needs to know about+-- module identities, e.g., when writing out reexported modules in+-- the 'InstalledPackageInfo'.+data Module =+ Module { moduleUnitId :: UnitId,+ moduleName :: ModuleName }+ deriving (Generic, Read, Show, Eq, Ord, Typeable, Data) -instance Text InstalledPackageId where- disp (InstalledPackageId str) = text str+instance Binary Module - parse = InstalledPackageId `fmap` Parse.munch1 abi_char- where abi_char c = Char.isAlphaNum c || c `elem` "-_."+instance Text Module where+ disp (Module uid mod_name) =+ disp uid <> Disp.text ":" <> disp mod_name+ parse = do+ uid <- parse+ _ <- Parse.char ':'+ mod_name <- parse+ return (Module uid mod_name) --- --------------------------------------------------------------- * Package Keys--- ------------------------------------------------------------+instance NFData Module where+ rnf (Module uid mod_name) = rnf uid `seq` rnf mod_name --- | A 'PackageKey' is the notion of "package ID" which is visible to the--- compiler. Why is this not a 'PackageId'? The 'PackageId' is a user-visible--- concept written explicity in Cabal files; on the other hand, a 'PackageKey'--- may contain, for example, information about the transitive dependency--- tree of a package. Why is this not an 'InstalledPackageId'? A 'PackageKey'--- should be stable so that we can incrementally recompile after a source edit;--- however, an 'InstalledPackageId' may change even with source.------ Package keys may be generated either by Cabal or GHC. In particular,--- ordinary, "old-style" packages which don't use Backpack features can--- have their package keys generated directly by Cabal and coincide with--- 'LibraryName's. However, Backpack keys are generated by GHC may exhibit--- more variation than a 'LibraryName'.+-- | A 'ComponentId' uniquely identifies the transitive source+-- code closure of a component. For non-Backpack components, it also+-- serves as the basis for install paths, symbols, etc. ---data PackageKey- -- | Modern package key which is a hash of the PackageId and the transitive- -- dependency key. It's manually inlined here so we can get the instances- -- we need. There's an optional prefix for compatibility with GHC 7.10.- = PackageKey (Maybe String) {-# UNPACK #-} !Word64 {-# UNPACK #-} !Word64- -- | Old-style package key which is just a 'PackageId'. Required because- -- old versions of GHC assume that the 'sourcePackageId' recorded for an- -- installed package coincides with the package key it was compiled with.- | OldPackageKey !PackageId+data ComponentId+ = ComponentId String deriving (Generic, Read, Show, Eq, Ord, Typeable, Data) -instance Binary PackageKey---- | Convenience function which converts a fingerprint into a new-style package--- key.-fingerprintPackageKey :: Fingerprint -> PackageKey-fingerprintPackageKey (Fingerprint a b) = PackageKey Nothing a b---- | Generates a 'PackageKey' from a 'PackageId', sorted package keys of the--- immediate dependencies.-mkPackageKey :: Bool -- are modern style package keys supported?- -> PackageId- -> [LibraryName] -- dependencies- -> PackageKey-mkPackageKey True pid deps =- fingerprintPackageKey . fingerprintString $- display pid ++ "\n" ++- concat [ display dep ++ "\n" | dep <- sort deps ]-mkPackageKey False pid _ = OldPackageKey pid---- The base-62 code is based off of 'locators'--- ((c) Operational Dynamics Consulting, BSD3 licensed)---- Note: Instead of base-62 encoding a single 128-bit integer--- (ceil(21.49) characters), we'll base-62 a pair of 64-bit integers--- (2 * ceil(10.75) characters). Luckily for us, it's the same number of--- characters! In the long term, this should go in GHC.Fingerprint,--- but not now...---- | Size of a 64-bit word when written as a base-62 string-word64Base62Len :: Int-word64Base62Len = 11---- | Converts a 64-bit word into a base-62 string-toBase62 :: Word64 -> String-toBase62 w = pad ++ str- where- pad = replicate len '0'- len = word64Base62Len - length str -- 11 == ceil(64 / lg 62)- str = showIntAtBase 62 represent w ""- represent :: Int -> Char- represent x- | x < 10 = Char.chr (48 + x)- | x < 36 = Char.chr (65 + x - 10)- | x < 62 = Char.chr (97 + x - 36)- | otherwise = error ("represent (base 62): impossible!")---- | Parses a base-62 string into a 64-bit word-fromBase62 :: String -> Word64-fromBase62 ss = foldl' multiply 0 ss- where- value :: Char -> Int- value c- | Char.isDigit c = Char.ord c - 48- | Char.isUpper c = Char.ord c - 65 + 10- | Char.isLower c = Char.ord c - 97 + 36- | otherwise = error ("value (base 62): impossible!")-- multiply :: Word64 -> Char -> Word64- multiply acc c = acc * 62 + (fromIntegral $ value c)---- | Parses a base-62 string into a fingerprint.-readBase62Fingerprint :: String -> Fingerprint-readBase62Fingerprint s = Fingerprint w1 w2- where (s1,s2) = splitAt word64Base62Len s- w1 = fromBase62 s1- w2 = fromBase62 (take word64Base62Len s2)---- | Compute the hash (without a prefix) of a package key. In GHC 7.12--- this is equivalent to display.-packageKeyHash :: PackageKey -> String-packageKeyHash (PackageKey _ w1 w2) = toBase62 w1 ++ toBase62 w2-packageKeyHash (OldPackageKey pid) = display pid---- | Legacy function for GHC 7.10 to compute a LibraryName based on--- the package key.-packageKeyLibraryName :: PackageId -> PackageKey -> LibraryName-packageKeyLibraryName pid (PackageKey _ w1 w2) =- LibraryName (display pid ++ "-" ++ toBase62 w1 ++ toBase62 w2)-packageKeyLibraryName _ (OldPackageKey pid) = LibraryName (display pid)--instance Text PackageKey where- disp (PackageKey mb_prefix w1 w2)- = maybe Disp.empty (\r -> Disp.text r <> Disp.char '_') mb_prefix <>- Disp.text (toBase62 w1) <> Disp.text (toBase62 w2)- disp (OldPackageKey pid) = disp pid-- parse = parseNewWithAnnot <++ parseNew <++ parseOld- where parseNew = do- fmap (fingerprintPackageKey . readBase62Fingerprint)- . Parse.count (word64Base62Len * 2)- $ Parse.satisfy Char.isAlphaNum- parseNewWithAnnot = do- -- this is ignored- prefix <- Parse.munch1 (\c -> Char.isAlphaNum c || c `elem` "-")- _ <- Parse.char '_' -- if we use '-' it's ambiguous- PackageKey _ w1 w2 <- parseNew- return (PackageKey (Just prefix) w1 w2)- parseOld = do pid <- parse- return (OldPackageKey pid)--instance NFData PackageKey where- rnf (PackageKey mb _ _) = rnf mb- rnf (OldPackageKey pid) = rnf pid+{-# DEPRECATED InstalledPackageId "Use UnitId instead" #-}+type InstalledPackageId = UnitId --- --------------------------------------------------------------- * Library names--- ------------------------------------------------------------+instance Binary ComponentId --- | A library name consists of not only a source package--- id ('PackageId') but also the library names of all textual--- dependencies; thus, a library name uniquely identifies an--- installed package up to the dependency resolution done by Cabal.--- Create using 'packageKeyLibraryName'. Library names are opaque,--- Cabal-defined strings.-newtype LibraryName- = LibraryName String- deriving (Generic, Read, Show, Eq, Ord, Typeable, Data)+instance Text ComponentId where+ disp (ComponentId str) = text str -instance Binary LibraryName+ parse = ComponentId `fmap` Parse.munch1 abi_char+ where abi_char c = Char.isAlphaNum c || c `elem` "-_." --- | Default library name for when it is not known.-emptyLibraryName :: LibraryName-emptyLibraryName = LibraryName ""+instance NFData ComponentId where+ rnf (ComponentId pk) = rnf pk -- | Returns library name prefixed with HS, suitable for filenames-getHSLibraryName :: LibraryName -> String-getHSLibraryName (LibraryName s) = "HS" ++ s+getHSLibraryName :: UnitId -> String+getHSLibraryName (SimpleUnitId (ComponentId s)) = "HS" ++ s -instance Text LibraryName where- disp (LibraryName s) = Disp.text s- parse = LibraryName `fmap` Parse.munch1 hash_char- where hash_char c = Char.isAlphaNum c || c `elem` "-_."+-- | For now, there is no distinction between component IDs+-- and unit IDs in Cabal.+newtype UnitId = SimpleUnitId ComponentId+ deriving (Generic, Read, Show, Eq, Ord, Typeable, Data, Binary, Text, NFData) -instance NFData LibraryName where- rnf (LibraryName s) = rnf s+-- | Makes a simple-style UnitId from a string.+mkUnitId :: String -> UnitId+mkUnitId = SimpleUnitId . ComponentId +-- | Make an old-style UnitId from a package identifier+mkLegacyUnitId :: PackageId -> UnitId+mkLegacyUnitId = SimpleUnitId . ComponentId . display+ -- ------------------------------------------------------------ -- * Package source dependencies -- ------------------------------------------------------------@@ -358,14 +240,30 @@ packageId = id -- | Packages that have an installed package ID-class Package pkg => HasInstalledPackageId pkg where- installedPackageId :: pkg -> InstalledPackageId+class Package pkg => HasUnitId pkg where+ installedUnitId :: pkg -> UnitId +{-# DEPRECATED installedPackageId "Use installedUnitId instead" #-}+-- | Compatibility wrapper for Cabal pre-1.24.+installedPackageId :: HasUnitId pkg => pkg -> UnitId+installedPackageId = installedUnitId+ -- | Class of installed packages. -- -- The primary data type which is an instance of this package is -- 'InstalledPackageInfo', but when we are doing install plans in Cabal install -- we may have other, installed package-like things which contain more metadata. -- Installed packages have exact dependencies 'installedDepends'.-class HasInstalledPackageId pkg => PackageInstalled pkg where- installedDepends :: pkg -> [InstalledPackageId]+class (HasUnitId pkg) => PackageInstalled pkg where+ installedDepends :: pkg -> [UnitId]++-- -----------------------------------------------------------------------------+-- ABI hash++newtype AbiHash = AbiHash String+ deriving (Eq, Show, Read, Generic)+instance Binary AbiHash++instance Text AbiHash where+ disp (AbiHash abi) = Disp.text abi+ parse = fmap AbiHash (Parse.munch Char.isAlphaNum)
cabal/Cabal/Distribution/PackageDescription.hs view
@@ -1,6 +1,6 @@-{-# LANGUAGE CPP #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE NoMonoLocalBinds #-} ----------------------------------------------------------------------------- -- |@@ -44,6 +44,7 @@ ModuleReexport(..), emptyLibrary, withLib,+ hasPublicLib, hasLibs, libModules, @@ -90,6 +91,8 @@ hcSharedOptions, -- ** Supplementary build information+ ComponentName(..),+ defaultLibName, HookedBuildInfo, emptyHookedBuildInfo, updatePackageDescription,@@ -110,43 +113,34 @@ SetupBuildInfo(..), ) where -import Distribution.Compat.Binary (Binary)+import Distribution.Compat.Binary+import qualified Distribution.Compat.Semigroup as Semi ((<>))+import Distribution.Compat.Semigroup as Semi (Monoid(..), Semigroup, gmempty, gmappend)+import qualified Distribution.Compat.ReadP as Parse+import Distribution.Compat.ReadP ((<++))+import Distribution.Package+import Distribution.ModuleName+import Distribution.Version+import Distribution.License+import Distribution.Compiler+import Distribution.System+import Distribution.Text+import Language.Haskell.Extension+ import Data.Data (Data)-import Data.Foldable (traverse_) import Data.List (nub, intercalate) import Data.Maybe (fromMaybe, maybeToList)-#if __GLASGOW_HASKELL__ < 710-import Control.Applicative (Applicative((<*>), pure))-import Data.Monoid (Monoid(mempty, mappend))-import Data.Foldable (Foldable(foldMap))-import Data.Traversable (Traversable(traverse))-#endif+import Data.Foldable as Fold (Foldable(foldMap))+import Data.Traversable as Trav (Traversable(traverse)) import Data.Typeable ( Typeable )-import Control.Applicative (Alternative(..))+import Control.Applicative as AP (Alternative(..), Applicative(..)) import Control.Monad (MonadPlus(mplus,mzero), ap) import GHC.Generics (Generic) import Text.PrettyPrint as Disp-import qualified Distribution.Compat.ReadP as Parse-import Distribution.Compat.ReadP ((<++)) import qualified Data.Char as Char (isAlphaNum, isDigit, toLower) import qualified Data.Map as Map import Data.Map (Map) -import Distribution.Package- ( PackageName(PackageName), PackageIdentifier(PackageIdentifier)- , Dependency, Package(..), PackageName, packageName )-import Distribution.ModuleName ( ModuleName )-import Distribution.Version- ( Version(Version), VersionRange, anyVersion, orLaterVersion- , asVersionIntervals, LowerBound(..) )-import Distribution.License (License(UnspecifiedLicense))-import Distribution.Compiler (CompilerFlavor)-import Distribution.System (OS, Arch)-import Distribution.Text- ( Text(..), display )-import Language.Haskell.Extension- ( Language, Extension )- -- ----------------------------------------------------------------------------- -- The PackageDescription type @@ -177,6 +171,7 @@ customFieldsPD :: [(String,String)], -- ^Custom fields starting -- with x-, stored in a -- simple assoc-list.+ -- | YOU PROBABLY DON'T WANT TO USE THIS FIELD. This field is -- special! Depending on how far along processing the -- PackageDescription we are, the contents of this field are@@ -197,7 +192,7 @@ buildType :: Maybe BuildType, setupBuildInfo :: Maybe SetupBuildInfo, -- components- library :: Maybe Library,+ libraries :: [Library], executables :: [Executable], testSuites :: [TestSuite], benchmarks :: [Benchmark],@@ -264,7 +259,7 @@ category = "", customFieldsPD = [], setupBuildInfo = Nothing,- library = Nothing,+ libraries = [], executables = [], testSuites = [], benchmarks = [],@@ -322,15 +317,13 @@ instance Binary SetupBuildInfo -instance Monoid SetupBuildInfo where- mempty = SetupBuildInfo {- setupDepends = mempty- }- mappend a b = SetupBuildInfo {- setupDepends = combine setupDepends- }- where combine field = field a `mappend` field b+instance Semi.Monoid SetupBuildInfo where+ mempty = gmempty+ mappend = (Semi.<>) +instance Semigroup SetupBuildInfo where+ (<>) = gmappend+ -- --------------------------------------------------------------------------- -- Module renaming @@ -352,10 +345,13 @@ instance Binary ModuleRenaming where instance Monoid ModuleRenaming where- ModuleRenaming b rns `mappend` ModuleRenaming b' rns'- = ModuleRenaming (b || b') (rns ++ rns') -- ToDo: dedupe? mempty = ModuleRenaming False []+ mappend = (Semi.<>) +instance Semigroup ModuleRenaming where+ ModuleRenaming b rns <> ModuleRenaming b' rns'+ = ModuleRenaming (b || b') (rns ++ rns') -- TODO: dedupe?+ -- NB: parentheses are mandatory, because later we may extend this syntax -- to allow "hiding (A, B)" or other modifier words. instance Text ModuleRenaming where@@ -394,6 +390,7 @@ -- The Library type data Library = Library {+ libName :: String, exposedModules :: [ModuleName], reexportedModules :: [ModuleReexport], requiredSignatures:: [ModuleName], -- ^ What sigs need implementations?@@ -407,6 +404,7 @@ instance Monoid Library where mempty = Library {+ libName = mempty, exposedModules = mempty, reexportedModules = mempty, requiredSignatures = mempty,@@ -414,7 +412,11 @@ libExposed = True, libBuildInfo = mempty }- mappend a b = Library {+ mappend = (Semi.<>)++instance Semigroup Library where+ a <> b = Library {+ libName = combine' libName, exposedModules = combine exposedModules, reexportedModules = combine reexportedModules, requiredSignatures = combine requiredSignatures,@@ -423,26 +425,31 @@ libBuildInfo = combine libBuildInfo } where combine field = field a `mappend` field b+ combine' field = case (field a, field b) of+ ("","") -> ""+ ("", x) -> x+ (x, "") -> x+ (x, y) -> error $ "Ambiguous values for library field: '"+ ++ x ++ "' and '" ++ y ++ "'" emptyLibrary :: Library emptyLibrary = mempty --- |does this package have any libraries?-hasLibs :: PackageDescription -> Bool-hasLibs p = maybe False (buildable . libBuildInfo) (library p)+-- | Does this package have a PUBLIC library?+hasPublicLib :: PackageDescription -> Bool+hasPublicLib p = any f (libraries p)+ where f lib = buildable (libBuildInfo lib) &&+ libName lib == display (packageName (package p)) --- |'Maybe' version of 'hasLibs'-maybeHasLibs :: PackageDescription -> Maybe Library-maybeHasLibs p =- library p >>= \lib -> if buildable (libBuildInfo lib)- then Just lib- else Nothing+-- | Does this package have any libraries?+hasLibs :: PackageDescription -> Bool+hasLibs p = any (buildable . libBuildInfo) (libraries p) -- |If the package description has a library section, call the given -- function with the library build info as argument. withLib :: PackageDescription -> (Library -> IO ()) -> IO () withLib pkg_descr f =- traverse_ f (maybeHasLibs pkg_descr)+ sequence_ [f lib | lib <- libraries pkg_descr, buildable (libBuildInfo lib)] -- | Get all the module names from the library (exposed and internal modules) -- which need to be compiled. (This does not include reexports, which@@ -499,12 +506,11 @@ instance Binary Executable instance Monoid Executable where- mempty = Executable {- exeName = mempty,- modulePath = mempty,- buildInfo = mempty- }- mappend a b = Executable{+ mempty = gmempty+ mappend = (Semi.<>)++instance Semigroup Executable where+ a <> b = Executable{ exeName = combine' exeName, modulePath = combine modulePath, buildInfo = combine buildInfo@@ -589,8 +595,10 @@ testBuildInfo = mempty, testEnabled = False }+ mappend = (Semi.<>) - mappend a b = TestSuite {+instance Semigroup TestSuite where+ a <> b = TestSuite { testName = combine' testName, testInterface = combine testInterface, testBuildInfo = combine testBuildInfo,@@ -605,9 +613,12 @@ instance Monoid TestSuiteInterface where mempty = TestSuiteUnsupported (TestTypeUnknown mempty (Version [] []))- mappend a (TestSuiteUnsupported _) = a- mappend _ b = b+ mappend = (Semi.<>) +instance Semigroup TestSuiteInterface where+ a <> (TestSuiteUnsupported _) = a+ _ <> b = b+ emptyTestSuite :: TestSuite emptyTestSuite = mempty @@ -722,8 +733,10 @@ benchmarkBuildInfo = mempty, benchmarkEnabled = False }+ mappend = (Semi.<>) - mappend a b = Benchmark {+instance Semigroup Benchmark where+ a <> b = Benchmark { benchmarkName = combine' benchmarkName, benchmarkInterface = combine benchmarkInterface, benchmarkBuildInfo = combine benchmarkBuildInfo,@@ -738,9 +751,12 @@ instance Monoid BenchmarkInterface where mempty = BenchmarkUnsupported (BenchmarkTypeUnknown mempty (Version [] []))- mappend a (BenchmarkUnsupported _) = a- mappend _ b = b+ mappend = (Semi.<>) +instance Semigroup BenchmarkInterface where+ a <> (BenchmarkUnsupported _) = a+ _ <> b = b+ emptyBenchmark :: Benchmark emptyBenchmark = mempty @@ -800,6 +816,7 @@ ldOptions :: [String], -- ^ options for linker pkgconfigDepends :: [Dependency], -- ^ pkg-config packages that are used frameworks :: [String], -- ^support frameworks for Mac OS X+ extraFrameworkDirs:: [String], -- ^ extra locations to find frameworks. cSources :: [FilePath], jsSources :: [FilePath], hsSourceDirs :: [FilePath], -- ^ where to look for the Haskell module hierarchy@@ -832,63 +849,68 @@ instance Monoid BuildInfo where mempty = BuildInfo {- buildable = True,- buildTools = [],- cppOptions = [],- ccOptions = [],- ldOptions = [],- pkgconfigDepends = [],- frameworks = [],- cSources = [],- jsSources = [],- hsSourceDirs = [],- otherModules = [],- defaultLanguage = Nothing,- otherLanguages = [],- defaultExtensions = [],- otherExtensions = [],- oldExtensions = [],- extraLibs = [],- extraGHCiLibs = [],- extraLibDirs = [],- includeDirs = [],- includes = [],- installIncludes = [],- options = [],- profOptions = [],- sharedOptions = [],- customFieldsBI = [],- targetBuildDepends = [],+ buildable = True,+ buildTools = [],+ cppOptions = [],+ ccOptions = [],+ ldOptions = [],+ pkgconfigDepends = [],+ frameworks = [],+ extraFrameworkDirs = [],+ cSources = [],+ jsSources = [],+ hsSourceDirs = [],+ otherModules = [],+ defaultLanguage = Nothing,+ otherLanguages = [],+ defaultExtensions = [],+ otherExtensions = [],+ oldExtensions = [],+ extraLibs = [],+ extraGHCiLibs = [],+ extraLibDirs = [],+ includeDirs = [],+ includes = [],+ installIncludes = [],+ options = [],+ profOptions = [],+ sharedOptions = [],+ customFieldsBI = [],+ targetBuildDepends = [], targetBuildRenaming = Map.empty }- mappend a b = BuildInfo {- buildable = buildable a && buildable b,- buildTools = combine buildTools,- cppOptions = combine cppOptions,- ccOptions = combine ccOptions,- ldOptions = combine ldOptions,- pkgconfigDepends = combine pkgconfigDepends,- frameworks = combineNub frameworks,- cSources = combineNub cSources,- jsSources = combineNub jsSources,- hsSourceDirs = combineNub hsSourceDirs,- otherModules = combineNub otherModules,- defaultLanguage = combineMby defaultLanguage,- otherLanguages = combineNub otherLanguages,- defaultExtensions = combineNub defaultExtensions,- otherExtensions = combineNub otherExtensions,- oldExtensions = combineNub oldExtensions,- extraLibs = combine extraLibs,- extraGHCiLibs = combine extraGHCiLibs,- extraLibDirs = combineNub extraLibDirs,- includeDirs = combineNub includeDirs,- includes = combineNub includes,- installIncludes = combineNub installIncludes,- options = combine options,- profOptions = combine profOptions,- sharedOptions = combine sharedOptions,- customFieldsBI = combine customFieldsBI,- targetBuildDepends = combineNub targetBuildDepends,+ mappend = (Semi.<>)++instance Semigroup BuildInfo where+ a <> b = BuildInfo {+ buildable = buildable a && buildable b,+ buildTools = combine buildTools,+ cppOptions = combine cppOptions,+ ccOptions = combine ccOptions,+ ldOptions = combine ldOptions,+ pkgconfigDepends = combine pkgconfigDepends,+ frameworks = combineNub frameworks,+ extraFrameworkDirs = combineNub extraFrameworkDirs,+ cSources = combineNub cSources,+ jsSources = combineNub jsSources,+ hsSourceDirs = combineNub hsSourceDirs,+ otherModules = combineNub otherModules,+ defaultLanguage = combineMby defaultLanguage,+ otherLanguages = combineNub otherLanguages,+ defaultExtensions = combineNub defaultExtensions,+ otherExtensions = combineNub otherExtensions,+ oldExtensions = combineNub oldExtensions,+ extraLibs = combine extraLibs,+ extraGHCiLibs = combine extraGHCiLibs,+ extraLibDirs = combineNub extraLibDirs,+ includeDirs = combineNub includeDirs,+ includes = combineNub includes,+ installIncludes = combineNub installIncludes,+ options = combine options,+ profOptions = combine profOptions,+ sharedOptions = combine sharedOptions,+ customFieldsBI = combine customFieldsBI,+ targetBuildDepends = combineNub targetBuildDepends, targetBuildRenaming = combineMap targetBuildRenaming } where@@ -904,7 +926,7 @@ -- all buildable executables, test suites and benchmarks. Useful for gathering -- dependencies. allBuildInfo :: PackageDescription -> [BuildInfo]-allBuildInfo pkg_descr = [ bi | Just lib <- [library pkg_descr]+allBuildInfo pkg_descr = [ bi | lib <- libraries pkg_descr , let bi = libBuildInfo lib , buildable bi ] ++ [ bi | exe <- executables pkg_descr@@ -939,10 +961,22 @@ usedExtensions bi = oldExtensions bi ++ defaultExtensions bi -type HookedBuildInfo = (Maybe BuildInfo, [(String, BuildInfo)])+-- Libraries live in a separate namespace, so must distinguish+data ComponentName = CLibName String+ | CExeName String+ | CTestName String+ | CBenchName String+ deriving (Eq, Generic, Ord, Read, Show) +instance Binary ComponentName++defaultLibName :: PackageIdentifier -> ComponentName+defaultLibName pid = CLibName (display (pkgName pid))++type HookedBuildInfo = [(ComponentName, BuildInfo)]+ emptyHookedBuildInfo :: HookedBuildInfo-emptyHookedBuildInfo = (Nothing, [])+emptyHookedBuildInfo = [] -- |Select options for a particular Haskell compiler. hcOptions :: CompilerFlavor -> BuildInfo -> [String]@@ -1098,28 +1132,38 @@ -- ------------------------------------------------------------ updatePackageDescription :: HookedBuildInfo -> PackageDescription -> PackageDescription-updatePackageDescription (mb_lib_bi, exe_bi) p- = p{ executables = updateExecutables exe_bi (executables p)- , library = updateLibrary mb_lib_bi (library p)- }+updatePackageDescription hooked_bis p+ = p{ executables = updateMany (CExeName . exeName) updateExecutable (executables p)+ , libraries = updateMany (CLibName . libName) updateLibrary (libraries p)+ , benchmarks = updateMany (CBenchName . benchmarkName) updateBenchmark (benchmarks p)+ , testSuites = updateMany (CTestName . testName) updateTestSuite (testSuites p)+ } where- updateLibrary :: Maybe BuildInfo -> Maybe Library -> Maybe Library- updateLibrary (Just bi) (Just lib) = Just (lib{libBuildInfo = bi `mappend` libBuildInfo lib})- updateLibrary Nothing mb_lib = mb_lib- updateLibrary (Just _) Nothing = Nothing+ updateMany :: (a -> ComponentName) -- ^ get 'ComponentName' from @a@+ -> (BuildInfo -> a -> a) -- ^ @updateExecutable@, @updateLibrary@, etc+ -> [a] -- ^list of components to update+ -> [a] -- ^list with updated components+ updateMany name update cs' = foldr (updateOne name update) cs' hooked_bis - updateExecutables :: [(String, BuildInfo)] -- ^[(exeName, new buildinfo)]- -> [Executable] -- ^list of executables to update- -> [Executable] -- ^list with exeNames updated- updateExecutables exe_bi' executables' = foldr updateExecutable executables' exe_bi'+ updateOne :: (a -> ComponentName) -- ^ get 'ComponentName' from @a@+ -> (BuildInfo -> a -> a) -- ^ @updateExecutable@, @updateLibrary@, etc+ -> (ComponentName, BuildInfo) -- ^(name, new buildinfo)+ -> [a] -- ^list of components to update+ -> [a] -- ^list with name component updated+ updateOne _ _ _ [] = []+ updateOne name_sel update hooked_bi'@(name,bi) (c:cs)+ | name_sel c == name ||+ -- Special case: an empty name means "please update the BuildInfo for+ -- the public library, i.e. the one with the same name as the+ -- package." See 'parseHookedBuildInfo'.+ name == CLibName "" && name_sel c == defaultLibName (package p)+ = update bi c : cs+ | otherwise = c : updateOne name_sel update hooked_bi' cs - updateExecutable :: (String, BuildInfo) -- ^(exeName, new buildinfo)- -> [Executable] -- ^list of executables to update- -> [Executable] -- ^list with exeName updated- updateExecutable _ [] = []- updateExecutable exe_bi'@(name,bi) (exe:exes)- | exeName exe == name = exe{buildInfo = bi `mappend` buildInfo exe} : exes- | otherwise = exe : updateExecutable exe_bi' exes+ updateExecutable bi exe = exe{buildInfo = bi `mappend` buildInfo exe}+ updateLibrary bi lib = lib{libBuildInfo = bi `mappend` libBuildInfo lib}+ updateBenchmark bi ben = ben{benchmarkBuildInfo = bi `mappend` benchmarkBuildInfo ben}+ updateTestSuite bi test = test{testBuildInfo = bi `mappend` testBuildInfo test} -- --------------------------------------------------------------------------- -- The GenericPackageDescription type@@ -1128,17 +1172,17 @@ GenericPackageDescription { packageDescription :: PackageDescription, genPackageFlags :: [Flag],- condLibrary :: Maybe (CondTree ConfVar [Dependency] Library),+ condLibraries :: [(String, CondTree ConfVar [Dependency] Library)], condExecutables :: [(String, CondTree ConfVar [Dependency] Executable)], condTestSuites :: [(String, CondTree ConfVar [Dependency] TestSuite)], condBenchmarks :: [(String, CondTree ConfVar [Dependency] Benchmark)] }- deriving (Show, Eq, Typeable, Data)+ deriving (Show, Eq, Typeable, Data, Generic) instance Package GenericPackageDescription where packageId = packageId . packageDescription ---TODO: make PackageDescription an instance of Text.+instance Binary GenericPackageDescription -- | A flag can represent a feature to be included, or a way of linking -- a target against its dependencies, or in fact whatever you can think of.@@ -1148,8 +1192,10 @@ , flagDefault :: Bool , flagManual :: Bool }- deriving (Show, Eq, Typeable, Data)+ deriving (Show, Eq, Typeable, Data, Generic) +instance Binary Flag+ -- | A 'FlagName' is the name of a user-defined configuration flag newtype FlagName = FlagName String deriving (Eq, Generic, Ord, Show, Read, Typeable, Data)@@ -1168,15 +1214,17 @@ | Arch Arch | Flag FlagName | Impl CompilerFlavor VersionRange- deriving (Eq, Show, Typeable, Data)+ deriving (Eq, Show, Typeable, Data, Generic) +instance Binary ConfVar+ -- | A boolean expression parameterized over the variable type used. data Condition c = Var c | Lit Bool | CNot (Condition c) | COr (Condition c) (Condition c) | CAnd (Condition c) (Condition c)- deriving (Show, Eq, Typeable, Data)+ deriving (Show, Eq, Typeable, Data, Generic) cNot :: Condition a -> Condition a cNot (Lit b) = Lit (not b)@@ -1193,23 +1241,23 @@ instance Foldable Condition where f `foldMap` Var c = f c _ `foldMap` Lit _ = mempty- f `foldMap` CNot c = foldMap f c+ f `foldMap` CNot c = Fold.foldMap f c f `foldMap` COr c d = foldMap f c `mappend` foldMap f d f `foldMap` CAnd c d = foldMap f c `mappend` foldMap f d instance Traversable Condition where f `traverse` Var c = Var `fmap` f c _ `traverse` Lit c = pure $ Lit c- f `traverse` CNot c = CNot `fmap` traverse f c+ f `traverse` CNot c = CNot `fmap` Trav.traverse f c f `traverse` COr c d = COr `fmap` traverse f c <*> traverse f d f `traverse` CAnd c d = CAnd `fmap` traverse f c <*> traverse f d instance Applicative Condition where- pure = return+ pure = Var (<*>) = ap instance Monad Condition where- return = Var+ return = AP.pure -- Terminating cases (>>=) (Lit x) _ = Lit x (>>=) (Var x) f = f x@@ -1220,8 +1268,11 @@ instance Monoid (Condition a) where mempty = Lit False- mappend = COr+ mappend = (Semi.<>) +instance Semigroup (Condition a) where+ (<>) = COr+ instance Alternative Condition where empty = mempty (<|>) = mappend@@ -1230,6 +1281,8 @@ mzero = mempty mplus = mappend +instance Binary c => Binary (Condition c)+ data CondTree v c a = CondNode { condTreeData :: a , condTreeConstraints :: c@@ -1237,4 +1290,6 @@ , CondTree v c a , Maybe (CondTree v c a))] }- deriving (Show, Eq, Typeable, Data)+ deriving (Show, Eq, Typeable, Data, Generic)++instance (Binary v, Binary c, Binary a) => Binary (CondTree v c a)
cabal/Cabal/Distribution/PackageDescription/Check.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE NoMonoLocalBinds #-} ----------------------------------------------------------------------------- -- | -- Module : Distribution.PackageDescription.Check@@ -33,50 +34,30 @@ checkPackageFileNames, ) where -import Data.Maybe- ( isNothing, isJust, catMaybes, maybeToList, fromMaybe )-import Data.List (sort, group, isPrefixOf, nub, find)-import Control.Monad- ( filterM, liftM )-import qualified System.Directory as System- ( doesFileExist, doesDirectoryExist )-import qualified Data.Map as Map- import Distribution.PackageDescription import Distribution.PackageDescription.Configuration- ( flattenPackageDescription, finalizePackageDescription ) import Distribution.Compiler- ( CompilerFlavor(..), buildCompilerFlavor, CompilerId(..)- , unknownCompilerInfo, AbiTag(..) ) import Distribution.System- ( OS(..), Arch(..), buildPlatform ) import Distribution.License- ( License(..), knownLicenses ) import Distribution.Simple.CCompiler- ( filenameCDialect )-import Distribution.Simple.Utils- ( cabalVersion, intercalate, parseFileGlob, FileGlob(..), lowercase, startsWithBOM, fromUTF8 )-+import Distribution.Simple.Utils hiding (findPackageDesc, notice) import Distribution.Version- ( Version(..)- , VersionRange(..), foldVersionRange'- , anyVersion, noVersion, thisVersion, laterVersion, earlierVersion- , orLaterVersion, orEarlierVersion- , unionVersionRanges, intersectVersionRanges- , asVersionIntervals, UpperBound(..), isNoVersion ) import Distribution.Package- ( PackageName(PackageName), packageName, packageVersion- , Dependency(..), pkgName )- import Distribution.Text- ( display, disp )+import Language.Haskell.Extension++import Data.Maybe+ ( isNothing, isJust, catMaybes, mapMaybe, fromMaybe )+import Data.List (sort, group, isPrefixOf, nub, find)+import Control.Monad+ ( filterM, liftM )+import qualified System.Directory as System+ ( doesFileExist, doesDirectoryExist )+import qualified Data.Map as Map+ import qualified Text.PrettyPrint as Disp import Text.PrettyPrint ((<>), (<+>)) -import qualified Language.Haskell.Extension as Extension (deprecatedExtensions)-import Language.Haskell.Extension- ( Language(UnknownLanguage), knownLanguages- , Extension(..), KnownExtension(..) ) import qualified System.Directory (getDirectoryContents) import System.IO (openBinaryFile, IOMode(ReadMode), hGetContents) import System.FilePath@@ -111,7 +92,7 @@ | PackageDistSuspicious { explanation :: String } -- | Like PackageDistSuspicious but will only display warnings- -- rather than causing abnormal exit.+ -- rather than causing abnormal exit when you run 'cabal check'. | PackageDistSuspiciousWarn { explanation :: String } -- | An issue that is OK in the author's environment but is almost@@ -193,19 +174,19 @@ , check (all ($ pkg) [ null . executables , null . testSuites , null . benchmarks- , isNothing . library ]) $+ , null . libraries ]) $ PackageBuildImpossible "No executables, libraries, tests, or benchmarks found. Nothing to do." , check (not (null duplicateNames)) $ PackageBuildImpossible $ "Duplicate sections: " ++ commaSep duplicateNames- ++ ". The name of every executable, test suite, and benchmark section in"+ ++ ". The name of every library, executable, test suite, and benchmark section in" ++ " the package must be unique." ] --TODO: check for name clashes case insensitively: windows file systems cannot --cope. - ++ maybe [] (checkLibrary pkg) (library pkg)+ ++ concatMap (checkLibrary pkg) (libraries pkg) ++ concatMap (checkExecutable pkg) (executables pkg) ++ concatMap (checkTestSuite pkg) (testSuites pkg) ++ concatMap (checkBenchmark pkg) (benchmarks pkg)@@ -219,10 +200,15 @@ ++ "tool only supports up to version " ++ display cabalVersion ++ "." ] where+ -- The public library gets special dispensation, because it+ -- is common practice to export a library and name the executable+ -- the same as the package. We always put the public library+ -- in the top-level directory in dist, so no conflicts either.+ libNames = filter (/= unPackageName (packageName pkg)) . map libName $ libraries pkg exeNames = map exeName $ executables pkg testNames = map testName $ testSuites pkg bmNames = map benchmarkName $ benchmarks pkg- duplicateNames = dups $ exeNames ++ testNames ++ bmNames+ duplicateNames = dups $ libNames ++ exeNames ++ testNames ++ bmNames checkLibrary :: PackageDescription -> Library -> [PackageCheck] checkLibrary pkg lib =@@ -317,14 +303,6 @@ PackageDistInexcusable $ "The package uses a C/C++/obj-C source file for the 'main-is' field. " ++ "To use this feature you must specify 'cabal-version: >= 1.18'."-- -- Test suites might be built as (internal) libraries named after- -- the test suite and thus their names must not clash with the- -- name of the package.- , check libNameClash $- PackageBuildImpossible $- "The test suite " ++ testName test- ++ " has the same name as the package." ] where moduleDuplicates = dups $ testModules test@@ -337,13 +315,8 @@ TestSuiteExeV10 _ f -> takeExtension f `notElem` [".hs", ".lhs"] _ -> False - libNameClash = testName test `elem` [ libName- | _lib <- maybeToList (library pkg)- , let PackageName libName =- pkgName (package pkg) ]- checkBenchmark :: PackageDescription -> Benchmark -> [PackageCheck]-checkBenchmark pkg bm =+checkBenchmark _pkg bm = catMaybes [ case benchmarkInterface bm of@@ -369,12 +342,6 @@ PackageBuildImpossible $ "The 'main-is' field must specify a '.hs' or '.lhs' file " ++ "(even if it is generated by a preprocessor)."-- -- See comment for similar check on test suites.- , check libNameClash $- PackageBuildImpossible $- "The benchmark " ++ benchmarkName bm- ++ " has the same name as the package." ] where moduleDuplicates = dups $ benchmarkModules bm@@ -383,11 +350,6 @@ BenchmarkExeV10 _ f -> takeExtension f `notElem` [".hs", ".lhs"] _ -> False - libNameClash = benchmarkName bm `elem` [ libName- | _lib <- maybeToList (library pkg)- , let PackageName libName =- pkgName (package pkg) ]- -- ------------------------------------------------------------ -- * Additional pure checks -- ------------------------------------------------------------@@ -403,6 +365,11 @@ ++ "need to convert package names to file names so using this name " ++ "would cause problems." + , check ((isPrefixOf "z-") . display . packageName $ pkg) $+ PackageDistInexcusable $+ "Package names with the prefix 'z-' are reserved by Cabal and "+ ++ "cannot be used."+ , check (isNothing (buildType pkg)) $ PackageBuildWarning $ "No 'build-type' specified. If you do not need a custom Setup.hs or "@@ -442,14 +409,14 @@ ++ ". Languages must be specified in either the 'default-language' " ++ " or the 'other-languages' field." - , check (not (null deprecatedExtensions)) $+ , check (not (null ourDeprecatedExtensions)) $ PackageDistSuspicious $ "Deprecated extensions: "- ++ commaSep (map (quote . display . fst) deprecatedExtensions)+ ++ commaSep (map (quote . display . fst) ourDeprecatedExtensions) ++ ". " ++ unwords [ "Instead of '" ++ display ext ++ "' use '" ++ display replacement ++ "'."- | (ext, Just replacement) <- deprecatedExtensions ]+ | (ext, Just replacement) <- ourDeprecatedExtensions ] , check (null (category pkg)) $ PackageDistSuspicious "No 'category' field."@@ -491,8 +458,8 @@ unknownExtensions = [ name | bi <- allBuildInfo pkg , UnknownExtension name <- allExtensions bi , name `notElem` map display knownLanguages ]- deprecatedExtensions = nub $ catMaybes- [ find ((==ext) . fst) Extension.deprecatedExtensions+ ourDeprecatedExtensions = nub $ catMaybes+ [ find ((==ext) . fst) deprecatedExtensions | bi <- allBuildInfo pkg , ext <- allExtensions bi ] languagesUsedAsExtensions =@@ -624,7 +591,7 @@ PackageBuildWarning $ "'ghc-options: -prof' is not necessary and will lead to problems " ++ "when used on a library. Use the configure flag "- ++ "--enable-library-profiling and/or --enable-executable-profiling."+ ++ "--enable-library-profiling and/or --enable-profiling." , checkFlags ["-o"] $ PackageBuildWarning $@@ -713,12 +680,21 @@ , checkAlternatives "ghc-options" "extra-lib-dirs" [ (flag, dir) | flag@('-':'L':dir) <- all_ghc_options ]++ , checkAlternatives "ghc-options" "frameworks"+ [ (flag, fmwk) | (flag@"-framework", fmwk) <-+ zip all_ghc_options (safeTail all_ghc_options) ]++ , checkAlternatives "ghc-options" "extra-framework-dirs"+ [ (flag, dir) | (flag@"-framework-path", dir) <-+ zip all_ghc_options (safeTail all_ghc_options) ] ] where all_ghc_options = concatMap get_ghc_options (allBuildInfo pkg)- lib_ghc_options = maybe [] (get_ghc_options . libBuildInfo) (library pkg)+ lib_ghc_options = concatMap (get_ghc_options . libBuildInfo) (libraries pkg) get_ghc_options bi = hcOptions GHC bi ++ hcProfOptions GHC bi+ ++ hcSharedOptions GHC bi checkFlags :: [String] -> PackageCheck -> Maybe PackageCheck checkFlags flags = check (any (`elem` flags) all_ghc_options)@@ -939,9 +915,18 @@ ++ "different modules then list the other ones in the " ++ "'other-languages' field." + , checkVersion [1,23]+ (case libraries pkg of+ [lib] -> libName lib /= unPackageName (packageName pkg)+ [] -> False+ _ -> True) $+ PackageDistInexcusable $+ "To use multiple 'library' sections or a named library section "+ ++ "the package needs to specify at least 'cabal-version >= 1.23'."+ -- check use of reexported-modules sections , checkVersion [1,21]- (maybe False (not.null.reexportedModules) (library pkg)) $+ (any (not.null.reexportedModules) (libraries pkg)) $ PackageDistInexcusable $ "To use the 'reexported-module' field the package needs to specify " ++ "at least 'cabal-version: >= 1.21'."@@ -955,6 +940,13 @@ ++ ". To use this new syntax, the package needs to specify at least" ++ "'cabal-version: >= 1.21'." + -- check use of 'extra-framework-dirs' field+ , checkVersion [1,23] (any (not . null) (buildInfoField extraFrameworkDirs)) $+ -- Just a warning, because this won't break on old Cabal versions.+ PackageDistSuspiciousWarn $+ "To use the 'extra-framework-dirs' field the package needs to specify"+ ++ " at least 'cabal-version: >= 1.23'."+ -- check use of default-extensions field -- don't need to do the equivalent check for other-extensions , checkVersion [1,10] (any (not . null) (buildInfoField defaultExtensions)) $@@ -1080,7 +1072,7 @@ , check (specVersion pkg < Version [1,23] [] && isNothing (setupBuildInfo pkg) && buildType pkg == Just Custom) $- PackageDistSuspicious $+ PackageDistSuspiciousWarn $ "From version 1.23 cabal supports specifiying explicit dependencies " ++ "for Custom setup scripts. Consider using cabal-version >= 1.23 and " ++ "adding a 'custom-setup' section with a 'setup-depends' field "@@ -1152,8 +1144,7 @@ depsUsingThinningRenamingSyntax = [ name | bi <- allBuildInfo pkg- , (name, rns) <- Map.toList (targetBuildRenaming bi)- , rns /= ModuleRenaming True [] ]+ , (name, _) <- Map.toList (targetBuildRenaming bi) ] testedWithUsingWildcardSyntax = [ Dependency (PackageName (display compiler)) vr@@ -1341,10 +1332,10 @@ unknownOSs = [ os | OS (OtherOS os) <- conditions ] unknownArches = [ arch | Arch (OtherArch arch) <- conditions ] unknownImpls = [ impl | Impl (OtherCompiler impl) _ <- conditions ]- conditions = maybe [] freeVars (condLibrary pkg)- ++ concatMap (freeVars . snd) (condExecutables pkg)- freeVars (CondNode _ _ ifs) = concatMap compfv ifs- compfv (c, ct, mct) = condfv c ++ freeVars ct ++ maybe [] freeVars mct+ conditions = concatMap (fvs . snd) (condLibraries pkg)+ ++ concatMap (fvs . snd) (condExecutables pkg)+ fvs (CondNode _ _ ifs) = concatMap compfv ifs -- free variables+ compfv (c, ct, mct) = condfv c ++ fvs ct ++ maybe [] fvs mct condfv c = case c of Var v -> [v] Lit _ -> []@@ -1408,6 +1399,7 @@ has_Wall = "-Wall" `elem` ghc_options has_W = "-W" `elem` ghc_options ghc_options = hcOptions GHC bi ++ hcProfOptions GHC bi+ ++ hcSharedOptions GHC bi checkFlags :: [String] -> PackageCheck -> Maybe PackageCheck checkFlags flags = check (any (`elem` flags) ghc_options)@@ -1444,8 +1436,8 @@ allConditionalBuildInfo :: [([Condition ConfVar], BuildInfo)] allConditionalBuildInfo =- concatMap (collectCondTreePaths libBuildInfo)- (maybeToList (condLibrary pkg))+ concatMap (collectCondTreePaths libBuildInfo . snd)+ (condLibraries pkg) ++ concatMap (collectCondTreePaths buildInfo . snd) (condExecutables pkg)@@ -1538,7 +1530,8 @@ pdfile ++ " starts with an Unicode byte order mark (BOM). This may cause problems with older cabal versions." -- |Find a package description file in the given directory. Looks for--- @.cabal@ files.+-- @.cabal@ files. Like 'Distribution.Simple.Utils.findPackageDesc',+-- but generalized over monads. findPackageDesc :: Monad m => CheckPackageContentOps m -> m (Either PackageCheck FilePath) -- ^<pkgname>.cabal findPackageDesc ops@@ -1610,6 +1603,8 @@ | bi <- allBuildInfo pkg , (dir, kind) <- [ (dir, "extra-lib-dirs") | dir <- extraLibDirs bi ]+ ++ [ (dir, "extra-framework-dirs")+ | dir <- extraFrameworkDirs bi ] ++ [ (dir, "include-dirs") | dir <- includeDirs bi ] ++ [ (dir, "hs-source-dirs") | dir <- hsSourceDirs bi ] , isRelative dir ]@@ -1658,8 +1653,8 @@ -- checkPackageFileNames :: [FilePath] -> [PackageCheck] checkPackageFileNames files =- (take 1 . catMaybes . map checkWindowsPath $ files)- ++ (take 1 . catMaybes . map checkTarPath $ files)+ (take 1 . mapMaybe checkWindowsPath $ files)+ ++ (take 1 . mapMaybe checkTarPath $ files) -- If we get any of these checks triggering then we're likely to get -- many, and that's probably not helpful, so return at most one.
cabal/Cabal/Distribution/PackageDescription/Configuration.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE CPP #-} -- -fno-warn-deprecations for use of Map.foldWithKey {-# OPTIONS_GHC -fno-warn-deprecations #-} -----------------------------------------------------------------------------@@ -23,46 +22,34 @@ -- Utils parseCondition, freeVars,+ extractCondition,+ addBuildableCondition, mapCondTree, mapTreeData, mapTreeConds, mapTreeConstrs,+ transformAllBuildInfos,+ transformAllBuildDepends, ) where import Distribution.Package- ( PackageName, Dependency(..) ) import Distribution.PackageDescription- ( GenericPackageDescription(..), PackageDescription(..)- , Library(..), Executable(..), BuildInfo(..)- , Flag(..), FlagName(..), FlagAssignment- , Benchmark(..), CondTree(..), ConfVar(..), Condition(..)- , TestSuite(..) ) import Distribution.PackageDescription.Utils- ( cabalBug, userBug ) import Distribution.Version- ( VersionRange, anyVersion, intersectVersionRanges, withinRange ) import Distribution.Compiler- ( CompilerId(CompilerId) ) import Distribution.System- ( Platform(..), OS, Arch ) import Distribution.Simple.Utils- ( currentDir, lowercase )-import Distribution.Simple.Compiler- ( CompilerInfo(..) )- import Distribution.Text- ( Text(parse) ) import Distribution.Compat.ReadP as ReadP hiding ( char )-import Control.Arrow (first) import qualified Distribution.Compat.ReadP as ReadP ( char )+import Distribution.Compat.Semigroup as Semi +import Control.Arrow (first) import Data.Char ( isAlphaNum )-import Data.Maybe ( catMaybes, maybeToList )+import Data.Maybe ( mapMaybe, maybeToList ) import Data.Map ( Map, fromListWith, toList ) import qualified Data.Map as Map-#if __GLASGOW_HASKELL__ < 710-import Data.Monoid-#endif+import Data.Tree ( Tree(Node) ) ------------------------------------------------------------------------------ @@ -185,14 +172,14 @@ -- clarity. data DepTestRslt d = DepOk | MissingDeps d -instance Monoid d => Monoid (DepTestRslt d) where+instance Semigroup d => Monoid (DepTestRslt d) where mempty = DepOk- mappend DepOk x = x- mappend x DepOk = x- mappend (MissingDeps d) (MissingDeps d') = MissingDeps (d `mappend` d')-+ mappend = (Semi.<>) -data BT a = BTN a | BTB (BT a) (BT a) -- very simple binary tree+instance Semigroup d => Semigroup (DepTestRslt d) where+ DepOk <> x = x+ x <> DepOk = x+ (MissingDeps d) <> (MissingDeps d') = MissingDeps (d <> d') -- | Try to find a flag assignment that satisfies the constraints of all trees.@@ -201,8 +188,9 @@ -- resulting data, the associated dependencies, and the chosen flag -- assignments. ----- In case of failure, the _smallest_ number of of missing dependencies is--- returned. [TODO: Could also be specified with a function argument.]+-- In case of failure, the union of the dependencies that led to backtracking+-- on all branches is returned.+-- [TODO: Could also be specified with a function argument.] -- -- TODO: The current algorithm is rather naive. A better approach would be to: --@@ -228,65 +216,132 @@ -- ^ Either the missing dependencies (error case), or a pair of -- (set of build targets with dependencies, chosen flag assignments) resolveWithFlags dom os arch impl constrs trees checkDeps =- case try dom [] of- Right r -> Right r- Left dbt -> Left $ findShortest dbt+ either (Left . fromDepMapUnion) Right $ explore (build [] dom) where extraConstrs = toDepMap constrs -- simplify trees by (partially) evaluating all conditions and converting -- dependencies to dependency maps.+ simplifiedTrees :: [CondTree FlagName DependencyMap PDTagged] simplifiedTrees = map ( mapTreeConstrs toDepMap -- convert to maps+ . addBuildableCondition pdTaggedBuildInfo . mapTreeConds (fst . simplifyWithSysParams os arch impl)) trees - -- @try@ recursively tries all possible flag assignments in the domain and- -- either succeeds or returns a binary tree with the missing dependencies- -- encountered in each run. Since the tree is constructed lazily, we- -- avoid some computation overhead in the successful case.- try [] flags =+ -- @explore@ searches a tree of assignments, backtracking whenever a flag+ -- introduces a dependency that cannot be satisfied. If there is no+ -- solution, @explore@ returns the union of all dependencies that caused+ -- it to backtrack. Since the tree is constructed lazily, we avoid some+ -- computation overhead in the successful case.+ explore :: Tree FlagAssignment+ -> Either DepMapUnion (TargetSet PDTagged, FlagAssignment)+ explore (Node flags ts) = let targetSet = TargetSet $ flip map simplifiedTrees $ -- apply additional constraints to all dependencies first (`constrainBy` extraConstrs) . simplifyCondTree (env flags) deps = overallDependencies targetSet in case checkDeps (fromDepMap deps) of- DepOk -> Right (targetSet, flags)- MissingDeps mds -> Left (BTN mds)+ DepOk | null ts -> Right (targetSet, flags)+ | otherwise -> tryAll $ map explore ts+ MissingDeps mds -> Left (toDepMapUnion mds) - try ((n, vals):rest) flags =- tryAll $ map (\v -> try rest ((n, v):flags)) vals+ -- Builds a tree of all possible flag assignments. Internal nodes+ -- have only partial assignments.+ build :: FlagAssignment -> [(FlagName, [Bool])] -> Tree FlagAssignment+ build assigned [] = Node assigned []+ build assigned ((fn, vals) : unassigned) =+ Node assigned $ map (\v -> build ((fn, v) : assigned) unassigned) vals + tryAll :: [Either DepMapUnion a] -> Either DepMapUnion a tryAll = foldr mp mz -- special version of `mplus' for our local purposes- mp (Left xs) (Left ys) = (Left (BTB xs ys))- mp (Left _) m@(Right _) = m+ mp :: Either DepMapUnion a -> Either DepMapUnion a -> Either DepMapUnion a mp m@(Right _) _ = m+ mp _ m@(Right _) = m+ mp (Left xs) (Left ys) =+ let union = Map.foldrWithKey (Map.insertWith' combine)+ (unDepMapUnion xs) (unDepMapUnion ys)+ combine x y = simplifyVersionRange $ unionVersionRanges x y+ in union `seq` Left (DepMapUnion union) -- `mzero'- mz = Left (BTN [])+ mz :: Either DepMapUnion a+ mz = Left (DepMapUnion Map.empty) + env :: FlagAssignment -> FlagName -> Either FlagName Bool env flags flag = (maybe (Left flag) Right . lookup flag) flags - -- for the error case we inspect our lazy tree of missing dependencies and- -- pick the shortest list of missing dependencies- findShortest (BTN x) = x- findShortest (BTB lt rt) =- let l = findShortest lt- r = findShortest rt- in case (l,r) of- ([], xs) -> xs -- [] is too short- (xs, []) -> xs- ([x], _) -> [x] -- single elem is optimum- (_, [x]) -> [x]- (xs, ys) -> if lazyLengthCmp xs ys- then xs else ys- -- lazy variant of @\xs ys -> length xs <= length ys@- lazyLengthCmp [] _ = True- lazyLengthCmp _ [] = False- lazyLengthCmp (_:xs) (_:ys) = lazyLengthCmp xs ys+ pdTaggedBuildInfo :: PDTagged -> BuildInfo+ pdTaggedBuildInfo (Lib _ l) = libBuildInfo l+ pdTaggedBuildInfo (Exe _ e) = buildInfo e+ pdTaggedBuildInfo (Test _ t) = testBuildInfo t+ pdTaggedBuildInfo (Bench _ b) = benchmarkBuildInfo b+ pdTaggedBuildInfo PDNull = mempty +-- | Transforms a 'CondTree' by putting the input under the "then" branch of a+-- conditional that is True when Buildable is True. If 'addBuildableCondition'+-- can determine that Buildable is always True, it returns the input unchanged.+-- If Buildable is always False, it returns the empty 'CondTree'.+addBuildableCondition :: (Eq v, Monoid a, Monoid c) => (a -> BuildInfo)+ -> CondTree v c a+ -> CondTree v c a+addBuildableCondition getInfo t =+ case extractCondition (buildable . getInfo) t of+ Lit True -> t+ Lit False -> CondNode mempty mempty []+ c -> CondNode mempty mempty [(c, t, Nothing)]++-- | Extract buildable condition from a cond tree.+--+-- Background: If the conditions in a cond tree lead to Buildable being set to False,+-- then none of the dependencies for this cond tree should actually be taken into+-- account. On the other hand, some of the flags may only be decided in the solver,+-- so we cannot necessarily make the decision whether a component is Buildable or not+-- prior to solving.+--+-- What we are doing here is to partially evaluate a condition tree in order to extract+-- the condition under which Buildable is True. The predicate determines whether data+-- under a 'CondTree' is buildable.+extractCondition :: Eq v => (a -> Bool) -> CondTree v c a -> Condition v+extractCondition p = go+ where+ go (CondNode x _ cs) | not (p x) = Lit False+ | otherwise = goList cs++ goList [] = Lit True+ goList ((c, t, e) : cs) =+ let+ ct = go t+ ce = maybe (Lit True) go e+ in+ ((c `cand` ct) `cor` (CNot c `cand` ce)) `cand` goList cs++ cand (Lit False) _ = Lit False+ cand _ (Lit False) = Lit False+ cand (Lit True) x = x+ cand x (Lit True) = x+ cand x y = CAnd x y++ cor (Lit True) _ = Lit True+ cor _ (Lit True) = Lit True+ cor (Lit False) x = x+ cor x (Lit False) = x+ cor c (CNot d)+ | c == d = Lit True+ cor x y = COr x y++-- | A map of dependencies that combines version ranges using 'unionVersionRanges'.+newtype DepMapUnion = DepMapUnion { unDepMapUnion :: Map PackageName VersionRange }++toDepMapUnion :: [Dependency] -> DepMapUnion+toDepMapUnion ds =+ DepMapUnion $ fromListWith unionVersionRanges [ (p,vr) | Dependency p vr <- ds ]++fromDepMapUnion :: DepMapUnion -> [Dependency]+fromDepMapUnion m = [ Dependency p vr | (p,vr) <- toList (unDepMapUnion m) ]+ -- | A map of dependencies. Newtyped since the default monoid instance is not -- appropriate. The monoid instance uses 'intersectVersionRanges'. newtype DependencyMap = DependencyMap { unDependencyMap :: Map PackageName VersionRange }@@ -294,7 +349,10 @@ instance Monoid DependencyMap where mempty = DependencyMap Map.empty- (DependencyMap a) `mappend` (DependencyMap b) =+ mappend = (Semi.<>)++instance Semigroup DependencyMap where+ (DependencyMap a) <> (DependencyMap b) = DependencyMap (Map.unionWith intersectVersionRanges a b) toDepMap :: [Dependency] -> DependencyMap@@ -304,18 +362,20 @@ fromDepMap :: DependencyMap -> [Dependency] fromDepMap m = [ Dependency p vr | (p,vr) <- toList (unDependencyMap m) ] +-- | Flattens a CondTree using a partial flag assignment. When a condition+-- cannot be evaluated, both branches are ignored. simplifyCondTree :: (Monoid a, Monoid d) => (v -> Either v Bool) -> CondTree v d a -> (d, a) simplifyCondTree env (CondNode a d ifs) =- mconcat $ (d, a) : catMaybes (map simplifyIf ifs)+ mconcat $ (d, a) : mapMaybe simplifyIf ifs where simplifyIf (cnd, t, me) = case simplifyCondition cnd env of (Lit True, _) -> Just $ simplifyCondTree env t (Lit False, _) -> fmap (simplifyCondTree env) me- _ -> error $ "Environment not defined for all free vars"+ _ -> Nothing -- | Flatten a CondTree. This will resolve the CondTree by taking all -- possible paths into account. Note that since branches represent exclusive@@ -350,11 +410,11 @@ where (depss, _) = unzip $ filter (removeDisabledSections . snd) targets removeDisabledSections :: PDTagged -> Bool- removeDisabledSections (Lib _) = True- removeDisabledSections (Exe _ _) = True- removeDisabledSections (Test _ t) = testEnabled t- removeDisabledSections (Bench _ b) = benchmarkEnabled b- removeDisabledSections PDNull = True+ removeDisabledSections (Lib _ l) = buildable (libBuildInfo l)+ removeDisabledSections (Exe _ e) = buildable (buildInfo e)+ removeDisabledSections (Test _ t) = testEnabled t && buildable (testBuildInfo t)+ removeDisabledSections (Bench _ b) = benchmarkEnabled b && buildable (benchmarkBuildInfo b)+ removeDisabledSections PDNull = True -- Apply extra constraints to a dependency map. -- Combines dependencies where the result will only contain keys from the left@@ -375,50 +435,53 @@ -- | Collect up the targets in a TargetSet of tagged targets, storing the -- dependencies as we go. flattenTaggedTargets :: TargetSet PDTagged ->- (Maybe Library, [(String, Executable)], [(String, TestSuite)]+ ([(String, Library)], [(String, Executable)], [(String, TestSuite)] , [(String, Benchmark)])-flattenTaggedTargets (TargetSet targets) = foldr untag (Nothing, [], [], []) targets+flattenTaggedTargets (TargetSet targets) = foldr untag ([], [], [], []) targets where- untag (_, Lib _) (Just _, _, _, _) = userBug "Only one library expected"- untag (deps, Lib l) (Nothing, exes, tests, bms) =- (Just l', exes, tests, bms)+ untag (deps, Lib n l) (libs, exes, tests, bms)+ | any ((== n) . fst) libs =+ userBug $ "There exist several libs with the same name: '" ++ n ++ "'"+ -- NB: libraries live in a different namespace than everything else+ -- TODO: no, (new-style) TESTS live in same namespace!!+ | otherwise = ((n, l'):libs, exes, tests, bms) where l' = l { libBuildInfo = (libBuildInfo l) { targetBuildDepends = fromDepMap deps } }- untag (deps, Exe n e) (mlib, exes, tests, bms)+ untag (deps, Exe n e) (libs, exes, tests, bms) | any ((== n) . fst) exes = userBug $ "There exist several exes with the same name: '" ++ n ++ "'" | any ((== n) . fst) tests = userBug $ "There exists a test with the same name as an exe: '" ++ n ++ "'" | any ((== n) . fst) bms = userBug $ "There exists a benchmark with the same name as an exe: '" ++ n ++ "'"- | otherwise = (mlib, (n, e'):exes, tests, bms)+ | otherwise = (libs, (n, e'):exes, tests, bms) where e' = e { buildInfo = (buildInfo e) { targetBuildDepends = fromDepMap deps } }- untag (deps, Test n t) (mlib, exes, tests, bms)+ untag (deps, Test n t) (libs, exes, tests, bms) | any ((== n) . fst) tests = userBug $ "There exist several tests with the same name: '" ++ n ++ "'" | any ((== n) . fst) exes = userBug $ "There exists an exe with the same name as the test: '" ++ n ++ "'" | any ((== n) . fst) bms = userBug $ "There exists a benchmark with the same name as the test: '" ++ n ++ "'"- | otherwise = (mlib, exes, (n, t'):tests, bms)+ | otherwise = (libs, exes, (n, t'):tests, bms) where t' = t { testBuildInfo = (testBuildInfo t) { targetBuildDepends = fromDepMap deps } }- untag (deps, Bench n b) (mlib, exes, tests, bms)+ untag (deps, Bench n b) (libs, exes, tests, bms) | any ((== n) . fst) bms = userBug $ "There exist several benchmarks with the same name: '" ++ n ++ "'" | any ((== n) . fst) exes = userBug $ "There exists an exe with the same name as the benchmark: '" ++ n ++ "'" | any ((== n) . fst) tests = userBug $ "There exists a test with the same name as the benchmark: '" ++ n ++ "'"- | otherwise = (mlib, exes, tests, (n, b'):bms)+ | otherwise = (libs, exes, tests, (n, b'):bms) where b' = b { benchmarkBuildInfo = (benchmarkBuildInfo b)@@ -431,7 +494,7 @@ -- Convert GenericPackageDescription to PackageDescription -- -data PDTagged = Lib Library+data PDTagged = Lib String Library | Exe String Executable | Test String TestSuite | Bench String Benchmark@@ -440,14 +503,17 @@ instance Monoid PDTagged where mempty = PDNull- PDNull `mappend` x = x- x `mappend` PDNull = x- Lib l `mappend` Lib l' = Lib (l `mappend` l')- Exe n e `mappend` Exe n' e' | n == n' = Exe n (e `mappend` e')- Test n t `mappend` Test n' t' | n == n' = Test n (t `mappend` t')- Bench n b `mappend` Bench n' b' | n == n' = Bench n (b `mappend` b')- _ `mappend` _ = cabalBug "Cannot combine incompatible tags"+ mappend = (Semi.<>) +instance Semigroup PDTagged where+ PDNull <> x = x+ x <> PDNull = x+ Lib n l <> Lib n' l' | n == n' = Lib n (l <> l')+ Exe n e <> Exe n' e' | n == n' = Exe n (e <> e')+ Test n t <> Test n' t' | n == n' = Test n (t <> t')+ Bench n b <> Bench n' b' | n == n' = Bench n (b <> b')+ _ <> _ = cabalBug "Cannot combine incompatible tags"+ -- | Create a package description with all configurations resolved. -- -- This function takes a `GenericPackageDescription` and several environment@@ -465,9 +531,10 @@ -- -- This function will fail if it cannot find a flag assignment that leads to -- satisfiable dependencies. (It will not try alternative assignments for--- explicitly specified flags.) In case of failure it will return a /minimum/--- number of dependencies that could not be satisfied. On success, it will--- return the package description and the full flag assignment chosen.+-- explicitly specified flags.) In case of failure it will return the missing+-- dependencies that it encountered when trying different flag assignments.+-- On success, it will return the package description and the full flag+-- assignment chosen. -- finalizePackageDescription :: FlagAssignment -- ^ Explicitly specified flag assignments@@ -484,25 +551,21 @@ -- description along with the flag assignments chosen. finalizePackageDescription userflags satisfyDep (Platform arch os) impl constraints- (GenericPackageDescription pkg flags mlib0 exes0 tests0 bms0) =+ (GenericPackageDescription pkg flags libs0 exes0 tests0 bms0) = case resolveFlags of- Right ((mlib, exes', tests', bms'), targetSet, flagVals) ->- Right ( pkg { library = mlib+ Right ((libs', exes', tests', bms'), targetSet, flagVals) ->+ Right ( pkg { libraries = libs' , executables = exes' , testSuites = tests' , benchmarks = bms' , buildDepends = fromDepMap (overallDependencies targetSet)- --TODO: we need to find a way to avoid pulling in deps- -- for non-buildable components. However cannot simply- -- filter at this stage, since if the package were not- -- available we would have failed already. } , flagVals ) Left missing -> Left missing where -- Combine lib, exes, and tests into one list of @CondTree@s with tagged data- condTrees = maybeToList (fmap (mapTreeData Lib) mlib0 )+ condTrees = map (\(name,tree) -> mapTreeData (Lib name) tree) libs0 ++ map (\(name,tree) -> mapTreeData (Exe name) tree) exes0 ++ map (\(name,tree) -> mapTreeData (Test name) tree) tests0 ++ map (\(name,tree) -> mapTreeData (Bench name) tree) bms0@@ -510,8 +573,8 @@ resolveFlags = case resolveWithFlags flagChoices os arch impl constraints condTrees check of Right (targetSet, fs) ->- let (mlib, exes, tests, bms) = flattenTaggedTargets targetSet in- Right ( (fmap libFillInDefaults mlib,+ let (libs, exes, tests, bms) = flattenTaggedTargets targetSet in+ Right ( (map (\(n,l) -> (libFillInDefaults l) { libName = n }) libs, map (\(n,e) -> (exeFillInDefaults e) { exeName = n }) exes, map (\(n,t) -> (testFillInDefaults t) { testName = n }) tests, map (\(n,b) -> (benchFillInDefaults b) { benchmarkName = n }) bms),@@ -554,21 +617,21 @@ -- default path will be missing from the package description returned by this -- function. flattenPackageDescription :: GenericPackageDescription -> PackageDescription-flattenPackageDescription (GenericPackageDescription pkg _ mlib0 exes0 tests0 bms0) =- pkg { library = mlib+flattenPackageDescription (GenericPackageDescription pkg _ libs0 exes0 tests0 bms0) =+ pkg { libraries = reverse libs , executables = reverse exes , testSuites = reverse tests , benchmarks = reverse bms- , buildDepends = ldeps ++ reverse edeps ++ reverse tdeps ++ reverse bdeps+ , buildDepends = reverse ldeps ++ reverse edeps ++ reverse tdeps ++ reverse bdeps } where- (mlib, ldeps) = case mlib0 of- Just lib -> let (l,ds) = ignoreConditions lib in- (Just (libFillInDefaults l), ds)- Nothing -> (Nothing, [])+ (libs, ldeps) = foldr flattenLib ([],[]) libs0 (exes, edeps) = foldr flattenExe ([],[]) exes0 (tests, tdeps) = foldr flattenTst ([],[]) tests0 (bms, bdeps) = foldr flattenBm ([],[]) bms0+ flattenLib (n, t) (es, ds) =+ let (e, ds') = ignoreConditions t in+ ( (libFillInDefaults $ e { libName = n }) : es, ds' ++ ds ) flattenExe (n, t) (es, ds) = let (e, ds') = ignoreConditions t in ( (exeFillInDefaults $ e { exeName = n }) : es, ds' ++ ds )@@ -607,3 +670,82 @@ if null (hsSourceDirs bi) then bi { hsSourceDirs = [currentDir] } else bi++-- Walk a 'GenericPackageDescription' and apply @onBuildInfo@/@onSetupBuildInfo@+-- to all nested 'BuildInfo'/'SetupBuildInfo' values.+transformAllBuildInfos :: (BuildInfo -> BuildInfo)+ -> (SetupBuildInfo -> SetupBuildInfo)+ -> GenericPackageDescription+ -> GenericPackageDescription+transformAllBuildInfos onBuildInfo onSetupBuildInfo gpd = gpd'+ where+ onLibrary lib = lib { libBuildInfo = onBuildInfo $ libBuildInfo lib }+ onExecutable exe = exe { buildInfo = onBuildInfo $ buildInfo exe }+ onTestSuite tst = tst { testBuildInfo = onBuildInfo $ testBuildInfo tst }+ onBenchmark bmk = bmk { benchmarkBuildInfo =+ onBuildInfo $ benchmarkBuildInfo bmk }++ pd = packageDescription gpd+ pd' = pd {+ libraries = map onLibrary (libraries pd),+ executables = map onExecutable (executables pd),+ testSuites = map onTestSuite (testSuites pd),+ benchmarks = map onBenchmark (benchmarks pd),+ setupBuildInfo = fmap onSetupBuildInfo (setupBuildInfo pd)+ }++ gpd' = transformAllCondTrees onLibrary onExecutable+ onTestSuite onBenchmark id+ $ gpd { packageDescription = pd' }++-- | Walk a 'GenericPackageDescription' and apply @f@ to all nested+-- @build-depends@ fields.+transformAllBuildDepends :: (Dependency -> Dependency)+ -> GenericPackageDescription+ -> GenericPackageDescription+transformAllBuildDepends f gpd = gpd'+ where+ onBI bi = bi { targetBuildDepends = map f $ targetBuildDepends bi }+ onSBI stp = stp { setupDepends = map f $ setupDepends stp }+ onPD pd = pd { buildDepends = map f $ buildDepends pd }++ pd' = onPD $ packageDescription gpd+ gpd' = transformAllCondTrees id id id id (map f)+ . transformAllBuildInfos onBI onSBI+ $ gpd { packageDescription = pd' }++-- | Walk all 'CondTree's inside a 'GenericPackageDescription' and apply+-- appropriate transformations to all nodes. Helper function used by+-- 'transformAllBuildDepends' and 'transformAllBuildInfos'.+transformAllCondTrees :: (Library -> Library)+ -> (Executable -> Executable)+ -> (TestSuite -> TestSuite)+ -> (Benchmark -> Benchmark)+ -> ([Dependency] -> [Dependency])+ -> GenericPackageDescription -> GenericPackageDescription+transformAllCondTrees onLibrary onExecutable+ onTestSuite onBenchmark onDepends gpd = gpd'+ where+ gpd' = gpd {+ condLibraries = condLibs',+ condExecutables = condExes',+ condTestSuites = condTests',+ condBenchmarks = condBenchs'+ }++ condLibs = condLibraries gpd+ condExes = condExecutables gpd+ condTests = condTestSuites gpd+ condBenchs = condBenchmarks gpd++ condLibs' = map (mapSnd $ onCondTree onLibrary) condLibs+ condExes' = map (mapSnd $ onCondTree onExecutable) condExes+ condTests' = map (mapSnd $ onCondTree onTestSuite) condTests+ condBenchs' = map (mapSnd $ onCondTree onBenchmark) condBenchs++ mapSnd :: (a -> b) -> (c,a) -> (c,b)+ mapSnd = fmap++ onCondTree :: (a -> b) -> CondTree v [Dependency] a+ -> CondTree v [Dependency] b+ onCondTree g = mapCondTree g onDepends id
cabal/Cabal/Distribution/PackageDescription/Parse.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE CPP #-}+{-# LANGUAGE PatternGuards #-} {-# LANGUAGE DeriveDataTypeable #-} ----------------------------------------------------------------------------- -- |@@ -41,8 +42,20 @@ flagFieldDescrs ) where +import Distribution.ParseUtils hiding (parseFields)+import Distribution.PackageDescription+import Distribution.PackageDescription.Utils+import Distribution.Package+import Distribution.ModuleName+import Distribution.Version+import Distribution.Verbosity+import Distribution.Compiler+import Distribution.PackageDescription.Configuration+import Distribution.Simple.Utils+import Distribution.Text+import Distribution.Compat.ReadP hiding (get)+ import Data.Char (isSpace)-import Data.Foldable (traverse_) import Data.Maybe (listToMaybe, isJust) import Data.List (nub, unfoldr, partition, (\\)) import Control.Monad (liftM, foldM, when, unless, ap)@@ -53,36 +66,10 @@ import Control.Arrow (first) import System.Directory (doesFileExist) import qualified Data.ByteString.Lazy.Char8 as BS.Char8-import Data.Typeable-import Data.Data-import qualified Data.Map as Map -import Distribution.Text- ( Text(disp, parse), display, simpleParse )-import Distribution.Compat.ReadP- ((+++), option)-import qualified Distribution.Compat.ReadP as Parse import Text.PrettyPrint -import Distribution.ParseUtils hiding (parseFields)-import Distribution.PackageDescription-import Distribution.PackageDescription.Utils- ( cabalBug, userBug )-import Distribution.Package- ( PackageIdentifier(..), Dependency(..), packageName, packageVersion )-import Distribution.ModuleName ( ModuleName )-import Distribution.Version- ( Version(Version), orLaterVersion- , LowerBound(..), asVersionIntervals )-import Distribution.Verbosity (Verbosity)-import Distribution.Compiler (CompilerFlavor(..))-import Distribution.PackageDescription.Configuration (parseCondition, freeVars)-import Distribution.Simple.Utils- ( die, dieWithLocation, warn, intercalate, lowercase, cabalVersion- , withFileContents, withUTF8FileContents- , writeFileAtomic, writeUTF8File ) - -- ----------------------------------------------------------------------------- -- The PackageDescription type @@ -402,8 +389,7 @@ buildTools (\xs binfo -> binfo{buildTools=xs}) , commaListFieldWithSep vcat "build-depends" disp parse- buildDependsWithRenaming- setBuildDependsWithRenaming+ targetBuildDepends (\xs binfo -> binfo{targetBuildDepends=xs}) , spaceListField "cpp-options" showToken parseTokenQ' cppOptions (\val binfo -> binfo{cppOptions=val})@@ -419,6 +405,9 @@ , listField "frameworks" showToken parseTokenQ frameworks (\val binfo -> binfo{frameworks=val})+ , listField "extra-framework-dirs"+ showToken parseFilePathQ+ extraFrameworkDirs (\val binfo -> binfo{extraFrameworkDirs=val}) , listFieldWithSep vcat "c-sources" showFilePath parseFilePathQ cSources (\paths binfo -> binfo{cSources=paths})@@ -597,7 +586,8 @@ -- prop_isMapM fs = mapSimpleFields return fs == return fs --- names of fields that represents dependencies, thus consrca+-- names of fields that represents dependencies+-- TODO: maybe build-tools should go here too? constraintFieldNames :: [String] constraintFieldNames = ["build-depends"] @@ -605,9 +595,9 @@ -- they add and define an accessor that specifies what the dependencies -- are. This way we would completely reuse the parsing knowledge from the -- field descriptor.-parseConstraint :: Field -> ParseResult [DependencyWithRenaming]+parseConstraint :: Field -> ParseResult [Dependency] parseConstraint (F l n v)- | n == "build-depends" = runP l n (parseCommaList parse) v+ | n `elem` constraintFieldNames = runP l n (parseCommaList parse) v parseConstraint f = userBug $ "Constraint was expected (got: " ++ show f ++ ")" {-@@ -640,12 +630,14 @@ #else instance (Monad m, Functor m) => Applicative (StT s m) where #endif- pure = return+ pure a = StT (\s -> return (a,s)) (<*>) = ap instance Monad m => Monad (StT s m) where+#if __GLASGOW_HASKELL__ < 710 return a = StT (\s -> return (a,s))+#endif StT f >>= g = StT $ \s -> do (a,s') <- f s runStT (g a) s'@@ -749,14 +741,14 @@ -- 'getBody' assumes that the remaining fields only consist of -- flags, lib and exe sections.- (repos, flags, mcsetup, mlib, exes, tests, bms) <- getBody+ (repos, flags, mcsetup, libs, exes, tests, bms) <- getBody pkg warnIfRest -- warn if getBody did not parse up to the last field. -- warn about using old/new syntax with wrong cabal-version: maybeWarnCabalVersion (not $ oldSyntax fields0) pkg- checkForUndefinedFlags flags mlib exes tests+ checkForUndefinedFlags flags libs exes tests return $ GenericPackageDescription pkg { sourceRepos = repos, setupBuildInfo = mcsetup }- flags mlib exes tests bms+ flags libs exes tests bms where oldSyntax = all isSimpleField@@ -856,17 +848,18 @@ _ -> return (reverse acc) --- -- body ::= { repo | flag | library | executable | test }+ -- at most one lib+ -- body ::= { repo | flag | library | executable | test }+ -- -- The body consists of an optional sequence of declarations of flags and- -- an arbitrary number of executables and at most one library.- getBody :: PM ([SourceRepo], [Flag]+ -- an arbitrary number of libraries/executables/tests.+ getBody :: PackageDescription+ -> PM ([SourceRepo], [Flag] ,Maybe SetupBuildInfo- ,Maybe (CondTree ConfVar [Dependency] Library)+ ,[(String, CondTree ConfVar [Dependency] Library)] ,[(String, CondTree ConfVar [Dependency] Executable)] ,[(String, CondTree ConfVar [Dependency] TestSuite)] ,[(String, CondTree ConfVar [Dependency] Benchmark)])- getBody = peekField >>= \mf -> case mf of+ getBody pkg = peekField >>= \mf -> case mf of Just (Section line_no sec_type sec_label sec_fields) | sec_type == "executable" -> do when (null sec_label) $ lift $ syntaxError line_no@@ -874,7 +867,7 @@ exename <- lift $ runP line_no "executable" parseTokenQ sec_label flds <- collectFields parseExeFields sec_fields skipField- (repos, flags, csetup, lib, exes, tests, bms) <- getBody+ (repos, flags, csetup, lib, exes, tests, bms) <- getBody pkg return (repos, flags, csetup, lib, (exename, flds): exes, tests, bms) | sec_type == "test-suite" -> do@@ -904,7 +897,6 @@ -- Does the current node specify a test type? hasTestType = testInterface ts' /= testInterface emptyTestSuite- components = condTreeComponents ct -- If the current level of the tree specifies a type, -- then we are done. If not, then one of the conditional -- branches below the current node must specify a type.@@ -912,11 +904,11 @@ -- only one need one to specify a type because the -- configure step uses 'mappend' to join together the -- results of flag resolution.- in hasTestType || any checkComponent components+ in hasTestType || any checkComponent (condTreeComponents ct) if checkTestType emptyTestSuite flds then do skipField- (repos, flags, csetup, lib, exes, tests, bms) <- getBody+ (repos, flags, csetup, lib, exes, tests, bms) <- getBody pkg return (repos, flags, csetup, lib, exes, (testname, flds) : tests, bms) else lift $ syntaxError line_no $@@ -953,7 +945,6 @@ -- Does the current node specify a benchmark type? hasBenchmarkType = benchmarkInterface ts' /= benchmarkInterface emptyBenchmark- components = condTreeComponents ct -- If the current level of the tree specifies a type, -- then we are done. If not, then one of the conditional -- branches below the current node must specify a type.@@ -961,11 +952,11 @@ -- only one need one to specify a type because the -- configure step uses 'mappend' to join together the -- results of flag resolution.- in hasBenchmarkType || any checkComponent components+ in hasBenchmarkType || any checkComponent (condTreeComponents ct) if checkBenchmarkType emptyBenchmark flds then do skipField- (repos, flags, csetup, lib, exes, tests, bms) <- getBody+ (repos, flags, csetup, lib, exes, tests, bms) <- getBody pkg return (repos, flags, csetup, lib, exes, tests, (benchname, flds) : bms) else lift $ syntaxError line_no $@@ -976,14 +967,15 @@ ++ intercalate ", " (map display knownBenchmarkTypes) | sec_type == "library" -> do- unless (null sec_label) $ lift $- syntaxError line_no "'library' expects no argument"+ libname <- if null sec_label+ then return (unPackageName (packageName pkg))+ -- TODO: relax this parsing so that scoping is handled+ -- correctly+ else lift $ runP line_no "library" parseTokenQ sec_label flds <- collectFields parseLibFields sec_fields skipField- (repos, flags, csetup, lib, exes, tests, bms) <- getBody- when (isJust lib) $ lift $ syntaxError line_no- "There can only be one library section in a package description."- return (repos, flags, csetup, Just flds, exes, tests, bms)+ (repos, flags, csetup, libs, exes, tests, bms) <- getBody pkg+ return (repos, flags, csetup, (libname, flds) : libs, exes, tests, bms) | sec_type == "flag" -> do when (null sec_label) $ lift $@@ -994,7 +986,7 @@ (MkFlag (FlagName (lowercase sec_label)) "" True False) sec_fields skipField- (repos, flags, csetup, lib, exes, tests, bms) <- getBody+ (repos, flags, csetup, lib, exes, tests, bms) <- getBody pkg return (repos, flag:flags, csetup, lib, exes, tests, bms) | sec_type == "source-repository" -> do@@ -1019,7 +1011,7 @@ } sec_fields skipField- (repos, flags, csetup, lib, exes, tests, bms) <- getBody+ (repos, flags, csetup, lib, exes, tests, bms) <- getBody pkg return (repo:repos, flags, csetup, lib, exes, tests, bms) | sec_type == "custom-setup" -> do@@ -1031,7 +1023,7 @@ mempty sec_fields skipField- (repos, flags, csetup0, lib, exes, tests, bms) <- getBody+ (repos, flags, csetup0, lib, exes, tests, bms) <- getBody pkg when (isJust csetup0) $ lift $ syntaxError line_no "There can only be one 'custom-setup' section in a package description." return (repos, flags, Just flds, lib, exes, tests, bms)@@ -1039,18 +1031,18 @@ | otherwise -> do lift $ warning $ "Ignoring unknown section type: " ++ sec_type skipField- getBody+ getBody pkg Just f@(F {}) -> do _ <- lift $ syntaxError (lineNo f) $ "Plain fields are not allowed in between stanzas: " ++ show f skipField- getBody+ getBody pkg Just f@(IfBlock {}) -> do _ <- lift $ syntaxError (lineNo f) $ "If-blocks are not allowed in between stanzas: " ++ show f skipField- getBody- Nothing -> return ([], [], Nothing, Nothing, [], [], [])+ getBody pkg+ Nothing -> return ([], [], Nothing, [], [], [], []) -- Extracts all fields in a block and returns a 'CondTree'. --@@ -1064,18 +1056,30 @@ condFlds = [ f | f@IfBlock{} <- allflds ] sections = [ s | s@Section{} <- allflds ] - -- Put these through the normal parsing pass too, so that we- -- collect the ModRenamings- let depFlds = filter isConstraint simplFlds- mapM_ (\(Section l n _ _) -> lift . warning $ "Unexpected section '" ++ n ++ "' on line " ++ show l) sections a <- parser simplFlds- deps <- liftM concat . mapM (lift . fmap (map dependency) . parseConstraint) $ depFlds + -- Dependencies must be treated specially: when we+ -- parse into a CondTree, not only do we parse them into+ -- the targetBuildDepends/etc field inside the+ -- PackageDescription, but we also have to put the+ -- combined dependencies into CondTree.+ --+ -- This information is, in principle, redundant, but+ -- putting it here makes it easier for the constraint+ -- solver to pick a flag assignment which supports+ -- all of the dependencies (because it only has+ -- to check the CondTree, rather than grovel everywhere+ -- inside the conditional bits).+ deps <- liftM concat+ . mapM (lift . parseConstraint)+ . filter isConstraint+ $ simplFlds+ ifs <- mapM processIfs condFlds return (CondNode a deps ifs)@@ -1115,13 +1119,13 @@ checkForUndefinedFlags :: [Flag] ->- Maybe (CondTree ConfVar [Dependency] Library) ->+ [(String, CondTree ConfVar [Dependency] Library)] -> [(String, CondTree ConfVar [Dependency] Executable)] -> [(String, CondTree ConfVar [Dependency] TestSuite)] -> PM ()- checkForUndefinedFlags flags mlib exes tests = do+ checkForUndefinedFlags flags libs exes tests = do let definedFlags = map flagName flags- traverse_ (checkCondTreeFlags definedFlags) mlib+ mapM_ (checkCondTreeFlags definedFlags . snd) libs mapM_ (checkCondTreeFlags definedFlags . snd) exes mapM_ (checkCondTreeFlags definedFlags . snd) tests @@ -1198,24 +1202,39 @@ parseHookedBuildInfo :: String -> ParseResult HookedBuildInfo parseHookedBuildInfo inp = do fields <- readFields inp- let ss@(mLibFields:exes) = stanzas fields+ let (mLibFields:rest) = stanzas fields mLib <- parseLib mLibFields- biExes <- mapM parseExe (maybe ss (const exes) mLib)- return (mLib, biExes)+ foldM parseStanza mLib rest where- parseLib :: [Field] -> ParseResult (Maybe BuildInfo)+ -- For backwards compatibility, if you have a bare stanza,+ -- we assume it's part of the public library. We don't+ -- know what the name is, so the people using the HookedBuildInfo+ -- have to handle this carefully.+ parseLib :: [Field] -> ParseResult [(ComponentName, BuildInfo)] parseLib (bi@(F _ inFieldName _:_))- | lowercase inFieldName /= "executable" = liftM Just (parseBI bi)- parseLib _ = return Nothing+ | lowercase inFieldName /= "executable" &&+ lowercase inFieldName /= "library" &&+ lowercase inFieldName /= "benchmark" &&+ lowercase inFieldName /= "test-suite"+ = liftM (\bis -> [(CLibName "", bis)]) (parseBI bi)+ parseLib _ = return [] - parseExe :: [Field] -> ParseResult (String, BuildInfo)- parseExe (F line inFieldName mName:bi)- | lowercase inFieldName == "executable"- = do bis <- parseBI bi- return (mName, bis)- | otherwise = syntaxError line "expecting 'executable' at top of stanza"- parseExe (_:_) = cabalBug "`parseExe' called on a non-field"- parseExe [] = syntaxError 0 "error in parsing buildinfo file. Expected executable stanza"+ parseStanza :: HookedBuildInfo -> [Field] -> ParseResult HookedBuildInfo+ parseStanza bis (F line inFieldName mName:bi)+ | Just k <- case lowercase inFieldName of+ "executable" -> Just CExeName+ "library" -> Just CLibName+ "benchmark" -> Just CBenchName+ "test-suite" -> Just CTestName+ _ -> Nothing+ = do bi' <- parseBI bi+ return ((k mName, bi'):bis)+ | otherwise+ = syntaxError line $+ "expecting 'executable', 'library', 'benchmark' or 'test-suite' " +++ "at top of stanza, but got '" ++ inFieldName ++ "'"+ parseStanza _ (_:_) = cabalBug "`parseStanza' called on a non-field"+ parseStanza _ [] = syntaxError 0 "error in parsing buildinfo file. Expected stanza" parseBI st = parseFields binfoFieldDescrs storeXFieldsBI emptyBuildInfo st @@ -1231,9 +1250,7 @@ showPackageDescription pkg = render $ ppPackage pkg $$ ppCustomFields (customFieldsPD pkg)- $$ (case library pkg of- Nothing -> empty- Just lib -> ppLibrary lib)+ $$ vcat [ space $$ ppLibrary lib | lib <- libraries pkg ] $$ vcat [ space $$ ppExecutable exe | exe <- executables pkg ] where ppPackage = ppFields pkgDescrFieldDescrs@@ -1251,15 +1268,16 @@ . showHookedBuildInfo showHookedBuildInfo :: HookedBuildInfo -> String-showHookedBuildInfo (mb_lib_bi, ex_bis) = render $- (case mb_lib_bi of- Nothing -> empty- Just bi -> ppBuildInfo bi)- $$ vcat [ space- $$ text "executable:" <+> text name+showHookedBuildInfo bis = render $+ vcat [ space+ $$ ppName name $$ ppBuildInfo bi- | (name, bi) <- ex_bis ]+ | (name, bi) <- bis ] where+ ppName (CLibName name) = text "library:" <+> text name+ ppName (CExeName name) = text "executable:" <+> text name+ ppName (CTestName name) = text "test-suite:" <+> text name+ ppName (CBenchName name) = text "benchmark:" <+> text name ppBuildInfo bi = ppFields binfoFieldDescrs bi $$ ppCustomFields (customFieldsBI bi) @@ -1278,32 +1296,3 @@ --test_findIndentTabs = findIndentTabs $ unlines $ -- [ "foo", " bar", " \t baz", "\t biz\t", "\t\t \t mib" ]---- | Dependencies plus module renamings. This is what users specify; however,--- renaming information is not used for dependency resolution.-data DependencyWithRenaming = DependencyWithRenaming Dependency ModuleRenaming- deriving (Read, Show, Eq, Typeable, Data)--dependency :: DependencyWithRenaming -> Dependency-dependency (DependencyWithRenaming dep _) = dep--instance Text DependencyWithRenaming where- disp (DependencyWithRenaming d rns) = disp d <+> disp rns- parse = do d <- parse- Parse.skipSpaces- rns <- parse- Parse.skipSpaces- return (DependencyWithRenaming d rns)--buildDependsWithRenaming :: BuildInfo -> [DependencyWithRenaming]-buildDependsWithRenaming pkg =- map (\dep@(Dependency n _) ->- DependencyWithRenaming dep- (Map.findWithDefault defaultRenaming n (targetBuildRenaming pkg)))- (targetBuildDepends pkg)--setBuildDependsWithRenaming :: [DependencyWithRenaming] -> BuildInfo -> BuildInfo-setBuildDependsWithRenaming deps pkg = pkg {- targetBuildDepends = map dependency deps,- targetBuildRenaming = Map.fromList (map (\(DependencyWithRenaming (Dependency n _) rns) -> (n, rns)) deps)- }
cabal/Cabal/Distribution/PackageDescription/PrettyPrint.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE CPP #-} ----------------------------------------------------------------------------- -- | -- Module : Distribution.PackageDescription.PrettyPrint@@ -18,27 +17,18 @@ showGenericPackageDescription, ) where -#if __GLASGOW_HASKELL__ < 710-import Data.Monoid (Monoid(mempty))-#endif import Distribution.PackageDescription- ( Benchmark(..), BenchmarkInterface(..), benchmarkType- , TestSuite(..), TestSuiteInterface(..), testType- , SourceRepo(..),- customFieldsBI, CondTree(..), Condition(..), cNot,- FlagName(..), ConfVar(..), Executable(..), Library(..),- Flag(..), PackageDescription(..),- GenericPackageDescription(..))+import Distribution.Simple.Utils+import Distribution.ParseUtils+import Distribution.PackageDescription.Parse+import Distribution.Package+import Distribution.Text++import Data.Monoid as Mon (Monoid(mempty))+import Data.Maybe (isJust) import Text.PrettyPrint (hsep, parens, char, nest, empty, isEmpty, ($$), (<+>), colon, (<>), text, vcat, ($+$), Doc, render)-import Distribution.Simple.Utils (writeUTF8File)-import Distribution.ParseUtils (showFreeText, FieldDescr(..), indentWith, ppField, ppFields)-import Distribution.PackageDescription.Parse (pkgDescrFieldDescrs,binfoFieldDescrs,libFieldDescrs,- sourceRepoFieldDescrs,flagFieldDescrs)-import Distribution.Package (Dependency(..))-import Distribution.Text (Text(..))-import Data.Maybe (isJust) -- | Recompile with false for regression testing simplifiedPrinting :: Bool@@ -56,7 +46,8 @@ ppGenericPackageDescription gpd = ppPackageDescription (packageDescription gpd) $+$ ppGenPackageFlags (genPackageFlags gpd)- $+$ ppLibrary (condLibrary gpd)+ $+$ ppLibraries (unPackageName (packageName (packageDescription gpd)))+ (condLibraries gpd) $+$ ppExecutables (condExecutables gpd) $+$ ppTestSuites (condTestSuites gpd) $+$ ppBenchmarks (condBenchmarks gpd)@@ -116,10 +107,10 @@ where fields = ppFieldsFiltered flagDefaults flagFieldDescrs flag -ppLibrary :: (Maybe (CondTree ConfVar [Dependency] Library)) -> Doc-ppLibrary Nothing = empty-ppLibrary (Just condTree) =- emptyLine $ text "library" $+$ nest indentWith (ppCondTree condTree Nothing ppLib)+ppLibraries :: String -> [(String, CondTree ConfVar [Dependency] Library)] -> Doc+ppLibraries pn libs =+ vcat [emptyLine $ text (if n == pn then "library" else "library " ++ n)+ $+$ nest indentWith (ppCondTree condTree Nothing ppLib)| (n,condTree) <- libs] where ppLib lib Nothing = ppFieldsFiltered libDefaults libFieldDescrs lib $$ ppCustomFields (customFieldsBI (libBuildInfo lib))@@ -236,9 +227,9 @@ -> Condition ConfVar -> CondTree ConfVar [Dependency] a -> Doc-ppIf' it ppIt c thenTree = +ppIf' it ppIt c thenTree = if isEmpty thenDoc- then mempty+ then Mon.mempty else ppIfCondition c $$ nest indentWith thenDoc where thenDoc = ppCondTree thenTree (if simplifiedPrinting then (Just it) else Nothing) ppIt @@ -249,7 +240,7 @@ -> Doc ppIfElse it ppIt c thenTree elseTree = case (isEmpty thenDoc, isEmpty elseDoc) of- (True, True) -> mempty+ (True, True) -> Mon.mempty (False, True) -> ppIfCondition c $$ nest indentWith thenDoc (True, False) -> ppIfCondition (cNot c) $$ nest indentWith elseDoc (False, False) -> (ppIfCondition c $$ nest indentWith thenDoc)
cabal/Cabal/Distribution/ParseUtils.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE CPP #-} ----------------------------------------------------------------------------- -- | -- Module : Distribution.ParseUtils@@ -40,21 +39,17 @@ UnrecFieldParser, warnUnrec, ignoreUnrec, ) where -import Distribution.Compiler (CompilerFlavor, parseCompilerFlavorCompat)+import Distribution.Compiler import Distribution.License import Distribution.Version- ( Version(..), VersionRange, anyVersion )-import Distribution.Package ( PackageName(..), Dependency(..) )-import Distribution.ModuleName (ModuleName)+import Distribution.Package+import Distribution.ModuleName+import qualified Distribution.Compat.MonadFail as Fail import Distribution.Compat.ReadP as ReadP hiding (get) import Distribution.ReadE import Distribution.Text- ( Text(..) ) import Distribution.Simple.Utils- ( comparing, dropWhileEndLE, intercalate, lowercase- , normaliseLineEndings ) import Language.Haskell.Extension- ( Language, Extension ) import Text.PrettyPrint hiding (braces) import Data.Char (isSpace, toLower, isAlphaNum, isDigit)@@ -62,9 +57,7 @@ import Data.Tree as Tree (Tree(..), flatten) import qualified Data.Map as Map import Control.Monad (foldM, ap)-#if __GLASGOW_HASKELL__ < 710-import Control.Applicative (Applicative(..))-#endif+import Control.Applicative as AP (Applicative(..)) import System.FilePath (normalise) import Data.List (sortBy) @@ -98,16 +91,19 @@ fmap f (ParseOk ws x) = ParseOk ws $ f x instance Applicative ParseResult where- pure = return+ pure = ParseOk [] (<*>) = ap instance Monad ParseResult where- return = ParseOk []+ return = AP.pure ParseFailed err >>= _ = ParseFailed err ParseOk ws x >>= f = case f x of ParseFailed err -> ParseFailed err ParseOk ws' x' -> ParseOk (ws'++ws) x'+ fail = Fail.fail++instance Fail.MonadFail ParseResult where fail s = ParseFailed (FromString s Nothing) catchParseError :: ParseResult a -> (PError -> ParseResult a)@@ -660,8 +656,7 @@ parseOptVersion :: ReadP r Version parseOptVersion = parseQuoted ver <++ ver where ver :: ReadP r Version- ver = parse <++ return noVersion- noVersion = Version [] []+ ver = parse <++ return (Version [] []) parseTestedWithQ :: ReadP r (CompilerFlavor,VersionRange) parseTestedWithQ = parseQuoted tw <++ tw
cabal/Cabal/Distribution/Simple.hs view
@@ -56,64 +56,47 @@ -- local import Distribution.Simple.Compiler hiding (Flag) import Distribution.Simple.UserHooks-import Distribution.Package --must not specify imports, since we're exporting module.-import Distribution.PackageDescription- ( PackageDescription(..), GenericPackageDescription, Executable(..)- , updatePackageDescription, hasLibs- , HookedBuildInfo, emptyHookedBuildInfo )+import Distribution.Package+import Distribution.PackageDescription hiding (Flag) import Distribution.PackageDescription.Parse- ( readPackageDescription, readHookedBuildInfo ) import Distribution.PackageDescription.Configuration- ( flattenPackageDescription ) import Distribution.Simple.Program- ( defaultProgramConfiguration, addKnownPrograms, builtinPrograms- , restoreProgramConfiguration, reconfigurePrograms )-import Distribution.Simple.PreProcess (knownSuffixHandlers, PPSuffixHandler)+import Distribution.Simple.Program.Db+import Distribution.Simple.PreProcess import Distribution.Simple.Setup import Distribution.Simple.Command -import Distribution.Simple.Build ( build, repl )-import Distribution.Simple.SrcDist ( sdist )+import Distribution.Simple.Build+import Distribution.Simple.SrcDist import Distribution.Simple.Register- ( register, unregister ) import Distribution.Simple.Configure- ( getPersistBuildConfig, maybeGetPersistBuildConfig- , writePersistBuildConfig, checkPersistBuildConfigOutdated- , configure, checkForeignDeps, findDistPrefOrDefault ) -import Distribution.Simple.LocalBuildInfo ( LocalBuildInfo(..) )-import Distribution.Simple.Bench (bench)-import Distribution.Simple.BuildPaths ( srcPref)-import Distribution.Simple.Test (test)-import Distribution.Simple.Install (install)-import Distribution.Simple.Haddock (haddock, hscolour)+import Distribution.Simple.LocalBuildInfo+import Distribution.Simple.Bench+import Distribution.Simple.BuildPaths+import Distribution.Simple.Test+import Distribution.Simple.Install+import Distribution.Simple.Haddock import Distribution.Simple.Utils- (die, notice, info, warn, setupMessage, chattyTry,- defaultPackageDesc, defaultHookedPackageDesc,- rawSystemExitWithEnv, cabalVersion, topHandler )-import Distribution.System- ( OS(..), buildOS )+import Distribution.Utils.NubList import Distribution.Verbosity import Language.Haskell.Extension import Distribution.Version import Distribution.License import Distribution.Text- ( display ) -- Base import System.Environment(getArgs, getProgName) import System.Directory(removeFile, doesFileExist, doesDirectoryExist, removeDirectoryRecursive) import System.Exit (exitWith,ExitCode(..))-import System.IO.Error (isDoesNotExistError)-import Control.Exception (throwIO)+import System.FilePath(searchPathSeparator) import Distribution.Compat.Environment (getEnvironment)-import Distribution.Compat.Exception (catchIO) import Control.Monad (when) import Data.Foldable (traverse_)-import Data.List (intercalate, unionBy, nub, (\\))+import Data.List (unionBy) -- | A simple implementation of @main@ for a Cabal setup script. -- It reads the package description file using IO, and performs the@@ -322,7 +305,7 @@ flags' = flags { copyDistPref = toFlag distPref } hookedAction preCopy copyHook postCopy (getBuildConfig hooks verbosity distPref)- hooks flags' args+ hooks flags' { copyArgs = args } args installAction :: UserHooks -> InstallFlags -> Args -> IO () installAction hooks flags args = do@@ -424,19 +407,15 @@ post_hook hooks args flags pkg_descr localbuildinfo sanityCheckHookedBuildInfo :: PackageDescription -> HookedBuildInfo -> IO ()-sanityCheckHookedBuildInfo PackageDescription { library = Nothing } (Just _,_)- = die $ "The buildinfo contains info for a library, "- ++ "but the package does not have a library."--sanityCheckHookedBuildInfo pkg_descr (_, hookExes)- | not (null nonExistant)- = die $ "The buildinfo contains info for an executable called '"- ++ head nonExistant ++ "' but the package does not have a "- ++ "executable with that name."+sanityCheckHookedBuildInfo pkg_descr hooked_bis+ | not (null nonExistentComponents)+ = die $ "The buildinfo contains info for these non-existent components:"+ ++ intercalate ", " (map showComponentName nonExistentComponents) where- pkgExeNames = nub (map exeName (executables pkg_descr))- hookExeNames = nub (map fst hookExes)- nonExistant = hookExeNames \\ pkgExeNames+ nonExistentComponents =+ [ cname+ | (cname, _) <- hooked_bis+ , Nothing <- [lookupComponent pkg_descr cname] ] sanityCheckHookedBuildInfo _ _ = return () @@ -470,9 +449,9 @@ -- Since the list of unconfigured programs is not serialized, -- restore it to the same value as normally used at the beginning -- of a configure run:- configPrograms = restoreProgramConfiguration+ configPrograms_ = restoreProgramConfiguration (builtinPrograms ++ hookedPrograms hooks)- (configPrograms cFlags),+ `fmap` configPrograms_ cFlags, -- Use the current, not saved verbosity level: configVerbosity = Flag verbosity@@ -587,12 +566,9 @@ = simpleUserHooks { postConf = defaultPostConf,- preBuild = \_ flags ->- -- not using 'readHook' here because 'build' takes- -- extra args- getHookedBuildInfo $ fromFlag $ buildVerbosity flags,+ preBuild = readHookWithArgs buildVerbosity,+ preCopy = readHookWithArgs copyVerbosity, preClean = readHook cleanVerbosity,- preCopy = readHook copyVerbosity, preInst = readHook installVerbosity, preHscolour = readHook hscolourVerbosity, preHaddock = readHook haddockVerbosity,@@ -616,6 +592,12 @@ backwardsCompatHack = False + readHookWithArgs :: (a -> Flag Verbosity) -> Args -> a -> IO HookedBuildInfo+ readHookWithArgs get_verbosity _ flags = do+ getHookedBuildInfo verbosity+ where+ verbosity = fromFlag (get_verbosity flags)+ readHook :: (a -> Flag Verbosity) -> Args -> a -> IO HookedBuildInfo readHook get_verbosity a flags = do noExtraFlags a@@ -626,7 +608,6 @@ runConfigureScript :: Verbosity -> Bool -> ConfigFlags -> LocalBuildInfo -> IO () runConfigureScript verbosity backwardsCompatHack flags lbi = do- env <- getEnvironment let programConfig = withPrograms lbi (ccProg, ccFlags) <- configureCCompiler verbosity programConfig@@ -636,32 +617,25 @@ -- to ccFlags -- We don't try and tell configure which ld to use, as we don't have -- a way to pass its flags too- let env' = appendToEnvironment ("CFLAGS", unwords ccFlags)- env- args' = args ++ ["--with-gcc=" ++ ccProg]- handleNoWindowsSH $- rawSystemExitWithEnv verbosity "sh" args' env'+ let extraPath = fromNubList $ configProgramPathExtra flags+ let cflagsEnv = maybe (unwords ccFlags) (++ (" " ++ unwords ccFlags)) $ lookup "CFLAGS" env+ spSep = [searchPathSeparator]+ pathEnv = maybe (intercalate spSep extraPath) ((intercalate spSep extraPath ++ spSep)++) $ lookup "PATH" env+ overEnv = ("CFLAGS", Just cflagsEnv) : [("PATH", Just pathEnv) | not (null extraPath)]+ args' = args ++ ["CC=" ++ ccProg]+ shProg = simpleProgram "sh"+ progDb = modifyProgramSearchPath (\p -> map ProgramSearchPathDir extraPath ++ p) emptyProgramDb+ shConfiguredProg <- lookupProgram shProg `fmap` configureProgram verbosity shProg progDb+ case shConfiguredProg of+ Just sh -> runProgramInvocation verbosity (programInvocation (sh {programOverrideEnv = overEnv}) args')+ Nothing -> die notFoundMsg where args = "./configure" : configureArgs backwardsCompatHack flags - appendToEnvironment (key, val) [] = [(key, val)]- appendToEnvironment (key, val) (kv@(k, v) : rest)- | key == k = (key, v ++ " " ++ val) : rest- | otherwise = kv : appendToEnvironment (key, val) rest-- handleNoWindowsSH action- | buildOS /= Windows- = action-- | otherwise- = action- `catchIO` \ioe -> if isDoesNotExistError ioe- then die notFoundMsg- else throwIO ioe-- notFoundMsg = "The package has a './configure' script. This requires a "- ++ "Unix compatibility toolchain such as MinGW+MSYS or Cygwin."+ notFoundMsg = "The package has a './configure' script. If you are on Windows, This requires a "+ ++ "Unix compatibility toolchain such as MinGW+MSYS or Cygwin. "+ ++ "If you are not on Windows, ensure that an 'sh' command is discoverable in your path." getHookedBuildInfo :: Verbosity -> IO HookedBuildInfo getHookedBuildInfo verbosity = do@@ -714,6 +688,5 @@ defaultRegHook pkg_descr localbuildinfo _ flags = if hasLibs pkg_descr then register pkg_descr localbuildinfo flags- else setupMessage verbosity+ else setupMessage (fromFlag (regVerbosity flags)) "Package contains no library to register:" (packageId pkg_descr)- where verbosity = fromFlag (regVerbosity flags)
cabal/Cabal/Distribution/Simple/Bench.hs view
@@ -16,22 +16,17 @@ ) where import qualified Distribution.PackageDescription as PD- ( PackageDescription(..), BuildInfo(buildable)- , Benchmark(..), BenchmarkInterface(..), benchmarkType, hasBenchmarks )-import Distribution.Simple.BuildPaths ( exeExtension )-import Distribution.Simple.Compiler ( compilerInfo )+import Distribution.Simple.BuildPaths+import Distribution.Simple.Compiler import Distribution.Simple.InstallDirs- ( fromPathTemplate, initialPathTemplateEnv, PathTemplateVariable(..)- , substPathTemplate , toPathTemplate, PathTemplate ) import qualified Distribution.Simple.LocalBuildInfo as LBI- ( LocalBuildInfo(..), localLibraryName )-import Distribution.Simple.Setup ( BenchmarkFlags(..), fromFlag )-import Distribution.Simple.UserHooks ( Args )-import Distribution.Simple.Utils ( die, notice, rawSystemExitCode )+import Distribution.Simple.Setup+import Distribution.Simple.UserHooks+import Distribution.Simple.Utils import Distribution.Text -import Control.Monad ( when, unless )-import System.Exit ( ExitCode(..), exitFailure, exitWith )+import Control.Monad ( when, unless, forM )+import System.Exit ( ExitCode(..), exitFailure, exitSuccess ) import System.Directory ( doesFileExist ) import System.FilePath ( (</>), (<.>) ) @@ -78,9 +73,9 @@ ++ show (disp $ PD.benchmarkType bm) exitFailure - when (not $ PD.hasBenchmarks pkg_descr) $ do+ unless (PD.hasBenchmarks pkg_descr) $ do notice verbosity "Package has no benchmarks."- exitWith ExitSuccess+ exitSuccess when (PD.hasBenchmarks pkg_descr && null enabledBenchmarks) $ die $ "No benchmarks enabled. Did you remember to configure with "@@ -88,7 +83,7 @@ bmsToRun <- case benchmarkNames of [] -> return enabledBenchmarks- names -> flip mapM names $ \bmName ->+ names -> forM names $ \bmName -> let benchmarkMap = zip enabledNames enabledBenchmarks enabledNames = map PD.benchmarkName enabledBenchmarks allNames = map PD.benchmarkName pkgBenchmarks@@ -123,6 +118,6 @@ fromPathTemplate $ substPathTemplate env template where env = initialPathTemplateEnv- (PD.package pkg_descr) (LBI.localLibraryName lbi)+ (PD.package pkg_descr) (LBI.localUnitId lbi) (compilerInfo $ LBI.compiler lbi) (LBI.hostPlatform lbi) ++ [(BenchmarkNameVar, toPathTemplate $ PD.benchmarkName bm)]
cabal/Cabal/Distribution/Simple/Build.hs view
@@ -23,6 +23,7 @@ writeAutogenFiles, ) where +import Distribution.Package import qualified Distribution.Simple.GHC as GHC import qualified Distribution.Simple.GHCJS as GHCJS import qualified Distribution.Simple.JHC as JHC@@ -32,66 +33,39 @@ import qualified Distribution.Simple.Build.Macros as Build.Macros import qualified Distribution.Simple.Build.PathsModule as Build.PathsModule+import qualified Distribution.Simple.Program.HcPkg as HcPkg -import Distribution.Package- ( Package(..), PackageName(..), PackageIdentifier(..)- , Dependency(..), thisPackageVersion, PackageKey(..), packageName- , LibraryName(..) )-import Distribution.Simple.Compiler- ( Compiler, CompilerFlavor(..), compilerFlavor- , PackageDB(..), PackageDBStack )-import Distribution.PackageDescription- ( PackageDescription(..), BuildInfo(..), Library(..), Executable(..)- , TestSuite(..), TestSuiteInterface(..), Benchmark(..)- , BenchmarkInterface(..), allBuildInfo, defaultRenaming )+import Distribution.Simple.Compiler hiding (Flag)+import Distribution.PackageDescription hiding (Flag) import qualified Distribution.InstalledPackageInfo as IPI import qualified Distribution.ModuleName as ModuleName-import Distribution.ModuleName (ModuleName) import Distribution.Simple.Setup- ( Flag(..), BuildFlags(..), ReplFlags(..), fromFlag ) import Distribution.Simple.BuildTarget- ( BuildTarget(..), readBuildTargets ) import Distribution.Simple.PreProcess- ( preprocessComponent, preprocessExtras, PPSuffixHandler ) import Distribution.Simple.LocalBuildInfo- ( LocalBuildInfo(compiler, buildDir, withPackageDB, withPrograms)- , Component(..), componentName, getComponent, componentBuildInfo- , ComponentLocalBuildInfo(..), pkgEnabledComponents- , withComponentsInBuildOrder, componentsInBuildOrder- , ComponentName(..), showComponentName- , ComponentDisabledReason(..), componentDisabledReason- , inplacePackageId ) import Distribution.Simple.Program.Types import Distribution.Simple.Program.Db-import qualified Distribution.Simple.Program.HcPkg as HcPkg import Distribution.Simple.BuildPaths- ( autogenModulesDir, autogenModuleName, cppHeaderName, exeExtension )+import Distribution.Simple.Configure import Distribution.Simple.Register- ( registerPackage, inplaceInstalledPackageInfo )-import Distribution.Simple.Test.LibV09 ( stubFilePath, stubName )+import Distribution.Simple.Test.LibV09 import Distribution.Simple.Utils- ( createDirectoryIfMissingVerbose, rewriteFile- , die, info, debug, warn, setupMessage ) -import Distribution.Verbosity- ( Verbosity )+import Distribution.System import Distribution.Text- ( display )+import Distribution.Verbosity import qualified Data.Map as Map import qualified Data.Set as Set-import Data.Either- ( partitionEithers ) import Data.List- ( intersect, intercalate )+ ( intersect ) import Control.Monad- ( when, unless, forM_ )+ ( when, unless ) import System.FilePath ( (</>), (<.>) ) import System.Directory- ( getCurrentDirectory, removeDirectoryRecursive, removeFile- , doesDirectoryExist, doesFileExist )+ ( getCurrentDirectory ) -- ----------------------------------------------------------------------------- -- |Build the libraries and executables in this package.@@ -107,26 +81,27 @@ targets <- readBuildTargets pkg_descr (buildArgs flags) targets' <- checkBuildTargets verbosity pkg_descr targets- let componentsToBuild = map fst (componentsInBuildOrder lbi (map fst targets'))+ let componentsToBuild = componentsInBuildOrder lbi (map fst targets') info verbosity $ "Component build order: "- ++ intercalate ", " (map showComponentName componentsToBuild)+ ++ intercalate ", " (map (showComponentName . componentLocalName) componentsToBuild) - initialBuildSteps distPref pkg_descr lbi verbosity when (null targets) $ -- Only bother with this message if we're building the whole package setupMessage verbosity "Building" (packageId pkg_descr) internalPackageDB <- createInternalPackageDB verbosity lbi distPref - withComponentsInBuildOrder pkg_descr lbi componentsToBuild $ \comp clbi ->+ -- TODO: we're computing this twice, do it once!+ withComponentsInBuildOrder pkg_descr lbi (map fst targets') $ \comp clbi -> do+ initialBuildSteps distPref pkg_descr lbi clbi verbosity let bi = componentBuildInfo comp progs' = addInternalBuildTools pkg_descr lbi bi (withPrograms lbi) lbi' = lbi { withPrograms = progs', withPackageDB = withPackageDB lbi ++ [internalPackageDB] }- in buildComponent verbosity (buildNumJobs flags) pkg_descr- lbi' suffixes comp clbi distPref+ buildComponent verbosity (buildNumJobs flags) pkg_descr+ lbi' suffixes comp clbi distPref repl :: PackageDescription -- ^ Mostly information from the .cabal file@@ -149,9 +124,7 @@ componentForRepl = last componentsToBuild debug verbosity $ "Component build order: " ++ intercalate ", "- [ showComponentName c | (c,_) <- componentsToBuild ]-- initialBuildSteps distPref pkg_descr lbi verbosity+ [ showComponentName (componentLocalName clbi) | clbi <- componentsToBuild ] internalPackageDB <- createInternalPackageDB verbosity lbi distPref @@ -164,25 +137,30 @@ -- build any dependent components sequence_- [ let comp = getComponent pkg_descr cname- lbi' = lbiForComponent comp lbi- in buildComponent verbosity NoFlag- pkg_descr lbi' suffixes comp clbi distPref- | (cname, clbi) <- init componentsToBuild ]+ [ do let cname = componentLocalName clbi+ comp = getComponent pkg_descr cname+ lbi' = lbiForComponent comp lbi+ initialBuildSteps distPref pkg_descr lbi clbi verbosity+ buildComponent verbosity NoFlag+ pkg_descr lbi' suffixes comp clbi distPref+ | clbi <- init componentsToBuild ] -- REPL for target components- let (cname, clbi) = componentForRepl+ let clbi = componentForRepl+ cname = componentLocalName clbi comp = getComponent pkg_descr cname lbi' = lbiForComponent comp lbi- in replComponent verbosity pkg_descr lbi' suffixes comp clbi distPref+ initialBuildSteps distPref pkg_descr lbi clbi verbosity+ replComponent verbosity pkg_descr lbi' suffixes comp clbi distPref -- | Start an interpreter without loading any package files.-startInterpreter :: Verbosity -> ProgramDb -> Compiler -> PackageDBStack -> IO ()-startInterpreter verbosity programDb comp packageDBs =+startInterpreter :: Verbosity -> ProgramDb -> Compiler -> Platform+ -> PackageDBStack -> IO ()+startInterpreter verbosity programDb comp platform packageDBs = case compilerFlavor comp of- GHC -> GHC.startInterpreter verbosity programDb comp packageDBs- GHCJS -> GHCJS.startInterpreter verbosity programDb comp packageDBs+ GHC -> GHC.startInterpreter verbosity programDb comp platform packageDBs+ GHCJS -> GHCJS.startInterpreter verbosity programDb comp platform packageDBs _ -> die "A REPL is not supported with this compiler." buildComponent :: Verbosity@@ -196,9 +174,9 @@ -> IO () buildComponent verbosity numJobs pkg_descr lbi suffixes comp@(CLib lib) clbi distPref = do- preprocessComponent pkg_descr comp lbi False verbosity suffixes+ preprocessComponent pkg_descr comp lbi clbi False verbosity suffixes extras <- preprocessExtras comp lbi- info verbosity "Building library..."+ info verbosity $ "Building library " ++ libName lib ++ "..." let libbi = libBuildInfo lib lib' = lib { libBuildInfo = addExtraCSources libbi extras } buildLib verbosity numJobs pkg_descr lbi lib' clbi@@ -207,17 +185,15 @@ -- on internally defined libraries. pwd <- getCurrentDirectory let -- The in place registration uses the "-inplace" suffix, not an ABI hash- ipkgid = inplacePackageId (packageId installedPkgInfo) installedPkgInfo = inplaceInstalledPackageInfo pwd distPref pkg_descr- ipkgid lib' lbi clbi+ (AbiHash "") lib' lbi clbi - registerPackage verbosity- installedPkgInfo pkg_descr lbi True -- True meaning in place- (withPackageDB lbi)+ registerPackage verbosity (compiler lbi) (withPrograms lbi) HcPkg.MultiInstance+ (withPackageDB lbi) installedPkgInfo buildComponent verbosity numJobs pkg_descr lbi suffixes comp@(CExe exe) clbi _ = do- preprocessComponent pkg_descr comp lbi False verbosity suffixes+ preprocessComponent pkg_descr comp lbi clbi False verbosity suffixes extras <- preprocessExtras comp lbi info verbosity $ "Building executable " ++ exeName exe ++ "..." let ebi = buildInfo exe@@ -229,7 +205,7 @@ comp@(CTest test@TestSuite { testInterface = TestSuiteExeV10{} }) clbi _distPref = do let exe = testSuiteExeV10AsExe test- preprocessComponent pkg_descr comp lbi False verbosity suffixes+ preprocessComponent pkg_descr comp lbi clbi False verbosity suffixes extras <- preprocessExtras comp lbi info verbosity $ "Building test suite " ++ testName test ++ "..." let ebi = buildInfo exe@@ -249,11 +225,15 @@ pwd <- getCurrentDirectory let (pkg, lib, libClbi, lbi, ipi, exe, exeClbi) = testSuiteLibV09AsLibAndExe pkg_descr test clbi lbi0 distPref pwd- preprocessComponent pkg_descr comp lbi False verbosity suffixes+ preprocessComponent pkg_descr comp lbi clbi False verbosity suffixes extras <- preprocessExtras comp lbi info verbosity $ "Building test suite " ++ testName test ++ "..." buildLib verbosity numJobs pkg lbi lib libClbi- registerPackage verbosity ipi pkg lbi True $ withPackageDB lbi+ -- NB: need to enable multiple instances here, because on 7.10++ -- the package name is the same as the library, and we still+ -- want the registration to go through.+ registerPackage verbosity (compiler lbi) (withPrograms lbi) HcPkg.MultiInstance+ (withPackageDB lbi) ipi let ebi = buildInfo exe exe' = exe { buildInfo = addExtraCSources ebi extras } buildExe verbosity numJobs pkg_descr lbi exe' exeClbi@@ -269,7 +249,7 @@ comp@(CBench bm@Benchmark { benchmarkInterface = BenchmarkExeV10 {} }) clbi _ = do let (exe, exeClbi) = benchmarkExeV10asExe bm clbi- preprocessComponent pkg_descr comp lbi False verbosity suffixes+ preprocessComponent pkg_descr comp lbi clbi False verbosity suffixes extras <- preprocessExtras comp lbi info verbosity $ "Building benchmark " ++ benchmarkName bm ++ "..." let ebi = buildInfo exe@@ -302,7 +282,7 @@ -> IO () replComponent verbosity pkg_descr lbi suffixes comp@(CLib lib) clbi _ = do- preprocessComponent pkg_descr comp lbi False verbosity suffixes+ preprocessComponent pkg_descr comp lbi clbi False verbosity suffixes extras <- preprocessExtras comp lbi let libbi = libBuildInfo lib lib' = lib { libBuildInfo = libbi { cSources = cSources libbi ++ extras } }@@ -310,7 +290,7 @@ replComponent verbosity pkg_descr lbi suffixes comp@(CExe exe) clbi _ = do- preprocessComponent pkg_descr comp lbi False verbosity suffixes+ preprocessComponent pkg_descr comp lbi clbi False verbosity suffixes extras <- preprocessExtras comp lbi let ebi = buildInfo exe exe' = exe { buildInfo = ebi { cSources = cSources ebi ++ extras } }@@ -321,7 +301,7 @@ comp@(CTest test@TestSuite { testInterface = TestSuiteExeV10{} }) clbi _distPref = do let exe = testSuiteExeV10AsExe test- preprocessComponent pkg_descr comp lbi False verbosity suffixes+ preprocessComponent pkg_descr comp lbi clbi False verbosity suffixes extras <- preprocessExtras comp lbi let ebi = buildInfo exe exe' = exe { buildInfo = ebi { cSources = cSources ebi ++ extras } }@@ -335,7 +315,7 @@ pwd <- getCurrentDirectory let (pkg, lib, libClbi, lbi, _, _, _) = testSuiteLibV09AsLibAndExe pkg_descr test clbi lbi0 distPref pwd- preprocessComponent pkg_descr comp lbi False verbosity suffixes+ preprocessComponent pkg_descr comp lbi clbi False verbosity suffixes extras <- preprocessExtras comp lbi let libbi = libBuildInfo lib lib' = lib { libBuildInfo = libbi { cSources = cSources libbi ++ extras } }@@ -352,7 +332,7 @@ comp@(CBench bm@Benchmark { benchmarkInterface = BenchmarkExeV10 {} }) clbi _ = do let (exe, exeClbi) = benchmarkExeV10asExe bm clbi- preprocessComponent pkg_descr comp lbi False verbosity suffixes+ preprocessComponent pkg_descr comp lbi clbi False verbosity suffixes extras <- preprocessExtras comp lbi let ebi = buildInfo exe exe' = exe { buildInfo = ebi { cSources = cSources ebi ++ extras } }@@ -397,6 +377,7 @@ where bi = testBuildInfo test lib = Library {+ libName = testName test, exposedModules = [ m ], reexportedModules = [], requiredSignatures = [],@@ -404,24 +385,29 @@ libExposed = True, libBuildInfo = bi }+ -- This is, like, the one place where we use a CTestName for a library.+ -- Should NOT use library name, since that could conflict!+ PackageIdentifier pkg_name pkg_ver = package pkg_descr+ compat_name = computeCompatPackageName pkg_name (CTestName (testName test))+ compat_key = computeCompatPackageKey (compiler lbi) compat_name pkg_ver (componentUnitId clbi) libClbi = LibComponentLocalBuildInfo { componentPackageDeps = componentPackageDeps clbi- , componentPackageRenaming = componentPackageRenaming clbi- , componentLibraryName = LibraryName (testName test)- , componentExposedModules = [IPI.ExposedModule m Nothing Nothing]- , componentPackageKey = OldPackageKey (PackageIdentifier (PackageName (testName test)) (pkgVersion (package pkg_descr)))+ , componentLocalName = CLibName (testName test)+ , componentIsPublic = False+ , componentIncludes = componentIncludes clbi+ , componentUnitId = componentUnitId clbi+ , componentCompatPackageName = compat_name+ , componentCompatPackageKey = compat_key+ , componentExposedModules = [IPI.ExposedModule m Nothing] } pkg = pkg_descr {- package = (package pkg_descr) {- pkgName = PackageName (testName test)- }+ package = (package pkg_descr) { pkgName = compat_name } , buildDepends = targetBuildDepends $ testBuildInfo test , executables = [] , testSuites = []- , library = Just lib+ , libraries = [lib] }- ipkgid = inplacePackageId (packageId pkg)- ipi = inplaceInstalledPackageInfo pwd distPref pkg ipkgid lib lbi libClbi+ ipi = inplaceInstalledPackageInfo pwd distPref pkg (AbiHash "") lib lbi libClbi testDir = buildDir lbi </> stubName test </> stubName test ++ "-tmp" testLibDep = thisPackageVersion $ package pkg@@ -432,22 +418,22 @@ hsSourceDirs = [ testDir ], targetBuildDepends = testLibDep : (targetBuildDepends $ testBuildInfo test),- targetBuildRenaming =- Map.insert (packageName pkg) defaultRenaming- (targetBuildRenaming $ testBuildInfo test)+ targetBuildRenaming = Map.empty } } -- | The stub executable needs a new 'ComponentLocalBuildInfo' -- that exposes the relevant test suite library.+ deps = (IPI.installedUnitId ipi, packageId ipi)+ : (filter (\(_, x) -> let PackageName name = pkgName x+ in name == "Cabal" || name == "base")+ (componentPackageDeps clbi)) exeClbi = ExeComponentLocalBuildInfo {- componentPackageDeps =- (IPI.installedPackageId ipi, packageId ipi)- : (filter (\(_, x) -> let PackageName name = pkgName x- in name == "Cabal" || name == "base")- (componentPackageDeps clbi)),- componentPackageRenaming =- Map.insert (packageName ipi) defaultRenaming- (componentPackageRenaming clbi)+ -- TODO: this is a hack, but as long as this is unique+ -- (doesn't clobber something) we won't run into trouble+ componentUnitId = mkUnitId (stubName test),+ componentLocalName = CExeName (stubName test),+ componentPackageDeps = deps,+ componentIncludes = zip (map fst deps) (repeat defaultRenaming) } testSuiteLibV09AsLibAndExe _ TestSuite{} _ _ _ _ = error "testSuiteLibV09AsLibAndExe: wrong kind" @@ -465,8 +451,10 @@ buildInfo = benchmarkBuildInfo bm } exeClbi = ExeComponentLocalBuildInfo {+ componentUnitId = componentUnitId clbi,+ componentLocalName = CExeName (benchmarkName bm), componentPackageDeps = componentPackageDeps clbi,- componentPackageRenaming = componentPackageRenaming clbi+ componentIncludes = componentIncludes clbi } benchmarkExeV10asExe Benchmark{} _ = error "benchmarkExeV10asExe: wrong kind" @@ -475,24 +463,14 @@ createInternalPackageDB :: Verbosity -> LocalBuildInfo -> FilePath -> IO PackageDB createInternalPackageDB verbosity lbi distPref = do- case compilerFlavor (compiler lbi) of- GHC -> createWith $ GHC.hcPkgInfo (withPrograms lbi)- GHCJS -> createWith $ GHCJS.hcPkgInfo (withPrograms lbi)- LHC -> createWith $ LHC.hcPkgInfo (withPrograms lbi)- _ -> return packageDB- where- dbPath = distPref </> "package.conf.inplace"- packageDB = SpecificPackageDB dbPath- createWith hpi = do- dir_exists <- doesDirectoryExist dbPath- if dir_exists- then removeDirectoryRecursive dbPath- else do file_exists <- doesFileExist dbPath- when file_exists $ removeFile dbPath- if HcPkg.useSingleFileDb hpi- then writeFile dbPath "[]"- else HcPkg.init hpi verbosity dbPath- return packageDB+ existsAlready <- doesPackageDBExist dbPath+ when existsAlready $ deletePackageDB dbPath+ createPackageDB verbosity (compiler lbi) (withPrograms lbi) False dbPath + return (SpecificPackageDB dbPath)+ where+ dbPath = case compilerFlavor (compiler lbi) of+ UHC -> UHC.inplacePackageDbPath lbi+ _ -> distPref </> "package.conf.inplace" addInternalBuildTools :: PackageDescription -> LocalBuildInfo -> BuildInfo -> ProgramDb -> ProgramDb@@ -559,76 +537,33 @@ initialBuildSteps :: FilePath -- ^"dist" prefix -> PackageDescription -- ^mostly information from the .cabal file -> LocalBuildInfo -- ^Configuration information+ -> ComponentLocalBuildInfo -> Verbosity -- ^The verbosity to use -> IO ()-initialBuildSteps _distPref pkg_descr lbi verbosity = do+initialBuildSteps _distPref pkg_descr lbi clbi verbosity = do -- check that there's something to build unless (not . null $ allBuildInfo pkg_descr) $ do let name = display (packageId pkg_descr) die $ "No libraries, executables, tests, or benchmarks " ++ "are enabled for package " ++ name ++ "." - createDirectoryIfMissingVerbose verbosity True (buildDir lbi)+ createDirectoryIfMissingVerbose verbosity True (componentBuildDir lbi clbi) - writeAutogenFiles verbosity pkg_descr lbi+ writeAutogenFiles verbosity pkg_descr lbi clbi -- | Generate and write out the Paths_<pkg>.hs and cabal_macros.h files -- writeAutogenFiles :: Verbosity -> PackageDescription -> LocalBuildInfo+ -> ComponentLocalBuildInfo -> IO ()-writeAutogenFiles verbosity pkg lbi = do- createDirectoryIfMissingVerbose verbosity True (autogenModulesDir lbi)+writeAutogenFiles verbosity pkg lbi clbi = do+ createDirectoryIfMissingVerbose verbosity True (autogenModulesDir lbi clbi) - let pathsModulePath = autogenModulesDir lbi+ let pathsModulePath = autogenModulesDir lbi clbi </> ModuleName.toFilePath (autogenModuleName pkg) <.> "hs"- rewriteFile pathsModulePath (Build.PathsModule.generate pkg lbi)-- let cppHeaderPath = autogenModulesDir lbi </> cppHeaderName- rewriteFile cppHeaderPath (Build.Macros.generate pkg lbi)---- | Check that the given build targets are valid in the current context.------ Also swizzle into a more convenient form.----checkBuildTargets :: Verbosity -> PackageDescription -> [BuildTarget]- -> IO [(ComponentName, Maybe (Either ModuleName FilePath))]-checkBuildTargets _ pkg [] =- return [ (componentName c, Nothing) | c <- pkgEnabledComponents pkg ]--checkBuildTargets verbosity pkg targets = do-- let (enabled, disabled) =- partitionEithers- [ case componentDisabledReason (getComponent pkg cname) of- Nothing -> Left target'- Just reason -> Right (cname, reason)- | target <- targets- , let target'@(cname,_) = swizzleTarget target ]-- case disabled of- [] -> return ()- ((cname,reason):_) -> die $ formatReason (showComponentName cname) reason-- forM_ [ (c, t) | (c, Just t) <- enabled ] $ \(c, t) ->- warn verbosity $ "Ignoring '" ++ either display id t ++ ". The whole "- ++ showComponentName c ++ " will be built. (Support for "- ++ "module and file targets has not been implemented yet.)"-- return enabled-- where- swizzleTarget (BuildTargetComponent c) = (c, Nothing)- swizzleTarget (BuildTargetModule c m) = (c, Just (Left m))- swizzleTarget (BuildTargetFile c f) = (c, Just (Right f))+ rewriteFile pathsModulePath (Build.PathsModule.generate pkg lbi clbi) - formatReason cn DisabledComponent =- "Cannot build the " ++ cn ++ " because the component is marked "- ++ "as disabled in the .cabal file."- formatReason cn DisabledAllTests =- "Cannot build the " ++ cn ++ " because test suites are not "- ++ "enabled. Run configure with the flag --enable-tests"- formatReason cn DisabledAllBenchmarks =- "Cannot build the " ++ cn ++ " because benchmarks are not "- ++ "enabled. Re-run configure with the flag --enable-benchmarks"+ let cppHeaderPath = autogenModulesDir lbi clbi </> cppHeaderName+ rewriteFile cppHeaderPath (Build.Macros.generate pkg lbi clbi)
cabal/Cabal/Distribution/Simple/Build/Macros.hs view
@@ -22,37 +22,30 @@ generatePackageVersionMacros, ) where -import Data.Maybe- ( isJust ) import Distribution.Package- ( PackageIdentifier(PackageIdentifier) ) import Distribution.Version- ( Version(versionBranch) ) import Distribution.PackageDescription- ( PackageDescription )-import Distribution.Simple.Compiler- ( packageKeySupported ) import Distribution.Simple.LocalBuildInfo- ( LocalBuildInfo(compiler, withPrograms), externalPackageDeps, localPackageKey ) import Distribution.Simple.Program.Db- ( configuredPrograms ) import Distribution.Simple.Program.Types- ( ConfiguredProgram(programId, programVersion) ) import Distribution.Text- ( display ) +import Data.Maybe+ ( isJust )+ -- ------------------------------------------------------------ -- * Generate cabal_macros.h -- ------------------------------------------------------------ -- | The contents of the @cabal_macros.h@ for the given configured package. ---generate :: PackageDescription -> LocalBuildInfo -> String-generate _pkg_descr lbi =+generate :: PackageDescription -> LocalBuildInfo -> ComponentLocalBuildInfo -> String+generate pkg_descr lbi clbi = "/* DO NOT EDIT: This file is automatically generated by Cabal */\n\n" ++- generatePackageVersionMacros (map snd (externalPackageDeps lbi)) +++ generatePackageVersionMacros+ (package pkg_descr : map snd (componentPackageDeps clbi)) ++ generateToolVersionMacros (configuredPrograms . withPrograms $ lbi) ++- generatePackageKeyMacro lbi+ generateComponentIdMacro lbi clbi -- | Helper function that generates just the @VERSION_pkg@ and @MIN_VERSION_pkg@ -- macros for a list of package ids (usually used with the specific deps of@@ -84,10 +77,10 @@ -- 'generateToolVersionMacros'. -- generateMacros :: String -> String -> Version -> String-generateMacros prefix name version =+generateMacros macro_prefix name version = concat- ["#define ", prefix, "VERSION_",name," ",show (display version),"\n"- ,"#define MIN_", prefix, "VERSION_",name,"(major1,major2,minor) (\\\n"+ ["#define ", macro_prefix, "VERSION_",name," ",show (display version),"\n"+ ,"#define MIN_", macro_prefix, "VERSION_",name,"(major1,major2,minor) (\\\n" ," (major1) < ",major1," || \\\n" ," (major1) == ",major1," && (major2) < ",major2," || \\\n" ," (major1) == ",major1," && (major2) == ",major2," && (minor) <= ",minor,")"@@ -96,14 +89,19 @@ where (major1:major2:minor:_) = map show (versionBranch version ++ repeat 0) --- | Generate the @CURRENT_PACKAGE_KEY@ definition for the package key--- of the current package, if supported by the compiler.--- NB: this only makes sense for definite packages.-generatePackageKeyMacro :: LocalBuildInfo -> String-generatePackageKeyMacro lbi- | packageKeySupported (compiler lbi) =- "#define CURRENT_PACKAGE_KEY \"" ++ display (localPackageKey lbi) ++ "\"\n\n"- | otherwise = ""+-- | Generate the @CURRENT_COMPONENT_ID@ definition for the component ID+-- of the current package.+generateComponentIdMacro :: LocalBuildInfo -> ComponentLocalBuildInfo -> String+generateComponentIdMacro lbi clbi =+ concat $+ (case clbi of+ LibComponentLocalBuildInfo{} ->+ ["#define CURRENT_PACKAGE_KEY \"" ++ componentCompatPackageKey clbi ++ "\"\n"]+ _ -> [])+ +++ ["#define CURRENT_COMPONENT_ID \"" ++ display (componentComponentId clbi) ++ "\"\n"+ ,"#define LOCAL_COMPONENT_ID \"" ++ display (localComponentId lbi) ++ "\"\n"+ ,"\n"] fixchar :: Char -> Char fixchar '-' = '_'
cabal/Cabal/Distribution/Simple/Build/PathsModule.hs view
@@ -19,25 +19,14 @@ ) where import Distribution.System- ( OS(Windows), buildOS, Arch(..), buildArch ) import Distribution.Simple.Compiler- ( CompilerFlavor(..), compilerFlavor, compilerVersion ) import Distribution.Package- ( packageId, packageName, packageVersion ) import Distribution.PackageDescription- ( PackageDescription(..), hasLibs ) import Distribution.Simple.LocalBuildInfo- ( LocalBuildInfo(..), InstallDirs(..)- , absoluteInstallDirs, prefixRelativeInstallDirs )-import Distribution.Simple.Setup ( CopyDest(NoCopyDest) ) import Distribution.Simple.BuildPaths- ( autogenModuleName ) import Distribution.Simple.Utils- ( shortRelativePath ) import Distribution.Text- ( display ) import Distribution.Version- ( Version(..), orLaterVersion, withinRange ) import System.FilePath ( pathSeparator )@@ -48,11 +37,11 @@ -- * Building Paths_<pkg>.hs -- ------------------------------------------------------------ -generate :: PackageDescription -> LocalBuildInfo -> String-generate pkg_descr lbi =+generate :: PackageDescription -> LocalBuildInfo -> ComponentLocalBuildInfo -> String+generate pkg_descr lbi clbi = let pragmas = cpp_pragma ++ ffi_pragmas ++ warning_pragmas - cpp_pragma | supports_cpp = "{-# LANGUAGE CPP #-}"+ cpp_pragma | supports_cpp = "{-# LANGUAGE CPP #-}\n" | otherwise = "" ffi_pragmas@@ -64,7 +53,8 @@ "{-# OPTIONS_JHC -fffi #-}\n" warning_pragmas =- "{-# OPTIONS_GHC -fno-warn-missing-import-lists #-}\n"+ "{-# OPTIONS_GHC -fno-warn-missing-import-lists #-}\n"+++ "{-# OPTIONS_GHC -fno-warn-implicit-prelude #-}\n" foreign_imports | absolute = ""@@ -179,6 +169,8 @@ in header++body where+ cid = componentUnitId clbi+ InstallDirs { prefix = flat_prefix, bindir = flat_bindir,@@ -186,14 +178,14 @@ datadir = flat_datadir, libexecdir = flat_libexecdir, sysconfdir = flat_sysconfdir- } = absoluteInstallDirs pkg_descr lbi NoCopyDest+ } = absoluteComponentInstallDirs pkg_descr lbi cid NoCopyDest InstallDirs { bindir = flat_bindirrel, libdir = flat_libdirrel, datadir = flat_datadirrel, libexecdir = flat_libexecdirrel, sysconfdir = flat_sysconfdirrel- } = prefixRelativeInstallDirs (packageId pkg_descr) lbi+ } = prefixRelativeComponentInstallDirs (packageId pkg_descr) lbi cid flat_bindirreloc = shortRelativePath flat_prefix flat_bindir flat_libdirreloc = shortRelativePath flat_prefix flat_libdir
cabal/Cabal/Distribution/Simple/BuildPaths.hs view
@@ -31,22 +31,17 @@ ) where -import System.FilePath ((</>), (<.>))- import Distribution.Package- ( packageName, LibraryName, getHSLibraryName )-import Distribution.ModuleName (ModuleName)-import qualified Distribution.ModuleName as ModuleName+import Distribution.ModuleName as ModuleName import Distribution.Compiler- ( CompilerId(..) )-import Distribution.PackageDescription (PackageDescription)+import Distribution.PackageDescription import Distribution.Simple.LocalBuildInfo- ( LocalBuildInfo(buildDir) )-import Distribution.Simple.Setup (defaultDistPref)+import Distribution.Simple.Setup import Distribution.Text- ( display )-import Distribution.System (OS(..), buildOS)+import Distribution.System +import System.FilePath ((</>), (<.>))+ -- --------------------------------------------------------------------------- -- Build directories and files @@ -61,8 +56,10 @@ = distPref </> "doc" </> "html" </> display (packageName pkg_descr) -- |The directory in which we put auto-generated modules-autogenModulesDir :: LocalBuildInfo -> String-autogenModulesDir lbi = buildDir lbi </> "autogen"+autogenModulesDir :: LocalBuildInfo -> ComponentLocalBuildInfo -> String+autogenModulesDir lbi clbi = componentBuildDir lbi clbi </> "autogen"+-- NB: Look at 'checkForeignDeps' for where a simplified version of this+-- has been copy-pasted. cppHeaderName :: String cppHeaderName = "cabal_macros.h"@@ -81,16 +78,16 @@ -- --------------------------------------------------------------------------- -- Library file names -mkLibName :: LibraryName -> String+mkLibName :: UnitId -> String mkLibName lib = "lib" ++ getHSLibraryName lib <.> "a" -mkProfLibName :: LibraryName -> String+mkProfLibName :: UnitId -> String mkProfLibName lib = "lib" ++ getHSLibraryName lib ++ "_p" <.> "a" -- Implement proper name mangling for dynamical shared objects -- libHS<packagename>-<compilerFlavour><compilerVersion> -- e.g. libHSbase-2.1-ghc6.6.1.so-mkSharedLibName :: CompilerId -> LibraryName -> String+mkSharedLibName :: CompilerId -> UnitId -> String mkSharedLibName (CompilerId compilerFlavor compilerVersion) lib = "lib" ++ getHSLibraryName lib ++ "-" ++ comp <.> dllExtension where comp = display compilerFlavor ++ display compilerVersion@@ -99,15 +96,13 @@ -- * Platform file extensions -- ------------------------------------------------------------ --- ToDo: This should be determined via autoconf (AC_EXEEXT)--- | Extension for executable files+-- | Default extension for executable files on the current platform. -- (typically @\"\"@ on Unix and @\"exe\"@ on Windows or OS\/2) exeExtension :: String exeExtension = case buildOS of Windows -> "exe" _ -> "" --- TODO: This should be determined via autoconf (AC_OBJEXT) -- | Extension for object files. For GHC the extension is @\"o\"@. objExtension :: String objExtension = "o"
cabal/Cabal/Distribution/Simple/BuildTarget.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveGeneric #-} ----------------------------------------------------------------------------- -- | -- Module : Distribution.Client.BuildTargets@@ -14,10 +14,14 @@ -- * Build targets BuildTarget(..), readBuildTargets,+ showBuildTarget,+ QualLevel(..),+ buildTargetComponentName, -- * Parsing user build targets UserBuildTarget, readUserBuildTargets,+ showUserBuildTarget, UserBuildTargetProblem(..), reportUserBuildTargetProblems, @@ -25,44 +29,33 @@ resolveBuildTargets, BuildTargetProblem(..), reportBuildTargetProblems,- ) where -import Distribution.Package- ( Package(..), PackageId, packageName )+ -- * Checking build targets+ checkBuildTargets+ ) where import Distribution.PackageDescription- ( PackageDescription- , Executable(..)- , TestSuite(..), TestSuiteInterface(..), testModules- , Benchmark(..), BenchmarkInterface(..), benchmarkModules- , BuildInfo(..), libModules, exeModules ) import Distribution.ModuleName- ( ModuleName, toFilePath ) import Distribution.Simple.LocalBuildInfo- ( Component(..), ComponentName(..)- , pkgComponents, componentName, componentBuildInfo )- import Distribution.Text- ( display ) import Distribution.Simple.Utils- ( die, lowercase, equating )+import Distribution.Verbosity +import Distribution.Compat.Binary (Binary)+import qualified Distribution.Compat.ReadP as Parse+import Distribution.Compat.ReadP+ ( (+++), (<++) )+ import Data.List- ( nub, stripPrefix, sortBy, groupBy, partition, intercalate )-import Data.Ord+ ( nub, stripPrefix, sortBy, groupBy, partition ) import Data.Maybe ( listToMaybe, catMaybes ) import Data.Either ( partitionEithers )+import GHC.Generics (Generic) import qualified Data.Map as Map import Control.Monad-#if __GLASGOW_HASKELL__ < 710-import Control.Applicative (Applicative(..))-#endif-import Control.Applicative (Alternative(..))-import qualified Distribution.Compat.ReadP as Parse-import Distribution.Compat.ReadP- ( (+++), (<++) )+import Control.Applicative as AP (Alternative(..), Applicative(..)) import Data.Char ( isSpace, isAlphaNum ) import System.FilePath as FilePath@@ -127,13 +120,19 @@ -- | A specific file within a specific component. -- | BuildTargetFile ComponentName FilePath- deriving (Show,Eq)+ deriving (Eq, Show, Generic) +instance Binary BuildTarget --- --------------------------------------------------------------- * Do everything--- ------------------------------------------------------------+buildTargetComponentName :: BuildTarget -> ComponentName+buildTargetComponentName (BuildTargetComponent cn) = cn+buildTargetComponentName (BuildTargetModule cn _) = cn+buildTargetComponentName (BuildTargetFile cn _) = cn +-- | Read a list of user-supplied build target strings and resolve them to+-- 'BuildTarget's according to a 'PackageDescription'. If there are problems+-- with any of the targets e.g. they don't exist or are misformatted, throw an+-- 'IOException'. readBuildTargets :: PackageDescription -> [String] -> IO [BuildTarget] readBuildTargets pkg targetStrs = do let (uproblems, utargets) = readUserBuildTargets targetStrs@@ -226,13 +225,17 @@ ++ " - build foo:Data/Foo.hsc -- file qualified by component" showUserBuildTarget :: UserBuildTarget -> String-showUserBuildTarget = intercalate ":" . components+showUserBuildTarget = intercalate ":" . getComponents where- components (UserBuildTargetSingle s1) = [s1]- components (UserBuildTargetDouble s1 s2) = [s1,s2]- components (UserBuildTargetTriple s1 s2 s3) = [s1,s2,s3]+ getComponents (UserBuildTargetSingle s1) = [s1]+ getComponents (UserBuildTargetDouble s1 s2) = [s1,s2]+ getComponents (UserBuildTargetTriple s1 s2 s3) = [s1,s2,s3] +showBuildTarget :: QualLevel -> BuildTarget -> String+showBuildTarget ql bt =+ showUserBuildTarget (renderBuildTarget ql bt) + -- ------------------------------------------------------------ -- * Resolving user targets to build targets -- ------------------------------------------------------------@@ -267,7 +270,7 @@ Unambiguous target -> Right target Ambiguous targets -> Left (BuildTargetAmbiguous userTarget targets') where targets' = disambiguateBuildTargets- (packageId pkg) userTarget+ userTarget targets None errs -> Left (classifyMatchErrors errs) @@ -291,9 +294,9 @@ deriving Show -disambiguateBuildTargets :: PackageId -> UserBuildTarget -> [BuildTarget]+disambiguateBuildTargets :: UserBuildTarget -> [BuildTarget] -> [(UserBuildTarget, BuildTarget)]-disambiguateBuildTargets pkgid original =+disambiguateBuildTargets original = disambiguate (userTargetQualLevel original) where disambiguate ql ts@@ -312,13 +315,13 @@ . partition (\g -> length g > 1) . groupBy (equating fst) . sortBy (comparing fst)- . map (\t -> (renderBuildTarget ql t pkgid, t))+ . map (\t -> (renderBuildTarget ql t, t)) data QualLevel = QL1 | QL2 | QL3 deriving (Enum, Show) -renderBuildTarget :: QualLevel -> BuildTarget -> PackageId -> UserBuildTarget-renderBuildTarget ql target pkgid =+renderBuildTarget :: QualLevel -> BuildTarget -> UserBuildTarget+renderBuildTarget ql target = case ql of QL1 -> UserBuildTargetSingle s1 where s1 = single target QL2 -> UserBuildTargetDouble s1 s2 where (s1, s2) = double target@@ -337,7 +340,7 @@ triple (BuildTargetModule cn m) = (dispKind cn, dispCName cn, display m) triple (BuildTargetFile cn f) = (dispKind cn, dispCName cn, f) - dispCName = componentStringName pkgid+ dispCName = componentStringName dispKind = showComponentKindShort . componentKind reportBuildTargetProblems :: [BuildTargetProblem] -> IO ()@@ -440,7 +443,7 @@ pkgComponentInfo pkg = [ ComponentInfo { cinfoName = componentName c,- cinfoStrName = componentStringName pkg (componentName c),+ cinfoStrName = componentStringName (componentName c), cinfoSrcDirs = hsSourceDirs bi, cinfoModules = componentModules c, cinfoHsFiles = componentHsFiles c,@@ -450,11 +453,11 @@ | c <- pkgComponents pkg , let bi = componentBuildInfo c ] -componentStringName :: Package pkg => pkg -> ComponentName -> ComponentStringName-componentStringName pkg CLibName = display (packageName pkg)-componentStringName _ (CExeName name) = name-componentStringName _ (CTestName name) = name-componentStringName _ (CBenchName name) = name+componentStringName :: ComponentName -> ComponentStringName+componentStringName (CLibName name) = name+componentStringName (CExeName name) = name+componentStringName (CTestName name) = name+componentStringName (CBenchName name) = name componentModules :: Component -> [ModuleName] componentModules (CLib lib) = libModules lib@@ -494,8 +497,8 @@ deriving (Eq, Ord, Show) componentKind :: ComponentName -> ComponentKind-componentKind CLibName = LibKind-componentKind (CExeName _) = ExeKind+componentKind (CLibName _) = LibKind+componentKind (CExeName _) = ExeKind componentKind (CTestName _) = TestKind componentKind (CBenchName _) = BenchKind @@ -798,11 +801,12 @@ fmap f (InexactMatch d xs) = InexactMatch d (fmap f xs) instance Applicative Match where- pure = return+ pure a = ExactMatch 0 [a] (<*>) = ap instance Monad Match where- return a = ExactMatch 0 [a]+ return = AP.pure+ NoMatch d ms >>= _ = NoMatch d ms ExactMatch d xs >>= f = addDepth d $ foldr matchPlus matchZero (map f xs)@@ -937,3 +941,49 @@ caseFold :: String -> String caseFold = lowercase+++-- | Check that the given build targets are valid in the current context.+--+-- Also swizzle into a more convenient form.+--+checkBuildTargets :: Verbosity -> PackageDescription -> [BuildTarget]+ -> IO [(ComponentName, Maybe (Either ModuleName FilePath))]+checkBuildTargets _ pkg [] =+ return [ (componentName c, Nothing) | c <- pkgEnabledComponents pkg ]++checkBuildTargets verbosity pkg targets = do++ let (enabled, disabled) =+ partitionEithers+ [ case componentDisabledReason (getComponent pkg cname) of+ Nothing -> Left target'+ Just reason -> Right (cname, reason)+ | target <- targets+ , let target'@(cname,_) = swizzleTarget target ]++ case disabled of+ [] -> return ()+ ((cname,reason):_) -> die $ formatReason (showComponentName cname) reason++ forM_ [ (c, t) | (c, Just t) <- enabled ] $ \(c, t) ->+ warn verbosity $ "Ignoring '" ++ either display id t ++ ". The whole "+ ++ showComponentName c ++ " will be processed. (Support for "+ ++ "module and file targets has not been implemented yet.)"++ return enabled++ where+ swizzleTarget (BuildTargetComponent c) = (c, Nothing)+ swizzleTarget (BuildTargetModule c m) = (c, Just (Left m))+ swizzleTarget (BuildTargetFile c f) = (c, Just (Right f))++ formatReason cn DisabledComponent =+ "Cannot process the " ++ cn ++ " because the component is marked "+ ++ "as disabled in the .cabal file."+ formatReason cn DisabledAllTests =+ "Cannot process the " ++ cn ++ " because test suites are not "+ ++ "enabled. Run configure with the flag --enable-tests"+ formatReason cn DisabledAllBenchmarks =+ "Cannot process the " ++ cn ++ " because benchmarks are not "+ ++ "enabled. Re-run configure with the flag --enable-benchmarks"
cabal/Cabal/Distribution/Simple/CCompiler.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE CPP #-} ----------------------------------------------------------------------------- -- | -- Module : Distribution.Simple.CCompiler@@ -47,10 +46,8 @@ filenameCDialect ) where -#if __GLASGOW_HASKELL__ < 710-import Data.Monoid- ( Monoid(..) )-#endif+import Distribution.Compat.Semigroup as Semi+ import System.FilePath ( takeExtension ) @@ -66,15 +63,16 @@ instance Monoid CDialect where mempty = C-- mappend C anything = anything- mappend ObjectiveC CPlusPlus = ObjectiveCPlusPlus- mappend CPlusPlus ObjectiveC = ObjectiveCPlusPlus- mappend _ ObjectiveCPlusPlus = ObjectiveCPlusPlus- mappend ObjectiveC _ = ObjectiveC- mappend CPlusPlus _ = CPlusPlus- mappend ObjectiveCPlusPlus _ = ObjectiveCPlusPlus+ mappend = (Semi.<>) +instance Semigroup CDialect where+ C <> anything = anything+ ObjectiveC <> CPlusPlus = ObjectiveCPlusPlus+ CPlusPlus <> ObjectiveC = ObjectiveCPlusPlus+ _ <> ObjectiveCPlusPlus = ObjectiveCPlusPlus+ ObjectiveC <> _ = ObjectiveC+ CPlusPlus <> _ = CPlusPlus+ ObjectiveCPlusPlus <> _ = ObjectiveCPlusPlus -- | A list of all file extensions which are recognized as possibly containing -- some dialect of C code. Note that this list is only for source files,
cabal/Cabal/Distribution/Simple/Command.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE CPP #-}+{-# LANGUAGE ExistentialQuantification #-} ----------------------------------------------------------------------------- -- | -- Module : Distribution.Simple.Command@@ -6,7 +6,7 @@ -- License : BSD3 -- -- Maintainer : cabal-devel@haskell.org--- Portability : portable+-- Portability : non-portable (ExistentialQuantification) -- -- This is to do with command line handling. The Cabal command line is -- organised into a number of named sub-commands (much like darcs). The@@ -38,6 +38,11 @@ commandAddAction, noExtraFlags, + -- ** Building lists of commands+ CommandType(..),+ CommandSpec(..),+ commandFromSpec,+ -- ** Running commands commandsRun, @@ -60,19 +65,17 @@ ) where -import Control.Monad-import Data.Char (isAlpha, toLower)-import Data.List (sortBy)-import Data.Maybe-#if __GLASGOW_HASKELL__ < 710-import Data.Monoid-#endif import qualified Distribution.GetOpt as GetOpt import Distribution.Text- ( Text(disp, parse) ) import Distribution.ParseUtils import Distribution.ReadE-import Distribution.Simple.Utils (die, intercalate)+import Distribution.Simple.Utils++import Control.Monad+import Data.Char (isAlpha, toLower)+import Data.List (sortBy)+import Data.Maybe+import Data.Monoid as Mon import Text.PrettyPrint ( punctuate, cat, comma, text ) import Text.PrettyPrint as PP ( empty ) @@ -171,7 +174,7 @@ reqArg ad (succeedReadE mkflag) showflag -- | (String -> a) variant of "optArg"-optArg' :: Monoid b => ArgPlaceHolder -> (Maybe String -> b)+optArg' :: Mon.Monoid b => ArgPlaceHolder -> (Maybe String -> b) -> (b -> [Maybe String]) -> MkOptDescr (a -> b) (b -> a -> a) a optArg' ad mkflag showflag =@@ -605,3 +608,13 @@ ++ " " ++ pname ++ " help help\n" ++ " Oh, appararently you already know this.\n" }++-- | wraps a @CommandUI@ together with a function that turns it into a @Command@.+-- By hiding the type of flags for the UI allows construction of a list of all UIs at the+-- top level of the program. That list can then be used for generation of manual page+-- as well as for executing the selected command.+data CommandSpec action+ = forall flags. CommandSpec (CommandUI flags) (CommandUI flags -> Command action) CommandType++commandFromSpec :: CommandSpec a -> Command a+commandFromSpec (CommandSpec ui action _) = action ui
cabal/Cabal/Distribution/Simple/Compiler.hs view
@@ -53,22 +53,25 @@ parmakeSupported, reexportedModulesSupported, renamingPackageFlagsSupported,+ unifiedIPIDRequired, packageKeySupported,+ unitIdSupported, -- * Support for profiling detail levels ProfDetailLevel(..), knownProfDetailLevels, flagToProfDetailLevel,+ showProfDetailLevel, ) where import Distribution.Compiler-import Distribution.Version (Version(..))-import Distribution.Text (display)-import Language.Haskell.Extension (Language(Haskell98), Extension)-import Distribution.Simple.Utils (lowercase)+import Distribution.Version+import Distribution.Text+import Language.Haskell.Extension+import Distribution.Simple.Utils+import Distribution.Compat.Binary import Control.Monad (liftM)-import Distribution.Compat.Binary (Binary) import Data.List (nub) import qualified Data.Map as M (Map, lookup) import Data.Maybe (catMaybes, isNothing, listToMaybe)@@ -89,7 +92,7 @@ compilerProperties :: M.Map String String -- ^ A key-value map for properties not covered by the above fields. }- deriving (Generic, Show, Read)+ deriving (Eq, Generic, Show, Read) instance Binary Compiler @@ -276,10 +279,18 @@ renamingPackageFlagsSupported :: Compiler -> Bool renamingPackageFlagsSupported = ghcSupported "Support thinning and renaming package flags" +-- | Does this compiler have unified IPIDs (so no package keys)+unifiedIPIDRequired :: Compiler -> Bool+unifiedIPIDRequired = ghcSupported "Requires unified installed package IDs"+ -- | Does this compiler support package keys? packageKeySupported :: Compiler -> Bool packageKeySupported = ghcSupported "Uses package keys" +-- | Does this compiler support unit IDs?+unitIdSupported :: Compiler -> Bool+unitIdSupported = ghcSupported "Uses unit IDs"+ -- | Utility function for GHC only features ghcSupported :: String -> Compiler -> Bool ghcSupported key comp =@@ -331,4 +342,13 @@ , ("toplevel-functions", ["toplevel", "top"], ProfDetailToplevelFunctions) , ("all-functions", ["all"], ProfDetailAllFunctions) ]++showProfDetailLevel :: ProfDetailLevel -> String+showProfDetailLevel dl = case dl of+ ProfDetailNone -> "none"+ ProfDetailDefault -> "default"+ ProfDetailExportedFunctions -> "exported-functions"+ ProfDetailToplevelFunctions -> "toplevel-functions"+ ProfDetailAllFunctions -> "all-functions"+ ProfDetailOther other -> other
cabal/Cabal/Distribution/Simple/Configure.hs view
@@ -1,1755 +1,2134 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-#if __GLASGOW_HASKELL__ >= 711-{-# LANGUAGE PatternSynonyms #-}-#endif---------------------------------------------------------------------------------- |--- Module : Distribution.Simple.Configure--- Copyright : Isaac Jones 2003-2005--- License : BSD3------ Maintainer : cabal-devel@haskell.org--- Portability : portable------ This deals with the /configure/ phase. It provides the 'configure' action--- which is given the package description and configure flags. It then tries--- to: configure the compiler; resolves any conditionals in the package--- description; resolve the package dependencies; check if all the extensions--- used by this package are supported by the compiler; check that all the build--- tools are available (including version checks if appropriate); checks for--- any required @pkg-config@ packages (updating the 'BuildInfo' with the--- results)------ Then based on all this it saves the info in the 'LocalBuildInfo' and writes--- it out to the @dist\/setup-config@ file. It also displays various details to--- the user, the amount of information displayed depending on the verbosity--- level.--module Distribution.Simple.Configure (configure,- writePersistBuildConfig,- getConfigStateFile,- getPersistBuildConfig,- checkPersistBuildConfigOutdated,- tryGetPersistBuildConfig,- maybeGetPersistBuildConfig,- findDistPref, findDistPrefOrDefault,- localBuildInfoFile,- getInstalledPackages, getPackageDBContents,- configCompiler, configCompilerAux,- configCompilerEx, configCompilerAuxEx,- ccLdOptionsBuildInfo,- checkForeignDeps,- interpretPackageDbFlags,- ConfigStateFileError(..),- tryGetConfigStateFile,- platformDefines,- )- where--import Distribution.Compiler- ( CompilerId(..) )-import Distribution.Utils.NubList-import Distribution.Simple.Compiler- ( CompilerFlavor(..), Compiler(..), compilerFlavor, compilerVersion- , compilerInfo, ProfDetailLevel(..), knownProfDetailLevels- , showCompilerId, unsupportedLanguages, unsupportedExtensions- , PackageDB(..), PackageDBStack, reexportedModulesSupported- , packageKeySupported, renamingPackageFlagsSupported )-import Distribution.Simple.PreProcess ( platformDefines )-import Distribution.Package- ( PackageName(PackageName), PackageIdentifier(..), PackageId- , packageName, packageVersion, Package(..)- , Dependency(Dependency), simplifyDependency- , InstalledPackageId(..), thisPackageVersion- , mkPackageKey, packageKeyLibraryName )-import qualified Distribution.InstalledPackageInfo as Installed-import Distribution.InstalledPackageInfo (InstalledPackageInfo, emptyInstalledPackageInfo)-import qualified Distribution.Simple.PackageIndex as PackageIndex-import Distribution.Simple.PackageIndex (InstalledPackageIndex)-import Distribution.PackageDescription as PD- ( PackageDescription(..), specVersion, GenericPackageDescription(..)- , Library(..), hasLibs, Executable(..), BuildInfo(..), allExtensions- , HookedBuildInfo, updatePackageDescription, allBuildInfo- , Flag(flagName), FlagName(..), TestSuite(..), Benchmark(..)- , ModuleReexport(..) , defaultRenaming )-import Distribution.ModuleName- ( ModuleName )-import Distribution.PackageDescription.Configuration- ( finalizePackageDescription, mapTreeData )-import Distribution.PackageDescription.Check- ( PackageCheck(..), checkPackage, checkPackageFiles )-import Distribution.Simple.Program- ( Program(..), ProgramLocation(..), ConfiguredProgram(..)- , ProgramConfiguration, defaultProgramConfiguration- , ProgramSearchPathEntry(..), getProgramSearchPath, setProgramSearchPath- , configureAllKnownPrograms, knownPrograms, lookupKnownProgram- , userSpecifyArgss, userSpecifyPaths- , lookupProgram, requireProgram, requireProgramVersion- , pkgConfigProgram, gccProgram, rawSystemProgramStdoutConf )-import Distribution.Simple.Setup as Setup- ( ConfigFlags(..), CopyDest(..), Flag(..), defaultDistPref- , fromFlag, fromFlagOrDefault, flagToMaybe, toFlag )-import Distribution.Simple.InstallDirs- ( InstallDirs(..), defaultInstallDirs, combineInstallDirs )-import Distribution.Simple.LocalBuildInfo- ( LocalBuildInfo(..), Component(..), ComponentLocalBuildInfo(..)- , absoluteInstallDirs, prefixRelativeInstallDirs, inplacePackageId- , ComponentName(..), showComponentName, pkgEnabledComponents- , componentBuildInfo, componentName, checkComponentsCyclic )-import Distribution.Simple.BuildPaths- ( autogenModulesDir )-import Distribution.Simple.Utils- ( die, warn, info, setupMessage- , createDirectoryIfMissingVerbose, moreRecentFile- , intercalate, cabalVersion- , writeFileAtomic- , withTempFile )-import Distribution.System- ( OS(..), buildOS, Platform (..), buildPlatform )-import Distribution.Version- ( Version(..), anyVersion, orLaterVersion, withinRange, isAnyVersion )-import Distribution.Verbosity- ( Verbosity, lessVerbose )--import qualified Distribution.Simple.GHC as GHC-import qualified Distribution.Simple.GHCJS as GHCJS-import qualified Distribution.Simple.JHC as JHC-import qualified Distribution.Simple.LHC as LHC-import qualified Distribution.Simple.UHC as UHC-import qualified Distribution.Simple.HaskellSuite as HaskellSuite---- Prefer the more generic Data.Traversable.mapM to Prelude.mapM-import Prelude hiding ( mapM )-import Control.Exception- ( Exception, evaluate, throw, throwIO, try )-#if __GLASGOW_HASKELL__ >= 711-import Control.Exception ( pattern ErrorCall )-#else-import Control.Exception ( ErrorCall(..) )-#endif-import Control.Monad- ( liftM, when, unless, foldM, filterM )-import Distribution.Compat.Binary ( decodeOrFailIO, encode )-import Data.ByteString.Lazy (ByteString)-import qualified Data.ByteString as BS-import qualified Data.ByteString.Lazy.Char8 as BLC8-import Data.List- ( (\\), nub, partition, isPrefixOf, inits, stripPrefix )-import Data.Maybe- ( isNothing, catMaybes, fromMaybe, isJust )-import Data.Either- ( partitionEithers )-import qualified Data.Set as Set-#if __GLASGOW_HASKELL__ < 710-import Data.Monoid- ( Monoid(..) )-#endif-import qualified Data.Map as Map-import Data.Map (Map)-import Data.Traversable- ( mapM )-import Data.Typeable-import System.Directory- ( doesFileExist, createDirectoryIfMissing, getTemporaryDirectory )-import System.FilePath- ( (</>), isAbsolute )-import qualified System.Info- ( compilerName, compilerVersion )-import System.IO- ( hPutStrLn, hClose )-import Distribution.Text- ( Text(disp), display, simpleParse )-import Text.PrettyPrint- ( render, (<>), ($+$), char, text, comma- , quotes, punctuate, nest, sep, hsep )-import Distribution.Compat.Environment ( lookupEnv )-import Distribution.Compat.Exception ( catchExit, catchIO )---- | The errors that can be thrown when reading the @setup-config@ file.-data ConfigStateFileError- = ConfigStateFileNoHeader -- ^ No header found.- | ConfigStateFileBadHeader -- ^ Incorrect header.- | ConfigStateFileNoParse -- ^ Cannot parse file contents.- | ConfigStateFileMissing -- ^ No file!- | ConfigStateFileBadVersion PackageIdentifier PackageIdentifier (Either ConfigStateFileError LocalBuildInfo) -- ^ Mismatched version.- deriving (Typeable)--instance Show ConfigStateFileError where- show ConfigStateFileNoHeader =- "Saved package config file header is missing. "- ++ "Try re-running the 'configure' command."- show ConfigStateFileBadHeader =- "Saved package config file header is corrupt. "- ++ "Try re-running the 'configure' command."- show ConfigStateFileNoParse =- "Saved package config file body is corrupt. "- ++ "Try re-running the 'configure' command."- show ConfigStateFileMissing = "Run the 'configure' command first."- show (ConfigStateFileBadVersion oldCabal oldCompiler _) =- "You need to re-run the 'configure' command. "- ++ "The version of Cabal being used has changed (was "- ++ display oldCabal ++ ", now "- ++ display currentCabalId ++ ")."- ++ badCompiler- where- badCompiler- | oldCompiler == currentCompilerId = ""- | otherwise =- " Additionally the compiler is different (was "- ++ display oldCompiler ++ ", now "- ++ display currentCompilerId- ++ ") which is probably the cause of the problem."--instance Exception ConfigStateFileError---- | Read the 'localBuildInfoFile'. Throw an exception if the file is--- missing, if the file cannot be read, or if the file was created by an older--- version of Cabal.-getConfigStateFile :: FilePath -- ^ The file path of the @setup-config@ file.- -> IO LocalBuildInfo-getConfigStateFile filename = do- exists <- doesFileExist filename- unless exists $ throwIO ConfigStateFileMissing- -- Read the config file into a strict ByteString to avoid problems with- -- lazy I/O, then convert to lazy because the binary package needs that.- contents <- BS.readFile filename- let (header, body) = BLC8.span (/='\n') (BLC8.fromChunks [contents])-- headerParseResult <- try $ evaluate $ parseHeader header- let (cabalId, compId) =- case headerParseResult of- Left (ErrorCall _) -> throw ConfigStateFileBadHeader- Right x -> x-- let getStoredValue = do- result <- decodeOrFailIO (BLC8.tail body)- case result of- Left _ -> throw ConfigStateFileNoParse- Right x -> return x- deferErrorIfBadVersion act- | cabalId /= currentCabalId = do- eResult <- try act- throw $ ConfigStateFileBadVersion cabalId compId eResult- | otherwise = act- deferErrorIfBadVersion getStoredValue---- | Read the 'localBuildInfoFile', returning either an error or the local build info.-tryGetConfigStateFile :: FilePath -- ^ The file path of the @setup-config@ file.- -> IO (Either ConfigStateFileError LocalBuildInfo)-tryGetConfigStateFile = try . getConfigStateFile---- | Try to read the 'localBuildInfoFile'.-tryGetPersistBuildConfig :: FilePath -- ^ The @dist@ directory path.- -> IO (Either ConfigStateFileError LocalBuildInfo)-tryGetPersistBuildConfig = try . getPersistBuildConfig---- | Read the 'localBuildInfoFile'. Throw an exception if the file is--- missing, if the file cannot be read, or if the file was created by an older--- version of Cabal.-getPersistBuildConfig :: FilePath -- ^ The @dist@ directory path.- -> IO LocalBuildInfo-getPersistBuildConfig = getConfigStateFile . localBuildInfoFile---- | Try to read the 'localBuildInfoFile'.-maybeGetPersistBuildConfig :: FilePath -- ^ The @dist@ directory path.- -> IO (Maybe LocalBuildInfo)-maybeGetPersistBuildConfig =- liftM (either (const Nothing) Just) . tryGetPersistBuildConfig---- | After running configure, output the 'LocalBuildInfo' to the--- 'localBuildInfoFile'.-writePersistBuildConfig :: FilePath -- ^ The @dist@ directory path.- -> LocalBuildInfo -- ^ The 'LocalBuildInfo' to write.- -> IO ()-writePersistBuildConfig distPref lbi = do- createDirectoryIfMissing False distPref- writeFileAtomic (localBuildInfoFile distPref) $- BLC8.unlines [showHeader pkgId, encode lbi]- where- pkgId = packageId $ localPkgDescr lbi---- | Identifier of the current Cabal package.-currentCabalId :: PackageIdentifier-currentCabalId = PackageIdentifier (PackageName "Cabal") cabalVersion---- | Identifier of the current compiler package.-currentCompilerId :: PackageIdentifier-currentCompilerId = PackageIdentifier (PackageName System.Info.compilerName)- System.Info.compilerVersion---- | Parse the @setup-config@ file header, returning the package identifiers --- for Cabal and the compiler.-parseHeader :: ByteString -- ^ The file contents.- -> (PackageIdentifier, PackageIdentifier)-parseHeader header = case BLC8.words header of- ["Saved", "package", "config", "for", pkgId, "written", "by", cabalId, "using", compId] ->- fromMaybe (throw ConfigStateFileBadHeader) $ do- _ <- simpleParse (BLC8.unpack pkgId) :: Maybe PackageIdentifier- cabalId' <- simpleParse (BLC8.unpack cabalId)- compId' <- simpleParse (BLC8.unpack compId)- return (cabalId', compId')- _ -> throw ConfigStateFileNoHeader---- | Generate the @setup-config@ file header.-showHeader :: PackageIdentifier -- ^ The processed package.- -> ByteString-showHeader pkgId = BLC8.unwords- [ "Saved", "package", "config", "for"- , BLC8.pack $ display pkgId- , "written", "by"- , BLC8.pack $ display currentCabalId- , "using"- , BLC8.pack $ display currentCompilerId- ]---- | Check that localBuildInfoFile is up-to-date with respect to the--- .cabal file.-checkPersistBuildConfigOutdated :: FilePath -> FilePath -> IO Bool-checkPersistBuildConfigOutdated distPref pkg_descr_file = do- pkg_descr_file `moreRecentFile` (localBuildInfoFile distPref)---- | Get the path of @dist\/setup-config@.-localBuildInfoFile :: FilePath -- ^ The @dist@ directory path.- -> FilePath-localBuildInfoFile distPref = distPref </> "setup-config"---- -------------------------------------------------------------------------------- * Configuration--- --------------------------------------------------------------------------------- | Return the \"dist/\" prefix, or the default prefix. The prefix is taken from--- (in order of highest to lowest preference) the override prefix, the \"CABAL_BUILDDIR\"--- environment variable, or the default prefix.-findDistPref :: FilePath -- ^ default \"dist\" prefix- -> Setup.Flag FilePath -- ^ override \"dist\" prefix- -> IO FilePath-findDistPref defDistPref overrideDistPref = do- envDistPref <- liftM parseEnvDistPref (lookupEnv "CABAL_BUILDDIR")- return $ fromFlagOrDefault defDistPref (mappend envDistPref overrideDistPref)- where- parseEnvDistPref env =- case env of- Just distPref | not (null distPref) -> toFlag distPref- _ -> NoFlag---- | Return the \"dist/\" prefix, or the default prefix. The prefix is taken from--- (in order of highest to lowest preference) the override prefix, the \"CABAL_BUILDDIR\"--- environment variable, or 'defaultDistPref' is used. Call this function to resolve a--- @*DistPref@ flag whenever it is not known to be set. (The @*DistPref@ flags are always--- set to a definite value before invoking 'UserHooks'.)-findDistPrefOrDefault :: Setup.Flag FilePath -- ^ override \"dist\" prefix- -> IO FilePath-findDistPrefOrDefault = findDistPref defaultDistPref---- |Perform the \"@.\/setup configure@\" action.--- Returns the @.setup-config@ file.-configure :: (GenericPackageDescription, HookedBuildInfo)- -> ConfigFlags -> IO LocalBuildInfo-configure (pkg_descr0, pbi) cfg- = do let distPref = fromFlag (configDistPref cfg)- buildDir' = distPref </> "build"-- setupMessage verbosity "Configuring" (packageId pkg_descr0)-- unless (configProfExe cfg == NoFlag) $ do- let enable | fromFlag (configProfExe cfg) = "enable"- | otherwise = "disable"- warn verbosity- ("The flag --" ++ enable ++ "-executable-profiling is deprecated. "- ++ "Please use --" ++ enable ++ "-profiling instead.")-- unless (configLibCoverage cfg == NoFlag) $ do- let enable | fromFlag (configLibCoverage cfg) = "enable"- | otherwise = "disable"- warn verbosity- ("The flag --" ++ enable ++ "-library-coverage is deprecated. "- ++ "Please use --" ++ enable ++ "-coverage instead.")-- createDirectoryIfMissingVerbose (lessVerbose verbosity) True distPref-- let programsConfig = mkProgramsConfig cfg (configPrograms cfg)- userInstall = fromFlag (configUserInstall cfg)- packageDbs = interpretPackageDbFlags userInstall- (configPackageDBs cfg)-- -- detect compiler- (comp, compPlatform, programsConfig') <- configCompilerEx- (flagToMaybe $ configHcFlavor cfg)- (flagToMaybe $ configHcPath cfg) (flagToMaybe $ configHcPkg cfg)- programsConfig (lessVerbose verbosity)- let version = compilerVersion comp- flavor = compilerFlavor comp-- -- Create a PackageIndex that makes *any libraries that might be*- -- defined internally to this package look like installed packages, in- -- case an executable should refer to any of them as dependencies.- --- -- It must be *any libraries that might be* defined rather than the- -- actual definitions, because these depend on conditionals in the .cabal- -- file, and we haven't resolved them yet. finalizePackageDescription- -- does the resolution of conditionals, and it takes internalPackageSet- -- as part of its input.- --- -- Currently a package can define no more than one library (which has- -- the same name as the package) but we could extend this later.- -- If we later allowed private internal libraries, then here we would- -- need to pre-scan the conditional data to make a list of all private- -- libraries that could possibly be defined by the .cabal file.- let pid = packageId pkg_descr0- internalPackage = emptyInstalledPackageInfo {- --TODO: should use a per-compiler method to map the source- -- package ID into an installed package id we can use- -- for the internal package set. The open-codes use of- -- InstalledPackageId . display here is a hack.- Installed.installedPackageId =- InstalledPackageId $ display $ pid,- Installed.sourcePackageId = pid- }- internalPackageSet = PackageIndex.fromList [internalPackage]- installedPackageSet <- getInstalledPackages (lessVerbose verbosity) comp- packageDbs programsConfig'-- (allConstraints, requiredDepsMap) <- either die return $- combinedConstraints (configConstraints cfg)- (configDependencies cfg)- installedPackageSet-- let exactConf = fromFlagOrDefault False (configExactConfiguration cfg)- -- Constraint test function for the solver- dependencySatisfiable d@(Dependency depName verRange)- | exactConf =- -- When we're given '--exact-configuration', we assume that all- -- dependencies and flags are exactly specified on the command- -- line. Thus we only consult the 'requiredDepsMap'. Note that- -- we're not doing the version range check, so if there's some- -- dependency that wasn't specified on the command line,- -- 'finalizePackageDescription' will fail.- --- -- TODO: mention '--exact-configuration' in the error message- -- when this fails?- (depName `Map.member` requiredDepsMap) || isInternalDep-- | otherwise =- -- Normal operation: just look up dependency in the package- -- index.- not . null . PackageIndex.lookupDependency pkgs' $ d- where- pkgs' = PackageIndex.insert internalPackage installedPackageSet- isInternalDep = pkgName pid == depName- && pkgVersion pid `withinRange` verRange- enableTest t = t { testEnabled = fromFlag (configTests cfg) }- flaggedTests = map (\(n, t) -> (n, mapTreeData enableTest t))- (condTestSuites pkg_descr0)- enableBenchmark bm = bm { benchmarkEnabled =- fromFlag (configBenchmarks cfg) }- flaggedBenchmarks = map (\(n, bm) ->- (n, mapTreeData enableBenchmark bm))- (condBenchmarks pkg_descr0)- pkg_descr0'' = pkg_descr0 { condTestSuites = flaggedTests- , condBenchmarks = flaggedBenchmarks }-- (pkg_descr0', flags) <-- case finalizePackageDescription- (configConfigurationsFlags cfg)- dependencySatisfiable- compPlatform- (compilerInfo comp)- allConstraints- pkg_descr0''- of Right r -> return r- Left missing ->- die $ "At least the following dependencies are missing:\n"- ++ (render . nest 4 . sep . punctuate comma- . map (disp . simplifyDependency)- $ missing)-- -- Sanity check: if '--exact-configuration' was given, ensure that the- -- complete flag assignment was specified on the command line.- when exactConf $ do- let cmdlineFlags = map fst (configConfigurationsFlags cfg)- allFlags = map flagName . genPackageFlags $ pkg_descr0- diffFlags = allFlags \\ cmdlineFlags- when (not . null $ diffFlags) $- die $ "'--exact-conf' was given, "- ++ "but the following flags were not specified: "- ++ intercalate ", " (map show diffFlags)-- -- add extra include/lib dirs as specified in cfg- -- we do it here so that those get checked too- let pkg_descr = addExtraIncludeLibDirs pkg_descr0'-- unless (renamingPackageFlagsSupported comp ||- and [ rn == defaultRenaming- | bi <- allBuildInfo pkg_descr- , rn <- Map.elems (targetBuildRenaming bi)]) $- die $ "Your compiler does not support thinning and renaming on "- ++ "package flags. To use this feature you probably must use "- ++ "GHC 7.9 or later."-- when (not (null flags)) $- info verbosity $ "Flags chosen: "- ++ intercalate ", " [ name ++ "=" ++ display value- | (FlagName name, value) <- flags ]-- when (maybe False (not.null.PD.reexportedModules) (PD.library pkg_descr)- && not (reexportedModulesSupported comp)) $ do- die $ "Your compiler does not support module re-exports. To use "- ++ "this feature you probably must use GHC 7.9 or later."-- checkPackageProblems verbosity pkg_descr0- (updatePackageDescription pbi pkg_descr)-- -- Handle hole instantiation- (holeDeps, hole_insts) <- configureInstantiateWith pkg_descr cfg installedPackageSet-- let selectDependencies :: [Dependency] ->- ([FailedDependency], [ResolvedDependency])- selectDependencies =- (\xs -> ([ x | Left x <- xs ], [ x | Right x <- xs ]))- . map (selectDependency internalPackageSet installedPackageSet- requiredDepsMap)-- (failedDeps, allPkgDeps) =- selectDependencies (buildDepends pkg_descr)-- internalPkgDeps = [ pkgid- | InternalDependency _ pkgid <- allPkgDeps ]- externalPkgDeps = [ pkg- | ExternalDependency _ pkg <- allPkgDeps ]-- when (not (null internalPkgDeps)- && not (newPackageDepsBehaviour pkg_descr)) $- die $ "The field 'build-depends: "- ++ intercalate ", " (map (display . packageName) internalPkgDeps)- ++ "' refers to a library which is defined within the same "- ++ "package. To use this feature the package must specify at "- ++ "least 'cabal-version: >= 1.8'."-- reportFailedDependencies failedDeps- reportSelectedDependencies verbosity allPkgDeps-- let installDeps = Map.elems- . Map.fromList- . map (\v -> (Installed.installedPackageId v, v))- $ externalPkgDeps ++ holeDeps-- packageDependsIndex <-- case PackageIndex.dependencyClosure installedPackageSet- (map Installed.installedPackageId installDeps) of- Left packageDependsIndex -> return packageDependsIndex- Right broken ->- die $ "The following installed packages are broken because other"- ++ " packages they depend on are missing. These broken "- ++ "packages must be rebuilt before they can be used.\n"- ++ unlines [ "package "- ++ display (packageId pkg)- ++ " is broken due to missing package "- ++ intercalate ", " (map display deps)- | (pkg, deps) <- broken ]-- let pseudoTopPkg = emptyInstalledPackageInfo {- Installed.installedPackageId =- InstalledPackageId (display (packageId pkg_descr)),- Installed.sourcePackageId = packageId pkg_descr,- Installed.depends =- map Installed.installedPackageId installDeps- }- case PackageIndex.dependencyInconsistencies- . PackageIndex.insert pseudoTopPkg- $ packageDependsIndex of- [] -> return ()- inconsistencies ->- warn verbosity $- "This package indirectly depends on multiple versions of the same "- ++ "package. This is highly likely to cause a compile failure.\n"- ++ unlines [ "package " ++ display pkg ++ " requires "- ++ display (PackageIdentifier name ver)- | (name, uses) <- inconsistencies- , (pkg, ver) <- uses ]-- -- installation directories- defaultDirs <- defaultInstallDirs flavor userInstall (hasLibs pkg_descr)- let installDirs = combineInstallDirs fromFlagOrDefault- defaultDirs (configInstallDirs cfg)-- -- check languages and extensions- let langlist = nub $ catMaybes $ map defaultLanguage- (allBuildInfo pkg_descr)- let langs = unsupportedLanguages comp langlist- when (not (null langs)) $- die $ "The package " ++ display (packageId pkg_descr0)- ++ " requires the following languages which are not "- ++ "supported by " ++ display (compilerId comp) ++ ": "- ++ intercalate ", " (map display langs)- let extlist = nub $ concatMap allExtensions (allBuildInfo pkg_descr)- let exts = unsupportedExtensions comp extlist- when (not (null exts)) $- die $ "The package " ++ display (packageId pkg_descr0)- ++ " requires the following language extensions which are not "- ++ "supported by " ++ display (compilerId comp) ++ ": "- ++ intercalate ", " (map display exts)-- -- configured known/required programs & external build tools- -- exclude build-tool deps on "internal" exes in the same package- let requiredBuildTools =- [ buildTool- | let exeNames = map exeName (executables pkg_descr)- , bi <- allBuildInfo pkg_descr- , buildTool@(Dependency (PackageName toolName) reqVer)- <- buildTools bi- , let isInternal =- toolName `elem` exeNames- -- we assume all internal build-tools are- -- versioned with the package:- && packageVersion pkg_descr `withinRange` reqVer- , not isInternal ]-- programsConfig'' <-- configureAllKnownPrograms (lessVerbose verbosity) programsConfig'- >>= configureRequiredPrograms verbosity requiredBuildTools-- (pkg_descr', programsConfig''') <-- configurePkgconfigPackages verbosity pkg_descr programsConfig''-- -- internal component graph- buildComponents <-- case mkComponentsGraph pkg_descr internalPkgDeps of- Left componentCycle -> reportComponentCycle componentCycle- Right components ->- mkComponentsLocalBuildInfo comp packageDependsIndex pkg_descr- internalPkgDeps externalPkgDeps holeDeps- (Map.fromList hole_insts)- components-- split_objs <-- if not (fromFlag $ configSplitObjs cfg)- then return False- else case flavor of- GHC | version >= Version [6,5] [] -> return True- GHCJS -> return True- _ -> do warn verbosity- ("this compiler does not support " ++- "--enable-split-objs; ignoring")- return False-- let ghciLibByDefault =- case compilerId comp of- CompilerId GHC _ ->- -- If ghc is non-dynamic, then ghci needs object files,- -- so we build one by default.- --- -- Technically, archive files should be sufficient for ghci,- -- but because of GHC bug #8942, it has never been safe to- -- rely on them. By the time that bug was fixed, ghci had- -- been changed to read shared libraries instead of archive- -- files (see next code block).- not (GHC.isDynamic comp)- CompilerId GHCJS _ ->- not (GHCJS.isDynamic comp)- _ -> False-- let sharedLibsByDefault- | fromFlag (configDynExe cfg) =- -- build a shared library if dynamically-linked- -- executables are requested- True- | otherwise = case compilerId comp of- CompilerId GHC _ ->- -- if ghc is dynamic, then ghci needs a shared- -- library, so we build one by default.- GHC.isDynamic comp- CompilerId GHCJS _ ->- GHCJS.isDynamic comp- _ -> False- withSharedLib_ =- -- build shared libraries if required by GHC or by the- -- executable linking mode, but allow the user to force- -- building only static library archives with- -- --disable-shared.- fromFlagOrDefault sharedLibsByDefault $ configSharedLib cfg- withDynExe_ = fromFlag $ configDynExe cfg- when (withDynExe_ && not withSharedLib_) $ warn verbosity $- "Executables will use dynamic linking, but a shared library "- ++ "is not being built. Linking will fail if any executables "- ++ "depend on the library."-- -- The --profiling flag sets the default for both libs and exes,- -- but can be overidden by --library-profiling, or the old deprecated- -- --executable-profiling flag.- let profEnabledLibOnly = configProfLib cfg- profEnabledBoth = fromFlagOrDefault False (configProf cfg)- profEnabledLib = fromFlagOrDefault profEnabledBoth profEnabledLibOnly- profEnabledExe = fromFlagOrDefault profEnabledBoth (configProfExe cfg)-- -- The --profiling-detail and --library-profiling-detail flags behave- -- similarly- profDetailLibOnly <- checkProfDetail (configProfLibDetail cfg)- profDetailBoth <- liftM (fromFlagOrDefault ProfDetailDefault)- (checkProfDetail (configProfDetail cfg))- let profDetailLib = fromFlagOrDefault profDetailBoth profDetailLibOnly- profDetailExe = profDetailBoth-- when (profEnabledExe && not profEnabledLib) $- warn verbosity $- "Executables will be built with profiling, but library "- ++ "profiling is disabled. Linking will fail if any executables "- ++ "depend on the library."-- let configCoverage_ =- mappend (configCoverage cfg) (configLibCoverage cfg)-- cfg' = cfg { configCoverage = configCoverage_ }-- reloc <-- if not (fromFlag $ configRelocatable cfg)- then return False- else return True-- let lbi = LocalBuildInfo {- configFlags = cfg',- extraConfigArgs = [], -- Currently configure does not- -- take extra args, but if it- -- did they would go here.- installDirTemplates = installDirs,- compiler = comp,- hostPlatform = compPlatform,- buildDir = buildDir',- componentsConfigs = buildComponents,- installedPkgs = packageDependsIndex,- pkgDescrFile = Nothing,- localPkgDescr = pkg_descr',- instantiatedWith = hole_insts,- withPrograms = programsConfig''',- withVanillaLib = fromFlag $ configVanillaLib cfg,- withProfLib = profEnabledLib,- withSharedLib = withSharedLib_,- withDynExe = withDynExe_,- withProfExe = profEnabledExe,- withProfLibDetail = profDetailLib,- withProfExeDetail = profDetailExe,- withOptimization = fromFlag $ configOptimization cfg,- withDebugInfo = fromFlag $ configDebugInfo cfg,- withGHCiLib = fromFlagOrDefault ghciLibByDefault $- configGHCiLib cfg,- splitObjs = split_objs,- stripExes = fromFlag $ configStripExes cfg,- stripLibs = fromFlag $ configStripLibs cfg,- withPackageDB = packageDbs,- progPrefix = fromFlag $ configProgPrefix cfg,- progSuffix = fromFlag $ configProgSuffix cfg,- relocatable = reloc- }-- when reloc (checkRelocatable verbosity pkg_descr lbi)-- let dirs = absoluteInstallDirs pkg_descr lbi NoCopyDest- relative = prefixRelativeInstallDirs (packageId pkg_descr) lbi-- unless (isAbsolute (prefix dirs)) $ die $- "expected an absolute directory name for --prefix: " ++ prefix dirs-- info verbosity $ "Using " ++ display currentCabalId- ++ " compiled by " ++ display currentCompilerId- info verbosity $ "Using compiler: " ++ showCompilerId comp- info verbosity $ "Using install prefix: " ++ prefix dirs-- let dirinfo name dir isPrefixRelative =- info verbosity $ name ++ " installed in: " ++ dir ++ relNote- where relNote = case buildOS of- Windows | not (hasLibs pkg_descr)- && isNothing isPrefixRelative- -> " (fixed location)"- _ -> ""-- dirinfo "Binaries" (bindir dirs) (bindir relative)- dirinfo "Libraries" (libdir dirs) (libdir relative)- dirinfo "Private binaries" (libexecdir dirs) (libexecdir relative)- dirinfo "Data files" (datadir dirs) (datadir relative)- dirinfo "Documentation" (docdir dirs) (docdir relative)- dirinfo "Configuration files" (sysconfdir dirs) (sysconfdir relative)-- sequence_ [ reportProgram verbosity prog configuredProg- | (prog, configuredProg) <- knownPrograms programsConfig''' ]-- return lbi-- where- verbosity = fromFlag (configVerbosity cfg)-- addExtraIncludeLibDirs pkg_descr =- let extraBi = mempty { extraLibDirs = configExtraLibDirs cfg- , PD.includeDirs = configExtraIncludeDirs cfg}- modifyLib l = l{ libBuildInfo = libBuildInfo l- `mappend` extraBi }- modifyExecutable e = e{ buildInfo = buildInfo e- `mappend` extraBi}- in pkg_descr{ library = modifyLib `fmap` library pkg_descr- , executables = modifyExecutable `map`- executables pkg_descr}-- checkProfDetail (Flag (ProfDetailOther other)) = do- warn verbosity $- "Unknown profiling detail level '" ++ other- ++ "', using default.\n"- ++ "The profiling detail levels are: " ++ intercalate ", "- [ name | (name, _, _) <- knownProfDetailLevels ]- return (Flag ProfDetailDefault)- checkProfDetail other = return other--mkProgramsConfig :: ConfigFlags -> ProgramConfiguration -> ProgramConfiguration-mkProgramsConfig cfg initialProgramsConfig = programsConfig- where- programsConfig = userSpecifyArgss (configProgramArgs cfg)- . userSpecifyPaths (configProgramPaths cfg)- . setProgramSearchPath searchpath- $ initialProgramsConfig- searchpath = getProgramSearchPath (initialProgramsConfig)- ++ map ProgramSearchPathDir (fromNubList $ configProgramPathExtra cfg)---- -------------------------------------------------------------------------------- Configuring package dependencies--reportProgram :: Verbosity -> Program -> Maybe ConfiguredProgram -> IO ()-reportProgram verbosity prog Nothing- = info verbosity $ "No " ++ programName prog ++ " found"-reportProgram verbosity prog (Just configuredProg)- = info verbosity $ "Using " ++ programName prog ++ version ++ location- where location = case programLocation configuredProg of- FoundOnSystem p -> " found on system at: " ++ p- UserSpecified p -> " given by user at: " ++ p- version = case programVersion configuredProg of- Nothing -> ""- Just v -> " version " ++ display v--hackageUrl :: String-hackageUrl = "http://hackage.haskell.org/package/"--data ResolvedDependency = ExternalDependency Dependency InstalledPackageInfo- | InternalDependency Dependency PackageId -- should be a- -- lib name--data FailedDependency = DependencyNotExists PackageName- | DependencyNoVersion Dependency---- | Test for a package dependency and record the version we have installed.-selectDependency :: InstalledPackageIndex -- ^ Internally defined packages- -> InstalledPackageIndex -- ^ Installed packages- -> Map PackageName InstalledPackageInfo- -- ^ Packages for which we have been given specific deps to use- -> Dependency- -> Either FailedDependency ResolvedDependency-selectDependency internalIndex installedIndex requiredDepsMap- dep@(Dependency pkgname vr) =- -- If the dependency specification matches anything in the internal package- -- index, then we prefer that match to anything in the second.- -- For example:- --- -- Name: MyLibrary- -- Version: 0.1- -- Library- -- ..- -- Executable my-exec- -- build-depends: MyLibrary- --- -- We want "build-depends: MyLibrary" always to match the internal library- -- even if there is a newer installed library "MyLibrary-0.2".- -- However, "build-depends: MyLibrary >= 0.2" should match the installed one.- case PackageIndex.lookupPackageName internalIndex pkgname of- [(_,[pkg])] | packageVersion pkg `withinRange` vr- -> Right $ InternalDependency dep (packageId pkg)-- _ -> case Map.lookup pkgname requiredDepsMap of- -- If we know the exact pkg to use, then use it.- Just pkginstance -> Right (ExternalDependency dep pkginstance)- -- Otherwise we just pick an arbitrary instance of the latest version.- Nothing -> case PackageIndex.lookupDependency installedIndex dep of- [] -> Left $ DependencyNotExists pkgname- pkgs -> Right $ ExternalDependency dep $- case last pkgs of- (_ver, pkginstances) -> head pkginstances--reportSelectedDependencies :: Verbosity- -> [ResolvedDependency] -> IO ()-reportSelectedDependencies verbosity deps =- info verbosity $ unlines- [ "Dependency " ++ display (simplifyDependency dep)- ++ ": using " ++ display pkgid- | resolved <- deps- , let (dep, pkgid) = case resolved of- ExternalDependency dep' pkg' -> (dep', packageId pkg')- InternalDependency dep' pkgid' -> (dep', pkgid') ]--reportFailedDependencies :: [FailedDependency] -> IO ()-reportFailedDependencies [] = return ()-reportFailedDependencies failed =- die (intercalate "\n\n" (map reportFailedDependency failed))-- where- reportFailedDependency (DependencyNotExists pkgname) =- "there is no version of " ++ display pkgname ++ " installed.\n"- ++ "Perhaps you need to download and install it from\n"- ++ hackageUrl ++ display pkgname ++ "?"-- reportFailedDependency (DependencyNoVersion dep) =- "cannot satisfy dependency " ++ display (simplifyDependency dep) ++ "\n"---- | List all installed packages in the given package databases.-getInstalledPackages :: Verbosity -> Compiler- -> PackageDBStack -- ^ The stack of package databases.- -> ProgramConfiguration- -> IO InstalledPackageIndex-getInstalledPackages verbosity comp packageDBs progconf = do- when (null packageDBs) $- die $ "No package databases have been specified. If you use "- ++ "--package-db=clear, you must follow it with --package-db= "- ++ "with 'global', 'user' or a specific file."-- info verbosity "Reading installed packages..."- case compilerFlavor comp of- GHC -> GHC.getInstalledPackages verbosity comp packageDBs progconf- GHCJS -> GHCJS.getInstalledPackages verbosity packageDBs progconf- JHC -> JHC.getInstalledPackages verbosity packageDBs progconf- LHC -> LHC.getInstalledPackages verbosity packageDBs progconf- UHC -> UHC.getInstalledPackages verbosity comp packageDBs progconf- HaskellSuite {} ->- HaskellSuite.getInstalledPackages verbosity packageDBs progconf- flv -> die $ "don't know how to find the installed packages for "- ++ display flv---- | Like 'getInstalledPackages', but for a single package DB.-getPackageDBContents :: Verbosity -> Compiler- -> PackageDB -> ProgramConfiguration- -> IO InstalledPackageIndex-getPackageDBContents verbosity comp packageDB progconf = do- info verbosity "Reading installed packages..."- case compilerFlavor comp of- GHC -> GHC.getPackageDBContents verbosity packageDB progconf- GHCJS -> GHCJS.getPackageDBContents verbosity packageDB progconf- -- For other compilers, try to fall back on 'getInstalledPackages'.- _ -> getInstalledPackages verbosity comp [packageDB] progconf----- | The user interface specifies the package dbs to use with a combination of--- @--global@, @--user@ and @--package-db=global|user|clear|$file@.--- This function combines the global/user flag and interprets the package-db--- flag into a single package db stack.----interpretPackageDbFlags :: Bool -> [Maybe PackageDB] -> PackageDBStack-interpretPackageDbFlags userInstall specificDBs =- extra initialStack specificDBs- where- initialStack | userInstall = [GlobalPackageDB, UserPackageDB]- | otherwise = [GlobalPackageDB]-- extra dbs' [] = dbs'- extra _ (Nothing:dbs) = extra [] dbs- extra dbs' (Just db:dbs) = extra (dbs' ++ [db]) dbs--newPackageDepsBehaviourMinVersion :: Version-newPackageDepsBehaviourMinVersion = Version [1,7,1] []---- In older cabal versions, there was only one set of package dependencies for--- the whole package. In this version, we can have separate dependencies per--- target, but we only enable this behaviour if the minimum cabal version--- specified is >= a certain minimum. Otherwise, for compatibility we use the--- old behaviour.-newPackageDepsBehaviour :: PackageDescription -> Bool-newPackageDepsBehaviour pkg =- specVersion pkg >= newPackageDepsBehaviourMinVersion---- We are given both --constraint="foo < 2.0" style constraints and also--- specific packages to pick via --dependency="foo=foo-2.0-177d5cdf20962d0581".------ When finalising the package we have to take into account the specific--- installed deps we've been given, and the finalise function expects--- constraints, so we have to translate these deps into version constraints.------ But after finalising we then have to make sure we pick the right specific--- deps in the end. So we still need to remember which installed packages to--- pick.-combinedConstraints :: [Dependency] ->- [(PackageName, InstalledPackageId)] ->- InstalledPackageIndex ->- Either String ([Dependency],- Map PackageName InstalledPackageInfo)-combinedConstraints constraints dependencies installedPackages = do-- when (not (null badInstalledPackageIds)) $- Left $ render $ text "The following package dependencies were requested"- $+$ nest 4 (dispDependencies badInstalledPackageIds)- $+$ text "however the given installed package instance does not exist."-- when (not (null badNames)) $- Left $ render $ text "The following package dependencies were requested"- $+$ nest 4 (dispDependencies badNames)- $+$ text "however the installed package's name does not match the name given."-- --TODO: we don't check that all dependencies are used!-- return (allConstraints, idConstraintMap)-- where- allConstraints :: [Dependency]- allConstraints = constraints- ++ [ thisPackageVersion (packageId pkg)- | (_, _, Just pkg) <- dependenciesPkgInfo ]-- idConstraintMap :: Map PackageName InstalledPackageInfo- idConstraintMap = Map.fromList- [ (packageName pkg, pkg)- | (_, _, Just pkg) <- dependenciesPkgInfo ]-- -- The dependencies along with the installed package info, if it exists- dependenciesPkgInfo :: [(PackageName, InstalledPackageId,- Maybe InstalledPackageInfo)]- dependenciesPkgInfo =- [ (pkgname, ipkgid, mpkg)- | (pkgname, ipkgid) <- dependencies- , let mpkg = PackageIndex.lookupInstalledPackageId- installedPackages ipkgid- ]-- -- If we looked up a package specified by an installed package id- -- (i.e. someone has written a hash) and didn't find it then it's- -- an error.- badInstalledPackageIds =- [ (pkgname, ipkgid)- | (pkgname, ipkgid, Nothing) <- dependenciesPkgInfo ]-- -- If someone has written e.g.- -- --dependency="foo=MyOtherLib-1.0-07...5bf30" then they have- -- probably made a mistake.- badNames =- [ (requestedPkgName, ipkgid)- | (requestedPkgName, ipkgid, Just pkg) <- dependenciesPkgInfo- , let foundPkgName = packageName pkg- , requestedPkgName /= foundPkgName ]-- dispDependencies deps =- hsep [ text "--dependency="- <> quotes (disp pkgname <> char '=' <> disp ipkgid)- | (pkgname, ipkgid) <- deps ]---- -------------------------------------------------------------------------------- Configuring hole instantiation--configureInstantiateWith :: PackageDescription- -> ConfigFlags- -> InstalledPackageIndex -- ^ installed packages- -> IO ([InstalledPackageInfo],- [(ModuleName, (InstalledPackageInfo, ModuleName))])-configureInstantiateWith pkg_descr cfg installedPackageSet = do- -- Holes: First, check and make sure the provided instantiation covers- -- all the holes we know about. Indefinite package installation is- -- not handled at all at this point.- -- NB: We union together /all/ of the requirements when calculating- -- the package key.- -- NB: For now, we assume that dependencies don't contribute signatures.- -- This will be handled by cabal-install; as far as ./Setup is- -- concerned, the most important thing is to be provided correctly- -- built dependencies.- let signatures =- maybe [] (\lib -> requiredSignatures lib ++ exposedSignatures lib)- (PD.library pkg_descr)- signatureSet = Set.fromList signatures- instantiateMap = Map.fromList (configInstantiateWith cfg)- missing_impls = filter (not . flip Map.member instantiateMap) signatures- hole_insts0 = filter (\(k,_) -> Set.member k signatureSet) (configInstantiateWith cfg)-- when (not (null missing_impls)) $- die $ "Missing signature implementations for these modules: "- ++ intercalate ", " (map display missing_impls)-- -- Holes: Next, we need to make sure we have packages to actually- -- provide the implementations we're talking about. This is on top- -- of the normal dependency resolution process.- -- TODO: internal dependencies (e.g. the test package depending on the- -- main library) is not currently supported- let selectHoleDependency (k,(i,m)) =- case PackageIndex.lookupInstalledPackageId installedPackageSet i of- Just pkginst -> Right (k,(pkginst, m))- Nothing -> Left i- (failed_hmap, hole_insts) = partitionEithers (map selectHoleDependency hole_insts0)- holeDeps = map (fst.snd) hole_insts -- could have dups-- -- Holes: Finally, any dependencies selected this way have to be- -- included in the allPkgs index, as well as the buildComponents.- -- But don't report these as potential inconsistencies!-- when (not (null failed_hmap)) $- die $ "Could not resolve these package IDs (from signature implementations): "- ++ intercalate ", " (map display failed_hmap)-- return (holeDeps, hole_insts)---- -------------------------------------------------------------------------------- Configuring program dependencies--configureRequiredPrograms :: Verbosity -> [Dependency] -> ProgramConfiguration- -> IO ProgramConfiguration-configureRequiredPrograms verbosity deps conf =- foldM (configureRequiredProgram verbosity) conf deps--configureRequiredProgram :: Verbosity -> ProgramConfiguration -> Dependency- -> IO ProgramConfiguration-configureRequiredProgram verbosity conf- (Dependency (PackageName progName) verRange) =- case lookupKnownProgram progName conf of- Nothing -> die ("Unknown build tool " ++ progName)- Just prog- -- requireProgramVersion always requires the program have a version- -- but if the user says "build-depends: foo" ie no version constraint- -- then we should not fail if we cannot discover the program version.- | verRange == anyVersion -> do- (_, conf') <- requireProgram verbosity prog conf- return conf'- | otherwise -> do- (_, _, conf') <- requireProgramVersion verbosity prog verRange conf- return conf'---- -------------------------------------------------------------------------------- Configuring pkg-config package dependencies--configurePkgconfigPackages :: Verbosity -> PackageDescription- -> ProgramConfiguration- -> IO (PackageDescription, ProgramConfiguration)-configurePkgconfigPackages verbosity pkg_descr conf- | null allpkgs = return (pkg_descr, conf)- | otherwise = do- (_, _, conf') <- requireProgramVersion- (lessVerbose verbosity) pkgConfigProgram- (orLaterVersion $ Version [0,9,0] []) conf- mapM_ requirePkg allpkgs- lib' <- mapM addPkgConfigBILib (library pkg_descr)- exes' <- mapM addPkgConfigBIExe (executables pkg_descr)- tests' <- mapM addPkgConfigBITest (testSuites pkg_descr)- benches' <- mapM addPkgConfigBIBench (benchmarks pkg_descr)- let pkg_descr' = pkg_descr { library = lib', executables = exes',- testSuites = tests', benchmarks = benches' }- return (pkg_descr', conf')-- where- allpkgs = concatMap pkgconfigDepends (allBuildInfo pkg_descr)- pkgconfig = rawSystemProgramStdoutConf (lessVerbose verbosity)- pkgConfigProgram conf-- requirePkg dep@(Dependency (PackageName pkg) range) = do- version <- pkgconfig ["--modversion", pkg]- `catchIO` (\_ -> die notFound)- `catchExit` (\_ -> die notFound)- case simpleParse version of- Nothing -> die "parsing output of pkg-config --modversion failed"- Just v | not (withinRange v range) -> die (badVersion v)- | otherwise -> info verbosity (depSatisfied v)- where- notFound = "The pkg-config package '" ++ pkg ++ "'"- ++ versionRequirement- ++ " is required but it could not be found."- badVersion v = "The pkg-config package '" ++ pkg ++ "'"- ++ versionRequirement- ++ " is required but the version installed on the"- ++ " system is version " ++ display v- depSatisfied v = "Dependency " ++ display dep- ++ ": using version " ++ display v-- versionRequirement- | isAnyVersion range = ""- | otherwise = " version " ++ display range-- -- Adds pkgconfig dependencies to the build info for a component- addPkgConfigBI compBI setCompBI comp = do- bi <- pkgconfigBuildInfo (pkgconfigDepends (compBI comp))- return $ setCompBI comp (compBI comp `mappend` bi)-- -- Adds pkgconfig dependencies to the build info for a library- addPkgConfigBILib = addPkgConfigBI libBuildInfo $- \lib bi -> lib { libBuildInfo = bi }-- -- Adds pkgconfig dependencies to the build info for an executable- addPkgConfigBIExe = addPkgConfigBI buildInfo $- \exe bi -> exe { buildInfo = bi }-- -- Adds pkgconfig dependencies to the build info for a test suite- addPkgConfigBITest = addPkgConfigBI testBuildInfo $- \test bi -> test { testBuildInfo = bi }-- -- Adds pkgconfig dependencies to the build info for a benchmark- addPkgConfigBIBench = addPkgConfigBI benchmarkBuildInfo $- \bench bi -> bench { benchmarkBuildInfo = bi }-- pkgconfigBuildInfo :: [Dependency] -> IO BuildInfo- pkgconfigBuildInfo [] = return mempty- pkgconfigBuildInfo pkgdeps = do- let pkgs = nub [ display pkg | Dependency pkg _ <- pkgdeps ]- ccflags <- pkgconfig ("--cflags" : pkgs)- ldflags <- pkgconfig ("--libs" : pkgs)- return (ccLdOptionsBuildInfo (words ccflags) (words ldflags))---- | Makes a 'BuildInfo' from C compiler and linker flags.------ This can be used with the output from configuration programs like pkg-config--- and similar package-specific programs like mysql-config, freealut-config etc.--- For example:------ > ccflags <- rawSystemProgramStdoutConf verbosity prog conf ["--cflags"]--- > ldflags <- rawSystemProgramStdoutConf verbosity prog conf ["--libs"]--- > return (ccldOptionsBuildInfo (words ccflags) (words ldflags))----ccLdOptionsBuildInfo :: [String] -> [String] -> BuildInfo-ccLdOptionsBuildInfo cflags ldflags =- let (includeDirs', cflags') = partition ("-I" `isPrefixOf`) cflags- (extraLibs', ldflags') = partition ("-l" `isPrefixOf`) ldflags- (extraLibDirs', ldflags'') = partition ("-L" `isPrefixOf`) ldflags'- in mempty {- PD.includeDirs = map (drop 2) includeDirs',- PD.extraLibs = map (drop 2) extraLibs',- PD.extraLibDirs = map (drop 2) extraLibDirs',- PD.ccOptions = cflags',- PD.ldOptions = ldflags''- }---- -------------------------------------------------------------------------------- Determining the compiler details--configCompilerAuxEx :: ConfigFlags- -> IO (Compiler, Platform, ProgramConfiguration)-configCompilerAuxEx cfg = configCompilerEx (flagToMaybe $ configHcFlavor cfg)- (flagToMaybe $ configHcPath cfg)- (flagToMaybe $ configHcPkg cfg)- programsConfig- (fromFlag (configVerbosity cfg))- where- programsConfig = mkProgramsConfig cfg defaultProgramConfiguration--configCompilerEx :: Maybe CompilerFlavor -> Maybe FilePath -> Maybe FilePath- -> ProgramConfiguration -> Verbosity- -> IO (Compiler, Platform, ProgramConfiguration)-configCompilerEx Nothing _ _ _ _ = die "Unknown compiler"-configCompilerEx (Just hcFlavor) hcPath hcPkg conf verbosity = do- (comp, maybePlatform, programsConfig) <- case hcFlavor of- GHC -> GHC.configure verbosity hcPath hcPkg conf- GHCJS -> GHCJS.configure verbosity hcPath hcPkg conf- JHC -> JHC.configure verbosity hcPath hcPkg conf- LHC -> do (_, _, ghcConf) <- GHC.configure verbosity Nothing hcPkg conf- LHC.configure verbosity hcPath Nothing ghcConf- UHC -> UHC.configure verbosity hcPath hcPkg conf- HaskellSuite {} -> HaskellSuite.configure verbosity hcPath hcPkg conf- _ -> die "Unknown compiler"- return (comp, fromMaybe buildPlatform maybePlatform, programsConfig)---- Ideally we would like to not have separate configCompiler* and--- configCompiler*Ex sets of functions, but there are many custom setup scripts--- in the wild that are using them, so the versions with old types are kept for--- backwards compatibility. Platform was added to the return triple in 1.18.--{-# DEPRECATED configCompiler- "'configCompiler' is deprecated. Use 'configCompilerEx' instead." #-}-configCompiler :: Maybe CompilerFlavor -> Maybe FilePath -> Maybe FilePath- -> ProgramConfiguration -> Verbosity- -> IO (Compiler, ProgramConfiguration)-configCompiler mFlavor hcPath hcPkg conf verbosity =- fmap (\(a,_,b) -> (a,b)) $ configCompilerEx mFlavor hcPath hcPkg conf verbosity--{-# DEPRECATED configCompilerAux- "configCompilerAux is deprecated. Use 'configCompilerAuxEx' instead." #-}-configCompilerAux :: ConfigFlags- -> IO (Compiler, ProgramConfiguration)-configCompilerAux = fmap (\(a,_,b) -> (a,b)) . configCompilerAuxEx---- -------------------------------------------------------------------------------- Making the internal component graph---mkComponentsGraph :: PackageDescription- -> [PackageId]- -> Either [ComponentName]- [(Component, [ComponentName])]-mkComponentsGraph pkg_descr internalPkgDeps =- let graph = [ (c, componentName c, componentDeps c)- | c <- pkgEnabledComponents pkg_descr ]- in case checkComponentsCyclic graph of- Just ccycle -> Left [ cname | (_,cname,_) <- ccycle ]- Nothing -> Right [ (c, cdeps) | (c, _, cdeps) <- graph ]- where- -- The dependencies for the given component- componentDeps component =- [ CExeName toolname | Dependency (PackageName toolname) _- <- buildTools bi- , toolname `elem` map exeName- (executables pkg_descr) ]-- ++ [ CLibName | Dependency pkgname _ <- targetBuildDepends bi- , pkgname `elem` map packageName internalPkgDeps ]- where- bi = componentBuildInfo component--reportComponentCycle :: [ComponentName] -> IO a-reportComponentCycle cnames =- die $ "Components in the package depend on each other in a cyclic way:\n "- ++ intercalate " depends on "- [ "'" ++ showComponentName cname ++ "'"- | cname <- cnames ++ [head cnames] ]--mkComponentsLocalBuildInfo :: Compiler- -> InstalledPackageIndex- -> PackageDescription- -> [PackageId] -- internal package deps- -> [InstalledPackageInfo] -- external package deps- -> [InstalledPackageInfo] -- hole package deps- -> Map ModuleName (InstalledPackageInfo, ModuleName)- -> [(Component, [ComponentName])]- -> IO [(ComponentName, ComponentLocalBuildInfo,- [ComponentName])]-mkComponentsLocalBuildInfo comp installedPackages pkg_descr- internalPkgDeps externalPkgDeps holePkgDeps hole_insts- graph =- sequence- [ do clbi <- componentLocalBuildInfo c- return (componentName c, clbi, cdeps)- | (c, cdeps) <- graph ]- where- -- The allPkgDeps contains all the package deps for the whole package- -- but we need to select the subset for this specific component.- -- we just take the subset for the package names this component- -- needs. Note, this only works because we cannot yet depend on two- -- versions of the same package.- componentLocalBuildInfo component =- case component of- CLib lib -> do- let exports = map (\n -> Installed.ExposedModule n Nothing Nothing)- (PD.exposedModules lib)- esigs = map (\n -> Installed.ExposedModule n Nothing- (fmap (\(pkg,m) -> Installed.OriginalModule- (Installed.installedPackageId pkg) m)- (Map.lookup n hole_insts)))- (PD.exposedSignatures lib)- let mb_reexports = resolveModuleReexports installedPackages- (packageId pkg_descr)- externalPkgDeps lib- reexports <- case mb_reexports of- Left problems -> reportModuleReexportProblems problems- Right r -> return r-- -- Calculate the version hash and package key.- let externalPkgs = selectSubset bi externalPkgDeps- pkg_key = mkPackageKey (packageKeySupported comp)- (package pkg_descr)- (map Installed.libraryName externalPkgs)- version_hash = packageKeyLibraryName (package pkg_descr) pkg_key-- return LibComponentLocalBuildInfo {- componentPackageDeps = cpds,- componentPackageKey = pkg_key,- componentLibraryName = version_hash,- componentPackageRenaming = cprns,- componentExposedModules = exports ++ reexports ++ esigs- }- CExe _ ->- return ExeComponentLocalBuildInfo {- componentPackageDeps = cpds,- componentPackageRenaming = cprns- }- CTest _ ->- return TestComponentLocalBuildInfo {- componentPackageDeps = cpds,- componentPackageRenaming = cprns- }- CBench _ ->- return BenchComponentLocalBuildInfo {- componentPackageDeps = cpds,- componentPackageRenaming = cprns- }- where- bi = componentBuildInfo component- dedup = Map.toList . Map.fromList- cpds = if newPackageDepsBehaviour pkg_descr- then dedup $- [ (Installed.installedPackageId pkg, packageId pkg)- | pkg <- selectSubset bi externalPkgDeps ]- ++ [ (inplacePackageId pkgid, pkgid)- | pkgid <- selectSubset bi internalPkgDeps ]- else [ (Installed.installedPackageId pkg, packageId pkg)- | pkg <- externalPkgDeps ]- cprns = if newPackageDepsBehaviour pkg_descr- then Map.unionWith mappend- -- We need hole dependencies passed to GHC, so add them here- -- (but note that they're fully thinned out. If they- -- appeared legitimately the monoid instance will- -- fill them out.- (Map.fromList [(packageName pkg, mempty) | pkg <- holePkgDeps])- (targetBuildRenaming bi)- -- Hack: if we have old package-deps behavior, it's impossible- -- for non-default renamings to be used, because the Cabal- -- version is too early. This is a good, because while all the- -- deps were bundled up in buildDepends, we didn't do this for- -- renamings, so it's not even clear how to get the merged- -- version. So just assume that all of them are the default..- else Map.fromList (map (\(_,pid) -> (packageName pid, defaultRenaming)) cpds)-- selectSubset :: Package pkg => BuildInfo -> [pkg] -> [pkg]- selectSubset bi pkgs =- [ pkg | pkg <- pkgs, packageName pkg `elem` names bi ]-- names bi = [ name | Dependency name _ <- targetBuildDepends bi ]---- | Given the author-specified re-export declarations from the .cabal file,--- resolve them to the form that we need for the package database.------ An invariant of the package database is that we always link the re-export--- directly to its original defining location (rather than indirectly via a--- chain of re-exporting packages).----resolveModuleReexports :: InstalledPackageIndex- -> PackageId- -> [InstalledPackageInfo]- -> Library- -> Either [(ModuleReexport, String)] -- errors- [Installed.ExposedModule] -- ok-resolveModuleReexports installedPackages srcpkgid externalPkgDeps lib =- case partitionEithers (map resolveModuleReexport (PD.reexportedModules lib)) of- ([], ok) -> Right ok- (errs, _) -> Left errs- where- -- A mapping from visible module names to their original defining- -- module name. We also record the package name of the package which- -- *immediately* provided the module (not the original) to handle if the- -- user explicitly says which build-depends they want to reexport from.- visibleModules :: Map ModuleName [(PackageName, Installed.ExposedModule)]- visibleModules =- Map.fromListWith (++) $- [ (Installed.exposedName exposedModule, [(exportingPackageName,- exposedModule)])- -- The package index here contains all the indirect deps of the- -- package we're configuring, but we want just the direct deps- | let directDeps = Set.fromList (map Installed.installedPackageId externalPkgDeps)- , pkg <- PackageIndex.allPackages installedPackages- , Installed.installedPackageId pkg `Set.member` directDeps- , let exportingPackageName = packageName pkg- , exposedModule <- visibleModuleDetails pkg- ]- ++ [ (visibleModuleName, [(exportingPackageName, exposedModule)])- | visibleModuleName <- PD.exposedModules lib- ++ otherModules (libBuildInfo lib)- , let exportingPackageName = packageName srcpkgid- definingModuleName = visibleModuleName- -- we don't know the InstalledPackageId of this package yet- -- we will fill it in later, before registration.- definingPackageId = InstalledPackageId ""- originalModule = Installed.OriginalModule definingPackageId- definingModuleName- exposedModule = Installed.ExposedModule visibleModuleName- (Just originalModule)- Nothing- ]-- -- All the modules exported from this package and their defining name and- -- package (either defined here in this package or re-exported from some- -- other package). Return an ExposedModule because we want to hold onto- -- signature information.- visibleModuleDetails :: InstalledPackageInfo -> [Installed.ExposedModule]- visibleModuleDetails pkg = do- exposedModule <- Installed.exposedModules pkg- case Installed.exposedReexport exposedModule of- -- The first case is the modules actually defined in this package.- -- In this case the reexport will point to this package.- Nothing -> return exposedModule { Installed.exposedReexport =- Just (Installed.OriginalModule (Installed.installedPackageId pkg)- (Installed.exposedName exposedModule)) }- -- On the other hand, a visible module might actually be itself- -- a re-export! In this case, the re-export info for the package- -- doing the re-export will point us to the original defining- -- module name and package, so we can reuse the entry.- Just _ -> return exposedModule-- resolveModuleReexport reexport@ModuleReexport {- moduleReexportOriginalPackage = moriginalPackageName,- moduleReexportOriginalName = originalName,- moduleReexportName = newName- } =-- let filterForSpecificPackage =- case moriginalPackageName of- Nothing -> id- Just originalPackageName ->- filter (\(pkgname, _) -> pkgname == originalPackageName)-- matches = filterForSpecificPackage- (Map.findWithDefault [] originalName visibleModules)- in- case (matches, moriginalPackageName) of- ((_, exposedModule):rest, _)- -- TODO: Refine this check for signatures- | all (\(_, exposedModule') -> Installed.exposedReexport exposedModule- == Installed.exposedReexport exposedModule') rest- -> Right exposedModule { Installed.exposedName = newName }-- ([], Just originalPackageName)- -> Left $ (,) reexport- $ "The package " ++ display originalPackageName- ++ " does not export a module " ++ display originalName-- ([], Nothing)- -> Left $ (,) reexport- $ "The module " ++ display originalName- ++ " is not exported by any suitable package (this package "- ++ "itself nor any of its 'build-depends' dependencies)."-- (ms, _)- -> Left $ (,) reexport- $ "The module " ++ display originalName ++ " is exported "- ++ "by more than one package ("- ++ intercalate ", " [ display pkgname | (pkgname,_) <- ms ]- ++ ") and so the re-export is ambiguous. The ambiguity can "- ++ "be resolved by qualifying by the package name. The "- ++ "syntax is 'packagename:moduleName [as newname]'."-- -- Note: if in future Cabal allows directly depending on multiple- -- instances of the same package (e.g. backpack) then an additional- -- ambiguity case is possible here: (_, Just originalPackageName)- -- with the module being ambiguous despite being qualified by a- -- package name. Presumably by that time we'll have a mechanism to- -- qualify the instance we're referring to.--reportModuleReexportProblems :: [(ModuleReexport, String)] -> IO a-reportModuleReexportProblems reexportProblems =- die $ unlines- [ "Problem with the module re-export '" ++ display reexport ++ "': " ++ msg- | (reexport, msg) <- reexportProblems ]---- -------------------------------------------------------------------------------- Testing C lib and header dependencies---- Try to build a test C program which includes every header and links every--- lib. If that fails, try to narrow it down by preprocessing (only) and linking--- with individual headers and libs. If none is the obvious culprit then give a--- generic error message.--- TODO: produce a log file from the compiler errors, if any.-checkForeignDeps :: PackageDescription -> LocalBuildInfo -> Verbosity -> IO ()-checkForeignDeps pkg lbi verbosity = do- ifBuildsWith allHeaders (commonCcArgs ++ makeLdArgs allLibs) -- I'm feeling- -- lucky- (return ())- (do missingLibs <- findMissingLibs- missingHdr <- findOffendingHdr- explainErrors missingHdr missingLibs)- where- allHeaders = collectField PD.includes- allLibs = collectField PD.extraLibs-- ifBuildsWith headers args success failure = do- ok <- builds (makeProgram headers) args- if ok then success else failure-- findOffendingHdr =- ifBuildsWith allHeaders ccArgs- (return Nothing)- (go . tail . inits $ allHeaders)- where- go [] = return Nothing -- cannot happen- go (hdrs:hdrsInits) =- -- Try just preprocessing first- ifBuildsWith hdrs cppArgs- -- If that works, try compiling too- (ifBuildsWith hdrs ccArgs- (go hdrsInits)- (return . Just . Right . last $ hdrs))- (return . Just . Left . last $ hdrs)-- cppArgs = "-E":commonCppArgs -- preprocess only- ccArgs = "-c":commonCcArgs -- don't try to link-- findMissingLibs = ifBuildsWith [] (makeLdArgs allLibs)- (return [])- (filterM (fmap not . libExists) allLibs)-- libExists lib = builds (makeProgram []) (makeLdArgs [lib])-- commonCppArgs = platformDefines lbi- ++ [ "-I" ++ autogenModulesDir lbi ]- ++ [ "-I" ++ dir | dir <- collectField PD.includeDirs ]- ++ ["-I."]- ++ collectField PD.cppOptions- ++ collectField PD.ccOptions- ++ [ "-I" ++ dir- | dep <- deps- , dir <- Installed.includeDirs dep ]- ++ [ opt- | dep <- deps- , opt <- Installed.ccOptions dep ]-- commonCcArgs = commonCppArgs- ++ collectField PD.ccOptions- ++ [ opt- | dep <- deps- , opt <- Installed.ccOptions dep ]-- commonLdArgs = [ "-L" ++ dir | dir <- collectField PD.extraLibDirs ]- ++ collectField PD.ldOptions- ++ [ "-L" ++ dir- | dep <- deps- , dir <- Installed.libraryDirs dep ]- --TODO: do we also need dependent packages' ld options?- makeLdArgs libs = [ "-l"++lib | lib <- libs ] ++ commonLdArgs-- makeProgram hdrs = unlines $- [ "#include \"" ++ hdr ++ "\"" | hdr <- hdrs ] ++- ["int main(int argc, char** argv) { return 0; }"]-- collectField f = concatMap f allBi- allBi = allBuildInfo pkg- deps = PackageIndex.topologicalOrder (installedPkgs lbi)-- builds program args = do- tempDir <- getTemporaryDirectory- withTempFile tempDir ".c" $ \cName cHnd ->- withTempFile tempDir "" $ \oNname oHnd -> do- hPutStrLn cHnd program- hClose cHnd- hClose oHnd- _ <- rawSystemProgramStdoutConf verbosity- gccProgram (withPrograms lbi) (cName:"-o":oNname:args)- return True- `catchIO` (\_ -> return False)- `catchExit` (\_ -> return False)-- explainErrors Nothing [] = return () -- should be impossible!- explainErrors _ _- | isNothing . lookupProgram gccProgram . withPrograms $ lbi-- = die $ unlines $- [ "No working gcc",- "This package depends on foreign library but we cannot "- ++ "find a working C compiler. If you have it in a "- ++ "non-standard location you can use the --with-gcc "- ++ "flag to specify it." ]-- explainErrors hdr libs = die $ unlines $- [ if plural- then "Missing dependencies on foreign libraries:"- else "Missing dependency on a foreign library:"- | missing ]- ++ case hdr of- Just (Left h) -> ["* Missing (or bad) header file: " ++ h ]- _ -> []- ++ case libs of- [] -> []- [lib] -> ["* Missing C library: " ++ lib]- _ -> ["* Missing C libraries: " ++ intercalate ", " libs]- ++ [if plural then messagePlural else messageSingular | missing]- ++ case hdr of- Just (Left _) -> [ headerCppMessage ]- Just (Right h) -> [ (if missing then "* " else "")- ++ "Bad header file: " ++ h- , headerCcMessage ]- _ -> []-- where- plural = length libs >= 2- -- Is there something missing? (as opposed to broken)- missing = not (null libs)- || case hdr of Just (Left _) -> True; _ -> False-- messageSingular =- "This problem can usually be solved by installing the system "- ++ "package that provides this library (you may need the "- ++ "\"-dev\" version). If the library is already installed "- ++ "but in a non-standard location then you can use the flags "- ++ "--extra-include-dirs= and --extra-lib-dirs= to specify "- ++ "where it is."- messagePlural =- "This problem can usually be solved by installing the system "- ++ "packages that provide these libraries (you may need the "- ++ "\"-dev\" versions). If the libraries are already installed "- ++ "but in a non-standard location then you can use the flags "- ++ "--extra-include-dirs= and --extra-lib-dirs= to specify "- ++ "where they are."- headerCppMessage =- "If the header file does exist, it may contain errors that "- ++ "are caught by the C compiler at the preprocessing stage. "- ++ "In this case you can re-run configure with the verbosity "- ++ "flag -v3 to see the error messages."- headerCcMessage =- "The header file contains a compile error. "- ++ "You can re-run configure with the verbosity flag "- ++ "-v3 to see the error messages from the C compiler."---- | Output package check warnings and errors. Exit if any errors.-checkPackageProblems :: Verbosity- -> GenericPackageDescription- -> PackageDescription- -> IO ()-checkPackageProblems verbosity gpkg pkg = do- ioChecks <- checkPackageFiles pkg "."- let pureChecks = checkPackage gpkg (Just pkg)- errors = [ e | PackageBuildImpossible e <- pureChecks ++ ioChecks ]- warnings = [ w | PackageBuildWarning w <- pureChecks ++ ioChecks ]- if null errors- then mapM_ (warn verbosity) warnings- else die (intercalate "\n\n" errors)---- | Preform checks if a relocatable build is allowed-checkRelocatable :: Verbosity- -> PackageDescription- -> LocalBuildInfo- -> IO ()-checkRelocatable verbosity pkg lbi- = sequence_ [ checkOS- , checkCompiler- , packagePrefixRelative- , depsPrefixRelative- ]- where- -- Check if the OS support relocatable builds.- --- -- If you add new OS' to this list, and your OS supports dynamic libraries- -- and RPATH, make sure you add your OS to RPATH-support list of:- -- Distribution.Simple.GHC.getRPaths- checkOS- = unless (os `elem` [ OSX, Linux ])- $ die $ "Operating system: " ++ display os ++- ", does not support relocatable builds"- where- (Platform _ os) = hostPlatform lbi-- -- Check if the Compiler support relocatable builds- checkCompiler- = unless (compilerFlavor comp `elem` [ GHC ])- $ die $ "Compiler: " ++ show comp ++- ", does not support relocatable builds"- where- comp = compiler lbi-- -- Check if all the install dirs are relative to same prefix- packagePrefixRelative- = unless (relativeInstallDirs installDirs)- $ die $ "Installation directories are not prefix_relative:\n" ++- show installDirs- where- installDirs = absoluteInstallDirs pkg lbi NoCopyDest- p = prefix installDirs- relativeInstallDirs (InstallDirs {..}) =- all isJust- (fmap (stripPrefix p)- [ bindir, libdir, dynlibdir, libexecdir, includedir, datadir- , docdir, mandir, htmldir, haddockdir, sysconfdir] )-- -- Check if the library dirs of the dependencies that are in the package- -- database to which the package is installed are relative to the- -- prefix of the package- depsPrefixRelative = do- pkgr <- GHC.pkgRoot verbosity lbi (last (withPackageDB lbi))- mapM_ (doCheck pkgr) ipkgs- where- doCheck pkgr ipkg- | maybe False (== pkgr) (Installed.pkgRoot ipkg)- = mapM_ (\l -> when (isNothing $ stripPrefix p l) (die (msg l)))- (Installed.libraryDirs ipkg)- | otherwise- = return ()+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE PatternGuards #-}+{-# LANGUAGE NoMonoLocalBinds #-}++-----------------------------------------------------------------------------+-- |+-- Module : Distribution.Simple.Configure+-- Copyright : Isaac Jones 2003-2005+-- License : BSD3+--+-- Maintainer : cabal-devel@haskell.org+-- Portability : portable+--+-- This deals with the /configure/ phase. It provides the 'configure' action+-- which is given the package description and configure flags. It then tries+-- to: configure the compiler; resolves any conditionals in the package+-- description; resolve the package dependencies; check if all the extensions+-- used by this package are supported by the compiler; check that all the build+-- tools are available (including version checks if appropriate); checks for+-- any required @pkg-config@ packages (updating the 'BuildInfo' with the+-- results)+--+-- Then based on all this it saves the info in the 'LocalBuildInfo' and writes+-- it out to the @dist\/setup-config@ file. It also displays various details to+-- the user, the amount of information displayed depending on the verbosity+-- level.++module Distribution.Simple.Configure (configure,+ writePersistBuildConfig,+ getConfigStateFile,+ getPersistBuildConfig,+ checkPersistBuildConfigOutdated,+ tryGetPersistBuildConfig,+ maybeGetPersistBuildConfig,+ findDistPref, findDistPrefOrDefault,+ computeComponentId,+ computeCompatPackageKey,+ computeCompatPackageName,+ localBuildInfoFile,+ getInstalledPackages,+ getInstalledPackagesMonitorFiles,+ getPackageDBContents,+ configCompiler, configCompilerAux,+ configCompilerEx, configCompilerAuxEx,+ ccLdOptionsBuildInfo,+ checkForeignDeps,+ interpretPackageDbFlags,+ ConfigStateFileError(..),+ tryGetConfigStateFile,+ platformDefines,+ relaxPackageDeps,+ )+ where++import Distribution.Compiler+import Distribution.Utils.NubList+import Distribution.Simple.Compiler hiding (Flag)+import Distribution.Simple.PreProcess+import Distribution.Package+import qualified Distribution.InstalledPackageInfo as Installed+import Distribution.InstalledPackageInfo (InstalledPackageInfo+ ,emptyInstalledPackageInfo)+import qualified Distribution.Simple.PackageIndex as PackageIndex+import Distribution.Simple.PackageIndex (InstalledPackageIndex)+import Distribution.PackageDescription as PD hiding (Flag)+import Distribution.ModuleName+import Distribution.PackageDescription.Configuration+import Distribution.PackageDescription.Check hiding (doesFileExist)+import Distribution.Simple.Program+import Distribution.Simple.Setup as Setup+import qualified Distribution.Simple.InstallDirs as InstallDirs+import Distribution.Simple.LocalBuildInfo+import Distribution.Simple.Utils+import Distribution.System+import Distribution.Version+import Distribution.Verbosity++import qualified Distribution.Simple.GHC as GHC+import qualified Distribution.Simple.GHCJS as GHCJS+import qualified Distribution.Simple.JHC as JHC+import qualified Distribution.Simple.LHC as LHC+import qualified Distribution.Simple.UHC as UHC+import qualified Distribution.Simple.HaskellSuite as HaskellSuite++-- Prefer the more generic Data.Traversable.mapM to Prelude.mapM+import Prelude hiding ( mapM )+import Control.Exception+ ( Exception, evaluate, throw, throwIO, try )+import Control.Exception ( ErrorCall )+import Control.Monad+ ( liftM, when, unless, foldM, filterM, mplus )+import Distribution.Compat.Binary ( decodeOrFailIO, encode )+import GHC.Fingerprint ( Fingerprint(..), fingerprintString )+import Data.ByteString.Lazy (ByteString)+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy.Char8 as BLC8+import Data.List+ ( (\\), nub, partition, isPrefixOf, inits, stripPrefix )+import Data.Maybe+ ( isNothing, catMaybes, fromMaybe, mapMaybe, isJust )+import Data.Either+ ( partitionEithers )+import qualified Data.Set as Set+import Data.Monoid as Mon ( Monoid(..) )+import qualified Data.Map as Map+import Data.Map (Map)+import Data.Traversable+ ( mapM )+import Data.Typeable+import Data.Char ( chr, isAlphaNum )+import Numeric ( showIntAtBase )+import System.Directory+ ( doesFileExist, createDirectoryIfMissing, getTemporaryDirectory )+import System.FilePath+ ( (</>), isAbsolute )+import qualified System.Info+ ( compilerName, compilerVersion )+import System.IO+ ( hPutStrLn, hClose )+import Distribution.Text+ ( Text(disp), defaultStyle, display, simpleParse )+import Text.PrettyPrint+ ( Doc, (<>), (<+>), ($+$), char, comma, empty, hsep, nest+ , punctuate, quotes, render, renderStyle, sep, text )+import Distribution.Compat.Environment ( lookupEnv )+import Distribution.Compat.Exception ( catchExit, catchIO )++import Data.Graph (graphFromEdges, topSort)++-- | The errors that can be thrown when reading the @setup-config@ file.+data ConfigStateFileError+ = ConfigStateFileNoHeader -- ^ No header found.+ | ConfigStateFileBadHeader -- ^ Incorrect header.+ | ConfigStateFileNoParse -- ^ Cannot parse file contents.+ | ConfigStateFileMissing -- ^ No file!+ | ConfigStateFileBadVersion PackageIdentifier PackageIdentifier+ (Either ConfigStateFileError LocalBuildInfo) -- ^ Mismatched version.+ deriving (Typeable)++-- | Format a 'ConfigStateFileError' as a user-facing error message.+dispConfigStateFileError :: ConfigStateFileError -> Doc+dispConfigStateFileError ConfigStateFileNoHeader =+ text "Saved package config file header is missing."+ <+> text "Re-run the 'configure' command."+dispConfigStateFileError ConfigStateFileBadHeader =+ text "Saved package config file header is corrupt."+ <+> text "Re-run the 'configure' command."+dispConfigStateFileError ConfigStateFileNoParse =+ text "Saved package config file is corrupt."+ <+> text "Re-run the 'configure' command."+dispConfigStateFileError ConfigStateFileMissing =+ text "Run the 'configure' command first."+dispConfigStateFileError (ConfigStateFileBadVersion oldCabal oldCompiler _) =+ text "Saved package config file is outdated:"+ $+$ badCabal $+$ badCompiler+ $+$ text "Re-run the 'configure' command."+ where+ badCabal =+ text "• the Cabal version changed from"+ <+> disp oldCabal <+> "to" <+> disp currentCabalId+ badCompiler+ | oldCompiler == currentCompilerId = empty+ | otherwise =+ text "• the compiler changed from"+ <+> disp oldCompiler <+> "to" <+> disp currentCompilerId++instance Show ConfigStateFileError where+ show = renderStyle defaultStyle . dispConfigStateFileError++instance Exception ConfigStateFileError++-- | Read the 'localBuildInfoFile'. Throw an exception if the file is+-- missing, if the file cannot be read, or if the file was created by an older+-- version of Cabal.+getConfigStateFile :: FilePath -- ^ The file path of the @setup-config@ file.+ -> IO LocalBuildInfo+getConfigStateFile filename = do+ exists <- doesFileExist filename+ unless exists $ throwIO ConfigStateFileMissing+ -- Read the config file into a strict ByteString to avoid problems with+ -- lazy I/O, then convert to lazy because the binary package needs that.+ contents <- BS.readFile filename+ let (header, body) = BLC8.span (/='\n') (BLC8.fromChunks [contents])++ headerParseResult <- try $ evaluate $ parseHeader header+ let (cabalId, compId) =+ case headerParseResult of+ Left (_ :: ErrorCall) -> throw ConfigStateFileBadHeader+ Right x -> x++ let getStoredValue = do+ result <- decodeOrFailIO (BLC8.tail body)+ case result of+ Left _ -> throw ConfigStateFileNoParse+ Right x -> return x+ deferErrorIfBadVersion act+ | cabalId /= currentCabalId = do+ eResult <- try act+ throw $ ConfigStateFileBadVersion cabalId compId eResult+ | otherwise = act+ deferErrorIfBadVersion getStoredValue++-- | Read the 'localBuildInfoFile', returning either an error or the local build+-- info.+tryGetConfigStateFile :: FilePath -- ^ The file path of the @setup-config@ file.+ -> IO (Either ConfigStateFileError LocalBuildInfo)+tryGetConfigStateFile = try . getConfigStateFile++-- | Try to read the 'localBuildInfoFile'.+tryGetPersistBuildConfig :: FilePath -- ^ The @dist@ directory path.+ -> IO (Either ConfigStateFileError LocalBuildInfo)+tryGetPersistBuildConfig = try . getPersistBuildConfig++-- | Read the 'localBuildInfoFile'. Throw an exception if the file is+-- missing, if the file cannot be read, or if the file was created by an older+-- version of Cabal.+getPersistBuildConfig :: FilePath -- ^ The @dist@ directory path.+ -> IO LocalBuildInfo+getPersistBuildConfig = getConfigStateFile . localBuildInfoFile++-- | Try to read the 'localBuildInfoFile'.+maybeGetPersistBuildConfig :: FilePath -- ^ The @dist@ directory path.+ -> IO (Maybe LocalBuildInfo)+maybeGetPersistBuildConfig =+ liftM (either (const Nothing) Just) . tryGetPersistBuildConfig++-- | After running configure, output the 'LocalBuildInfo' to the+-- 'localBuildInfoFile'.+writePersistBuildConfig :: FilePath -- ^ The @dist@ directory path.+ -> LocalBuildInfo -- ^ The 'LocalBuildInfo' to write.+ -> IO ()+writePersistBuildConfig distPref lbi = do+ createDirectoryIfMissing False distPref+ writeFileAtomic (localBuildInfoFile distPref) $+ BLC8.unlines [showHeader pkgId, encode lbi]+ where+ pkgId = packageId $ localPkgDescr lbi++-- | Identifier of the current Cabal package.+currentCabalId :: PackageIdentifier+currentCabalId = PackageIdentifier (PackageName "Cabal") cabalVersion++-- | Identifier of the current compiler package.+currentCompilerId :: PackageIdentifier+currentCompilerId = PackageIdentifier (PackageName System.Info.compilerName)+ System.Info.compilerVersion++-- | Parse the @setup-config@ file header, returning the package identifiers+-- for Cabal and the compiler.+parseHeader :: ByteString -- ^ The file contents.+ -> (PackageIdentifier, PackageIdentifier)+parseHeader header = case BLC8.words header of+ ["Saved", "package", "config", "for", pkgId, "written", "by", cabalId,+ "using", compId] ->+ fromMaybe (throw ConfigStateFileBadHeader) $ do+ _ <- simpleParse (BLC8.unpack pkgId) :: Maybe PackageIdentifier+ cabalId' <- simpleParse (BLC8.unpack cabalId)+ compId' <- simpleParse (BLC8.unpack compId)+ return (cabalId', compId')+ _ -> throw ConfigStateFileNoHeader++-- | Generate the @setup-config@ file header.+showHeader :: PackageIdentifier -- ^ The processed package.+ -> ByteString+showHeader pkgId = BLC8.unwords+ [ "Saved", "package", "config", "for"+ , BLC8.pack $ display pkgId+ , "written", "by"+ , BLC8.pack $ display currentCabalId+ , "using"+ , BLC8.pack $ display currentCompilerId+ ]++-- | Check that localBuildInfoFile is up-to-date with respect to the+-- .cabal file.+checkPersistBuildConfigOutdated :: FilePath -> FilePath -> IO Bool+checkPersistBuildConfigOutdated distPref pkg_descr_file = do+ pkg_descr_file `moreRecentFile` (localBuildInfoFile distPref)++-- | Get the path of @dist\/setup-config@.+localBuildInfoFile :: FilePath -- ^ The @dist@ directory path.+ -> FilePath+localBuildInfoFile distPref = distPref </> "setup-config"++-- -----------------------------------------------------------------------------+-- * Configuration+-- -----------------------------------------------------------------------------++-- | Return the \"dist/\" prefix, or the default prefix. The prefix is taken+-- from (in order of highest to lowest preference) the override prefix, the+-- \"CABAL_BUILDDIR\" environment variable, or the default prefix.+findDistPref :: FilePath -- ^ default \"dist\" prefix+ -> Setup.Flag FilePath -- ^ override \"dist\" prefix+ -> IO FilePath+findDistPref defDistPref overrideDistPref = do+ envDistPref <- liftM parseEnvDistPref (lookupEnv "CABAL_BUILDDIR")+ return $ fromFlagOrDefault defDistPref (mappend envDistPref overrideDistPref)+ where+ parseEnvDistPref env =+ case env of+ Just distPref | not (null distPref) -> toFlag distPref+ _ -> NoFlag++-- | Return the \"dist/\" prefix, or the default prefix. The prefix is taken+-- from (in order of highest to lowest preference) the override prefix, the+-- \"CABAL_BUILDDIR\" environment variable, or 'defaultDistPref' is used. Call+-- this function to resolve a @*DistPref@ flag whenever it is not known to be+-- set. (The @*DistPref@ flags are always set to a definite value before+-- invoking 'UserHooks'.)+findDistPrefOrDefault :: Setup.Flag FilePath -- ^ override \"dist\" prefix+ -> IO FilePath+findDistPrefOrDefault = findDistPref defaultDistPref++-- |Perform the \"@.\/setup configure@\" action.+-- Returns the @.setup-config@ file.+configure :: (GenericPackageDescription, HookedBuildInfo)+ -> ConfigFlags -> IO LocalBuildInfo+configure (pkg_descr0', pbi) cfg = do+ let pkg_descr0 =+ -- Ignore '--allow-newer' when we're given '--exact-configuration'.+ if fromFlagOrDefault False (configExactConfiguration cfg)+ then pkg_descr0'+ else relaxPackageDeps+ (fromMaybe AllowNewerNone $ configAllowNewer cfg)+ pkg_descr0'++ setupMessage verbosity "Configuring" (packageId pkg_descr0)++ checkDeprecatedFlags verbosity cfg+ checkExactConfiguration pkg_descr0 cfg++ -- Where to build the package+ let buildDir :: FilePath -- e.g. dist/build+ -- fromFlag OK due to Distribution.Simple calling+ -- findDistPrefOrDefault to fill it in+ buildDir = fromFlag (configDistPref cfg) </> "build"+ createDirectoryIfMissingVerbose (lessVerbose verbosity) True buildDir++ -- What package database(s) to use+ let packageDbs :: PackageDBStack+ packageDbs+ = interpretPackageDbFlags+ (fromFlag (configUserInstall cfg))+ (configPackageDBs cfg)++ -- comp: the compiler we're building with+ -- compPlatform: the platform we're building for+ -- programsConfig: location and args of all programs we're+ -- building with+ (comp :: Compiler,+ compPlatform :: Platform,+ programsConfig :: ProgramConfiguration)+ <- configCompilerEx+ (flagToMaybe (configHcFlavor cfg))+ (flagToMaybe (configHcPath cfg))+ (flagToMaybe (configHcPkg cfg))+ (mkProgramsConfig cfg (configPrograms cfg))+ (lessVerbose verbosity)++ -- The InstalledPackageIndex of all installed packages+ installedPackageSet :: InstalledPackageIndex+ <- getInstalledPackages (lessVerbose verbosity) comp+ packageDbs programsConfig++ -- An approximate InstalledPackageIndex of all (possible) internal libraries.+ -- This database is used to bootstrap the process before we know precisely+ -- what these libraries are supposed to be.+ let internalPackageSet :: InstalledPackageIndex+ internalPackageSet = getInternalPackages pkg_descr0++ -- allConstraints: The set of all 'Dependency's we have. Used ONLY+ -- to 'configureFinalizedPackage'.+ -- requiredDepsMap: A map from 'PackageName' to the specifically+ -- required 'InstalledPackageInfo', due to --dependency+ --+ -- NB: These constraints are to be applied to ALL components of+ -- a package. Thus, it's not an error if allConstraints contains+ -- more constraints than is necessary for a component (another+ -- component might need it.)+ --+ -- NB: The fact that we bundle all the constraints together means+ -- that is not possible to configure a test-suite to use one+ -- version of a dependency, and the executable to use another.+ (allConstraints :: [Dependency],+ requiredDepsMap :: Map PackageName InstalledPackageInfo)+ <- either die return $+ combinedConstraints (configConstraints cfg)+ (configDependencies cfg)+ installedPackageSet++ -- pkg_descr: The resolved package description, that does not contain any+ -- conditionals, because we have have an assignment for+ -- every flag, either picking them ourselves using a+ -- simple naive algorithm, or having them be passed to+ -- us by 'configConfigurationsFlags')+ -- flags: The 'FlagAssignment' that the conditionals were+ -- resolved with.+ --+ -- NB: Why doesn't finalizing a package also tell us what the+ -- dependencies are (e.g. when we run the naive algorithm,+ -- we are checking if dependencies are satisfiable)? The+ -- primary reason is that we may NOT have done any solving:+ -- if the flags are all chosen for us, this step is a simple+ -- matter of flattening according to that assignment. It's+ -- cleaner to then configure the dependencies afterwards.+ (pkg_descr :: PackageDescription,+ flags :: FlagAssignment)+ <- configureFinalizedPackage verbosity cfg+ allConstraints+ (dependencySatisfiable+ (fromFlagOrDefault False (configExactConfiguration cfg))+ installedPackageSet+ internalPackageSet+ requiredDepsMap)+ comp+ compPlatform+ pkg_descr0++ checkCompilerProblems comp pkg_descr+ checkPackageProblems verbosity pkg_descr0+ (updatePackageDescription pbi pkg_descr)++ -- The list of 'InstalledPackageInfo' recording the selected+ -- dependencies...+ -- internalPkgDeps: ...on internal packages (these are fake!)+ -- externalPkgDeps: ...on external packages+ --+ -- Invariant: For any package name, there is at most one package+ -- in externalPackageDeps which has that name.+ --+ -- NB: The dependency selection is global over ALL components+ -- in the package (similar to how allConstraints and+ -- requiredDepsMap are global over all components). In particular,+ -- if *any* component (post-flag resolution) has an unsatisfiable+ -- dependency, we will fail. This can sometimes be undesirable+ -- for users, see #1786 (benchmark conflicts with executable),+ (internalPkgDeps :: [PackageId],+ externalPkgDeps :: [InstalledPackageInfo])+ <- configureDependencies+ verbosity+ internalPackageSet+ installedPackageSet+ requiredDepsMap+ pkg_descr++ -- The database of transitively reachable installed packages that the+ -- external components the package (as a whole) depends on. This will be+ -- used in several ways:+ --+ -- * We'll use it to do a consistency check so we're not depending+ -- on multiple versions of the same package (TODO: someday relax+ -- this for private dependencies.) See right below.+ --+ -- * We feed it in when configuring the components to resolve+ -- module reexports. (TODO: axe this.)+ --+ -- * We'll pass it on in the LocalBuildInfo, where preprocessors+ -- and other things will incorrectly use it to determine what+ -- the include paths and everything should be.+ --+ packageDependsIndex :: InstalledPackageIndex <-+ case PackageIndex.dependencyClosure installedPackageSet+ (map Installed.installedUnitId externalPkgDeps) of+ Left packageDependsIndex -> return packageDependsIndex+ Right broken ->+ die $ "The following installed packages are broken because other"+ ++ " packages they depend on are missing. These broken "+ ++ "packages must be rebuilt before they can be used.\n"+ ++ unlines [ "package "+ ++ display (packageId pkg)+ ++ " is broken due to missing package "+ ++ intercalate ", " (map display deps)+ | (pkg, deps) <- broken ]++ -- In this section, we'd like to look at the 'packageDependsIndex'+ -- and see if we've picked multiple versions of the same+ -- installed package (this is bad, because it means you might+ -- get an error could not match foo-0.1:Type with foo-0.2:Type).+ --+ -- What is pseudoTopPkg for? I have no idea. It was used+ -- in the very original commit which introduced checking for+ -- inconsistencies 5115bb2be4e13841ea07dc9166b9d9afa5f0d012,+ -- and then moved out of PackageIndex and put here later.+ -- TODO: Try this code without it...+ --+ -- TODO: Move this into a helper function+ let pseudoTopPkg :: InstalledPackageInfo+ pseudoTopPkg = emptyInstalledPackageInfo {+ Installed.installedUnitId =+ mkLegacyUnitId (packageId pkg_descr),+ Installed.sourcePackageId = packageId pkg_descr,+ Installed.depends =+ map Installed.installedUnitId externalPkgDeps+ }+ case PackageIndex.dependencyInconsistencies+ . PackageIndex.insert pseudoTopPkg+ $ packageDependsIndex of+ [] -> return ()+ inconsistencies ->+ warn verbosity $+ "This package indirectly depends on multiple versions of the same "+ ++ "package. This is highly likely to cause a compile failure.\n"+ ++ unlines [ "package " ++ display pkg ++ " requires "+ ++ display (PackageIdentifier name ver)+ | (name, uses) <- inconsistencies+ , (pkg, ver) <- uses ]++ -- Compute installation directory templates, based on user+ -- configuration.+ --+ -- TODO: Move this into a helper function.+ defaultDirs :: InstallDirTemplates+ <- defaultInstallDirs (compilerFlavor comp)+ (fromFlag (configUserInstall cfg))+ (hasLibs pkg_descr)+ let installDirs :: InstallDirTemplates+ installDirs = combineInstallDirs fromFlagOrDefault+ defaultDirs (configInstallDirs cfg)++ -- Check languages and extensions+ -- TODO: Move this into a helper function.+ let langlist = nub $ catMaybes $ map defaultLanguage+ (allBuildInfo pkg_descr)+ let langs = unsupportedLanguages comp langlist+ when (not (null langs)) $+ die $ "The package " ++ display (packageId pkg_descr0)+ ++ " requires the following languages which are not "+ ++ "supported by " ++ display (compilerId comp) ++ ": "+ ++ intercalate ", " (map display langs)+ let extlist = nub $ concatMap allExtensions (allBuildInfo pkg_descr)+ let exts = unsupportedExtensions comp extlist+ when (not (null exts)) $+ die $ "The package " ++ display (packageId pkg_descr0)+ ++ " requires the following language extensions which are not "+ ++ "supported by " ++ display (compilerId comp) ++ ": "+ ++ intercalate ", " (map display exts)++ -- Configure known/required programs & external build tools.+ -- Exclude build-tool deps on "internal" exes in the same package+ --+ -- TODO: Factor this into a helper package.+ let requiredBuildTools =+ [ buildTool+ | let exeNames = map exeName (executables pkg_descr)+ , bi <- allBuildInfo pkg_descr+ , buildTool@(Dependency (PackageName toolName) reqVer)+ <- buildTools bi+ , let isInternal =+ toolName `elem` exeNames+ -- we assume all internal build-tools are+ -- versioned with the package:+ && packageVersion pkg_descr `withinRange` reqVer+ , not isInternal ]++ programsConfig' <-+ configureAllKnownPrograms (lessVerbose verbosity) programsConfig+ >>= configureRequiredPrograms verbosity requiredBuildTools++ (pkg_descr', programsConfig'') <-+ configurePkgconfigPackages verbosity pkg_descr programsConfig'++ -- Compute internal component graph+ --+ -- The general idea is that we take a look at all the source level+ -- components (which may build-depends on each other) and form a graph.+ -- From there, we build a ComponentLocalBuildInfo for each of the+ -- components, which lets us actually build each component.+ buildComponents <-+ case mkComponentsGraph pkg_descr internalPkgDeps of+ Left componentCycle -> reportComponentCycle componentCycle+ Right comps ->+ mkComponentsLocalBuildInfo cfg comp packageDependsIndex pkg_descr+ internalPkgDeps externalPkgDeps+ comps (configConfigurationsFlags cfg)++ -- Decide if we're going to compile with split objects.+ split_objs :: Bool <-+ if not (fromFlag $ configSplitObjs cfg)+ then return False+ else case compilerFlavor comp of+ GHC | compilerVersion comp >= Version [6,5] []+ -> return True+ GHCJS+ -> return True+ _ -> do warn verbosity+ ("this compiler does not support " +++ "--enable-split-objs; ignoring")+ return False++ let ghciLibByDefault =+ case compilerId comp of+ CompilerId GHC _ ->+ -- If ghc is non-dynamic, then ghci needs object files,+ -- so we build one by default.+ --+ -- Technically, archive files should be sufficient for ghci,+ -- but because of GHC bug #8942, it has never been safe to+ -- rely on them. By the time that bug was fixed, ghci had+ -- been changed to read shared libraries instead of archive+ -- files (see next code block).+ not (GHC.isDynamic comp)+ CompilerId GHCJS _ ->+ not (GHCJS.isDynamic comp)+ _ -> False++ let sharedLibsByDefault+ | fromFlag (configDynExe cfg) =+ -- build a shared library if dynamically-linked+ -- executables are requested+ True+ | otherwise = case compilerId comp of+ CompilerId GHC _ ->+ -- if ghc is dynamic, then ghci needs a shared+ -- library, so we build one by default.+ GHC.isDynamic comp+ CompilerId GHCJS _ ->+ GHCJS.isDynamic comp+ _ -> False+ withSharedLib_ =+ -- build shared libraries if required by GHC or by the+ -- executable linking mode, but allow the user to force+ -- building only static library archives with+ -- --disable-shared.+ fromFlagOrDefault sharedLibsByDefault $ configSharedLib cfg+ withDynExe_ = fromFlag $ configDynExe cfg+ when (withDynExe_ && not withSharedLib_) $ warn verbosity $+ "Executables will use dynamic linking, but a shared library "+ ++ "is not being built. Linking will fail if any executables "+ ++ "depend on the library."++ -- The --profiling flag sets the default for both libs and exes,+ -- but can be overidden by --library-profiling, or the old deprecated+ -- --executable-profiling flag.+ let profEnabledLibOnly = configProfLib cfg+ profEnabledBoth = fromFlagOrDefault False (configProf cfg)+ profEnabledLib = fromFlagOrDefault profEnabledBoth profEnabledLibOnly+ profEnabledExe = fromFlagOrDefault profEnabledBoth (configProfExe cfg)++ -- The --profiling-detail and --library-profiling-detail flags behave+ -- similarly+ profDetailLibOnly <- checkProfDetail (configProfLibDetail cfg)+ profDetailBoth <- liftM (fromFlagOrDefault ProfDetailDefault)+ (checkProfDetail (configProfDetail cfg))+ let profDetailLib = fromFlagOrDefault profDetailBoth profDetailLibOnly+ profDetailExe = profDetailBoth++ when (profEnabledExe && not profEnabledLib) $+ warn verbosity $+ "Executables will be built with profiling, but library "+ ++ "profiling is disabled. Linking will fail if any executables "+ ++ "depend on the library."++ let configCoverage_ =+ mappend (configCoverage cfg) (configLibCoverage cfg)++ cfg' = cfg { configCoverage = configCoverage_ }++ reloc <-+ if not (fromFlag $ configRelocatable cfg)+ then return False+ else return True++ let lbi = LocalBuildInfo {+ configFlags = cfg',+ flagAssignment = flags,+ extraConfigArgs = [], -- Currently configure does not+ -- take extra args, but if it+ -- did they would go here.+ installDirTemplates = installDirs,+ compiler = comp,+ hostPlatform = compPlatform,+ buildDir = buildDir,+ componentsConfigs = buildComponents,+ installedPkgs = packageDependsIndex,+ pkgDescrFile = Nothing,+ localPkgDescr = pkg_descr',+ withPrograms = programsConfig'',+ withVanillaLib = fromFlag $ configVanillaLib cfg,+ withProfLib = profEnabledLib,+ withSharedLib = withSharedLib_,+ withDynExe = withDynExe_,+ withProfExe = profEnabledExe,+ withProfLibDetail = profDetailLib,+ withProfExeDetail = profDetailExe,+ withOptimization = fromFlag $ configOptimization cfg,+ withDebugInfo = fromFlag $ configDebugInfo cfg,+ withGHCiLib = fromFlagOrDefault ghciLibByDefault $+ configGHCiLib cfg,+ splitObjs = split_objs,+ stripExes = fromFlag $ configStripExes cfg,+ stripLibs = fromFlag $ configStripLibs cfg,+ withPackageDB = packageDbs,+ progPrefix = fromFlag $ configProgPrefix cfg,+ progSuffix = fromFlag $ configProgSuffix cfg,+ relocatable = reloc+ }++ when reloc (checkRelocatable verbosity pkg_descr lbi)++ -- TODO: This is not entirely correct, because the dirs may vary+ -- across libraries/executables+ let dirs = absoluteInstallDirs pkg_descr lbi NoCopyDest+ relative = prefixRelativeInstallDirs (packageId pkg_descr) lbi++ unless (isAbsolute (prefix dirs)) $ die $+ "expected an absolute directory name for --prefix: " ++ prefix dirs++ info verbosity $ "Using " ++ display currentCabalId+ ++ " compiled by " ++ display currentCompilerId+ info verbosity $ "Using compiler: " ++ showCompilerId comp+ info verbosity $ "Using install prefix: " ++ prefix dirs++ let dirinfo name dir isPrefixRelative =+ info verbosity $ name ++ " installed in: " ++ dir ++ relNote+ where relNote = case buildOS of+ Windows | not (hasLibs pkg_descr)+ && isNothing isPrefixRelative+ -> " (fixed location)"+ _ -> ""++ dirinfo "Binaries" (bindir dirs) (bindir relative)+ dirinfo "Libraries" (libdir dirs) (libdir relative)+ dirinfo "Private binaries" (libexecdir dirs) (libexecdir relative)+ dirinfo "Data files" (datadir dirs) (datadir relative)+ dirinfo "Documentation" (docdir dirs) (docdir relative)+ dirinfo "Configuration files" (sysconfdir dirs) (sysconfdir relative)++ sequence_ [ reportProgram verbosity prog configuredProg+ | (prog, configuredProg) <- knownPrograms programsConfig'' ]++ return lbi++ where+ verbosity = fromFlag (configVerbosity cfg)++ checkProfDetail (Flag (ProfDetailOther other)) = do+ warn verbosity $+ "Unknown profiling detail level '" ++ other+ ++ "', using default.\n"+ ++ "The profiling detail levels are: " ++ intercalate ", "+ [ name | (name, _, _) <- knownProfDetailLevels ]+ return (Flag ProfDetailDefault)+ checkProfDetail other = return other++mkProgramsConfig :: ConfigFlags -> ProgramConfiguration -> ProgramConfiguration+mkProgramsConfig cfg initialProgramsConfig = programsConfig+ where+ programsConfig = userSpecifyArgss (configProgramArgs cfg)+ . userSpecifyPaths (configProgramPaths cfg)+ . setProgramSearchPath searchpath+ $ initialProgramsConfig+ searchpath = getProgramSearchPath (initialProgramsConfig)+ ++ map ProgramSearchPathDir+ (fromNubList $ configProgramPathExtra cfg)++-- -----------------------------------------------------------------------------+-- Helper functions for configure++-- | Check if the user used any deprecated flags.+checkDeprecatedFlags :: Verbosity -> ConfigFlags -> IO ()+checkDeprecatedFlags verbosity cfg = do+ unless (configProfExe cfg == NoFlag) $ do+ let enable | fromFlag (configProfExe cfg) = "enable"+ | otherwise = "disable"+ warn verbosity+ ("The flag --" ++ enable ++ "-executable-profiling is deprecated. "+ ++ "Please use --" ++ enable ++ "-profiling instead.")++ unless (configLibCoverage cfg == NoFlag) $ do+ let enable | fromFlag (configLibCoverage cfg) = "enable"+ | otherwise = "disable"+ warn verbosity+ ("The flag --" ++ enable ++ "-library-coverage is deprecated. "+ ++ "Please use --" ++ enable ++ "-coverage instead.")++-- | Sanity check: if '--exact-configuration' was given, ensure that the+-- complete flag assignment was specified on the command line.+checkExactConfiguration :: GenericPackageDescription -> ConfigFlags -> IO ()+checkExactConfiguration pkg_descr0 cfg = do+ when (fromFlagOrDefault False (configExactConfiguration cfg)) $ do+ let cmdlineFlags = map fst (configConfigurationsFlags cfg)+ allFlags = map flagName . genPackageFlags $ pkg_descr0+ diffFlags = allFlags \\ cmdlineFlags+ when (not . null $ diffFlags) $+ die $ "'--exact-configuration' was given, "+ ++ "but the following flags were not specified: "+ ++ intercalate ", " (map show diffFlags)++-- | Create a PackageIndex that makes *any libraries that might be*+-- defined internally to this package look like installed packages, in+-- case an executable should refer to any of them as dependencies.+--+-- It must be *any libraries that might be* defined rather than the+-- actual definitions, because these depend on conditionals in the .cabal+-- file, and we haven't resolved them yet. finalizePackageDescription+-- does the resolution of conditionals, and it takes internalPackageSet+-- as part of its input.+getInternalPackages :: GenericPackageDescription+ -> InstalledPackageIndex+getInternalPackages pkg_descr0 =+ let pkg_descr = flattenPackageDescription pkg_descr0+ mkInternalPackage lib = emptyInstalledPackageInfo {+ --TODO: should use a per-compiler method to map the source+ -- package ID into an installed package id we can use+ -- for the internal package set. What we do here+ -- is skeevy, but we're highly unlikely to accidentally+ -- shadow something legitimate.+ Installed.installedUnitId = mkUnitId (libName lib),+ -- NB: we TEMPORARILY set the package name to be the+ -- library name. When we actually register, it won't+ -- look like this; this is just so that internal+ -- build-depends get resolved correctly.+ Installed.sourcePackageId = PackageIdentifier (PackageName (libName lib))+ (pkgVersion (package pkg_descr))+ }+ in PackageIndex.fromList (map mkInternalPackage (libraries pkg_descr))+++-- | Returns true if a dependency is satisfiable. This is to be passed+-- to finalizePackageDescription.+dependencySatisfiable+ :: Bool+ -> InstalledPackageIndex -- ^ installed set+ -> InstalledPackageIndex -- ^ internal set+ -> Map PackageName InstalledPackageInfo -- ^ required dependencies+ -> (Dependency -> Bool)+dependencySatisfiable+ exact_config installedPackageSet internalPackageSet requiredDepsMap+ d@(Dependency depName _)+ | exact_config =+ -- When we're given '--exact-configuration', we assume that all+ -- dependencies and flags are exactly specified on the command+ -- line. Thus we only consult the 'requiredDepsMap'. Note that+ -- we're not doing the version range check, so if there's some+ -- dependency that wasn't specified on the command line,+ -- 'finalizePackageDescription' will fail.+ --+ -- TODO: mention '--exact-configuration' in the error message+ -- when this fails?+ --+ -- (However, note that internal deps don't have to be+ -- specified!)+ (depName `Map.member` requiredDepsMap) || isInternalDep++ | otherwise =+ -- Normal operation: just look up dependency in the combined+ -- package index.+ not . null . PackageIndex.lookupDependency pkgs $ d+ where+ -- NB: Prefer the INTERNAL package set+ pkgs = PackageIndex.merge installedPackageSet internalPackageSet+ isInternalDep = not . null+ $ PackageIndex.lookupDependency internalPackageSet d++-- | Relax the dependencies of this package if needed.+relaxPackageDeps :: AllowNewer -> GenericPackageDescription+ -> GenericPackageDescription+relaxPackageDeps AllowNewerNone gpd = gpd+relaxPackageDeps AllowNewerAll gpd = transformAllBuildDepends relaxAll gpd+ where+ relaxAll = \(Dependency pkgName verRange) ->+ Dependency pkgName (removeUpperBound verRange)+relaxPackageDeps (AllowNewerSome allowNewerDeps') gpd =+ transformAllBuildDepends relaxSome gpd+ where+ thisPkgName = packageName gpd+ allowNewerDeps = mapMaybe f allowNewerDeps'++ f (Setup.AllowNewerDep p) = Just p+ f (Setup.AllowNewerDepScoped scope p) | scope == thisPkgName = Just p+ | otherwise = Nothing++ relaxSome = \d@(Dependency depName verRange) ->+ if depName `elem` allowNewerDeps+ then Dependency depName (removeUpperBound verRange)+ else d++-- | Finalize a generic package description. The workhorse is+-- 'finalizePackageDescription' but there's a bit of other nattering+-- about necessary.+--+-- TODO: what exactly is the business with @flaggedTests@ and+-- @flaggedBenchmarks@?+configureFinalizedPackage+ :: Verbosity+ -> ConfigFlags+ -> [Dependency]+ -> (Dependency -> Bool) -- ^ tests if a dependency is satisfiable.+ -- Might say it's satisfiable even when not.+ -> Compiler+ -> Platform+ -> GenericPackageDescription+ -> IO (PackageDescription, FlagAssignment)+configureFinalizedPackage verbosity cfg+ allConstraints satisfies comp compPlatform pkg_descr0 = do+ let enableTest t = t { testEnabled = fromFlag (configTests cfg) }+ flaggedTests = map (\(n, t) -> (n, mapTreeData enableTest t))+ (condTestSuites pkg_descr0)+ enableBenchmark bm = bm { benchmarkEnabled =+ fromFlag (configBenchmarks cfg) }+ flaggedBenchmarks = map (\(n, bm) ->+ (n, mapTreeData enableBenchmark bm))+ (condBenchmarks pkg_descr0)+ pkg_descr0'' = pkg_descr0 { condTestSuites = flaggedTests+ , condBenchmarks = flaggedBenchmarks }++ (pkg_descr0', flags) <-+ case finalizePackageDescription+ (configConfigurationsFlags cfg)+ satisfies+ compPlatform+ (compilerInfo comp)+ allConstraints+ pkg_descr0''+ of Right r -> return r+ Left missing ->+ die $ "Encountered missing dependencies:\n"+ ++ (render . nest 4 . sep . punctuate comma+ . map (disp . simplifyDependency)+ $ missing)++ -- add extra include/lib dirs as specified in cfg+ -- we do it here so that those get checked too+ let pkg_descr = addExtraIncludeLibDirs pkg_descr0'++ when (not (null flags)) $+ info verbosity $ "Flags chosen: "+ ++ intercalate ", " [ name ++ "=" ++ display value+ | (FlagName name, value) <- flags ]++ return (pkg_descr, flags)+ where+ addExtraIncludeLibDirs pkg_descr =+ let extraBi = mempty { extraLibDirs = configExtraLibDirs cfg+ , extraFrameworkDirs = configExtraFrameworkDirs cfg+ , PD.includeDirs = configExtraIncludeDirs cfg}+ modifyLib l = l{ libBuildInfo = libBuildInfo l+ `mappend` extraBi }+ modifyExecutable e = e{ buildInfo = buildInfo e+ `mappend` extraBi}+ in pkg_descr{ libraries = modifyLib `map` libraries pkg_descr+ , executables = modifyExecutable `map`+ executables pkg_descr}++-- | Check for use of Cabal features which require compiler support+checkCompilerProblems :: Compiler -> PackageDescription -> IO ()+checkCompilerProblems comp pkg_descr = do+ unless (renamingPackageFlagsSupported comp ||+ and [ True+ | bi <- allBuildInfo pkg_descr+ , _ <- Map.elems (targetBuildRenaming bi)]) $+ die $ "Your compiler does not support thinning and renaming on "+ ++ "package flags. To use this feature you probably must use "+ ++ "GHC 7.9 or later."++ when (any (not.null.PD.reexportedModules) (PD.libraries pkg_descr)+ && not (reexportedModulesSupported comp)) $ do+ die $ "Your compiler does not support module re-exports. To use "+ ++ "this feature you probably must use GHC 7.9 or later."++-- | Select dependencies for the package.+configureDependencies+ :: Verbosity+ -> InstalledPackageIndex -- ^ internal packages+ -> InstalledPackageIndex -- ^ installed packages+ -> Map PackageName InstalledPackageInfo -- ^ required deps+ -> PackageDescription+ -> IO ([PackageId], [InstalledPackageInfo])+configureDependencies verbosity+ internalPackageSet installedPackageSet requiredDepsMap pkg_descr = do+ let selectDependencies :: [Dependency] ->+ ([FailedDependency], [ResolvedDependency])+ selectDependencies =+ partitionEithers+ . map (selectDependency internalPackageSet installedPackageSet+ requiredDepsMap)++ (failedDeps, allPkgDeps) =+ selectDependencies (buildDepends pkg_descr)++ internalPkgDeps = [ pkgid+ | InternalDependency _ pkgid <- allPkgDeps ]+ externalPkgDeps = [ pkg+ | ExternalDependency _ pkg <- allPkgDeps ]++ when (not (null internalPkgDeps)+ && not (newPackageDepsBehaviour pkg_descr)) $+ die $ "The field 'build-depends: "+ ++ intercalate ", " (map (display . packageName) internalPkgDeps)+ ++ "' refers to a library which is defined within the same "+ ++ "package. To use this feature the package must specify at "+ ++ "least 'cabal-version: >= 1.8'."++ reportFailedDependencies failedDeps+ reportSelectedDependencies verbosity allPkgDeps++ return (internalPkgDeps, externalPkgDeps)++-- -----------------------------------------------------------------------------+-- Configuring package dependencies++reportProgram :: Verbosity -> Program -> Maybe ConfiguredProgram -> IO ()+reportProgram verbosity prog Nothing+ = info verbosity $ "No " ++ programName prog ++ " found"+reportProgram verbosity prog (Just configuredProg)+ = info verbosity $ "Using " ++ programName prog ++ version ++ location+ where location = case programLocation configuredProg of+ FoundOnSystem p -> " found on system at: " ++ p+ UserSpecified p -> " given by user at: " ++ p+ version = case programVersion configuredProg of+ Nothing -> ""+ Just v -> " version " ++ display v++hackageUrl :: String+hackageUrl = "http://hackage.haskell.org/package/"++data ResolvedDependency = ExternalDependency Dependency InstalledPackageInfo+ | InternalDependency Dependency PackageId -- should be a+ -- lib name++data FailedDependency = DependencyNotExists PackageName+ | DependencyNoVersion Dependency++-- | Test for a package dependency and record the version we have installed.+selectDependency :: InstalledPackageIndex -- ^ Internally defined packages+ -> InstalledPackageIndex -- ^ Installed packages+ -> Map PackageName InstalledPackageInfo+ -- ^ Packages for which we have been given specific deps to+ -- use+ -> Dependency+ -> Either FailedDependency ResolvedDependency+selectDependency internalIndex installedIndex requiredDepsMap+ dep@(Dependency pkgname vr) =+ -- If the dependency specification matches anything in the internal package+ -- index, then we prefer that match to anything in the second.+ -- For example:+ --+ -- Name: MyLibrary+ -- Version: 0.1+ -- Library+ -- ..+ -- Executable my-exec+ -- build-depends: MyLibrary+ --+ -- We want "build-depends: MyLibrary" always to match the internal library+ -- even if there is a newer installed library "MyLibrary-0.2".+ -- However, "build-depends: MyLibrary >= 0.2" should match the installed one.+ case PackageIndex.lookupPackageName internalIndex pkgname of+ [(_,[pkg])] | packageVersion pkg `withinRange` vr+ -> Right $ InternalDependency dep (packageId pkg)++ _ -> case Map.lookup pkgname requiredDepsMap of+ -- If we know the exact pkg to use, then use it.+ Just pkginstance -> Right (ExternalDependency dep pkginstance)+ -- Otherwise we just pick an arbitrary instance of the latest version.+ Nothing -> case PackageIndex.lookupDependency installedIndex dep of+ [] -> Left $ DependencyNotExists pkgname+ pkgs -> Right $ ExternalDependency dep $+ case last pkgs of+ (_ver, pkginstances) -> head pkginstances++reportSelectedDependencies :: Verbosity+ -> [ResolvedDependency] -> IO ()+reportSelectedDependencies verbosity deps =+ info verbosity $ unlines+ [ "Dependency " ++ display (simplifyDependency dep)+ ++ ": using " ++ display pkgid+ | resolved <- deps+ , let (dep, pkgid) = case resolved of+ ExternalDependency dep' pkg' -> (dep', packageId pkg')+ InternalDependency dep' pkgid' -> (dep', pkgid') ]++reportFailedDependencies :: [FailedDependency] -> IO ()+reportFailedDependencies [] = return ()+reportFailedDependencies failed =+ die (intercalate "\n\n" (map reportFailedDependency failed))++ where+ reportFailedDependency (DependencyNotExists pkgname) =+ "there is no version of " ++ display pkgname ++ " installed.\n"+ ++ "Perhaps you need to download and install it from\n"+ ++ hackageUrl ++ display pkgname ++ "?"++ reportFailedDependency (DependencyNoVersion dep) =+ "cannot satisfy dependency " ++ display (simplifyDependency dep) ++ "\n"++-- | List all installed packages in the given package databases.+getInstalledPackages :: Verbosity -> Compiler+ -> PackageDBStack -- ^ The stack of package databases.+ -> ProgramConfiguration+ -> IO InstalledPackageIndex+getInstalledPackages verbosity comp packageDBs progconf = do+ when (null packageDBs) $+ die $ "No package databases have been specified. If you use "+ ++ "--package-db=clear, you must follow it with --package-db= "+ ++ "with 'global', 'user' or a specific file."++ info verbosity "Reading installed packages..."+ case compilerFlavor comp of+ GHC -> GHC.getInstalledPackages verbosity comp packageDBs progconf+ GHCJS -> GHCJS.getInstalledPackages verbosity packageDBs progconf+ JHC -> JHC.getInstalledPackages verbosity packageDBs progconf+ LHC -> LHC.getInstalledPackages verbosity packageDBs progconf+ UHC -> UHC.getInstalledPackages verbosity comp packageDBs progconf+ HaskellSuite {} ->+ HaskellSuite.getInstalledPackages verbosity packageDBs progconf+ flv -> die $ "don't know how to find the installed packages for "+ ++ display flv++-- | Like 'getInstalledPackages', but for a single package DB.+--+-- NB: Why isn't this always a fall through to 'getInstalledPackages'?+-- That is because 'getInstalledPackages' performs some sanity checks+-- on the package database stack in question. However, when sandboxes+-- are involved these sanity checks are not desirable.+getPackageDBContents :: Verbosity -> Compiler+ -> PackageDB -> ProgramConfiguration+ -> IO InstalledPackageIndex+getPackageDBContents verbosity comp packageDB progconf = do+ info verbosity "Reading installed packages..."+ case compilerFlavor comp of+ GHC -> GHC.getPackageDBContents verbosity packageDB progconf+ GHCJS -> GHCJS.getPackageDBContents verbosity packageDB progconf+ -- For other compilers, try to fall back on 'getInstalledPackages'.+ _ -> getInstalledPackages verbosity comp [packageDB] progconf+++-- | A set of files (or directories) that can be monitored to detect when+-- there might have been a change in the installed packages.+--+getInstalledPackagesMonitorFiles :: Verbosity -> Compiler+ -> PackageDBStack+ -> ProgramConfiguration -> Platform+ -> IO [FilePath]+getInstalledPackagesMonitorFiles verbosity comp packageDBs progconf platform =+ case compilerFlavor comp of+ GHC -> GHC.getInstalledPackagesMonitorFiles+ verbosity platform progconf packageDBs+ other -> do+ warn verbosity $ "don't know how to find change monitoring files for "+ ++ "the installed package databases for " ++ display other+ return []++-- | The user interface specifies the package dbs to use with a combination of+-- @--global@, @--user@ and @--package-db=global|user|clear|$file@.+-- This function combines the global/user flag and interprets the package-db+-- flag into a single package db stack.+--+interpretPackageDbFlags :: Bool -> [Maybe PackageDB] -> PackageDBStack+interpretPackageDbFlags userInstall specificDBs =+ extra initialStack specificDBs+ where+ initialStack | userInstall = [GlobalPackageDB, UserPackageDB]+ | otherwise = [GlobalPackageDB]++ extra dbs' [] = dbs'+ extra _ (Nothing:dbs) = extra [] dbs+ extra dbs' (Just db:dbs) = extra (dbs' ++ [db]) dbs++newPackageDepsBehaviourMinVersion :: Version+newPackageDepsBehaviourMinVersion = Version [1,7,1] []++-- In older cabal versions, there was only one set of package dependencies for+-- the whole package. In this version, we can have separate dependencies per+-- target, but we only enable this behaviour if the minimum cabal version+-- specified is >= a certain minimum. Otherwise, for compatibility we use the+-- old behaviour.+newPackageDepsBehaviour :: PackageDescription -> Bool+newPackageDepsBehaviour pkg =+ specVersion pkg >= newPackageDepsBehaviourMinVersion++-- We are given both --constraint="foo < 2.0" style constraints and also+-- specific packages to pick via --dependency="foo=foo-2.0-177d5cdf20962d0581".+--+-- When finalising the package we have to take into account the specific+-- installed deps we've been given, and the finalise function expects+-- constraints, so we have to translate these deps into version constraints.+--+-- But after finalising we then have to make sure we pick the right specific+-- deps in the end. So we still need to remember which installed packages to+-- pick.+combinedConstraints :: [Dependency] ->+ [(PackageName, UnitId)] ->+ InstalledPackageIndex ->+ Either String ([Dependency],+ Map PackageName InstalledPackageInfo)+combinedConstraints constraints dependencies installedPackages = do++ when (not (null badUnitIds)) $+ Left $ render $ text "The following package dependencies were requested"+ $+$ nest 4 (dispDependencies badUnitIds)+ $+$ text "however the given installed package instance does not exist."++ when (not (null badNames)) $+ Left $ render $ text "The following package dependencies were requested"+ $+$ nest 4 (dispDependencies badNames)+ $+$ text ("however the installed package's name does not match "+ ++ "the name given.")++ --TODO: we don't check that all dependencies are used!++ return (allConstraints, idConstraintMap)++ where+ allConstraints :: [Dependency]+ allConstraints = constraints+ ++ [ thisPackageVersion (packageId pkg)+ | (_, _, Just pkg) <- dependenciesPkgInfo ]++ idConstraintMap :: Map PackageName InstalledPackageInfo+ idConstraintMap = Map.fromList+ [ (packageName pkg, pkg)+ | (_, _, Just pkg) <- dependenciesPkgInfo ]++ -- The dependencies along with the installed package info, if it exists+ dependenciesPkgInfo :: [(PackageName, UnitId,+ Maybe InstalledPackageInfo)]+ dependenciesPkgInfo =+ [ (pkgname, ipkgid, mpkg)+ | (pkgname, ipkgid) <- dependencies+ , let mpkg = PackageIndex.lookupUnitId+ installedPackages ipkgid+ ]++ -- If we looked up a package specified by an installed package id+ -- (i.e. someone has written a hash) and didn't find it then it's+ -- an error.+ badUnitIds =+ [ (pkgname, ipkgid)+ | (pkgname, ipkgid, Nothing) <- dependenciesPkgInfo ]++ -- If someone has written e.g.+ -- --dependency="foo=MyOtherLib-1.0-07...5bf30" then they have+ -- probably made a mistake.+ badNames =+ [ (requestedPkgName, ipkgid)+ | (requestedPkgName, ipkgid, Just pkg) <- dependenciesPkgInfo+ , let foundPkgName = packageName pkg+ , requestedPkgName /= foundPkgName ]++ dispDependencies deps =+ hsep [ text "--dependency="+ <> quotes (disp pkgname <> char '=' <> disp ipkgid)+ | (pkgname, ipkgid) <- deps ]++-- -----------------------------------------------------------------------------+-- Configuring program dependencies++configureRequiredPrograms :: Verbosity -> [Dependency] -> ProgramConfiguration+ -> IO ProgramConfiguration+configureRequiredPrograms verbosity deps conf =+ foldM (configureRequiredProgram verbosity) conf deps++configureRequiredProgram :: Verbosity -> ProgramConfiguration -> Dependency+ -> IO ProgramConfiguration+configureRequiredProgram verbosity conf+ (Dependency (PackageName progName) verRange) =+ case lookupKnownProgram progName conf of+ Nothing -> die ("Unknown build tool " ++ progName)+ Just prog+ -- requireProgramVersion always requires the program have a version+ -- but if the user says "build-depends: foo" ie no version constraint+ -- then we should not fail if we cannot discover the program version.+ | verRange == anyVersion -> do+ (_, conf') <- requireProgram verbosity prog conf+ return conf'+ | otherwise -> do+ (_, _, conf') <- requireProgramVersion verbosity prog verRange conf+ return conf'++-- -----------------------------------------------------------------------------+-- Configuring pkg-config package dependencies++configurePkgconfigPackages :: Verbosity -> PackageDescription+ -> ProgramConfiguration+ -> IO (PackageDescription, ProgramConfiguration)+configurePkgconfigPackages verbosity pkg_descr conf+ | null allpkgs = return (pkg_descr, conf)+ | otherwise = do+ (_, _, conf') <- requireProgramVersion+ (lessVerbose verbosity) pkgConfigProgram+ (orLaterVersion $ Version [0,9,0] []) conf+ mapM_ requirePkg allpkgs+ libs' <- mapM addPkgConfigBILib (libraries pkg_descr)+ exes' <- mapM addPkgConfigBIExe (executables pkg_descr)+ tests' <- mapM addPkgConfigBITest (testSuites pkg_descr)+ benches' <- mapM addPkgConfigBIBench (benchmarks pkg_descr)+ let pkg_descr' = pkg_descr { libraries = libs', executables = exes',+ testSuites = tests', benchmarks = benches' }+ return (pkg_descr', conf')++ where+ allpkgs = concatMap pkgconfigDepends (allBuildInfo pkg_descr)+ pkgconfig = rawSystemProgramStdoutConf (lessVerbose verbosity)+ pkgConfigProgram conf++ requirePkg dep@(Dependency (PackageName pkg) range) = do+ version <- pkgconfig ["--modversion", pkg]+ `catchIO` (\_ -> die notFound)+ `catchExit` (\_ -> die notFound)+ case simpleParse version of+ Nothing -> die "parsing output of pkg-config --modversion failed"+ Just v | not (withinRange v range) -> die (badVersion v)+ | otherwise -> info verbosity (depSatisfied v)+ where+ notFound = "The pkg-config package '" ++ pkg ++ "'"+ ++ versionRequirement+ ++ " is required but it could not be found."+ badVersion v = "The pkg-config package '" ++ pkg ++ "'"+ ++ versionRequirement+ ++ " is required but the version installed on the"+ ++ " system is version " ++ display v+ depSatisfied v = "Dependency " ++ display dep+ ++ ": using version " ++ display v++ versionRequirement+ | isAnyVersion range = ""+ | otherwise = " version " ++ display range++ -- Adds pkgconfig dependencies to the build info for a component+ addPkgConfigBI compBI setCompBI comp = do+ bi <- pkgconfigBuildInfo (pkgconfigDepends (compBI comp))+ return $ setCompBI comp (compBI comp `mappend` bi)++ -- Adds pkgconfig dependencies to the build info for a library+ addPkgConfigBILib = addPkgConfigBI libBuildInfo $+ \lib bi -> lib { libBuildInfo = bi }++ -- Adds pkgconfig dependencies to the build info for an executable+ addPkgConfigBIExe = addPkgConfigBI buildInfo $+ \exe bi -> exe { buildInfo = bi }++ -- Adds pkgconfig dependencies to the build info for a test suite+ addPkgConfigBITest = addPkgConfigBI testBuildInfo $+ \test bi -> test { testBuildInfo = bi }++ -- Adds pkgconfig dependencies to the build info for a benchmark+ addPkgConfigBIBench = addPkgConfigBI benchmarkBuildInfo $+ \bench bi -> bench { benchmarkBuildInfo = bi }++ pkgconfigBuildInfo :: [Dependency] -> IO BuildInfo+ pkgconfigBuildInfo [] = return Mon.mempty+ pkgconfigBuildInfo pkgdeps = do+ let pkgs = nub [ display pkg | Dependency pkg _ <- pkgdeps ]+ ccflags <- pkgconfig ("--cflags" : pkgs)+ ldflags <- pkgconfig ("--libs" : pkgs)+ return (ccLdOptionsBuildInfo (words ccflags) (words ldflags))++-- | Makes a 'BuildInfo' from C compiler and linker flags.+--+-- This can be used with the output from configuration programs like pkg-config+-- and similar package-specific programs like mysql-config, freealut-config etc.+-- For example:+--+-- > ccflags <- rawSystemProgramStdoutConf verbosity prog conf ["--cflags"]+-- > ldflags <- rawSystemProgramStdoutConf verbosity prog conf ["--libs"]+-- > return (ccldOptionsBuildInfo (words ccflags) (words ldflags))+--+ccLdOptionsBuildInfo :: [String] -> [String] -> BuildInfo+ccLdOptionsBuildInfo cflags ldflags =+ let (includeDirs', cflags') = partition ("-I" `isPrefixOf`) cflags+ (extraLibs', ldflags') = partition ("-l" `isPrefixOf`) ldflags+ (extraLibDirs', ldflags'') = partition ("-L" `isPrefixOf`) ldflags'+ in mempty {+ PD.includeDirs = map (drop 2) includeDirs',+ PD.extraLibs = map (drop 2) extraLibs',+ PD.extraLibDirs = map (drop 2) extraLibDirs',+ PD.ccOptions = cflags',+ PD.ldOptions = ldflags''+ }++-- -----------------------------------------------------------------------------+-- Determining the compiler details++configCompilerAuxEx :: ConfigFlags+ -> IO (Compiler, Platform, ProgramConfiguration)+configCompilerAuxEx cfg = configCompilerEx (flagToMaybe $ configHcFlavor cfg)+ (flagToMaybe $ configHcPath cfg)+ (flagToMaybe $ configHcPkg cfg)+ programsConfig+ (fromFlag (configVerbosity cfg))+ where+ programsConfig = mkProgramsConfig cfg defaultProgramConfiguration++configCompilerEx :: Maybe CompilerFlavor -> Maybe FilePath -> Maybe FilePath+ -> ProgramConfiguration -> Verbosity+ -> IO (Compiler, Platform, ProgramConfiguration)+configCompilerEx Nothing _ _ _ _ = die "Unknown compiler"+configCompilerEx (Just hcFlavor) hcPath hcPkg conf verbosity = do+ (comp, maybePlatform, programsConfig) <- case hcFlavor of+ GHC -> GHC.configure verbosity hcPath hcPkg conf+ GHCJS -> GHCJS.configure verbosity hcPath hcPkg conf+ JHC -> JHC.configure verbosity hcPath hcPkg conf+ LHC -> do (_, _, ghcConf) <- GHC.configure verbosity Nothing hcPkg conf+ LHC.configure verbosity hcPath Nothing ghcConf+ UHC -> UHC.configure verbosity hcPath hcPkg conf+ HaskellSuite {} -> HaskellSuite.configure verbosity hcPath hcPkg conf+ _ -> die "Unknown compiler"+ return (comp, fromMaybe buildPlatform maybePlatform, programsConfig)++-- Ideally we would like to not have separate configCompiler* and+-- configCompiler*Ex sets of functions, but there are many custom setup scripts+-- in the wild that are using them, so the versions with old types are kept for+-- backwards compatibility. Platform was added to the return triple in 1.18.++{-# DEPRECATED configCompiler+ "'configCompiler' is deprecated. Use 'configCompilerEx' instead." #-}+configCompiler :: Maybe CompilerFlavor -> Maybe FilePath -> Maybe FilePath+ -> ProgramConfiguration -> Verbosity+ -> IO (Compiler, ProgramConfiguration)+configCompiler mFlavor hcPath hcPkg conf verbosity =+ fmap (\(a,_,b) -> (a,b)) $ configCompilerEx mFlavor hcPath hcPkg conf verbosity++{-# DEPRECATED configCompilerAux+ "configCompilerAux is deprecated. Use 'configCompilerAuxEx' instead." #-}+configCompilerAux :: ConfigFlags+ -> IO (Compiler, ProgramConfiguration)+configCompilerAux = fmap (\(a,_,b) -> (a,b)) . configCompilerAuxEx++-- -----------------------------------------------------------------------------+-- Making the internal component graph++-- | Given the package description and the set of package names which+-- are considered internal (the current package name and any internal+-- libraries are considered internal), create a graph of dependencies+-- between the components. This is NOT necessarily the build order+-- (although it is in the absence of Backpack.)+--+-- TODO: tighten up the type of 'internalPkgDeps'+mkComponentsGraph :: PackageDescription+ -> [PackageId]+ -> Either [ComponentName]+ [(Component, [ComponentName])]+mkComponentsGraph pkg_descr internalPkgDeps =+ let graph = [ (c, componentName c, componentDeps c)+ | c <- pkgEnabledComponents pkg_descr ]+ in case checkComponentsCyclic graph of+ Just ccycle -> Left [ cname | (_,cname,_) <- ccycle ]+ Nothing -> Right [ (c, cdeps) | (c, _, cdeps) <- topSortFromEdges graph ]+ where+ -- The dependencies for the given component+ componentDeps component =+ [ CExeName toolname | Dependency (PackageName toolname) _+ <- buildTools bi+ , toolname `elem` map exeName+ (executables pkg_descr) ]++ ++ [ CLibName toolname | Dependency pkgname@(PackageName toolname) _+ <- targetBuildDepends bi+ , pkgname `elem` map packageName internalPkgDeps ]+ where+ bi = componentBuildInfo component++reportComponentCycle :: [ComponentName] -> IO a+reportComponentCycle cnames =+ die $ "Components in the package depend on each other in a cyclic way:\n "+ ++ intercalate " depends on "+ [ "'" ++ showComponentName cname ++ "'"+ | cname <- cnames ++ [head cnames] ]++-- | This method computes a default, "good enough" 'ComponentId'+-- for a package. The intent is that cabal-install (or the user) will+-- specify a more detailed IPID via the @--ipid@ flag if necessary.+computeComponentId+ :: Flag String+ -> PackageIdentifier+ -> ComponentName+ -- TODO: careful here!+ -> [ComponentId] -- IPIDs of the component dependencies+ -> FlagAssignment+ -> ComponentId+computeComponentId mb_explicit pid cname dep_ipids flagAssignment = do+ -- show is found to be faster than intercalate and then replacement of+ -- special character used in intercalating. We cannot simply hash by+ -- doubly concating list, as it just flatten out the nested list, so+ -- different sources can produce same hash+ let hash = hashToBase62 $+ -- For safety, include the package + version here+ -- for GHC 7.10, where just the hash is used as+ -- the package key+ display pid+ ++ show dep_ipids+ ++ show flagAssignment+ generated_base = display pid ++ "-" ++ hash+ explicit_base cid0 = fromPathTemplate (InstallDirs.substPathTemplate env+ (toPathTemplate cid0))+ -- Hack to reuse install dirs machinery+ -- NB: no real IPID available at this point+ where env = packageTemplateEnv pid (mkUnitId "")+ actual_base = case mb_explicit of+ Flag cid0 -> explicit_base cid0+ NoFlag -> generated_base+ ComponentId $ actual_base+ ++ (case componentNameString (pkgName pid) cname of+ Nothing -> ""+ Just s -> "-" ++ s)++hashToBase62 :: String -> String+hashToBase62 s = showFingerprint $ fingerprintString s+ where+ showIntAtBase62 x = showIntAtBase 62 representBase62 x ""+ representBase62 x+ | x < 10 = chr (48 + x)+ | x < 36 = chr (65 + x - 10)+ | x < 62 = chr (97 + x - 36)+ | otherwise = '@'+ showFingerprint (Fingerprint a b) = showIntAtBase62 a ++ showIntAtBase62 b++-- | Computes the package name for a library. If this is the public+-- library, it will just be the original package name; otherwise,+-- it will be a munged package name recording the original package+-- name as well as the name of the internal library.+--+-- A lot of tooling in the Haskell ecosystem assumes that if something+-- is installed to the package database with the package name 'foo',+-- then it actually is an entry for the (only public) library in package+-- 'foo'. With internal packages, this is not necessarily true:+-- a public library as well as arbitrarily many internal libraries may+-- come from the same package. To prevent tools from getting confused+-- in this case, the package name of these internal libraries is munged+-- so that they do not conflict the public library proper.+--+-- We munge into a reserved namespace, "z-", and encode both the+-- component name and the package name of an internal library using the+-- following format:+--+-- compat-pkg-name ::= "z-" package-name "-z-" library-name+--+-- where package-name and library-name have "-" ( "z" + ) "-"+-- segments encoded by adding an extra "z".+--+-- When we have the public library, the compat-pkg-name is just the+-- package-name, no surprises there!+--+computeCompatPackageName :: PackageName -> ComponentName -> PackageName+computeCompatPackageName pkg_name cname+ | Just cname_str <- componentNameString pkg_name cname+ = let zdashcode s = go s (Nothing :: Maybe Int) []+ where go [] _ r = reverse r+ go ('-':z) (Just n) r | n > 0 = go z (Just 0) ('-':'z':r)+ go ('-':z) _ r = go z (Just 0) ('-':r)+ go ('z':z) (Just n) r = go z (Just (n+1)) ('z':r)+ go (c:z) _ r = go z Nothing (c:r)+ in PackageName $ "z-" ++ zdashcode (display pkg_name)+ ++ "-z-" ++ zdashcode cname_str+ | otherwise+ = pkg_name++-- | In GHC 8.0, the string we pass to GHC to use for symbol+-- names for a package can be an arbitrary, IPID-compatible string.+-- However, prior to GHC 8.0 there are some restrictions on what+-- format this string can be (due to how ghc-pkg parsed the key):+--+-- 1. In GHC 7.10, the string had either be of the form+-- foo_ABCD, where foo is a non-semantic alphanumeric/hyphenated+-- prefix and ABCD is two base-64 encoded 64-bit integers,+-- or a GHC 7.8 style identifier.+--+-- 2. In GHC 7.8, the string had to be a valid package identifier+-- like foo-0.1.+--+-- So, the problem is that Cabal, in general, has a general IPID,+-- but needs to figure out a package key / package ID that the+-- old ghc-pkg will actually accept. But there's an EVERY WORSE+-- problem: if ghc-pkg decides to parse an identifier foo-0.1-xxx+-- as if it were a package identifier, which means it will SILENTLY+-- DROP the "xxx" (because it's a tag, and Cabal does not allow tags.)+-- So we must CONNIVE to ensure that we don't pick something that+-- looks like this.+--+-- So this function attempts to define a mapping into the old formats.+--+-- The mapping for GHC 7.8 and before:+--+-- * We use the *compatibility* package name and version. For+-- public libraries this is just the package identifier; for+-- internal libraries, it's something like "z-pkgname-z-libname-0.1".+-- See 'computeCompatPackageName' for more details.+--+-- The mapping for GHC 7.10:+--+-- * For CLibName:+-- If the IPID is of the form foo-0.1-ABCDEF where foo_ABCDEF would+-- validly parse as a package key, we pass "ABCDEF". (NB: not+-- all hashes parse this way, because GHC 7.10 mandated that+-- these hashes be two base-62 encoded 64 bit integers),+-- but hashes that Cabal generated using 'computeComponentId'+-- are guaranteed to have this form.+--+-- If it is not of this form, we rehash the IPID into the+-- correct form and pass that.+--+-- * For sub-components, we rehash the IPID into the correct format+-- and pass that.+--+computeCompatPackageKey+ :: Compiler+ -> PackageName+ -> Version+ -> UnitId+ -> String+computeCompatPackageKey comp pkg_name pkg_version (SimpleUnitId (ComponentId str))+ | not (packageKeySupported comp) =+ display pkg_name ++ "-" ++ display pkg_version+ | not (unifiedIPIDRequired comp) =+ let mb_verbatim_key+ = case simpleParse str :: Maybe PackageId of+ -- Something like 'foo-0.1', use it verbatim.+ -- (NB: hash tags look like tags, so they are parsed,+ -- so the extra equality check tests if a tag was dropped.)+ Just pid0 | display pid0 == str -> Just str+ _ -> Nothing+ mb_truncated_key+ = let cand = reverse (takeWhile isAlphaNum (reverse str))+ in if length cand == 22 && all isAlphaNum cand+ then Just cand+ else Nothing+ rehashed_key = hashToBase62 str+ in fromMaybe rehashed_key (mb_verbatim_key `mplus` mb_truncated_key)+ | otherwise = str+++topSortFromEdges :: Ord key => [(node, key, [key])]+ -> [(node, key, [key])]+topSortFromEdges es =+ let (graph, vertexToNode, _) = graphFromEdges es+ in reverse (map vertexToNode (topSort graph))++mkComponentsLocalBuildInfo :: ConfigFlags+ -> Compiler+ -> InstalledPackageIndex+ -> PackageDescription+ -> [PackageId] -- internal package deps+ -> [InstalledPackageInfo] -- external package deps+ -> [(Component, [ComponentName])]+ -> FlagAssignment+ -> IO [(ComponentLocalBuildInfo,+ [UnitId])]+mkComponentsLocalBuildInfo cfg comp installedPackages pkg_descr+ internalPkgDeps externalPkgDeps+ graph flagAssignment =+ foldM go [] graph+ where+ go z (component, dep_cnames) = do+ clbi <- componentLocalBuildInfo z component+ -- NB: We want to preserve cdeps because it contains extra+ -- information like build-tools ordering+ let dep_uids = [ componentUnitId dep_clbi+ | cname <- dep_cnames+ -- Being in z relies on topsort!+ , (dep_clbi, _) <- z+ , componentLocalName dep_clbi == cname ]+ return ((clbi, dep_uids):z)++ -- The allPkgDeps contains all the package deps for the whole package+ -- but we need to select the subset for this specific component.+ -- we just take the subset for the package names this component+ -- needs. Note, this only works because we cannot yet depend on two+ -- versions of the same package.+ componentLocalBuildInfo :: [(ComponentLocalBuildInfo, [UnitId])]+ -> Component -> IO ComponentLocalBuildInfo+ componentLocalBuildInfo internalComps component =+ case component of+ CLib lib -> do+ let exports = map (\n -> Installed.ExposedModule n Nothing)+ (PD.exposedModules lib)+ mb_reexports = resolveModuleReexports installedPackages+ (packageId pkg_descr)+ uid+ externalPkgDeps lib+ reexports <- case mb_reexports of+ Left problems -> reportModuleReexportProblems problems+ Right r -> return r++ return LibComponentLocalBuildInfo {+ componentPackageDeps = cpds,+ componentUnitId = uid,+ componentLocalName = componentName component,+ componentIsPublic = libName lib == display (packageName (package pkg_descr)),+ componentCompatPackageKey = compat_key,+ componentCompatPackageName = compat_name,+ componentIncludes = includes,+ componentExposedModules = exports ++ reexports+ }+ CExe _ ->+ return ExeComponentLocalBuildInfo {+ componentUnitId = uid,+ componentLocalName = componentName component,+ componentPackageDeps = cpds,+ componentIncludes = includes+ }+ CTest _ ->+ return TestComponentLocalBuildInfo {+ componentUnitId = uid,+ componentLocalName = componentName component,+ componentPackageDeps = cpds,+ componentIncludes = includes+ }+ CBench _ ->+ return BenchComponentLocalBuildInfo {+ componentUnitId = uid,+ componentLocalName = componentName component,+ componentPackageDeps = cpds,+ componentIncludes = includes+ }+ where++ -- TODO configIPID should have name changed+ cid = computeComponentId (configIPID cfg) (package pkg_descr)+ (componentName component)+ (getDeps (componentName component))+ flagAssignment+ uid = SimpleUnitId cid+ PackageIdentifier pkg_name pkg_ver = package pkg_descr+ compat_name = computeCompatPackageName pkg_name (componentName component)+ compat_key = computeCompatPackageKey comp compat_name pkg_ver uid++ bi = componentBuildInfo component++ lookupInternalPkg :: PackageId -> UnitId+ lookupInternalPkg pkgid = do+ let matcher (clbi, _)+ | CLibName str <- componentLocalName clbi+ , str == display (pkgName pkgid)+ = Just (componentUnitId clbi)+ matcher _ = Nothing+ case catMaybes (map matcher internalComps) of+ [x] -> x+ _ -> error "lookupInternalPkg"++ cpds = if newPackageDepsBehaviour pkg_descr+ then dedup $+ [ (Installed.installedUnitId pkg, packageId pkg)+ | pkg <- selectSubset bi externalPkgDeps ]+ ++ [ (lookupInternalPkg pkgid, pkgid)+ | pkgid <- selectSubset bi internalPkgDeps ]+ else [ (Installed.installedUnitId pkg, packageId pkg)+ | pkg <- externalPkgDeps ]+ includes = map (\(i,p) -> (i,lookupRenaming p cprns)) cpds+ cprns = if newPackageDepsBehaviour pkg_descr+ then targetBuildRenaming bi+ else Map.empty++ dedup = Map.toList . Map.fromList++ -- TODO: this should include internal deps too+ getDeps :: ComponentName -> [ComponentId]+ getDeps cname =+ let externalPkgs+ = maybe [] (\lib -> selectSubset (componentBuildInfo lib)+ externalPkgDeps)+ (lookupComponent pkg_descr cname)+ in map Installed.installedComponentId externalPkgs++ selectSubset :: Package pkg => BuildInfo -> [pkg] -> [pkg]+ selectSubset bi pkgs =+ [ pkg | pkg <- pkgs, packageName pkg `elem` names bi ]++ names :: BuildInfo -> [PackageName]+ names bi = [ name | Dependency name _ <- targetBuildDepends bi ]++-- | Given the author-specified re-export declarations from the .cabal file,+-- resolve them to the form that we need for the package database.+--+-- An invariant of the package database is that we always link the re-export+-- directly to its original defining location (rather than indirectly via a+-- chain of re-exporting packages).+--+resolveModuleReexports :: InstalledPackageIndex+ -> PackageId+ -> UnitId+ -> [InstalledPackageInfo]+ -> Library+ -> Either [(ModuleReexport, String)] -- errors+ [Installed.ExposedModule] -- ok+resolveModuleReexports installedPackages srcpkgid key externalPkgDeps lib =+ case partitionEithers+ (map resolveModuleReexport (PD.reexportedModules lib)) of+ ([], ok) -> Right ok+ (errs, _) -> Left errs+ where+ -- A mapping from visible module names to their original defining+ -- module name. We also record the package name of the package which+ -- *immediately* provided the module (not the original) to handle if the+ -- user explicitly says which build-depends they want to reexport from.+ visibleModules :: Map ModuleName [(PackageName, Installed.ExposedModule)]+ visibleModules =+ Map.fromListWith (++) $+ [ (Installed.exposedName exposedModule, [(exportingPackageName,+ exposedModule)])+ -- The package index here contains all the indirect deps of the+ -- package we're configuring, but we want just the direct deps+ | let directDeps = Set.fromList+ (map Installed.installedUnitId externalPkgDeps)+ , pkg <- PackageIndex.allPackages installedPackages+ , Installed.installedUnitId pkg `Set.member` directDeps+ , let exportingPackageName = packageName pkg+ , exposedModule <- visibleModuleDetails pkg+ ]+ ++ [ (visibleModuleName, [(exportingPackageName, exposedModule)])+ | visibleModuleName <- PD.exposedModules lib+ ++ otherModules (libBuildInfo lib)+ , let exportingPackageName = packageName srcpkgid+ definingModuleName = visibleModuleName+ definingPackageId = key+ originalModule = Module definingPackageId+ definingModuleName+ exposedModule = Installed.ExposedModule visibleModuleName+ (Just originalModule)+ ]++ -- All the modules exported from this package and their defining name and+ -- package (either defined here in this package or re-exported from some+ -- other package). Return an ExposedModule because we want to hold onto+ -- signature information.+ visibleModuleDetails :: InstalledPackageInfo -> [Installed.ExposedModule]+ visibleModuleDetails pkg = do+ exposedModule <- Installed.exposedModules pkg+ case Installed.exposedReexport exposedModule of+ -- The first case is the modules actually defined in this package.+ -- In this case the reexport will point to this package.+ Nothing -> return exposedModule {+ Installed.exposedReexport =+ Just (Module+ (Installed.installedUnitId pkg)+ (Installed.exposedName exposedModule)) }+ -- On the other hand, a visible module might actually be itself+ -- a re-export! In this case, the re-export info for the package+ -- doing the re-export will point us to the original defining+ -- module name and package, so we can reuse the entry.+ Just _ -> return exposedModule++ resolveModuleReexport reexport@ModuleReexport {+ moduleReexportOriginalPackage = moriginalPackageName,+ moduleReexportOriginalName = originalName,+ moduleReexportName = newName+ } =++ let filterForSpecificPackage =+ case moriginalPackageName of+ Nothing -> id+ Just originalPackageName ->+ filter (\(pkgname, _) -> pkgname == originalPackageName)++ matches = filterForSpecificPackage+ (Map.findWithDefault [] originalName visibleModules)+ in+ case (matches, moriginalPackageName) of+ ((_, exposedModule):rest, _)+ -- TODO: Refine this check for signatures+ | all (\(_, exposedModule') ->+ Installed.exposedReexport exposedModule+ == Installed.exposedReexport exposedModule') rest+ -> Right exposedModule { Installed.exposedName = newName }++ ([], Just originalPackageName)+ -> Left $ (,) reexport+ $ "The package " ++ display originalPackageName+ ++ " does not export a module " ++ display originalName++ ([], Nothing)+ -> Left $ (,) reexport+ $ "The module " ++ display originalName+ ++ " is not exported by any suitable package (this package "+ ++ "itself nor any of its 'build-depends' dependencies)."++ (ms, _)+ -> Left $ (,) reexport+ $ "The module " ++ display originalName ++ " is exported "+ ++ "by more than one package ("+ ++ intercalate ", " [ display pkgname | (pkgname,_) <- ms ]+ ++ ") and so the re-export is ambiguous. The ambiguity can "+ ++ "be resolved by qualifying by the package name. The "+ ++ "syntax is 'packagename:moduleName [as newname]'."++ -- Note: if in future Cabal allows directly depending on multiple+ -- instances of the same package (e.g. backpack) then an additional+ -- ambiguity case is possible here: (_, Just originalPackageName)+ -- with the module being ambiguous despite being qualified by a+ -- package name. Presumably by that time we'll have a mechanism to+ -- qualify the instance we're referring to.++reportModuleReexportProblems :: [(ModuleReexport, String)] -> IO a+reportModuleReexportProblems reexportProblems =+ die $ unlines+ [ "Problem with the module re-export '" ++ display reexport ++ "': " ++ msg+ | (reexport, msg) <- reexportProblems ]++-- -----------------------------------------------------------------------------+-- Testing C lib and header dependencies++-- Try to build a test C program which includes every header and links every+-- lib. If that fails, try to narrow it down by preprocessing (only) and linking+-- with individual headers and libs. If none is the obvious culprit then give a+-- generic error message.+-- TODO: produce a log file from the compiler errors, if any.+checkForeignDeps :: PackageDescription -> LocalBuildInfo -> Verbosity -> IO ()+checkForeignDeps pkg lbi verbosity = do+ ifBuildsWith allHeaders (commonCcArgs ++ makeLdArgs allLibs) -- I'm feeling+ -- lucky+ (return ())+ (do missingLibs <- findMissingLibs+ missingHdr <- findOffendingHdr+ explainErrors missingHdr missingLibs)+ where+ allHeaders = collectField PD.includes+ allLibs = collectField PD.extraLibs++ ifBuildsWith headers args success failure = do+ ok <- builds (makeProgram headers) args+ if ok then success else failure++ findOffendingHdr =+ ifBuildsWith allHeaders ccArgs+ (return Nothing)+ (go . tail . inits $ allHeaders)+ where+ go [] = return Nothing -- cannot happen+ go (hdrs:hdrsInits) =+ -- Try just preprocessing first+ ifBuildsWith hdrs cppArgs+ -- If that works, try compiling too+ (ifBuildsWith hdrs ccArgs+ (go hdrsInits)+ (return . Just . Right . last $ hdrs))+ (return . Just . Left . last $ hdrs)++ cppArgs = "-E":commonCppArgs -- preprocess only+ ccArgs = "-c":commonCcArgs -- don't try to link++ findMissingLibs = ifBuildsWith [] (makeLdArgs allLibs)+ (return [])+ (filterM (fmap not . libExists) allLibs)++ libExists lib = builds (makeProgram []) (makeLdArgs [lib])++ commonCppArgs = platformDefines lbi+ -- TODO: This is a massive hack, to work around the+ -- fact that the test performed here should be+ -- PER-component (c.f. the "I'm Feeling Lucky"; we+ -- should NOT be glomming everything together.)+ ++ [ "-I" ++ buildDir lbi </> "autogen" ]+ ++ [ "-I" ++ dir | dir <- collectField PD.includeDirs ]+ ++ ["-I."]+ ++ collectField PD.cppOptions+ ++ collectField PD.ccOptions+ ++ [ "-I" ++ dir+ | dep <- deps+ , dir <- Installed.includeDirs dep ]+ ++ [ opt+ | dep <- deps+ , opt <- Installed.ccOptions dep ]++ commonCcArgs = commonCppArgs+ ++ collectField PD.ccOptions+ ++ [ opt+ | dep <- deps+ , opt <- Installed.ccOptions dep ]++ commonLdArgs = [ "-L" ++ dir | dir <- collectField PD.extraLibDirs ]+ ++ collectField PD.ldOptions+ ++ [ "-L" ++ dir+ | dep <- deps+ , dir <- Installed.libraryDirs dep ]+ --TODO: do we also need dependent packages' ld options?+ makeLdArgs libs = [ "-l"++lib | lib <- libs ] ++ commonLdArgs++ makeProgram hdrs = unlines $+ [ "#include \"" ++ hdr ++ "\"" | hdr <- hdrs ] +++ ["int main(int argc, char** argv) { return 0; }"]++ collectField f = concatMap f allBi+ allBi = allBuildInfo pkg+ deps = PackageIndex.topologicalOrder (installedPkgs lbi)++ builds program args = do+ tempDir <- getTemporaryDirectory+ withTempFile tempDir ".c" $ \cName cHnd ->+ withTempFile tempDir "" $ \oNname oHnd -> do+ hPutStrLn cHnd program+ hClose cHnd+ hClose oHnd+ _ <- rawSystemProgramStdoutConf verbosity+ gccProgram (withPrograms lbi) (cName:"-o":oNname:args)+ return True+ `catchIO` (\_ -> return False)+ `catchExit` (\_ -> return False)++ explainErrors Nothing [] = return () -- should be impossible!+ explainErrors _ _+ | isNothing . lookupProgram gccProgram . withPrograms $ lbi++ = die $ unlines $+ [ "No working gcc",+ "This package depends on foreign library but we cannot "+ ++ "find a working C compiler. If you have it in a "+ ++ "non-standard location you can use the --with-gcc "+ ++ "flag to specify it." ]++ explainErrors hdr libs = die $ unlines $+ [ if plural+ then "Missing dependencies on foreign libraries:"+ else "Missing dependency on a foreign library:"+ | missing ]+ ++ case hdr of+ Just (Left h) -> ["* Missing (or bad) header file: " ++ h ]+ _ -> []+ ++ case libs of+ [] -> []+ [lib] -> ["* Missing C library: " ++ lib]+ _ -> ["* Missing C libraries: " ++ intercalate ", " libs]+ ++ [if plural then messagePlural else messageSingular | missing]+ ++ case hdr of+ Just (Left _) -> [ headerCppMessage ]+ Just (Right h) -> [ (if missing then "* " else "")+ ++ "Bad header file: " ++ h+ , headerCcMessage ]+ _ -> []++ where+ plural = length libs >= 2+ -- Is there something missing? (as opposed to broken)+ missing = not (null libs)+ || case hdr of Just (Left _) -> True; _ -> False++ messageSingular =+ "This problem can usually be solved by installing the system "+ ++ "package that provides this library (you may need the "+ ++ "\"-dev\" version). If the library is already installed "+ ++ "but in a non-standard location then you can use the flags "+ ++ "--extra-include-dirs= and --extra-lib-dirs= to specify "+ ++ "where it is."+ messagePlural =+ "This problem can usually be solved by installing the system "+ ++ "packages that provide these libraries (you may need the "+ ++ "\"-dev\" versions). If the libraries are already installed "+ ++ "but in a non-standard location then you can use the flags "+ ++ "--extra-include-dirs= and --extra-lib-dirs= to specify "+ ++ "where they are."+ headerCppMessage =+ "If the header file does exist, it may contain errors that "+ ++ "are caught by the C compiler at the preprocessing stage. "+ ++ "In this case you can re-run configure with the verbosity "+ ++ "flag -v3 to see the error messages."+ headerCcMessage =+ "The header file contains a compile error. "+ ++ "You can re-run configure with the verbosity flag "+ ++ "-v3 to see the error messages from the C compiler."++-- | Output package check warnings and errors. Exit if any errors.+checkPackageProblems :: Verbosity+ -> GenericPackageDescription+ -> PackageDescription+ -> IO ()+checkPackageProblems verbosity gpkg pkg = do+ ioChecks <- checkPackageFiles pkg "."+ let pureChecks = checkPackage gpkg (Just pkg)+ errors = [ e | PackageBuildImpossible e <- pureChecks ++ ioChecks ]+ warnings = [ w | PackageBuildWarning w <- pureChecks ++ ioChecks ]+ if null errors+ then mapM_ (warn verbosity) warnings+ else die (intercalate "\n\n" errors)++-- | Preform checks if a relocatable build is allowed+checkRelocatable :: Verbosity+ -> PackageDescription+ -> LocalBuildInfo+ -> IO ()+checkRelocatable verbosity pkg lbi+ = sequence_ [ checkOS+ , checkCompiler+ , packagePrefixRelative+ , depsPrefixRelative+ ]+ where+ -- Check if the OS support relocatable builds.+ --+ -- If you add new OS' to this list, and your OS supports dynamic libraries+ -- and RPATH, make sure you add your OS to RPATH-support list of:+ -- Distribution.Simple.GHC.getRPaths+ checkOS+ = unless (os `elem` [ OSX, Linux ])+ $ die $ "Operating system: " ++ display os +++ ", does not support relocatable builds"+ where+ (Platform _ os) = hostPlatform lbi++ -- Check if the Compiler support relocatable builds+ checkCompiler+ = unless (compilerFlavor comp `elem` [ GHC ])+ $ die $ "Compiler: " ++ show comp +++ ", does not support relocatable builds"+ where+ comp = compiler lbi++ -- Check if all the install dirs are relative to same prefix+ packagePrefixRelative+ = unless (relativeInstallDirs installDirs)+ $ die $ "Installation directories are not prefix_relative:\n" +++ show installDirs+ where+ -- NB: should be good enough to check this against the default+ -- component ID, but if we wanted to be strictly correct we'd+ -- check for each ComponentId.+ installDirs = absoluteInstallDirs pkg lbi NoCopyDest+ p = prefix installDirs+ relativeInstallDirs (InstallDirs {..}) =+ all isJust+ (fmap (stripPrefix p)+ [ bindir, libdir, dynlibdir, libexecdir, includedir, datadir+ , docdir, mandir, htmldir, haddockdir, sysconfdir] )++ -- Check if the library dirs of the dependencies that are in the package+ -- database to which the package is installed are relative to the+ -- prefix of the package+ depsPrefixRelative = do+ pkgr <- GHC.pkgRoot verbosity lbi (last (withPackageDB lbi))+ mapM_ (doCheck pkgr) ipkgs+ where+ doCheck pkgr ipkg+ | maybe False (== pkgr) (Installed.pkgRoot ipkg)+ = mapM_ (\l -> when (isNothing $ stripPrefix p l) (die (msg l)))+ (Installed.libraryDirs ipkg)+ | otherwise+ = return ()+ -- NB: should be good enough to check this against the default+ -- component ID, but if we wanted to be strictly correct we'd+ -- check for each ComponentId. installDirs = absoluteInstallDirs pkg lbi NoCopyDest p = prefix installDirs ipkgs = PackageIndex.allPackages (installedPkgs lbi)
cabal/Cabal/Distribution/Simple/GHC.hs view
@@ -1,4 +1,5 @@-{-# LANGUAGE CPP #-}+{-# LANGUAGE PatternGuards #-}+ ----------------------------------------------------------------------------- -- | -- Module : Distribution.Simple.GHC@@ -33,7 +34,10 @@ module Distribution.Simple.GHC ( getGhcInfo,- configure, getInstalledPackages, getPackageDBContents,+ configure,+ getInstalledPackages,+ getInstalledPackagesMonitorFiles,+ getPackageDBContents, buildLib, buildExe, replLib, replExe, startInterpreter,@@ -49,74 +53,52 @@ pkgRoot ) where -import qualified Distribution.Simple.GHC.IPI641 as IPI641+import Control.Applicative -- 7.10 -Werror workaround+import Prelude -- https://ghc.haskell.org/trac/ghc/wiki/Migration/7.10#GHCsaysTheimportof...isredundant+ import qualified Distribution.Simple.GHC.IPI642 as IPI642 import qualified Distribution.Simple.GHC.Internal as Internal import Distribution.Simple.GHC.ImplInfo import Distribution.PackageDescription as PD- ( PackageDescription(..), BuildInfo(..), Executable(..), Library(..)- , allExtensions, libModules, exeModules- , hcOptions, hcSharedOptions, hcProfOptions )-import Distribution.InstalledPackageInfo- ( InstalledPackageInfo )+import Distribution.InstalledPackageInfo (InstalledPackageInfo) import qualified Distribution.InstalledPackageInfo as InstalledPackageInfo- ( InstalledPackageInfo(..) ) import Distribution.Simple.PackageIndex (InstalledPackageIndex) import qualified Distribution.Simple.PackageIndex as PackageIndex import Distribution.Simple.LocalBuildInfo- ( LocalBuildInfo(..), ComponentLocalBuildInfo(..)- , absoluteInstallDirs, depLibraryPaths ) import qualified Distribution.Simple.Hpc as Hpc-import Distribution.Simple.InstallDirs hiding ( absoluteInstallDirs ) import Distribution.Simple.BuildPaths import Distribution.Simple.Utils import Distribution.Package- ( PackageName(..) ) import qualified Distribution.ModuleName as ModuleName import Distribution.Simple.Program- ( Program(..), ConfiguredProgram(..), ProgramConfiguration- , ProgramSearchPath- , rawSystemProgramStdout, rawSystemProgramStdoutConf- , getProgramInvocationOutput, requireProgramVersion, requireProgram- , userMaybeSpecifyPath, programPath, lookupProgram, addKnownProgram- , ghcProgram, ghcPkgProgram, haddockProgram, hsc2hsProgram, ldProgram ) import qualified Distribution.Simple.Program.HcPkg as HcPkg import qualified Distribution.Simple.Program.Ar as Ar import qualified Distribution.Simple.Program.Ld as Ld import qualified Distribution.Simple.Program.Strip as Strip import Distribution.Simple.Program.GHC import Distribution.Simple.Setup- ( toFlag, fromFlag, fromFlagOrDefault, configCoverage, configDistPref ) import qualified Distribution.Simple.Setup as Cabal- ( Flag(..) )-import Distribution.Simple.Compiler- ( CompilerFlavor(..), CompilerId(..), Compiler(..), compilerVersion- , PackageDB(..), PackageDBStack, AbiTag(..) )+import Distribution.Simple.Compiler hiding (Flag) import Distribution.Version- ( Version(..), anyVersion, orLaterVersion ) import Distribution.System- ( Platform(..), OS(..) ) import Distribution.Verbosity import Distribution.Text- ( display ) import Distribution.Utils.NubList- ( NubListR, overNubListR, toNubListR )-import Language.Haskell.Extension (Extension(..), KnownExtension(..))+import Language.Haskell.Extension import Control.Monad ( unless, when ) import Data.Char ( isDigit, isSpace ) import Data.List-import qualified Data.Map as M ( fromList )+import qualified Data.Map as M ( fromList, lookup ) import Data.Maybe ( catMaybes )-#if __GLASGOW_HASKELL__ < 710-import Data.Monoid ( Monoid(..) )-#endif+import Data.Monoid as Mon ( Monoid(..) ) import Data.Version ( showVersion ) import System.Directory- ( doesFileExist, getAppUserDataDirectory, createDirectoryIfMissing )-import System.FilePath ( (</>), (<.>), takeExtension,- takeDirectory, replaceExtension,- splitExtension, isRelative )+ ( doesFileExist, getAppUserDataDirectory, createDirectoryIfMissing+ , canonicalizePath )+import System.FilePath ( (</>), (<.>), takeExtension+ , takeDirectory, replaceExtension+ , isRelative ) import qualified System.Info -- -----------------------------------------------------------------------------@@ -129,7 +111,7 @@ (ghcProg, ghcVersion, conf1) <- requireProgramVersion verbosity ghcProgram- (orLaterVersion (Version [6,4] []))+ (orLaterVersion (Version [6,11] [])) (userMaybeSpecifyPath "ghc" hcPath conf0) let implInfo = ghcVersionImplInfo ghcVersion @@ -154,15 +136,29 @@ haddockProgram' = haddockProgram { programFindLocation = guessHaddockFromGhcPath ghcProg }+ hpcProgram' = hpcProgram {+ programFindLocation = guessHpcFromGhcPath ghcProg+ } conf3 = addKnownProgram haddockProgram' $- addKnownProgram hsc2hsProgram' conf2+ addKnownProgram hsc2hsProgram' $+ addKnownProgram hpcProgram' conf2 languages <- Internal.getLanguages verbosity implInfo ghcProg- extensions <- Internal.getExtensions verbosity implInfo ghcProg+ extensions0 <- Internal.getExtensions verbosity implInfo ghcProg ghcInfo <- Internal.getGhcInfo verbosity implInfo ghcProg let ghcInfoMap = M.fromList ghcInfo + -- starting with GHC 8.0, `TemplateHaskell` will be omitted from+ -- `--supported-extensions` when it's not available.+ -- for older GHCs we can use the "Have interpreter" property to+ -- filter out `TemplateHaskell`+ extensions | ghcVersion < Version [8] []+ , Just "NO" <- M.lookup "Have interpreter" ghcInfoMap+ = filter ((/= EnableExtension TemplateHaskell) . fst)+ extensions0+ | otherwise = extensions0+ let comp = Compiler { compilerId = CompilerId GHC ghcVersion, compilerAbiTag = NoAbiTag,@@ -186,30 +182,42 @@ -- guessToolFromGhcPath :: Program -> ConfiguredProgram -> Verbosity -> ProgramSearchPath- -> IO (Maybe FilePath)+ -> IO (Maybe (FilePath, [FilePath])) guessToolFromGhcPath tool ghcProg verbosity searchpath = do let toolname = programName tool- path = programPath ghcProg- dir = takeDirectory path- versionSuffix = takeVersionSuffix (dropExeExtension path)- guessNormal = dir </> toolname <.> exeExtension- guessGhcVersioned = dir </> (toolname ++ "-ghc" ++ versionSuffix)- <.> exeExtension- guessVersioned = dir </> (toolname ++ versionSuffix)- <.> exeExtension- guesses | null versionSuffix = [guessNormal]- | otherwise = [guessGhcVersioned,- guessVersioned,- guessNormal]+ given_path = programPath ghcProg+ given_dir = takeDirectory given_path+ real_path <- canonicalizePath given_path+ let real_dir = takeDirectory real_path+ versionSuffix path = takeVersionSuffix (dropExeExtension path)+ given_suf = versionSuffix given_path+ real_suf = versionSuffix real_path+ guessNormal dir = dir </> toolname <.> exeExtension+ guessGhcVersioned dir suf = dir </> (toolname ++ "-ghc" ++ suf)+ <.> exeExtension+ guessVersioned dir suf = dir </> (toolname ++ suf)+ <.> exeExtension+ mkGuesses dir suf | null suf = [guessNormal dir]+ | otherwise = [guessGhcVersioned dir suf,+ guessVersioned dir suf,+ guessNormal dir]+ guesses = mkGuesses given_dir given_suf +++ if real_path == given_path+ then []+ else mkGuesses real_dir real_suf info verbosity $ "looking for tool " ++ toolname- ++ " near compiler in " ++ dir+ ++ " near compiler in " ++ given_dir+ debug verbosity $ "candidate locations: " ++ show guesses exists <- mapM doesFileExist guesses case [ file | (file, True) <- zip guesses exists ] of -- If we can't find it near ghc, fall back to the usual -- method. [] -> programFindLocation tool verbosity searchpath (fp:_) -> do info verbosity $ "found " ++ toolname ++ " in " ++ fp- return (Just fp)+ let lookedAt = map fst+ . takeWhile (\(_file, exist) -> not exist)+ $ zip guesses exists+ return (Just (fp, lookedAt)) where takeVersionSuffix :: FilePath -> String takeVersionSuffix = takeWhileEndLE isSuffixChar@@ -217,12 +225,6 @@ isSuffixChar :: Char -> Bool isSuffixChar c = isDigit c || c == '.' || c == '-' - dropExeExtension :: FilePath -> FilePath- dropExeExtension filepath =- case splitExtension filepath of- (filepath', extension) | extension == exeExtension -> filepath'- | otherwise -> filepath- -- | Given something like /usr/local/bin/ghc-6.6.1(.exe) we try and find a -- corresponding ghc-pkg, we try looking for both a versioned and unversioned -- ghc-pkg in the same dir, that is:@@ -232,7 +234,8 @@ -- > /usr/local/bin/ghc-pkg(.exe) -- guessGhcPkgFromGhcPath :: ConfiguredProgram- -> Verbosity -> ProgramSearchPath -> IO (Maybe FilePath)+ -> Verbosity -> ProgramSearchPath+ -> IO (Maybe (FilePath, [FilePath])) guessGhcPkgFromGhcPath = guessToolFromGhcPath ghcPkgProgram -- | Given something like /usr/local/bin/ghc-6.6.1(.exe) we try and find a@@ -244,7 +247,8 @@ -- > /usr/local/bin/hsc2hs(.exe) -- guessHsc2hsFromGhcPath :: ConfiguredProgram- -> Verbosity -> ProgramSearchPath -> IO (Maybe FilePath)+ -> Verbosity -> ProgramSearchPath+ -> IO (Maybe (FilePath, [FilePath])) guessHsc2hsFromGhcPath = guessToolFromGhcPath hsc2hsProgram -- | Given something like /usr/local/bin/ghc-6.6.1(.exe) we try and find a@@ -256,9 +260,16 @@ -- > /usr/local/bin/haddock(.exe) -- guessHaddockFromGhcPath :: ConfiguredProgram- -> Verbosity -> ProgramSearchPath -> IO (Maybe FilePath)+ -> Verbosity -> ProgramSearchPath+ -> IO (Maybe (FilePath, [FilePath])) guessHaddockFromGhcPath = guessToolFromGhcPath haddockProgram +guessHpcFromGhcPath :: ConfiguredProgram+ -> Verbosity -> ProgramSearchPath+ -> IO (Maybe (FilePath, [FilePath]))+guessHpcFromGhcPath = guessToolFromGhcPath hpcProgram++ getGhcInfo :: Verbosity -> ConfiguredProgram -> IO [(String, String)] getGhcInfo verbosity ghcProg = Internal.getGhcInfo verbosity implInfo ghcProg where@@ -273,7 +284,8 @@ toPackageIndex verbosity pkgss conf -- | Given a package DB stack, return all installed packages.-getInstalledPackages :: Verbosity -> Compiler -> PackageDBStack -> ProgramConfiguration+getInstalledPackages :: Verbosity -> Compiler -> PackageDBStack+ -> ProgramConfiguration -> IO InstalledPackageIndex getInstalledPackages verbosity comp packagedbs conf = do checkPackageDbEnvVar@@ -304,7 +316,7 @@ topDir <- getLibDir' verbosity ghcProg let indices = [ PackageIndex.fromList (map (Internal.substTopDir topDir) pkgs) | (_, pkgs) <- pkgss ]- return $! (mconcat indices)+ return $! mconcat indices where Just ghcProg = lookupProgram ghcProgram conf@@ -327,6 +339,23 @@ dropWhileEndLE isSpace `fmap` rawSystemProgramStdout verbosity ghcProg ["--print-global-package-db"] +-- | Return the 'FilePath' to the per-user GHC package database.+getUserPackageDB :: Verbosity -> ConfiguredProgram -> Platform -> IO FilePath+getUserPackageDB _verbosity ghcProg (Platform arch os) = do+ -- It's rather annoying that we have to reconstruct this, because ghc+ -- hides this information from us otherwise. But for certain use cases+ -- like change monitoring it really can't remain hidden.+ appdir <- getAppUserDataDirectory "ghc"+ return (appdir </> platformAndVersion </> packageConfFileName)+ where+ platformAndVersion = intercalate "-" [ Internal.showArchString arch+ , Internal.showOsString os+ , display ghcVersion ]+ packageConfFileName+ | ghcVersion >= Version [6,12] [] = "package.conf.d"+ | otherwise = "package.conf"+ Just ghcVersion = programVersion ghcProg+ checkPackageDbEnvVar :: IO () checkPackageDbEnvVar = Internal.checkPackageDbEnvVar "GHC" "GHC_PACKAGE_PATH"@@ -405,14 +434,41 @@ = \file content -> case reads content of [(pkgs, _)] -> return (map IPI642.toCurrent pkgs) _ -> failToRead file+ -- We dropped support for 6.4.2 and earlier. | otherwise- = \file content -> case reads content of- [(pkgs, _)] -> return (map IPI641.toCurrent pkgs)- _ -> failToRead file+ = \file _ -> failToRead file Just ghcProg = lookupProgram ghcProgram conf Just ghcVersion = programVersion ghcProg failToRead file = die $ "cannot read ghc package database " ++ file +getInstalledPackagesMonitorFiles :: Verbosity -> Platform+ -> ProgramConfiguration+ -> [PackageDB]+ -> IO [FilePath]+getInstalledPackagesMonitorFiles verbosity platform progdb =+ mapM getPackageDBPath+ where+ getPackageDBPath :: PackageDB -> IO FilePath+ getPackageDBPath GlobalPackageDB =+ selectMonitorFile =<< getGlobalPackageDB verbosity ghcProg++ getPackageDBPath UserPackageDB =+ selectMonitorFile =<< getUserPackageDB verbosity ghcProg platform++ getPackageDBPath (SpecificPackageDB path) = selectMonitorFile path++ -- GHC has old style file dbs, and new style directory dbs.+ -- Note that for dir style dbs, we only need to monitor the cache file, not+ -- the whole directory. The ghc program itself only reads the cache file+ -- so it's safe to only monitor this one file.+ selectMonitorFile path = do+ isFileStyle <- doesFileExist path+ if isFileStyle then return path+ else return (path </> "package.cache")++ Just ghcProg = lookupProgram ghcProgram progdb++ -- ----------------------------------------------------------------------------- -- Building @@ -428,8 +484,8 @@ -> PackageDescription -> LocalBuildInfo -> Library -> ComponentLocalBuildInfo -> IO () buildOrReplLib forRepl verbosity numJobs pkg_descr lbi lib clbi = do- let libName = componentLibraryName clbi- libTargetDir = buildDir lbi+ let uid = componentUnitId clbi+ libTargetDir = componentBuildDir lbi clbi whenVanillaLib forceVanilla = when (forceVanilla || withVanillaLib lbi) whenProfLib = when (withProfLib lbi)@@ -440,10 +496,10 @@ comp = compiler lbi ghcVersion = compilerVersion comp implInfo = getImplInfo comp- (Platform _hostArch hostOS) = hostPlatform lbi+ platform@(Platform _hostArch hostOS) = hostPlatform lbi (ghcProg, _) <- requireProgram verbosity ghcProgram (withPrograms lbi)- let runGhcProg = runGHC verbosity ghcProg comp+ let runGhcProg = runGHC verbosity ghcProg comp platform libBi <- hackThreadedFlag verbosity comp (withProfLib lbi) (libBuildInfo lib)@@ -458,13 +514,15 @@ -- Determine if program coverage should be enabled and if so, what -- '-hpcdir' should be. let isCoverageEnabled = fromFlag $ configCoverage $ configFlags lbi- -- Component name. Not 'libName' because that has the "HS" prefix- -- that GHC gives Haskell libraries.- cname = display $ PD.package $ localPkgDescr lbi+ -- TODO: Historically HPC files have been put into a directory which+ -- has the package name. I'm going to avoid changing this for+ -- now, but it would probably be better for this to be the+ -- component ID instead...+ pkg_name = display $ PD.package $ localPkgDescr lbi distPref = fromFlag $ configDistPref $ configFlags lbi hpcdir way- | forRepl = mempty -- HPC is not supported in ghci- | isCoverageEnabled = toFlag $ Hpc.mixDir distPref way cname+ | forRepl = Mon.mempty -- HPC is not supported in ghci+ | isCoverageEnabled = toFlag $ Hpc.mixDir distPref way pkg_name | otherwise = mempty createDirectoryIfMissingVerbose verbosity True libTargetDir@@ -498,17 +556,20 @@ ghcOptHPCDir = hpcdir Hpc.Dyn } linkerOpts = mempty {- ghcOptLinkOptions = toNubListR $ PD.ldOptions libBi,- ghcOptLinkLibs = toNubListR $ extraLibs libBi,- ghcOptLinkLibPath = toNubListR $ extraLibDirs libBi,- ghcOptLinkFrameworks = toNubListR $ PD.frameworks libBi,+ ghcOptLinkOptions = toNubListR $ PD.ldOptions libBi,+ ghcOptLinkLibs = toNubListR $ extraLibs libBi,+ ghcOptLinkLibPath = toNubListR $ extraLibDirs libBi,+ ghcOptLinkFrameworks = toNubListR $+ PD.frameworks libBi,+ ghcOptLinkFrameworkDirs = toNubListR $+ PD.extraFrameworkDirs libBi, ghcOptInputFiles = toNubListR [libTargetDir </> x | x <- cObjs] } replOpts = vanillaOpts { ghcOptExtra = overNubListR Internal.filterGhciFlags $- (ghcOptExtra vanillaOpts),+ ghcOptExtra vanillaOpts, ghcOptNumJobs = mempty } `mappend` linkerOpts@@ -535,7 +596,7 @@ then do runGhcProg vanillaSharedOpts case (hpcdir Hpc.Dyn, hpcdir Hpc.Vanilla) of- (Cabal.Flag dynDir, Cabal.Flag vanillaDir) -> do+ (Cabal.Flag dynDir, Cabal.Flag vanillaDir) -> -- When the vanilla and shared library builds are done -- in one pass, only one set of HPC module interfaces -- are generated. This set should suffice for both@@ -571,12 +632,13 @@ } odir = fromFlag (ghcOptObjDir vanillaCcOpts) createDirectoryIfMissingVerbose verbosity True odir- needsRecomp <- checkNeedsRecompilation filename vanillaCcOpts- when needsRecomp $ do- runGhcProg vanillaCcOpts- unless forRepl $- whenSharedLib forceSharedLib (runGhcProg sharedCcOpts)- unless forRepl $ whenProfLib (runGhcProg profCcOpts)+ let runGhcProgIfNeeded ccOpts = do+ needsRecomp <- checkNeedsRecompilation filename ccOpts+ when needsRecomp $ runGhcProg ccOpts+ runGhcProgIfNeeded vanillaCcOpts+ unless forRepl $+ whenSharedLib forceSharedLib (runGhcProgIfNeeded sharedCcOpts)+ unless forRepl $ whenProfLib (runGhcProgIfNeeded profCcOpts) | filename <- cSources libBi] -- TODO: problem here is we need the .c files built first, so we can load them@@ -594,25 +656,25 @@ (cSources libBi) cSharedObjs = map (`replaceExtension` ("dyn_" ++ objExtension)) (cSources libBi)- cid = compilerId (compiler lbi)- vanillaLibFilePath = libTargetDir </> mkLibName libName- profileLibFilePath = libTargetDir </> mkProfLibName libName- sharedLibFilePath = libTargetDir </> mkSharedLibName cid libName- ghciLibFilePath = libTargetDir </> Internal.mkGHCiLibName libName- libInstallPath = libdir $ absoluteInstallDirs pkg_descr lbi NoCopyDest- sharedLibInstallPath = libInstallPath </> mkSharedLibName cid libName+ compiler_id = compilerId (compiler lbi)+ vanillaLibFilePath = libTargetDir </> mkLibName uid+ profileLibFilePath = libTargetDir </> mkProfLibName uid+ sharedLibFilePath = libTargetDir </> mkSharedLibName compiler_id uid+ ghciLibFilePath = libTargetDir </> Internal.mkGHCiLibName uid+ libInstallPath = libdir $ absoluteComponentInstallDirs pkg_descr lbi uid NoCopyDest+ sharedLibInstallPath = libInstallPath </> mkSharedLibName compiler_id uid - stubObjs <- fmap catMaybes $ sequence+ stubObjs <- catMaybes <$> sequence [ findFileWithExtension [objExtension] [libTargetDir] (ModuleName.toFilePath x ++"_stub") | ghcVersion < Version [7,2] [] -- ghc-7.2+ does not make _stub.o files , x <- libModules lib ]- stubProfObjs <- fmap catMaybes $ sequence+ stubProfObjs <- catMaybes <$> sequence [ findFileWithExtension ["p_" ++ objExtension] [libTargetDir] (ModuleName.toFilePath x ++"_stub") | ghcVersion < Version [7,2] [] -- ghc-7.2+ does not make _stub.o files , x <- libModules lib ]- stubSharedObjs <- fmap catMaybes $ sequence+ stubSharedObjs <- catMaybes <$> sequence [ findFileWithExtension ["dyn_" ++ objExtension] [libTargetDir] (ModuleName.toFilePath x ++"_stub") | ghcVersion < Version [7,2] [] -- ghc-7.2+ does not make _stub.o files@@ -621,12 +683,12 @@ hObjs <- Internal.getHaskellObjects implInfo lib lbi libTargetDir objExtension True hProfObjs <-- if (withProfLib lbi)+ if withProfLib lbi then Internal.getHaskellObjects implInfo lib lbi libTargetDir ("p_" ++ objExtension) True else return [] hSharedObjs <-- if (withSharedLib lbi)+ if withSharedLib lbi then Internal.getHaskellObjects implInfo lib lbi libTargetDir ("dyn_" ++ objExtension) False else return []@@ -659,11 +721,13 @@ ghcOptDynLinkMode = toFlag GhcDynamicOnly, ghcOptInputFiles = toNubListR dynamicObjectFiles, ghcOptOutputFile = toFlag sharedLibFilePath,+ ghcOptExtra = toNubListR $+ hcSharedOptions GHC libBi, -- For dynamic libs, Mac OS/X needs to know the install location -- at build time. This only applies to GHC < 7.8 - see the -- discussion in #1660.- ghcOptDylibName = if (hostOS == OSX- && ghcVersion < Version [7,8] [])+ ghcOptDylibName = if hostOS == OSX+ && ghcVersion < Version [7,8] [] then toFlag sharedLibInstallPath else mempty, ghcOptNoAutoLinkPackages = toFlag True,@@ -673,15 +737,17 @@ ghcOptLinkLibs = toNubListR $ extraLibs libBi, ghcOptLinkLibPath = toNubListR $ extraLibDirs libBi, ghcOptLinkFrameworks = toNubListR $ PD.frameworks libBi,+ ghcOptLinkFrameworkDirs =+ toNubListR $ PD.extraFrameworkDirs libBi, ghcOptRPaths = rpaths } info verbosity (show (ghcOptPackages ghcSharedLinkArgs)) - whenVanillaLib False $ do+ whenVanillaLib False $ Ar.createArLibArchive verbosity lbi vanillaLibFilePath staticObjectFiles - whenProfLib $ do+ whenProfLib $ Ar.createArLibArchive verbosity lbi profileLibFilePath profObjectFiles whenGHCiLib $ do@@ -693,16 +759,16 @@ runGhcProg ghcSharedLinkArgs -- | Start a REPL without loading any source files.-startInterpreter :: Verbosity -> ProgramConfiguration -> Compiler+startInterpreter :: Verbosity -> ProgramConfiguration -> Compiler -> Platform -> PackageDBStack -> IO ()-startInterpreter verbosity conf comp packageDBs = do+startInterpreter verbosity conf comp platform packageDBs = do let replOpts = mempty { ghcOptMode = toFlag GhcModeInteractive, ghcOptPackageDBs = packageDBs } checkPackageDbStack comp packageDBs (ghcProg, _) <- requireProgram verbosity ghcProgram conf- runGHC verbosity ghcProg comp replOpts+ runGHC verbosity ghcProg comp platform replOpts -- | Build an executable with GHC. --@@ -720,8 +786,9 @@ (ghcProg, _) <- requireProgram verbosity ghcProgram (withPrograms lbi) let comp = compiler lbi+ platform = hostPlatform lbi implInfo = getImplInfo comp- runGhcProg = runGHC verbosity ghcProg comp+ runGhcProg = runGHC verbosity ghcProg comp platform exeBi <- hackThreadedFlag verbosity comp (withProfExe lbi) (buildInfo exe)@@ -732,8 +799,8 @@ then exeExtension else "") - let targetDir = (buildDir lbi) </> exeName'- let exeDir = targetDir </> (exeName' ++ "-tmp")+ let targetDir = componentBuildDir lbi clbi+ exeDir = targetDir </> (exeName' ++ "-tmp") createDirectoryIfMissingVerbose verbosity True targetDir createDirectoryIfMissingVerbose verbosity True exeDir -- TODO: do we need to put hs-boot files into place for mutually recursive@@ -773,10 +840,11 @@ profOpts = baseOpts `mappend` mempty { ghcOptProfilingMode = toFlag True, ghcOptProfilingAuto = Internal.profDetailLevelFlag False- (withProfExeDetail lbi),+ (withProfExeDetail lbi), ghcOptHiSuffix = toFlag "p_hi", ghcOptObjSuffix = toFlag "p_o",- ghcOptExtra = toNubListR (hcProfOptions GHC exeBi),+ ghcOptExtra = toNubListR+ (hcProfOptions GHC exeBi), ghcOptHPCDir = hpcdir Hpc.Prof } dynOpts = baseOpts `mappend` mempty {@@ -794,10 +862,13 @@ ghcOptHPCDir = hpcdir Hpc.Dyn } linkerOpts = mempty {- ghcOptLinkOptions = toNubListR $ PD.ldOptions exeBi,- ghcOptLinkLibs = toNubListR $ extraLibs exeBi,- ghcOptLinkLibPath = toNubListR $ extraLibDirs exeBi,- ghcOptLinkFrameworks = toNubListR $ PD.frameworks exeBi,+ ghcOptLinkOptions = toNubListR $ PD.ldOptions exeBi,+ ghcOptLinkLibs = toNubListR $ extraLibs exeBi,+ ghcOptLinkLibPath = toNubListR $ extraLibDirs exeBi,+ ghcOptLinkFrameworks = toNubListR $+ PD.frameworks exeBi,+ ghcOptLinkFrameworkDirs = toNubListR $+ PD.extraFrameworkDirs exeBi, ghcOptInputFiles = toNubListR [exeDir </> x | x <- cObjs] }@@ -846,10 +917,11 @@ | isGhcDynamic = doingTH && (withProfExe lbi || withStaticExe) | otherwise = doingTH && (withProfExe lbi || withDynExe lbi) - linkOpts = commonOpts `mappend`- linkerOpts `mappend`- mempty { ghcOptLinkNoHsMain = toFlag (not isHaskellMain) } `mappend`- (if withDynExe lbi then dynLinkerOpts else mempty)+ linkOpts =+ commonOpts `mappend`+ linkerOpts `mappend`+ mempty { ghcOptLinkNoHsMain = toFlag (not isHaskellMain) } `mappend`+ (if withDynExe lbi then dynLinkerOpts else mempty) -- Build static/dynamic object files for TH, if needed. when compileForTH $@@ -874,7 +946,7 @@ odir = fromFlag (ghcOptObjDir opts) createDirectoryIfMissingVerbose verbosity True odir needsRecomp <- checkNeedsRecompilation filename opts- when needsRecomp $ + when needsRecomp $ runGhcProg opts | filename <- cSrcs ] @@ -974,8 +1046,9 @@ (compiler lbi) (withProfLib lbi) (libBuildInfo lib) let comp = compiler lbi+ platform = hostPlatform lbi vanillaArgs =- (componentGhcOptions verbosity lbi libBi clbi (buildDir lbi))+ (componentGhcOptions verbosity lbi libBi clbi (componentBuildDir lbi clbi)) `mappend` mempty { ghcOptMode = toFlag GhcModeAbiHash, ghcOptInputModules = toNubListR $ exposedModules lib@@ -995,14 +1068,15 @@ ghcOptObjSuffix = toFlag "p_o", ghcOptExtra = toNubListR $ hcProfOptions GHC libBi }- ghcArgs = if withVanillaLib lbi then vanillaArgs- else if withSharedLib lbi then sharedArgs- else if withProfLib lbi then profArgs- else error "libAbiHash: Can't find an enabled library way"- --+ ghcArgs+ | withVanillaLib lbi = vanillaArgs+ | withSharedLib lbi = sharedArgs+ | withProfLib lbi = profArgs+ | otherwise = error "libAbiHash: Can't find an enabled library way"+ (ghcProg, _) <- requireProgram verbosity ghcProgram (withPrograms lbi) hash <- getProgramInvocationOutput verbosity- (ghcInvocation ghcProg comp ghcArgs)+ (ghcInvocation ghcProg comp platform ghcArgs) return (takeWhile (not . isSpace) hash) componentGhcOptions :: Verbosity -> LocalBuildInfo@@ -1057,19 +1131,24 @@ -> Library -> ComponentLocalBuildInfo -> IO ()-installLib verbosity lbi targetDir dynlibTargetDir builtDir _pkg lib clbi = do+installLib verbosity lbi targetDir dynlibTargetDir _builtDir pkg lib clbi = do -- copy .hi files over:- whenVanilla $ copyModuleFiles "hi"- whenProf $ copyModuleFiles "p_hi"- whenShared $ copyModuleFiles "dyn_hi"+ whenRegistered $ do+ whenVanilla $ copyModuleFiles "hi"+ whenProf $ copyModuleFiles "p_hi"+ whenShared $ copyModuleFiles "dyn_hi" -- copy the built library files over:- whenVanilla $ installOrdinary builtDir targetDir vanillaLibName- whenProf $ installOrdinary builtDir targetDir profileLibName- whenGHCi $ installOrdinary builtDir targetDir ghciLibName- whenShared $ installShared builtDir dynlibTargetDir sharedLibName+ whenRegistered $ do+ whenVanilla $ installOrdinary builtDir targetDir vanillaLibName+ whenProf $ installOrdinary builtDir targetDir profileLibName+ whenGHCi $ installOrdinary builtDir targetDir ghciLibName+ whenRegisteredOrDynExecutable $ do+ whenShared $ installShared builtDir dynlibTargetDir sharedLibName where+ builtDir = componentBuildDir lbi clbi+ install isShared srcDir dstDir name = do let src = srcDir </> name dst = dstDir </> name@@ -1089,12 +1168,12 @@ findModuleFiles [builtDir] [ext] (libModules lib) >>= installOrdinaryFiles verbosity targetDir - cid = compilerId (compiler lbi)- libName = componentLibraryName clbi- vanillaLibName = mkLibName libName- profileLibName = mkProfLibName libName- ghciLibName = Internal.mkGHCiLibName libName- sharedLibName = (mkSharedLibName cid) libName+ compiler_id = compilerId (compiler lbi)+ uid = componentUnitId clbi+ vanillaLibName = mkLibName uid+ profileLibName = mkProfLibName uid+ ghciLibName = Internal.mkGHCiLibName uid+ sharedLibName = (mkSharedLibName compiler_id) uid hasLib = not $ null (libModules lib) && null (cSources (libBuildInfo lib))@@ -1103,6 +1182,17 @@ whenGHCi = when (hasLib && withGHCiLib lbi) whenShared = when (hasLib && withSharedLib lbi) + -- Some files (e.g. interface files) are completely unnecessary when+ -- we are not actually going to register the library. A library is+ -- not registered if there is no "public library", e.g. in the case+ -- that we have an internal library and executables, but no public+ -- library.+ whenRegistered = when (hasPublicLib pkg)++ -- However, we must always install dynamic libraries when linking+ -- dynamic executables, because we'll try to load them!+ whenRegisteredOrDynExecutable = when (hasPublicLib pkg || (hasExes pkg && withDynExe lbi))+ -- ----------------------------------------------------------------------------- -- Registering @@ -1111,7 +1201,10 @@ , HcPkg.noPkgDbStack = v < [6,9] , HcPkg.noVerboseFlag = v < [6,11] , HcPkg.flagPackageConf = v < [7,5]- , HcPkg.useSingleFileDb = v < [7,9]+ , HcPkg.supportsDirDbs = v >= [6,8]+ , HcPkg.requiresDirDbs = v >= [7,10]+ , HcPkg.nativeMultiInstance = v >= [7,10]+ , HcPkg.recacheMultiInstance = v >= [6,12] } where v = versionBranch ver@@ -1120,15 +1213,19 @@ registerPackage :: Verbosity- -> InstalledPackageInfo- -> PackageDescription- -> LocalBuildInfo- -> Bool+ -> ProgramConfiguration+ -> HcPkg.MultiInstance -> PackageDBStack+ -> InstalledPackageInfo -> IO ()-registerPackage verbosity installedPkgInfo _pkg lbi _inplace packageDbs =- HcPkg.reregister (hcPkgInfo $ withPrograms lbi) verbosity- packageDbs (Right installedPkgInfo)+registerPackage verbosity progdb multiInstance packageDbs installedPkgInfo+ | HcPkg.MultiInstance <- multiInstance+ = HcPkg.registerMultiInstance (hcPkgInfo progdb) verbosity+ packageDbs installedPkgInfo++ | otherwise+ = HcPkg.reregister (hcPkgInfo progdb) verbosity+ packageDbs (Right installedPkgInfo) pkgRoot :: Verbosity -> LocalBuildInfo -> PackageDB -> IO FilePath pkgRoot verbosity lbi = pkgRoot'
− cabal/Cabal/Distribution/Simple/GHC/IPI641.hs
@@ -1,106 +0,0 @@--------------------------------------------------------------------------------- |--- Module : Distribution.Simple.GHC.IPI641--- Copyright : (c) The University of Glasgow 2004--- License : BSD3------ Maintainer : cabal-devel@haskell.org--- Portability : portable-----module Distribution.Simple.GHC.IPI641 (- InstalledPackageInfo(..),- toCurrent,- ) where--import qualified Distribution.InstalledPackageInfo as Current-import qualified Distribution.Package as Current hiding (installedPackageId)-import Distribution.Text (display)--import Distribution.Simple.GHC.IPI642- ( PackageIdentifier, convertPackageId- , License, convertLicense, convertModuleName )---- | This is the InstalledPackageInfo type used by ghc-6.4 and 6.4.1.------ It's here purely for the 'Read' instance so that we can read the package--- database used by those ghc versions. It is a little hacky to read the--- package db directly, but we do need the info and until ghc-6.9 there was--- no better method.------ In ghc-6.4.2 the format changed a bit. See "Distribution.Simple.GHC.IPI642"----data InstalledPackageInfo = InstalledPackageInfo {- package :: PackageIdentifier,- license :: License,- copyright :: String,- maintainer :: String,- author :: String,- stability :: String,- homepage :: String,- pkgUrl :: String,- description :: String,- category :: String,- exposed :: Bool,- exposedModules :: [String],- hiddenModules :: [String],- importDirs :: [FilePath],- libraryDirs :: [FilePath],- hsLibraries :: [String],- extraLibraries :: [String],- includeDirs :: [FilePath],- includes :: [String],- depends :: [PackageIdentifier],- hugsOptions :: [String],- ccOptions :: [String],- ldOptions :: [String],- frameworkDirs :: [FilePath],- frameworks :: [String],- haddockInterfaces :: [FilePath],- haddockHTMLs :: [FilePath]- }- deriving Read--mkInstalledPackageId :: Current.PackageIdentifier -> Current.InstalledPackageId-mkInstalledPackageId = Current.InstalledPackageId . display--toCurrent :: InstalledPackageInfo -> Current.InstalledPackageInfo-toCurrent ipi@InstalledPackageInfo{} =- let pid = convertPackageId (package ipi)- mkExposedModule m = Current.ExposedModule m Nothing Nothing- in Current.InstalledPackageInfo {- Current.installedPackageId = mkInstalledPackageId (convertPackageId (package ipi)),- Current.sourcePackageId = pid,- Current.packageKey = Current.OldPackageKey pid,- Current.license = convertLicense (license ipi),- Current.copyright = copyright ipi,- Current.maintainer = maintainer ipi,- Current.author = author ipi,- Current.stability = stability ipi,- Current.homepage = homepage ipi,- Current.pkgUrl = pkgUrl ipi,- Current.synopsis = "",- Current.description = description ipi,- Current.category = category ipi,- Current.exposed = exposed ipi,- Current.exposedModules = map (mkExposedModule . convertModuleName) (exposedModules ipi),- Current.instantiatedWith = [],- Current.hiddenModules = map convertModuleName (hiddenModules ipi),- Current.trusted = Current.trusted Current.emptyInstalledPackageInfo,- Current.importDirs = importDirs ipi,- Current.libraryDirs = libraryDirs ipi,- Current.dataDir = "",- Current.hsLibraries = hsLibraries ipi,- Current.extraLibraries = extraLibraries ipi,- Current.extraGHCiLibraries = [],- Current.includeDirs = includeDirs ipi,- Current.includes = includes ipi,- Current.depends = map (mkInstalledPackageId.convertPackageId) (depends ipi),- Current.ccOptions = ccOptions ipi,- Current.ldOptions = ldOptions ipi,- Current.frameworkDirs = frameworkDirs ipi,- Current.frameworks = frameworks ipi,- Current.haddockInterfaces = haddockInterfaces ipi,- Current.haddockHTMLs = haddockHTMLs ipi,- Current.pkgRoot = Nothing- }
cabal/Cabal/Distribution/Simple/GHC/IPI642.hs view
@@ -11,22 +11,11 @@ module Distribution.Simple.GHC.IPI642 ( InstalledPackageInfo(..), toCurrent,-- -- Don't use these, they're only for conversion purposes- PackageIdentifier, convertPackageId,- License, convertLicense,- convertModuleName ) where import qualified Distribution.InstalledPackageInfo as Current-import qualified Distribution.Package as Current hiding (installedPackageId)-import qualified Distribution.License as Current--import Distribution.Version (Version)-import Distribution.ModuleName (ModuleName)-import Distribution.Text (simpleParse,display)--import Data.Maybe+import qualified Distribution.Package as Current hiding (installedUnitId)+import Distribution.Simple.GHC.IPIConvert -- | This is the InstalledPackageInfo type used by ghc-6.4.2 and later. --@@ -70,43 +59,15 @@ } deriving Read -data PackageIdentifier = PackageIdentifier {- pkgName :: String,- pkgVersion :: Version- }- deriving Read--data License = GPL | LGPL | BSD3 | BSD4- | PublicDomain | AllRightsReserved | OtherLicense- deriving Read--convertPackageId :: PackageIdentifier -> Current.PackageIdentifier-convertPackageId PackageIdentifier { pkgName = n, pkgVersion = v } =- Current.PackageIdentifier (Current.PackageName n) v--mkInstalledPackageId :: Current.PackageIdentifier -> Current.InstalledPackageId-mkInstalledPackageId = Current.InstalledPackageId . display--convertModuleName :: String -> ModuleName-convertModuleName s = fromJust $ simpleParse s--convertLicense :: License -> Current.License-convertLicense GPL = Current.GPL Nothing-convertLicense LGPL = Current.LGPL Nothing-convertLicense BSD3 = Current.BSD3-convertLicense BSD4 = Current.BSD4-convertLicense PublicDomain = Current.PublicDomain-convertLicense AllRightsReserved = Current.AllRightsReserved-convertLicense OtherLicense = Current.OtherLicense- toCurrent :: InstalledPackageInfo -> Current.InstalledPackageInfo toCurrent ipi@InstalledPackageInfo{} = let pid = convertPackageId (package ipi)- mkExposedModule m = Current.ExposedModule m Nothing Nothing+ mkExposedModule m = Current.ExposedModule m Nothing in Current.InstalledPackageInfo {- Current.installedPackageId = mkInstalledPackageId (convertPackageId (package ipi)), Current.sourcePackageId = pid,- Current.packageKey = Current.OldPackageKey pid,+ Current.installedUnitId = Current.mkLegacyUnitId pid,+ Current.compatPackageKey = "",+ Current.abiHash = Current.AbiHash "", -- bogus but old GHCs don't care. Current.license = convertLicense (license ipi), Current.copyright = copyright ipi, Current.maintainer = maintainer ipi,@@ -120,7 +81,6 @@ Current.exposed = exposed ipi, Current.exposedModules = map (mkExposedModule . convertModuleName) (exposedModules ipi), Current.hiddenModules = map convertModuleName (hiddenModules ipi),- Current.instantiatedWith = [], Current.trusted = Current.trusted Current.emptyInstalledPackageInfo, Current.importDirs = importDirs ipi, Current.libraryDirs = libraryDirs ipi,@@ -130,7 +90,7 @@ Current.extraGHCiLibraries = extraGHCiLibraries ipi, Current.includeDirs = includeDirs ipi, Current.includes = includes ipi,- Current.depends = map (mkInstalledPackageId.convertPackageId) (depends ipi),+ Current.depends = map (Current.mkLegacyUnitId . convertPackageId) (depends ipi), Current.ccOptions = ccOptions ipi, Current.ldOptions = ldOptions ipi, Current.frameworkDirs = frameworkDirs ipi,
+ cabal/Cabal/Distribution/Simple/GHC/IPIConvert.hs view
@@ -0,0 +1,50 @@+-----------------------------------------------------------------------------+-- |+-- Module : Distribution.Simple.GHC.IPI642+-- Copyright : (c) The University of Glasgow 2004+-- License : BSD3+--+-- Maintainer : cabal-devel@haskell.org+-- Portability : portable+--+-- Helper functions for 'Distribution.Simple.GHC.IPI642'.+module Distribution.Simple.GHC.IPIConvert (+ PackageIdentifier, convertPackageId,+ License, convertLicense,+ convertModuleName+ ) where++import qualified Distribution.Package as Current hiding (installedUnitId)+import qualified Distribution.License as Current++import Distribution.Version+import Distribution.ModuleName+import Distribution.Text++import Data.Maybe++data PackageIdentifier = PackageIdentifier {+ pkgName :: String,+ pkgVersion :: Version+ }+ deriving Read++convertPackageId :: PackageIdentifier -> Current.PackageIdentifier+convertPackageId PackageIdentifier { pkgName = n, pkgVersion = v } =+ Current.PackageIdentifier (Current.PackageName n) v++data License = GPL | LGPL | BSD3 | BSD4+ | PublicDomain | AllRightsReserved | OtherLicense+ deriving Read++convertModuleName :: String -> ModuleName+convertModuleName s = fromJust $ simpleParse s++convertLicense :: License -> Current.License+convertLicense GPL = Current.GPL Nothing+convertLicense LGPL = Current.LGPL Nothing+convertLicense BSD3 = Current.BSD3+convertLicense BSD4 = Current.BSD4+convertLicense PublicDomain = Current.PublicDomain+convertLicense AllRightsReserved = Current.AllRightsReserved+convertLicense OtherLicense = Current.OtherLicense
cabal/Cabal/Distribution/Simple/GHC/ImplInfo.hs view
@@ -15,9 +15,7 @@ ) where import Distribution.Simple.Compiler- ( Compiler(..), CompilerFlavor(..)- , compilerFlavor, compilerVersion, compilerCompatVersion )-import Distribution.Version ( Version(..) )+import Distribution.Version {- | Information about features and quirks of a GHC-based implementation.@@ -33,17 +31,7 @@ -} data GhcImplInfo = GhcImplInfo- { hasCcOdirBug :: Bool -- ^ bug in -odir handling for C compilations.- , flagInfoLanguages :: Bool -- ^ --info and --supported-languages flags- , fakeRecordPuns :: Bool -- ^ use -XRecordPuns for NamedFieldPuns- , flagStubdir :: Bool -- ^ -stubdir flag supported- , flagOutputDir :: Bool -- ^ -outputdir flag supported- , noExtInSplitSuffix :: Bool -- ^ split-obj suffix does not contain p_o ext- , flagFfiIncludes :: Bool -- ^ -#include on command line for FFI includes- , flagBuildingCabalPkg :: Bool -- ^ -fbuilding-cabal-package flag supported- , flagPackageId :: Bool -- ^ -package-id / -package flags supported- , separateGccMingw :: Bool -- ^ mingw and gcc are in separate directories- , supportsHaskell2010 :: Bool -- ^ -XHaskell2010 and -XHaskell98 flags+ { supportsHaskell2010 :: Bool -- ^ -XHaskell2010 and -XHaskell98 flags , reportsNoExt :: Bool -- ^ --supported-languages gives Ext and NoExt , alwaysNondecIndent :: Bool -- ^ NondecreasingIndentation is always on , flagGhciScript :: Bool -- ^ -ghci-script flag supported@@ -67,17 +55,7 @@ ghcVersionImplInfo :: Version -> GhcImplInfo ghcVersionImplInfo (Version v _) = GhcImplInfo- { hasCcOdirBug = v < [6,4,1]- , flagInfoLanguages = v >= [6,7]- , fakeRecordPuns = v >= [6,8] && v < [6,10]- , flagStubdir = v >= [6,8]- , flagOutputDir = v >= [6,10]- , noExtInSplitSuffix = v < [6,11]- , flagFfiIncludes = v < [6,11]- , flagBuildingCabalPkg = v >= [6,11]- , flagPackageId = v > [6,11]- , separateGccMingw = v < [6,12]- , supportsHaskell2010 = v >= [7]+ { supportsHaskell2010 = v >= [7] , reportsNoExt = v >= [7] , alwaysNondecIndent = v < [7,1] , flagGhciScript = v >= [7,2]@@ -88,17 +66,7 @@ ghcjsVersionImplInfo :: Version -> Version -> GhcImplInfo ghcjsVersionImplInfo _ghcjsVer _ghcVer = GhcImplInfo- { hasCcOdirBug = False- , flagInfoLanguages = True- , fakeRecordPuns = False- , flagStubdir = True- , flagOutputDir = True- , noExtInSplitSuffix = False- , flagFfiIncludes = False- , flagBuildingCabalPkg = True- , flagPackageId = True- , separateGccMingw = False- , supportsHaskell2010 = True+ { supportsHaskell2010 = True , reportsNoExt = True , alwaysNondecIndent = False , flagGhciScript = True
cabal/Cabal/Distribution/Simple/GHC/Internal.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE CPP #-} {-# LANGUAGE PatternGuards #-} ----------------------------------------------------------------------------- -- |@@ -27,56 +26,40 @@ substTopDir, checkPackageDbEnvVar, profDetailLevelFlag,+ showArchString,+ showOsString, ) where -import Distribution.Simple.GHC.ImplInfo ( GhcImplInfo (..) )+import Distribution.Simple.GHC.ImplInfo import Distribution.Package- ( InstalledPackageId, PackageId, LibraryName- , getHSLibraryName ) import Distribution.InstalledPackageInfo- ( InstalledPackageInfo ) import qualified Distribution.InstalledPackageInfo as InstalledPackageInfo- ( InstalledPackageInfo(..) )-import Distribution.PackageDescription as PD- ( BuildInfo(..), Library(..), libModules- , hcOptions, usedExtensions, ModuleRenaming, lookupRenaming )-import Distribution.Compat.Exception ( catchExit, catchIO )-import Distribution.Lex (tokenizeQuotedWords)-import Distribution.Simple.Compiler- ( CompilerFlavor(..), Compiler(..), DebugInfoLevel(..)- , OptimisationLevel(..), ProfDetailLevel(..) )+import Distribution.PackageDescription as PD hiding (Flag)+import Distribution.Compat.Exception+import Distribution.Lex+import Distribution.Simple.Compiler hiding (Flag) import Distribution.Simple.Program.GHC import Distribution.Simple.Setup- ( Flag, toFlag ) import qualified Distribution.ModuleName as ModuleName import Distribution.Simple.Program- ( Program(..), ConfiguredProgram(..), ProgramConfiguration- , ProgramLocation(..), ProgramSearchPath, ProgramSearchPathEntry(..)- , rawSystemProgram, rawSystemProgramStdout, programPath- , addKnownProgram, arProgram, ldProgram, gccProgram, stripProgram- , getProgramOutput )-import Distribution.Simple.Program.Types ( suppressOverrideArgs ) import Distribution.Simple.LocalBuildInfo- ( LocalBuildInfo(..), ComponentLocalBuildInfo(..) ) import Distribution.Simple.Utils import Distribution.Simple.BuildPaths-import Distribution.System ( buildOS, OS(..), Platform, platformFromTriple )+import Distribution.System import Distribution.Text ( display, simpleParse ) import Distribution.Utils.NubList ( toNubListR ) import Distribution.Verbosity import Language.Haskell.Extension- ( Language(..), Extension(..), KnownExtension(..) ) import qualified Data.Map as M import Data.Char ( isSpace ) import Data.Maybe ( fromMaybe, maybeToList, isJust ) import Control.Monad ( unless, when )-#if __GLASGOW_HASKELL__ < 710-import Data.Monoid ( Monoid(..) )-#endif+import Data.Monoid as Mon ( Monoid(..) ) import System.Directory ( getDirectoryContents, getTemporaryDirectory ) import System.Environment ( getEnv )-import System.FilePath ( (</>), (<.>), takeExtension, takeDirectory )+import System.FilePath ( (</>), (<.>), takeExtension+ , takeDirectory, takeFileName) import System.IO ( hClose, hPutStrLn ) targetPlatform :: [(String, String)] -> Maybe Platform@@ -89,30 +72,36 @@ -> M.Map String String -> ProgramConfiguration -> ProgramConfiguration-configureToolchain implInfo ghcProg ghcInfo =+configureToolchain _implInfo ghcProg ghcInfo = addKnownProgram gccProgram {- programFindLocation = findProg gccProgram extraGccPath,+ programFindLocation = findProg gccProgramName extraGccPath, programPostConf = configureGcc } . addKnownProgram ldProgram {- programFindLocation = findProg ldProgram extraLdPath,+ programFindLocation = findProg ldProgramName extraLdPath, programPostConf = configureLd } . addKnownProgram arProgram {- programFindLocation = findProg arProgram extraArPath+ programFindLocation = findProg arProgramName extraArPath } . addKnownProgram stripProgram {- programFindLocation = findProg stripProgram extraStripPath+ programFindLocation = findProg stripProgramName extraStripPath } where compilerDir = takeDirectory (programPath ghcProg) baseDir = takeDirectory compilerDir mingwBinDir = baseDir </> "mingw" </> "bin"- libDir = baseDir </> "gcc-lib"- includeDir = baseDir </> "include" </> "mingw" isWindows = case buildOS of Windows -> True; _ -> False binPrefix = "" + maybeName :: Program -> Maybe FilePath -> String+ maybeName prog = maybe (programName prog) (dropExeExtension . takeFileName)++ gccProgramName = maybeName gccProgram mbGccLocation+ ldProgramName = maybeName ldProgram mbLdLocation+ arProgramName = maybeName arProgram mbArLocation+ stripProgramName = maybeName stripProgram mbStripLocation+ mkExtraPath :: Maybe FilePath -> FilePath -> [FilePath] mkExtraPath mbPath mingwPath | isWindows = mbDir ++ [mingwPath] | otherwise = mbDir@@ -126,16 +115,15 @@ -- on Windows finding and configuring ghc's gcc & binutils is a bit special (windowsExtraGccDir, windowsExtraLdDir,- windowsExtraArDir, windowsExtraStripDir)- | separateGccMingw implInfo = (baseDir, libDir, libDir, libDir)- | otherwise = -- GHC >= 6.12+ windowsExtraArDir, windowsExtraStripDir) = let b = mingwBinDir </> binPrefix in (b, b, b, b) - findProg :: Program -> [FilePath]- -> Verbosity -> ProgramSearchPath -> IO (Maybe FilePath)- findProg prog extraPath v searchpath =- programFindLocation prog v searchpath'+ findProg :: String -> [FilePath]+ -> Verbosity -> ProgramSearchPath+ -> IO (Maybe (FilePath, [FilePath]))+ findProg progName extraPath v searchpath =+ findProgramOnSearchPath v searchpath' progName where searchpath' = (map ProgramSearchPathDir extraPath) ++ searchpath @@ -165,28 +153,12 @@ | otherwise -> tokenizeQuotedWords flags configureGcc :: Verbosity -> ConfiguredProgram -> IO ConfiguredProgram- configureGcc v gccProg = do- gccProg' <- configureGcc' v gccProg- return gccProg' {- programDefaultArgs = programDefaultArgs gccProg'+ configureGcc _v gccProg = do+ return gccProg {+ programDefaultArgs = programDefaultArgs gccProg ++ ccFlags ++ gccLinkerFlags } - configureGcc' :: Verbosity -> ConfiguredProgram -> IO ConfiguredProgram- configureGcc'- | isWindows = \_ gccProg -> case programLocation gccProg of- -- if it's found on system then it means we're using the result- -- of programFindLocation above rather than a user-supplied path- -- Pre GHC 6.12, that meant we should add these flags to tell- -- ghc's gcc where it lives and thus where gcc can find its- -- various files:- FoundOnSystem {}- | separateGccMingw implInfo ->- return gccProg { programDefaultArgs = ["-B" ++ libDir,- "-I" ++ includeDir] }- _ -> return gccProg- | otherwise = \_ gccProg -> return gccProg- configureLd :: Verbosity -> ConfiguredProgram -> IO ConfiguredProgram configureLd v ldProg = do ldProg' <- configureLd' v ldProg@@ -226,8 +198,7 @@ getGhcInfo :: Verbosity -> GhcImplInfo -> ConfiguredProgram -> IO [(String, String)]-getGhcInfo verbosity implInfo ghcProg- | flagInfoLanguages implInfo = do+getGhcInfo verbosity _implInfo ghcProg = do xs <- getProgramOutput verbosity (suppressOverrideArgs ghcProg) ["--info"] case reads xs of@@ -236,13 +207,10 @@ return i _ -> die "Can't parse --info output of GHC"- | otherwise =- return [] getExtensions :: Verbosity -> GhcImplInfo -> ConfiguredProgram -> IO [(Extension, String)]-getExtensions verbosity implInfo ghcProg- | flagInfoLanguages implInfo = do+getExtensions verbosity implInfo ghcProg = do str <- getProgramOutput verbosity (suppressOverrideArgs ghcProg) ["--supported-languages"] let extStrs = if reportsNoExt implInfo@@ -258,94 +226,27 @@ ] let extensions0 = [ (ext, "-X" ++ display ext) | Just ext <- map simpleParse extStrs ]- extensions1 = if fakeRecordPuns implInfo- then -- ghc-6.8 introduced RecordPuns however it- -- should have been NamedFieldPuns. We now- -- encourage packages to use NamedFieldPuns- -- so for compatibility we fake support for- -- it in ghc-6.8 by making it an alias for- -- the old RecordPuns extension.- (EnableExtension NamedFieldPuns, "-XRecordPuns") :- (DisableExtension NamedFieldPuns, "-XNoRecordPuns") :- extensions0- else extensions0- extensions2 = if alwaysNondecIndent implInfo+ extensions1 = if alwaysNondecIndent implInfo then -- ghc-7.2 split NondecreasingIndentation off -- into a proper extension. Before that it -- was always on. (EnableExtension NondecreasingIndentation, "") : (DisableExtension NondecreasingIndentation, "") :- extensions1- else extensions1- return extensions2-- | otherwise = return oldLanguageExtensions---- | For GHC 6.6.x and earlier, the mapping from supported extensions to flags-oldLanguageExtensions :: [(Extension, String)]-oldLanguageExtensions =- let doFlag (f, (enable, disable)) = [(EnableExtension f, enable),- (DisableExtension f, disable)]- fglasgowExts = ("-fglasgow-exts",- "") -- This is wrong, but we don't want to turn- -- all the extensions off when asked to just- -- turn one off- fFlag flag = ("-f" ++ flag, "-fno-" ++ flag)- in concatMap doFlag- [(OverlappingInstances , fFlag "allow-overlapping-instances")- ,(TypeSynonymInstances , fglasgowExts)- ,(TemplateHaskell , fFlag "th")- ,(ForeignFunctionInterface , fFlag "ffi")- ,(MonomorphismRestriction , fFlag "monomorphism-restriction")- ,(MonoPatBinds , fFlag "mono-pat-binds")- ,(UndecidableInstances , fFlag "allow-undecidable-instances")- ,(IncoherentInstances , fFlag "allow-incoherent-instances")- ,(Arrows , fFlag "arrows")- ,(Generics , fFlag "generics")- ,(ImplicitPrelude , fFlag "implicit-prelude")- ,(ImplicitParams , fFlag "implicit-params")- ,(CPP , ("-cpp", ""{- Wrong -}))- ,(BangPatterns , fFlag "bang-patterns")- ,(KindSignatures , fglasgowExts)- ,(RecursiveDo , fglasgowExts)- ,(ParallelListComp , fglasgowExts)- ,(MultiParamTypeClasses , fglasgowExts)- ,(FunctionalDependencies , fglasgowExts)- ,(Rank2Types , fglasgowExts)- ,(RankNTypes , fglasgowExts)- ,(PolymorphicComponents , fglasgowExts)- ,(ExistentialQuantification , fglasgowExts)- ,(ScopedTypeVariables , fFlag "scoped-type-variables")- ,(FlexibleContexts , fglasgowExts)- ,(FlexibleInstances , fglasgowExts)- ,(EmptyDataDecls , fglasgowExts)- ,(PatternGuards , fglasgowExts)- ,(GeneralizedNewtypeDeriving , fglasgowExts)- ,(MagicHash , fglasgowExts)- ,(UnicodeSyntax , fglasgowExts)- ,(PatternSignatures , fglasgowExts)- ,(UnliftedFFITypes , fglasgowExts)- ,(LiberalTypeSynonyms , fglasgowExts)- ,(TypeOperators , fglasgowExts)- ,(GADTs , fglasgowExts)- ,(RelaxedPolyRec , fglasgowExts)- ,(ExtendedDefaultRules , fFlag "extended-default-rules")- ,(UnboxedTuples , fglasgowExts)- ,(DeriveDataTypeable , fglasgowExts)- ,(ConstrainedClassMethods , fglasgowExts)- ]+ extensions0+ else extensions0+ return extensions1 componentCcGhcOptions :: Verbosity -> GhcImplInfo -> LocalBuildInfo -> BuildInfo -> ComponentLocalBuildInfo -> FilePath -> FilePath -> GhcOptions-componentCcGhcOptions verbosity implInfo lbi bi clbi pref filename =+componentCcGhcOptions verbosity _implInfo lbi bi clbi odir filename = mempty { ghcOptVerbosity = toFlag verbosity, ghcOptMode = toFlag GhcModeCompile, ghcOptInputFiles = toNubListR [filename], - ghcOptCppIncludePath = toNubListR $ [autogenModulesDir lbi, odir]+ ghcOptCppIncludePath = toNubListR $ [autogenModulesDir lbi clbi, odir] ++ PD.includeDirs bi, ghcOptPackageDBs = withPackageDB lbi, ghcOptPackages = toNubListR $ mkGhcOptPackages clbi,@@ -361,10 +262,6 @@ PD.ccOptions bi, ghcOptObjDir = toFlag odir }- where- odir | hasCcOdirBug implInfo = pref </> takeDirectory filename- | otherwise = pref- -- ghc 6.4.0 had a bug in -odir handling for C compilations. componentGhcOptions :: Verbosity -> LocalBuildInfo -> BuildInfo -> ComponentLocalBuildInfo -> FilePath@@ -374,21 +271,21 @@ ghcOptVerbosity = toFlag verbosity, ghcOptHideAllPackages = toFlag True, ghcOptCabal = toFlag True,- ghcOptPackageKey = case clbi of- LibComponentLocalBuildInfo { componentPackageKey = pk } -> toFlag pk- _ -> mempty,- ghcOptSigOf = hole_insts,+ ghcOptThisUnitId = case clbi of+ LibComponentLocalBuildInfo { componentCompatPackageKey = pk }+ -> toFlag pk+ _ -> Mon.mempty, ghcOptPackageDBs = withPackageDB lbi, ghcOptPackages = toNubListR $ mkGhcOptPackages clbi, ghcOptSplitObjs = toFlag (splitObjs lbi), ghcOptSourcePathClear = toFlag True, ghcOptSourcePath = toNubListR $ [odir] ++ (hsSourceDirs bi)- ++ [autogenModulesDir lbi],- ghcOptCppIncludePath = toNubListR $ [autogenModulesDir lbi, odir]+ ++ [autogenModulesDir lbi clbi],+ ghcOptCppIncludePath = toNubListR $ [autogenModulesDir lbi clbi, odir] ++ PD.includeDirs bi, ghcOptCppOptions = toNubListR $ cppOptions bi, ghcOptCppIncludes = toNubListR $- [autogenModulesDir lbi </> cppHeaderName],+ [autogenModulesDir lbi clbi </> cppHeaderName], ghcOptFfiIncludes = toNubListR $ PD.includes bi, ghcOptObjDir = toFlag odir, ghcOptHiDir = toFlag odir,@@ -413,9 +310,6 @@ toGhcDebugInfo NormalDebugInfo = toFlag True toGhcDebugInfo MaximalDebugInfo = toFlag True - hole_insts = map (\(k,(p,n)) -> (k, (InstalledPackageInfo.packageKey p,n)))- (instantiatedWith lbi)- -- | Strip out flags that are not supported in ghci filterGhciFlags :: [String] -> [String] filterGhciFlags = filter supported@@ -429,7 +323,7 @@ supported "-unreg" = False supported _ = True -mkGHCiLibName :: LibraryName -> String+mkGHCiLibName :: UnitId -> String mkGHCiLibName lib = getHSLibraryName lib <.> "o" ghcLookupProperty :: String -> Compiler -> Bool@@ -442,11 +336,9 @@ -- Module_split directory for each module. getHaskellObjects :: GhcImplInfo -> Library -> LocalBuildInfo -> FilePath -> String -> Bool -> IO [FilePath]-getHaskellObjects implInfo lib lbi pref wanted_obj_ext allow_split_objs+getHaskellObjects _implInfo lib lbi pref wanted_obj_ext allow_split_objs | splitObjs lbi && allow_split_objs = do- let splitSuffix = if noExtInSplitSuffix implInfo- then "_split"- else "_" ++ wanted_obj_ext ++ "_split"+ let splitSuffix = "_" ++ wanted_obj_ext ++ "_split" dirs = [ pref </> (ModuleName.toFilePath x ++ splitSuffix) | x <- libModules lib ] objss <- mapM getDirectoryContents dirs@@ -460,10 +352,8 @@ | x <- libModules lib ] mkGhcOptPackages :: ComponentLocalBuildInfo- -> [(InstalledPackageId, PackageId, ModuleRenaming)]-mkGhcOptPackages clbi =- map (\(i,p) -> (i,p,lookupRenaming p (componentPackageRenaming clbi)))- (componentPackageDeps clbi)+ -> [(UnitId, ModuleRenaming)]+mkGhcOptPackages = componentIncludes substTopDir :: FilePath -> InstalledPackageInfo -> InstalledPackageInfo substTopDir topDir ipo@@ -502,7 +392,8 @@ unless (mPP == mcsPP) abort where lookupEnv :: String -> IO (Maybe String)- lookupEnv name = (Just `fmap` getEnv name) `catchIO` const (return Nothing)+ lookupEnv name = (Just `fmap` getEnv name)+ `catchIO` const (return Nothing) abort = die $ "Use of " ++ compilerName ++ "'s environment variable " ++ packagePathEnvVar ++ " is incompatible with Cabal. Use the "@@ -519,3 +410,20 @@ ProfDetailToplevelFunctions -> toFlag GhcProfAutoToplevel ProfDetailAllFunctions -> toFlag GhcProfAutoAll ProfDetailOther _ -> mempty++-- | GHC's rendering of it's host or target 'Arch' as used in its platform+-- strings and certain file locations (such as user package db location).+--+showArchString :: Arch -> String+showArchString PPC = "powerpc"+showArchString PPC64 = "powerpc64"+showArchString other = display other++-- | GHC's rendering of it's host or target 'OS' as used in its platform+-- strings and certain file locations (such as user package db location).+--+showOsString :: OS -> String+showOsString Windows = "mingw32"+showOsString OSX = "darwin"+showOsString Solaris = "solaris2"+showOsString other = display other
cabal/Cabal/Distribution/Simple/GHCJS.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE CPP #-}+{-# LANGUAGE PatternGuards #-} module Distribution.Simple.GHCJS ( configure, getInstalledPackages, getPackageDBContents, buildLib, buildExe,@@ -15,70 +15,40 @@ runCmd ) where -import Distribution.Simple.GHC.ImplInfo ( getImplInfo, ghcjsVersionImplInfo )+import Distribution.Simple.GHC.ImplInfo import qualified Distribution.Simple.GHC.Internal as Internal import Distribution.PackageDescription as PD- ( PackageDescription(..), BuildInfo(..), Executable(..)- , Library(..), libModules, exeModules- , hcOptions, hcProfOptions, hcSharedOptions- , allExtensions ) import Distribution.InstalledPackageInfo- ( InstalledPackageInfo )-import qualified Distribution.InstalledPackageInfo as InstalledPackageInfo- ( InstalledPackageInfo(..) )-import Distribution.Package ( LibraryName(..), getHSLibraryName )+import Distribution.Package import Distribution.Simple.PackageIndex ( InstalledPackageIndex ) import qualified Distribution.Simple.PackageIndex as PackageIndex import Distribution.Simple.LocalBuildInfo- ( LocalBuildInfo(..), ComponentLocalBuildInfo(..) ) import qualified Distribution.Simple.Hpc as Hpc-import Distribution.Simple.InstallDirs hiding ( absoluteInstallDirs ) import Distribution.Simple.BuildPaths import Distribution.Simple.Utils import Distribution.Simple.Program- ( Program(..), ConfiguredProgram(..), ProgramConfiguration- , ProgramSearchPath- , rawSystemProgramConf- , rawSystemProgramStdout, rawSystemProgramStdoutConf- , getProgramInvocationOutput- , requireProgramVersion, requireProgram- , userMaybeSpecifyPath, programPath- , lookupProgram, addKnownPrograms- , ghcjsProgram, ghcjsPkgProgram, c2hsProgram, hsc2hsProgram- , ldProgram, haddockProgram, stripProgram ) import qualified Distribution.Simple.Program.HcPkg as HcPkg import qualified Distribution.Simple.Program.Ar as Ar import qualified Distribution.Simple.Program.Ld as Ld import qualified Distribution.Simple.Program.Strip as Strip import Distribution.Simple.Program.GHC-import Distribution.Simple.Setup- ( toFlag, fromFlag, configCoverage, configDistPref )+import Distribution.Simple.Setup hiding ( Flag ) import qualified Distribution.Simple.Setup as Cabal- ( Flag(..) )-import Distribution.Simple.Compiler- ( CompilerFlavor(..), CompilerId(..), Compiler(..)- , PackageDB(..), PackageDBStack, AbiTag(..) )+import Distribution.Simple.Compiler hiding ( Flag ) import Distribution.Version- ( Version(..), anyVersion, orLaterVersion ) import Distribution.System- ( Platform(..) ) import Distribution.Verbosity import Distribution.Utils.NubList- ( overNubListR, toNubListR )-import Distribution.Text ( display )-import Language.Haskell.Extension ( Extension(..)- , KnownExtension(..))+import Distribution.Text+import Language.Haskell.Extension import Control.Monad ( unless, when ) import Data.Char ( isSpace ) import qualified Data.Map as M ( fromList )-#if __GLASGOW_HASKELL__ < 710-import Data.Monoid ( Monoid(..) )-#endif+import Data.Monoid as Mon ( Monoid(..) ) import System.Directory ( doesFileExist )-import System.FilePath ( (</>), (<.>), takeExtension,- takeDirectory, replaceExtension,- splitExtension )+import System.FilePath ( (</>), (<.>), takeExtension+ , takeDirectory, replaceExtension ) configure :: Verbosity -> Maybe FilePath -> Maybe FilePath -> ProgramConfiguration@@ -155,24 +125,24 @@ ghcjsNativeToo = Internal.ghcLookupProperty "Native Too" guessGhcjsPkgFromGhcjsPath :: ConfiguredProgram -> Verbosity- -> ProgramSearchPath -> IO (Maybe FilePath)+ -> ProgramSearchPath -> IO (Maybe (FilePath, [FilePath])) guessGhcjsPkgFromGhcjsPath = guessToolFromGhcjsPath ghcjsPkgProgram guessHsc2hsFromGhcjsPath :: ConfiguredProgram -> Verbosity- -> ProgramSearchPath -> IO (Maybe FilePath)+ -> ProgramSearchPath -> IO (Maybe (FilePath, [FilePath])) guessHsc2hsFromGhcjsPath = guessToolFromGhcjsPath hsc2hsProgram guessC2hsFromGhcjsPath :: ConfiguredProgram -> Verbosity- -> ProgramSearchPath -> IO (Maybe FilePath)+ -> ProgramSearchPath -> IO (Maybe (FilePath, [FilePath])) guessC2hsFromGhcjsPath = guessToolFromGhcjsPath c2hsProgram guessHaddockFromGhcjsPath :: ConfiguredProgram -> Verbosity- -> ProgramSearchPath -> IO (Maybe FilePath)+ -> ProgramSearchPath -> IO (Maybe (FilePath, [FilePath])) guessHaddockFromGhcjsPath = guessToolFromGhcjsPath haddockProgram guessToolFromGhcjsPath :: Program -> ConfiguredProgram -> Verbosity -> ProgramSearchPath- -> IO (Maybe FilePath)+ -> IO (Maybe (FilePath, [FilePath])) guessToolFromGhcjsPath tool ghcjsProg verbosity searchpath = do let toolname = programName tool path = programPath ghcjsProg@@ -197,19 +167,15 @@ -- method. [] -> programFindLocation tool verbosity searchpath (fp:_) -> do info verbosity $ "found " ++ toolname ++ " in " ++ fp- return (Just fp)+ let lookedAt = map fst+ . takeWhile (\(_file, exist) -> not exist)+ $ zip guesses exists+ return (Just (fp, lookedAt)) where takeVersionSuffix :: FilePath -> String takeVersionSuffix = reverse . takeWhile (`elem ` "0123456789.-") . reverse - dropExeExtension :: FilePath -> FilePath- dropExeExtension filepath =- case splitExtension filepath of- (filepath', extension) | extension == exeExtension -> filepath'- | otherwise -> filepath-- -- | Given a single package DB, return all installed packages. getPackageDBContents :: Verbosity -> PackageDB -> ProgramConfiguration -> IO InstalledPackageIndex@@ -301,7 +267,7 @@ -> PackageDescription -> LocalBuildInfo -> Library -> ComponentLocalBuildInfo -> IO () buildOrReplLib forRepl verbosity numJobs _pkg_descr lbi lib clbi = do- let libName@(LibraryName cname) = componentLibraryName clbi+ let uid = componentUnitId clbi libTargetDir = buildDir lbi whenVanillaLib forceVanilla = when (not forRepl && (forceVanilla || withVanillaLib lbi))@@ -310,14 +276,13 @@ when (not forRepl && (forceShared || withSharedLib lbi)) whenGHCiLib = when (not forRepl && withGHCiLib lbi && withVanillaLib lbi) ifReplLib = when forRepl- comp = compiler lbi- implInfo = getImplInfo comp- hole_insts = map (\(k,(p,n)) -> (k,(InstalledPackageInfo.packageKey p,n)))- (instantiatedWith lbi)+ comp = compiler lbi+ platform = hostPlatform lbi+ implInfo = getImplInfo comp nativeToo = ghcjsNativeToo comp (ghcjsProg, _) <- requireProgram verbosity ghcjsProgram (withPrograms lbi)- let runGhcjsProg = runGHC verbosity ghcjsProg comp+ let runGhcjsProg = runGHC verbosity ghcjsProg comp platform libBi = libBuildInfo lib isGhcjsDynamic = isDynamic comp dynamicTooSupported = supportsDynamicToo comp@@ -329,10 +294,11 @@ -- Determine if program coverage should be enabled and if so, what -- '-hpcdir' should be. let isCoverageEnabled = fromFlag $ configCoverage $ configFlags lbi+ pkg_name = display $ PD.package $ localPkgDescr lbi distPref = fromFlag $ configDistPref $ configFlags lbi hpcdir way- | isCoverageEnabled = toFlag $ Hpc.mixDir distPref way cname- | otherwise = mempty+ | isCoverageEnabled = toFlag $ Hpc.mixDir distPref way pkg_name+ | otherwise = Mon.mempty createDirectoryIfMissingVerbose verbosity True libTargetDir -- TODO: do we need to put hs-boot files into place for mutually recursive@@ -342,14 +308,13 @@ baseOpts = componentGhcOptions verbosity lbi libBi clbi libTargetDir linkJsLibOpts = mempty { ghcOptExtra = toNubListR $- [ "-link-js-lib" , getHSLibraryName libName+ [ "-link-js-lib" , getHSLibraryName uid , "-js-lib-outputdir", libTargetDir ] ++ concatMap (\x -> ["-js-lib-src",x]) jsSrcs } vanillaOptsNoJsLib = baseOpts `mappend` mempty { ghcOptMode = toFlag GhcModeMake, ghcOptNumJobs = numJobs,- ghcOptSigOf = hole_insts, ghcOptInputModules = toNubListR $ libModules lib, ghcOptHPCDir = hpcdir Hpc.Vanilla }@@ -457,11 +422,11 @@ (cSources libBi) cSharedObjs = map (`replaceExtension` ("dyn_" ++ objExtension)) (cSources libBi)- cid = compilerId (compiler lbi)- vanillaLibFilePath = libTargetDir </> mkLibName libName- profileLibFilePath = libTargetDir </> mkProfLibName libName- sharedLibFilePath = libTargetDir </> mkSharedLibName cid libName- ghciLibFilePath = libTargetDir </> Internal.mkGHCiLibName libName+ compiler_id = compilerId (compiler lbi)+ vanillaLibFilePath = libTargetDir </> mkLibName uid+ profileLibFilePath = libTargetDir </> mkProfLibName uid+ sharedLibFilePath = libTargetDir </> mkSharedLibName compiler_id uid+ ghciLibFilePath = libTargetDir </> Internal.mkGHCiLibName uid hObjs <- Internal.getHaskellObjects implInfo lib lbi libTargetDir objExtension True@@ -499,6 +464,8 @@ ghcOptDynLinkMode = toFlag GhcDynamicOnly, ghcOptInputFiles = toNubListR dynamicObjectFiles, ghcOptOutputFile = toFlag sharedLibFilePath,+ ghcOptExtra = toNubListR $+ ghcjsSharedOptions libBi, ghcOptNoAutoLinkPackages = toFlag True, ghcOptPackageDBs = withPackageDB lbi, ghcOptPackages = toNubListR $@@ -522,16 +489,16 @@ runGhcjsProg ghcSharedLinkArgs -- | Start a REPL without loading any source files.-startInterpreter :: Verbosity -> ProgramConfiguration -> Compiler+startInterpreter :: Verbosity -> ProgramConfiguration -> Compiler -> Platform -> PackageDBStack -> IO ()-startInterpreter verbosity conf comp packageDBs = do+startInterpreter verbosity conf comp platform packageDBs = do let replOpts = mempty { ghcOptMode = toFlag GhcModeInteractive, ghcOptPackageDBs = packageDBs } checkPackageDbStack packageDBs (ghcjsProg, _) <- requireProgram verbosity ghcjsProgram conf- runGHC verbosity ghcjsProg comp replOpts+ runGHC verbosity ghcjsProg comp platform replOpts buildExe, replExe :: Verbosity -> Cabal.Flag (Maybe Int) -> PackageDescription -> LocalBuildInfo@@ -547,8 +514,9 @@ (ghcjsProg, _) <- requireProgram verbosity ghcjsProgram (withPrograms lbi) let comp = compiler lbi+ platform = hostPlatform lbi implInfo = getImplInfo comp- runGhcjsProg = runGHC verbosity ghcjsProg comp+ runGhcjsProg = runGHC verbosity ghcjsProg comp platform exeBi = buildInfo exe -- exeNameReal, the name that GHC really uses (with .exe on Windows)@@ -755,12 +723,12 @@ findModuleFiles [builtDir] [ext] (libModules lib) >>= installOrdinaryFiles verbosity targetDir - cid = compilerId (compiler lbi)- libName = componentLibraryName clbi- vanillaLibName = mkLibName libName- profileLibName = mkProfLibName libName- ghciLibName = Internal.mkGHCiLibName libName- sharedLibName = (mkSharedLibName cid) libName+ compiler_id = compilerId (compiler lbi)+ uid = componentUnitId clbi+ vanillaLibName = mkLibName uid+ profileLibName = mkProfLibName uid+ ghciLibName = Internal.mkGHCiLibName uid+ sharedLibName = (mkSharedLibName compiler_id) uid hasLib = not $ null (libModules lib) && null (cSources (libBuildInfo lib))@@ -800,11 +768,12 @@ let libBi = libBuildInfo lib comp = compiler lbi+ platform = hostPlatform lbi vanillaArgs = (componentGhcOptions verbosity lbi libBi clbi (buildDir lbi)) `mappend` mempty { ghcOptMode = toFlag GhcModeAbiHash,- ghcOptInputModules = toNubListR $ exposedModules lib+ ghcOptInputModules = toNubListR $ PD.exposedModules lib } profArgs = adjustExts "js_p_hi" "js_p_o" vanillaArgs `mappend` mempty { ghcOptProfilingMode = toFlag True,@@ -815,7 +784,8 @@ else error "libAbiHash: Can't find an enabled library way" -- (ghcjsProg, _) <- requireProgram verbosity ghcjsProgram (withPrograms lbi)- getProgramInvocationOutput verbosity (ghcInvocation ghcjsProg comp ghcArgs)+ getProgramInvocationOutput verbosity+ (ghcInvocation ghcjsProg comp platform ghcArgs) adjustExts :: String -> String -> GhcOptions -> GhcOptions adjustExts hiSuf objSuf opts =@@ -825,16 +795,20 @@ } registerPackage :: Verbosity- -> InstalledPackageInfo- -> PackageDescription- -> LocalBuildInfo- -> Bool+ -> ProgramConfiguration+ -> HcPkg.MultiInstance -> PackageDBStack+ -> InstalledPackageInfo -> IO ()-registerPackage verbosity installedPkgInfo _pkg lbi _inplace packageDbs =- HcPkg.reregister (hcPkgInfo $ withPrograms lbi) verbosity packageDbs- (Right installedPkgInfo)+registerPackage verbosity progdb multiInstance packageDbs installedPkgInfo+ | HcPkg.MultiInstance <- multiInstance+ = HcPkg.registerMultiInstance (hcPkgInfo progdb) verbosity+ packageDbs installedPkgInfo + | otherwise+ = HcPkg.reregister (hcPkgInfo progdb) verbosity+ packageDbs (Right installedPkgInfo)+ componentGhcOptions :: Verbosity -> LocalBuildInfo -> BuildInfo -> ComponentLocalBuildInfo -> FilePath -> GhcOptions@@ -874,7 +848,10 @@ , HcPkg.noPkgDbStack = False , HcPkg.noVerboseFlag = False , HcPkg.flagPackageConf = False- , HcPkg.useSingleFileDb = v < [7,9]+ , HcPkg.supportsDirDbs = True+ , HcPkg.requiresDirDbs = v >= [7,10]+ , HcPkg.nativeMultiInstance = v >= [7,10]+ , HcPkg.recacheMultiInstance = True } where v = versionBranch ver
cabal/Cabal/Distribution/Simple/Haddock.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE DeriveGeneric #-}+ ----------------------------------------------------------------------------- -- | -- Module : Distribution.Simple.Haddock@@ -24,76 +26,42 @@ import qualified Distribution.Simple.GHCJS as GHCJS -- local+import Distribution.Compat.Semigroup as Semi import Distribution.Package- ( PackageIdentifier(..)- , Package(..)- , PackageName(..), packageName, LibraryName(..) ) import qualified Distribution.ModuleName as ModuleName-import Distribution.PackageDescription as PD- ( PackageDescription(..), BuildInfo(..), usedExtensions- , hcSharedOptions- , Library(..), hasLibs, Executable(..)- , TestSuite(..), TestSuiteInterface(..)- , Benchmark(..), BenchmarkInterface(..) )-import Distribution.Simple.Compiler- ( Compiler, compilerInfo, CompilerFlavor(..)- , compilerFlavor, compilerCompatVersion )+import Distribution.PackageDescription as PD hiding (Flag)+import Distribution.Simple.Compiler hiding (Flag) import Distribution.Simple.Program.GHC- ( GhcOptions(..), GhcDynLinkMode(..), renderGhcOptions ) import Distribution.Simple.Program- ( ConfiguredProgram(..), lookupProgramVersion, requireProgramVersion- , rawSystemProgram, rawSystemProgramStdout- , hscolourProgram, haddockProgram ) import Distribution.Simple.PreProcess- ( PPSuffixHandler, preprocessComponent) import Distribution.Simple.Setup- ( defaultHscolourFlags- , Flag(..), toFlag, flagToMaybe, flagToList, fromFlag- , HaddockFlags(..), HscolourFlags(..) )-import Distribution.Simple.Build (initialBuildSteps)+import Distribution.Simple.Build import Distribution.Simple.InstallDirs- ( InstallDirs(..)- , PathTemplateEnv, PathTemplate, PathTemplateVariable(..)- , toPathTemplate, fromPathTemplate- , substPathTemplate, initialPathTemplateEnv )-import Distribution.Simple.LocalBuildInfo- ( LocalBuildInfo(..), Component(..), ComponentLocalBuildInfo(..)- , withAllComponentsInBuildOrder )+import Distribution.Simple.LocalBuildInfo hiding (substPathTemplate) import Distribution.Simple.BuildPaths- ( haddockName, hscolourPref, autogenModulesDir)-import Distribution.Simple.PackageIndex (dependencyClosure) import qualified Distribution.Simple.PackageIndex as PackageIndex import qualified Distribution.InstalledPackageInfo as InstalledPackageInfo- ( InstalledPackageInfo(..) )-import Distribution.InstalledPackageInfo- ( InstalledPackageInfo )+import Distribution.InstalledPackageInfo ( InstalledPackageInfo ) import Distribution.Simple.Utils- ( die, copyFileTo, warn, notice, intercalate, setupMessage- , createDirectoryIfMissingVerbose- , TempFileOptions(..), defaultTempFileOptions- , withTempFileEx, copyFileVerbose- , withTempDirectoryEx, matchFileGlob- , findFileWithExtension, findFile )+import Distribution.System import Distribution.Text- ( display, simpleParse ) import Distribution.Utils.NubList- ( toNubListR )-+import Distribution.Version import Distribution.Verbosity import Language.Haskell.Extension import Control.Monad ( when, forM_ )+import Data.Char ( isSpace ) import Data.Either ( rights )-import Data.Foldable ( traverse_ )-import Data.Monoid+import Data.Foldable ( traverse_, foldl' ) import Data.Maybe ( fromMaybe, listToMaybe )+import GHC.Generics ( Generic ) import System.Directory (doesFileExist) import System.FilePath ( (</>), (<.>) , normalise, splitPath, joinPath, isAbsolute )-import System.IO (hClose, hPutStrLn, hSetEncoding, utf8)-import Distribution.Version+import System.IO (hClose, hPutStr, hPutStrLn, hSetEncoding, utf8) -- ------------------------------------------------------------------------------ -- Types@@ -132,7 +100,7 @@ -- ^ To find the correct GHC, required. argTargets :: [FilePath] -- ^ Modules to process.-}+} deriving Generic -- | The FilePath of a directory, it's a monoid under '(</>)'. newtype Directory = Dir { unDir' :: FilePath } deriving (Read,Show,Eq,Ord)@@ -162,7 +130,28 @@ ++ "a library. Perhaps you want to use the --executables, --tests or" ++ " --benchmarks flags." -haddock pkg_descr lbi suffixes flags = do+haddock pkg_descr lbi suffixes flags' = do+ let verbosity = flag haddockVerbosity+ comp = compiler lbi+ platform = hostPlatform lbi++ flags+ | fromFlag (haddockForHackage flags') = flags'+ { haddockHoogle = Flag True+ , haddockHtml = Flag True+ , haddockHtmlLocation = Flag (pkg_url ++ "/docs")+ , haddockContents = Flag (toPathTemplate pkg_url)+ , haddockHscolour = Flag True+ }+ | otherwise = flags'+ pkg_url = "/package/$pkg-$version"+ flag f = fromFlag $ f flags++ tmpFileOpts = defaultTempFileOptions+ { optKeepTempFiles = flag haddockKeepTempFiles }+ htmlTemplate = fmap toPathTemplate . flagToMaybe . haddockHtmlLocation+ $ flags+ setupMessage verbosity "Running Haddock for" (packageId pkg_descr) (confHaddock, version, _) <- requireProgramVersion verbosity haddockProgram@@ -188,8 +177,6 @@ -- the tools match the requests, we can proceed - initialBuildSteps (flag haddockDistPref) pkg_descr lbi verbosity- when (flag haddockHscolour) $ hscolour' (warn verbosity) pkg_descr lbi suffixes (defaultHscolourFlags `mappend` haddockToHscolour flags)@@ -198,11 +185,12 @@ let commonArgs = mconcat [ libdirArgs , fromFlags (haddockTemplateEnv lbi (packageId pkg_descr)) flags- , fromPackageDescription pkg_descr ]+ , fromPackageDescription forDist pkg_descr ]+ forDist = fromFlagOrDefault False (haddockForHackage flags) - let pre c = preprocessComponent pkg_descr c lbi False verbosity suffixes withAllComponentsInBuildOrder pkg_descr lbi $ \component clbi -> do- pre component+ initialBuildSteps (flag haddockDistPref) pkg_descr lbi clbi verbosity+ preprocessComponent pkg_descr component lbi clbi False verbosity suffixes let doExe com = case (compToExe com) of Just exe -> do@@ -211,7 +199,8 @@ exeArgs <- fromExecutable verbosity tmp lbi exe clbi htmlTemplate version let exeArgs' = commonArgs `mappend` exeArgs- runHaddock verbosity tmpFileOpts comp confHaddock exeArgs'+ runHaddock verbosity tmpFileOpts comp platform+ confHaddock exeArgs' Nothing -> do warn (fromFlag $ haddockVerbosity flags) "Unsupported component, skipping..."@@ -223,7 +212,7 @@ libArgs <- fromLibrary verbosity tmp lbi lib clbi htmlTemplate version let libArgs' = commonArgs `mappend` libArgs- runHaddock verbosity tmpFileOpts comp confHaddock libArgs'+ runHaddock verbosity tmpFileOpts comp platform confHaddock libArgs' CExe _ -> when (flag haddockExecutables) $ doExe component CTest _ -> when (flag haddockTestSuites) $ doExe component CBench _ -> when (flag haddockBenchmarks) $ doExe component@@ -231,14 +220,6 @@ forM_ (extraDocFiles pkg_descr) $ \ fpath -> do files <- matchFileGlob fpath forM_ files $ copyFileTo verbosity (unDir $ argOutputDir commonArgs)- where- verbosity = flag haddockVerbosity- keepTempFiles = flag haddockKeepTempFiles- comp = compiler lbi- tmpFileOpts = defaultTempFileOptions { optKeepTempFiles = keepTempFiles }- flag f = fromFlag $ f flags- htmlTemplate = fmap toPathTemplate . flagToMaybe . haddockHtmlLocation- $ flags -- ------------------------------------------------------------------------------ -- Contributions to HaddockArgs.@@ -266,12 +247,11 @@ argOutputDir = maybe mempty Dir . flagToMaybe $ haddockDistPref flags } -fromPackageDescription :: PackageDescription -> HaddockArgs-fromPackageDescription pkg_descr =+fromPackageDescription :: Bool -> PackageDescription -> HaddockArgs+fromPackageDescription forDist pkg_descr = mempty { argInterfaceFile = Flag $ haddockName pkg_descr, argPackageName = Flag $ packageId $ pkg_descr,- argOutputDir = Dir $ "doc" </> "html"- </> display (packageName pkg_descr),+ argOutputDir = Dir $ "doc" </> "html" </> name, argPrologue = Flag $ if null desc then synopsis pkg_descr else desc, argTitle = Flag $ showPkg ++ subtitle@@ -279,6 +259,9 @@ where desc = PD.description pkg_descr showPkg = display (packageId pkg_descr)+ name+ | forDist = showPkg ++ "-docs"+ | otherwise = display (packageName pkg_descr) subtitle | null (synopsis pkg_descr) = "" | otherwise = ": " ++ synopsis pkg_descr @@ -301,7 +284,7 @@ -> Version -> IO HaddockArgs fromLibrary verbosity tmp lbi lib clbi htmlTemplate haddockVersion = do- inFiles <- map snd `fmap` getLibSourceFiles lbi lib+ inFiles <- map snd `fmap` getLibSourceFiles lbi lib clbi ifaceArgs <- getInterfaces verbosity lbi clbi htmlTemplate let vanillaOpts = (componentGhcOptions normal lbi bi clbi (buildDir lbi)) { -- Noooooooooo!!!!!111@@ -346,7 +329,7 @@ -> Version -> IO HaddockArgs fromExecutable verbosity tmp lbi exe clbi htmlTemplate haddockVersion = do- inFiles <- map snd `fmap` getExeSourceFiles lbi exe+ inFiles <- map snd `fmap` getExeSourceFiles lbi exe clbi ifaceArgs <- getInterfaces verbosity lbi clbi htmlTemplate let vanillaOpts = (componentGhcOptions normal lbi bi clbi (buildDir lbi)) { -- Noooooooooo!!!!!111@@ -444,13 +427,14 @@ runHaddock :: Verbosity -> TempFileOptions -> Compiler+ -> Platform -> ConfiguredProgram -> HaddockArgs -> IO ()-runHaddock verbosity tmpFileOpts comp confHaddock args = do+runHaddock verbosity tmpFileOpts comp platform confHaddock args = do let haddockVersion = fromMaybe (error "unable to determine haddock version") (programVersion confHaddock)- renderArgs verbosity tmpFileOpts haddockVersion comp args $+ renderArgs verbosity tmpFileOpts haddockVersion comp platform args $ \(flags,result)-> do rawSystemProgram verbosity confHaddock flags@@ -462,12 +446,13 @@ -> TempFileOptions -> Version -> Compiler+ -> Platform -> HaddockArgs -> (([String], FilePath) -> IO a) -> IO a-renderArgs verbosity tmpFileOpts version comp args k = do+renderArgs verbosity tmpFileOpts version comp platform args k = do let haddockSupportsUTF8 = version >= Version [2,14,4] []- haddockSupportsResponseFiles = version > Version [2,16,1] []+ haddockSupportsResponseFiles = version > Version [2,16,2] [] createDirectoryIfMissingVerbose verbosity True outputDir withTempFileEx tmpFileOpts outputDir "haddock-prologue.txt" $ \prologueFileName h -> do@@ -476,13 +461,13 @@ hPutStrLn h $ fromFlag $ argPrologue args hClose h let pflag = "--prologue=" ++ prologueFileName- renderedArgs = pflag : renderPureArgs version comp args- if haddockSupportsResponseFiles + renderedArgs = pflag : renderPureArgs version comp platform args+ if haddockSupportsResponseFiles then withTempFileEx tmpFileOpts outputDir "haddock-response.txt" $ \responseFileName hf -> do when haddockSupportsUTF8 (hSetEncoding hf utf8)- mapM_ (hPutStrLn hf) renderedArgs+ hPutStr hf $ unlines $ map escapeArg renderedArgs hClose hf let respFile = "@" ++ responseFileName k ([respFile], result)@@ -500,9 +485,22 @@ pkgstr = display $ packageName pkgid pkgid = arg argPackageName arg f = fromFlag $ f args+ -- Support a gcc-like response file syntax. Each separate+ -- argument and its possible parameter(s), will be separated in the+ -- response file by an actual newline; all other whitespace,+ -- single quotes, double quotes, and the character used for escaping+ -- (backslash) are escaped. The called program will need to do a similar+ -- inverse operation to de-escape and re-constitute the argument list.+ escape cs c+ | isSpace c+ || '\\' == c+ || '\'' == c+ || '"' == c = c:'\\':cs -- n.b., our caller must reverse the result+ | otherwise = c:cs+ escapeArg = reverse . foldl' escape [] -renderPureArgs :: Version -> Compiler -> HaddockArgs -> [String]-renderPureArgs version comp args = concat+renderPureArgs :: Version -> Compiler -> Platform -> HaddockArgs -> [String]+renderPureArgs version comp platform args = concat [ (:[]) . (\f -> "--dump-interface="++ unDir (argOutputDir args) </> f) . fromFlag . argInterfaceFile $ args @@ -544,7 +542,7 @@ . fromFlag . argTitle $ args , [ "--optghc=" ++ opt | (opts, _ghcVer) <- flagToList (argGhcOptions args)- , opt <- renderGhcOptions comp opts ]+ , opt <- renderGhcOptions comp platform opts ] , maybe [] (\l -> ["-B"++l]) $ flagToMaybe (argGhcLibDir args) -- error if Nothing?@@ -616,7 +614,7 @@ haddockPackageFlags lbi clbi htmlTemplate = do let allPkgs = installedPkgs lbi directDeps = map fst (componentPackageDeps clbi)- transitiveDeps <- case dependencyClosure allPkgs directDeps of+ transitiveDeps <- case PackageIndex.dependencyClosure allPkgs directDeps of Left x -> return x Right inf -> die $ "internal error when calculating transitive " ++ "package dependencies.\nDebug info: " ++ show inf@@ -631,7 +629,9 @@ haddockTemplateEnv :: LocalBuildInfo -> PackageIdentifier -> PathTemplateEnv haddockTemplateEnv lbi pkg_id = (PrefixVar, prefix (installDirTemplates lbi))- : initialPathTemplateEnv pkg_id (LibraryName (display pkg_id)) (compilerInfo (compiler lbi))+ -- We want the legacy unit ID here, because it gives us nice paths+ -- (Haddock people don't care about the dependencies)+ : initialPathTemplateEnv pkg_id (mkLegacyUnitId pkg_id) (compilerInfo (compiler lbi)) (hostPlatform lbi) -- ------------------------------------------------------------------------------@@ -643,13 +643,7 @@ -> HscolourFlags -> IO () hscolour pkg_descr lbi suffixes flags = do- -- we preprocess even if hscolour won't be found on the machine- -- will this upset someone?- initialBuildSteps distPref pkg_descr lbi verbosity hscolour' die pkg_descr lbi suffixes flags- where- verbosity = fromFlag (hscolourVerbosity flags)- distPref = fromFlag $ hscolourDistPref flags hscolour' :: (String -> IO ()) -- ^ Called when the 'hscolour' exe is not found. -> PackageDescription@@ -668,15 +662,15 @@ createDirectoryIfMissingVerbose verbosity True $ hscolourPref distPref pkg_descr - let pre c = preprocessComponent pkg_descr c lbi False verbosity suffixes- withAllComponentsInBuildOrder pkg_descr lbi $ \comp _ -> do- pre comp+ withAllComponentsInBuildOrder pkg_descr lbi $ \comp clbi -> do+ initialBuildSteps distPref pkg_descr lbi clbi verbosity+ preprocessComponent pkg_descr comp lbi clbi False verbosity suffixes let doExe com = case (compToExe com) of Just exe -> do let outputDir = hscolourPref distPref pkg_descr </> exeName exe </> "src"- runHsColour hscolourProg outputDir =<< getExeSourceFiles lbi exe+ runHsColour hscolourProg outputDir =<< getExeSourceFiles lbi exe clbi Nothing -> do warn (fromFlag $ hscolourVerbosity flags) "Unsupported component, skipping..."@@ -684,7 +678,7 @@ case comp of CLib lib -> do let outputDir = hscolourPref distPref pkg_descr </> "src"- runHsColour hscolourProg outputDir =<< getLibSourceFiles lbi lib+ runHsColour hscolourProg outputDir =<< getLibSourceFiles lbi lib clbi CExe _ -> when (fromFlag (hscolourExecutables flags)) $ doExe comp CTest _ -> when (fromFlag (hscolourTestSuites flags)) $ doExe comp CBench _ -> when (fromFlag (hscolourBenchmarks flags)) $ doExe comp@@ -726,24 +720,26 @@ getLibSourceFiles :: LocalBuildInfo -> Library+ -> ComponentLocalBuildInfo -> IO [(ModuleName.ModuleName, FilePath)]-getLibSourceFiles lbi lib = getSourceFiles searchpaths modules+getLibSourceFiles lbi lib clbi = getSourceFiles searchpaths modules where bi = libBuildInfo lib modules = PD.exposedModules lib ++ otherModules bi- searchpaths = autogenModulesDir lbi : buildDir lbi : hsSourceDirs bi+ searchpaths = autogenModulesDir lbi clbi : buildDir lbi : hsSourceDirs bi getExeSourceFiles :: LocalBuildInfo -> Executable+ -> ComponentLocalBuildInfo -> IO [(ModuleName.ModuleName, FilePath)]-getExeSourceFiles lbi exe = do+getExeSourceFiles lbi exe clbi = do moduleFiles <- getSourceFiles searchpaths modules srcMainPath <- findFile (hsSourceDirs bi) (modulePath exe) return ((ModuleName.main, srcMainPath) : moduleFiles) where bi = buildInfo exe modules = otherModules bi- searchpaths = autogenModulesDir lbi : exeBuildDir lbi exe : hsSourceDirs bi+ searchpaths = autogenModulesDir lbi clbi : exeBuildDir lbi exe : hsSourceDirs bi getSourceFiles :: [FilePath] -> [ModuleName.ModuleName]@@ -761,44 +757,15 @@ -- ------------------------------------------------------------------------------ -- Boilerplate Monoid instance. instance Monoid HaddockArgs where- mempty = HaddockArgs {- argInterfaceFile = mempty,- argPackageName = mempty,- argHideModules = mempty,- argIgnoreExports = mempty,- argLinkSource = mempty,- argCssFile = mempty,- argContents = mempty,- argVerbose = mempty,- argOutput = mempty,- argInterfaces = mempty,- argOutputDir = mempty,- argTitle = mempty,- argPrologue = mempty,- argGhcOptions = mempty,- argGhcLibDir = mempty,- argTargets = mempty- }- mappend a b = HaddockArgs {- argInterfaceFile = mult argInterfaceFile,- argPackageName = mult argPackageName,- argHideModules = mult argHideModules,- argIgnoreExports = mult argIgnoreExports,- argLinkSource = mult argLinkSource,- argCssFile = mult argCssFile,- argContents = mult argContents,- argVerbose = mult argVerbose,- argOutput = mult argOutput,- argInterfaces = mult argInterfaces,- argOutputDir = mult argOutputDir,- argTitle = mult argTitle,- argPrologue = mult argPrologue,- argGhcOptions = mult argGhcOptions,- argGhcLibDir = mult argGhcLibDir,- argTargets = mult argTargets- }- where mult f = f a `mappend` f b+ mempty = gmempty+ mappend = (Semi.<>) +instance Semigroup HaddockArgs where+ (<>) = gmappend+ instance Monoid Directory where mempty = Dir "."- mappend (Dir m) (Dir n) = Dir $ m </> n+ mappend = (Semi.<>)++instance Semigroup Directory where+ Dir m <> Dir n = Dir $ m </> n
cabal/Cabal/Distribution/Simple/HaskellSuite.hs view
@@ -1,10 +1,6 @@-{-# LANGUAGE CPP #-} module Distribution.Simple.HaskellSuite where import Control.Monad-#if __GLASGOW_HASKELL__ < 710-import Control.Applicative-#endif import Data.Maybe import Data.Version import qualified Data.Map as M (empty)@@ -24,7 +20,6 @@ import Distribution.Compat.Exception import Language.Haskell.Extension import Distribution.Simple.Program.Builtin- (haskellSuiteProgram, haskellSuitePkgProgram) configure :: Verbosity -> Maybe FilePath -> Maybe FilePath@@ -60,7 +55,7 @@ let haskellSuiteProgram' = haskellSuiteProgram- { programFindLocation = \v _p -> findProgramLocation v hcPath }+ { programFindLocation = \v p -> findProgramOnSearchPath v p hcPath } -- NB: cannot call requireProgram right away — it'd think that -- the program is already configured and won't reconfigure it again.@@ -106,7 +101,7 @@ getExtensions :: Verbosity -> ConfiguredProgram -> IO [(Extension, Compiler.Flag)] getExtensions verbosity prog = do extStrs <-- lines <$>+ lines `fmap` rawSystemStdout verbosity (programPath prog) ["--supported-extensions"] return [ (ext, "-X" ++ display ext) | Just ext <- map simpleParse extStrs ]@@ -114,7 +109,7 @@ getLanguages :: Verbosity -> ConfiguredProgram -> IO [(Language, Compiler.Flag)] getLanguages verbosity prog = do langStrs <-- lines <$>+ lines `fmap` rawSystemStdout verbosity (programPath prog) ["--supported-languages"] return [ (ext, "-G" ++ display ext) | Just ext <- map simpleParse langStrs ]@@ -170,7 +165,7 @@ runDbProgram verbosity haskellSuiteProgram conf $ [ "compile", "--build-dir", odir ] ++ concat [ ["-i", d] | d <- srcDirs ] ++- concat [ ["-I", d] | d <- [autogenModulesDir lbi, odir] ++ includeDirs bi ] +++ concat [ ["-I", d] | d <- [autogenModulesDir lbi clbi, odir] ++ includeDirs bi ] ++ [ packageDbOpt pkgDb | pkgDb <- dbStack ] ++ [ "--package-name", display pkgid ] ++ concat [ ["--package-id", display ipkgid ]@@ -190,8 +185,9 @@ -> FilePath -- ^Build location -> PackageDescription -> Library+ -> ComponentLocalBuildInfo -> IO ()-installLib verbosity lbi targetDir dynlibTargetDir builtDir pkg lib = do+installLib verbosity lbi targetDir dynlibTargetDir builtDir pkg lib _clbi = do let conf = withPrograms lbi runDbProgram verbosity haskellSuitePkgProgram conf $ [ "install-library"@@ -203,14 +199,12 @@ registerPackage :: Verbosity- -> InstalledPackageInfo- -> PackageDescription- -> LocalBuildInfo- -> Bool+ -> ProgramConfiguration -> PackageDBStack+ -> InstalledPackageInfo -> IO ()-registerPackage verbosity installedPkgInfo _pkg lbi _inplace packageDbs = do- (hspkg, _) <- requireProgram verbosity haskellSuitePkgProgram (withPrograms lbi)+registerPackage verbosity progdb packageDbs installedPkgInfo = do+ (hspkg, _) <- requireProgram verbosity haskellSuitePkgProgram progdb runProgramInvocation verbosity $ (programInvocation hspkg
cabal/Cabal/Distribution/Simple/Install.hs view
@@ -16,13 +16,9 @@ install, ) where -import Distribution.PackageDescription (- PackageDescription(..), BuildInfo(..), Library(..),- hasLibs, withLib, hasExes, withExe )+import Distribution.PackageDescription import Distribution.Package (Package(..))-import Distribution.Simple.LocalBuildInfo (- LocalBuildInfo(..), InstallDirs(..), absoluteInstallDirs,- substPathTemplate, withLibLBI)+import Distribution.Simple.LocalBuildInfo import Distribution.Simple.BuildPaths (haddockName, haddockPref) import Distribution.Simple.Utils ( createDirectoryIfMissingVerbose@@ -31,6 +27,7 @@ import Distribution.Simple.Compiler ( CompilerFlavor(..), compilerFlavor ) import Distribution.Simple.Setup (CopyFlags(..), fromFlag)+import Distribution.Simple.BuildTarget import qualified Distribution.Simple.GHC as GHC import qualified Distribution.Simple.GHCJS as GHCJS@@ -51,6 +48,9 @@ -- |Perform the \"@.\/setup install@\" and \"@.\/setup copy@\" -- actions. Move files into place based on the prefix argument.+--+-- This does NOT register libraries, you should call 'register'+-- to do that. install :: PackageDescription -- ^information from the .cabal file -> LocalBuildInfo -- ^information from the configure step@@ -60,33 +60,41 @@ let distPref = fromFlag (copyDistPref flags) verbosity = fromFlag (copyVerbosity flags) copydest = fromFlag (copyDest flags)- installDirs@(InstallDirs {- bindir = binPref,- libdir = libPref,--- dynlibdir = dynlibPref, --see TODO below+ -- This is a bit of a hack, to handle files which are not+ -- per-component (data files and Haddock files.)+ InstallDirs { datadir = dataPref,+ -- NB: The situation with Haddock is a bit delicate. On the+ -- one hand, the easiest to understand Haddock documentation+ -- path is pkgname-0.1, which means it's per-package (not+ -- per-component). But this means that it's impossible to+ -- install Haddock documentation for internal libraries. We'll+ -- keep this constraint for now; this means you can't use+ -- Cabal to Haddock internal libraries. This does not seem+ -- like a big problem. docdir = docPref, htmldir = htmlPref,- haddockdir = interfacePref,- includedir = incPref})+ haddockdir = interfacePref}+ -- Notice use of 'absoluteInstallDirs' (not the+ -- per-component variant). This means for non-library+ -- packages we'll just pick a nondescriptive foo-0.1 = absoluteInstallDirs pkg_descr lbi copydest - --TODO: decide if we need the user to be able to control the libdir- -- for shared libs independently of the one for static libs. If so- -- it should also have a flag in the command line UI- -- For the moment use dynlibdir = libdir- dynlibPref = libPref- progPrefixPref = substPathTemplate (packageId pkg_descr) lbi (progPrefix lbi)- progSuffixPref = substPathTemplate (packageId pkg_descr) lbi (progSuffix lbi)- unless (hasLibs pkg_descr || hasExes pkg_descr) $ die "No executables and no library found. Nothing to do."++ targets <- readBuildTargets pkg_descr (copyArgs flags)+ targets' <- checkBuildTargets verbosity pkg_descr targets++ -- Install (package-global) data files+ installDataFiles verbosity pkg_descr dataPref++ -- Install (package-global) Haddock files+ -- TODO: these should be done per-library docExists <- doesDirectoryExist $ haddockPref distPref pkg_descr info verbosity ("directory " ++ haddockPref distPref pkg_descr ++ " does exist: " ++ show docExists) - installDataFiles verbosity pkg_descr dataPref- when docExists $ do createDirectoryIfMissingVerbose verbosity True htmlPref installDirectoryContents verbosity@@ -114,45 +122,74 @@ [ installOrdinaryFile verbosity lfile (docPref </> takeFileName lfile) | lfile <- lfiles ] - let buildPref = buildDir lbi- when (hasLibs pkg_descr) $- notice verbosity ("Installing library in " ++ libPref)- when (hasExes pkg_descr) $ do- notice verbosity ("Installing executable(s) in " ++ binPref)+ -- It's not necessary to do these in build-order, but it's harmless+ withComponentsInBuildOrder pkg_descr lbi (map fst targets') $ \comp clbi ->+ copyComponent verbosity pkg_descr lbi comp clbi copydest++copyComponent :: Verbosity -> PackageDescription+ -> LocalBuildInfo -> Component -> ComponentLocalBuildInfo+ -> CopyDest+ -> IO ()+copyComponent verbosity pkg_descr lbi (CLib lib) clbi copydest = do+ let InstallDirs{+ libdir = libPref,+ includedir = incPref+ } = absoluteComponentInstallDirs pkg_descr lbi (componentUnitId clbi) copydest+ buildPref = componentBuildDir lbi clbi+ -- TODO: decide if we need the user to be able to control the libdir+ -- for shared libs independently of the one for static libs. If so+ -- it should also have a flag in the command line UI+ -- For the moment use dynlibdir = libdir+ dynlibPref = libPref++ if componentUnitId clbi == localUnitId lbi+ then notice verbosity ("Installing library in " ++ libPref)+ else notice verbosity ("Installing internal library " ++ libName lib ++ " in " ++ libPref)++ -- install include files for all compilers - they may be needed to compile+ -- haskell files (using the CPP extension)+ installIncludeFiles verbosity lib incPref++ case compilerFlavor (compiler lbi) of+ GHC -> GHC.installLib verbosity lbi libPref dynlibPref buildPref pkg_descr lib clbi+ GHCJS -> GHCJS.installLib verbosity lbi libPref dynlibPref buildPref pkg_descr lib clbi+ LHC -> LHC.installLib verbosity lbi libPref dynlibPref buildPref pkg_descr lib clbi+ JHC -> JHC.installLib verbosity lbi libPref dynlibPref buildPref pkg_descr lib clbi+ UHC -> UHC.installLib verbosity lbi libPref dynlibPref buildPref pkg_descr lib clbi+ HaskellSuite _ -> HaskellSuite.installLib+ verbosity lbi libPref dynlibPref buildPref pkg_descr lib clbi+ _ -> die $ "installing with "+ ++ display (compilerFlavor (compiler lbi))+ ++ " is not implemented"++copyComponent verbosity pkg_descr lbi (CExe exe) clbi copydest = do+ let installDirs@InstallDirs {+ bindir = binPref+ } = absoluteComponentInstallDirs pkg_descr lbi (componentUnitId clbi) copydest+ -- the installers know how to find the actual location of the+ -- binaries+ buildPref = buildDir lbi+ uid = componentUnitId clbi+ progPrefixPref = substPathTemplate (packageId pkg_descr) lbi uid (progPrefix lbi)+ progSuffixPref = substPathTemplate (packageId pkg_descr) lbi uid (progSuffix lbi)+ notice verbosity ("Installing executable " ++ exeName exe ++ " in " ++ binPref) inPath <- isInSearchPath binPref when (not inPath) $ warn verbosity ("The directory " ++ binPref ++ " is not in the system search path.")-- -- install include files for all compilers - they may be needed to compile- -- haskell files (using the CPP extension)- when (hasLibs pkg_descr) $ installIncludeFiles verbosity pkg_descr incPref+ case compilerFlavor (compiler lbi) of+ GHC -> GHC.installExe verbosity lbi installDirs buildPref (progPrefixPref, progSuffixPref) pkg_descr exe+ GHCJS -> GHCJS.installExe verbosity lbi installDirs buildPref (progPrefixPref, progSuffixPref) pkg_descr exe+ LHC -> LHC.installExe verbosity lbi installDirs buildPref (progPrefixPref, progSuffixPref) pkg_descr exe+ JHC -> JHC.installExe verbosity binPref buildPref (progPrefixPref, progSuffixPref) pkg_descr exe+ UHC -> return ()+ HaskellSuite {} -> return ()+ _ -> die $ "installing with "+ ++ display (compilerFlavor (compiler lbi))+ ++ " is not implemented" - case compilerFlavor (compiler lbi) of- GHC -> do withLibLBI pkg_descr lbi $- GHC.installLib verbosity lbi libPref dynlibPref buildPref pkg_descr- withExe pkg_descr $- GHC.installExe verbosity lbi installDirs buildPref (progPrefixPref, progSuffixPref) pkg_descr- GHCJS-> do withLibLBI pkg_descr lbi $- GHCJS.installLib verbosity lbi libPref dynlibPref buildPref pkg_descr- withExe pkg_descr $- GHCJS.installExe verbosity lbi installDirs buildPref (progPrefixPref, progSuffixPref) pkg_descr- LHC -> do withLibLBI pkg_descr lbi $- LHC.installLib verbosity lbi libPref dynlibPref buildPref pkg_descr- withExe pkg_descr $- LHC.installExe verbosity lbi installDirs buildPref (progPrefixPref, progSuffixPref) pkg_descr- JHC -> do withLib pkg_descr $- JHC.installLib verbosity libPref buildPref pkg_descr- withExe pkg_descr $- JHC.installExe verbosity binPref buildPref (progPrefixPref, progSuffixPref) pkg_descr- UHC -> do withLib pkg_descr $ UHC.installLib verbosity lbi libPref dynlibPref buildPref pkg_descr- HaskellSuite {} ->- withLib pkg_descr $- HaskellSuite.installLib verbosity lbi libPref dynlibPref buildPref pkg_descr- _ -> die $ "installing with "- ++ display (compilerFlavor (compiler lbi))- ++ " is not implemented"- -- register step should be performed by caller.+-- Nothing to do for benchmark/testsuite+copyComponent _ _ _ _ _ _ = return () -- | Install the files listed in data-files --@@ -167,26 +204,23 @@ (destDataDir </> file') | file' <- files ] --- | Install the files listed in install-includes+-- | Install the files listed in install-includes for a library ---installIncludeFiles :: Verbosity -> PackageDescription -> FilePath -> IO ()-installIncludeFiles verbosity- PackageDescription { library = Just lib } destIncludeDir = do-- incs <- mapM (findInc relincdirs) (installIncludes lbi)- sequence_- [ do createDirectoryIfMissingVerbose verbosity True destDir- installOrdinaryFile verbosity srcFile destFile- | (relFile, srcFile) <- incs- , let destFile = destIncludeDir </> relFile- destDir = takeDirectory destFile ]+installIncludeFiles :: Verbosity -> Library -> FilePath -> IO ()+installIncludeFiles verbosity lib destIncludeDir = do+ let relincdirs = "." : filter (not.isAbsolute) (includeDirs lbi)+ lbi = libBuildInfo lib+ incs <- mapM (findInc relincdirs) (installIncludes lbi)+ sequence_+ [ do createDirectoryIfMissingVerbose verbosity True destDir+ installOrdinaryFile verbosity srcFile destFile+ | (relFile, srcFile) <- incs+ , let destFile = destIncludeDir </> relFile+ destDir = takeDirectory destFile ] where- relincdirs = "." : filter (not.isAbsolute) (includeDirs lbi)- lbi = libBuildInfo lib findInc [] file = die ("can't find include file " ++ file) findInc (dir:dirs) file = do let path = dir </> file exists <- doesFileExist path if exists then return (file, path) else findInc dirs file-installIncludeFiles _ _ _ = die "installIncludeFiles: Can't happen?"
cabal/Cabal/Distribution/Simple/InstallDirs.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE ForeignFunctionInterface #-}+{-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveGeneric #-} -----------------------------------------------------------------------------@@ -46,25 +47,19 @@ import Distribution.Compat.Binary (Binary)+import Distribution.Compat.Semigroup as Semi+import Distribution.Package+import Distribution.System+import Distribution.Compiler+import Distribution.Text+ import Data.List (isPrefixOf) import Data.Maybe (fromMaybe)-#if __GLASGOW_HASKELL__ < 710-import Data.Monoid (Monoid(..))-#endif import GHC.Generics (Generic) import System.Directory (getAppUserDataDirectory) import System.FilePath ((</>), isPathSeparator, pathSeparator) import System.FilePath (dropDrive) -import Distribution.Package- ( PackageIdentifier, packageName, packageVersion, LibraryName )-import Distribution.System- ( OS(..), buildOS, Platform(..) )-import Distribution.Compiler- ( AbiTag(..), abiTagString, CompilerInfo(..), CompilerFlavor(..) )-import Distribution.Text- ( display )- #if mingw32_HOST_OS import Foreign import Foreign.C@@ -96,46 +91,16 @@ htmldir :: dir, haddockdir :: dir, sysconfdir :: dir- } deriving (Generic, Read, Show)+ } deriving (Eq, Read, Show, Functor, Generic) instance Binary dir => Binary (InstallDirs dir) -instance Functor InstallDirs where- fmap f dirs = InstallDirs {- prefix = f (prefix dirs),- bindir = f (bindir dirs),- libdir = f (libdir dirs),- libsubdir = f (libsubdir dirs),- dynlibdir = f (dynlibdir dirs),- libexecdir = f (libexecdir dirs),- includedir = f (includedir dirs),- datadir = f (datadir dirs),- datasubdir = f (datasubdir dirs),- docdir = f (docdir dirs),- mandir = f (mandir dirs),- htmldir = f (htmldir dirs),- haddockdir = f (haddockdir dirs),- sysconfdir = f (sysconfdir dirs)- }+instance (Semigroup dir, Monoid dir) => Monoid (InstallDirs dir) where+ mempty = gmempty+ mappend = (Semi.<>) -instance Monoid dir => Monoid (InstallDirs dir) where- mempty = InstallDirs {- prefix = mempty,- bindir = mempty,- libdir = mempty,- libsubdir = mempty,- dynlibdir = mempty,- libexecdir = mempty,- includedir = mempty,- datadir = mempty,- datasubdir = mempty,- docdir = mempty,- mandir = mempty,- htmldir = mempty,- haddockdir = mempty,- sysconfdir = mempty- }- mappend = combineInstallDirs mappend+instance Semigroup dir => Semigroup (InstallDirs dir) where+ (<>) = gmappend combineInstallDirs :: (a -> b -> c) -> InstallDirs a@@ -287,7 +252,7 @@ -- substituting for all the variables in the abstract paths, to get real -- absolute path. absoluteInstallDirs :: PackageIdentifier- -> LibraryName+ -> UnitId -> CompilerInfo -> CopyDest -> Platform@@ -317,7 +282,7 @@ -- independent\" package). -- prefixRelativeInstallDirs :: PackageIdentifier- -> LibraryName+ -> UnitId -> CompilerInfo -> Platform -> InstallDirTemplates@@ -349,7 +314,8 @@ -- | An abstract path, possibly containing variables that need to be -- substituted for to get a real 'FilePath'. ---newtype PathTemplate = PathTemplate [PathComponent] deriving (Eq, Generic, Ord)+newtype PathTemplate = PathTemplate [PathComponent]+ deriving (Eq, Ord, Generic) instance Binary PathTemplate @@ -372,7 +338,7 @@ | PkgNameVar -- ^ The @$pkg@ package name path variable | PkgVerVar -- ^ The @$version@ package version path variable | PkgIdVar -- ^ The @$pkgid@ package Id path variable, eg @foo-1.0@- | LibNameVar -- ^ The @$libname@ expanded package key path variable+ | LibNameVar -- ^ The @$libname@ path variable | CompilerVar -- ^ The compiler name and version, eg @ghc-6.6.1@ | OSVar -- ^ The operating system name, eg @windows@ or @linux@ | ArchVar -- ^ The CPU architecture name, eg @i386@ or @x86_64@@@ -415,7 +381,7 @@ -- | The initial environment has all the static stuff but no paths initialPathTemplateEnv :: PackageIdentifier- -> LibraryName+ -> UnitId -> CompilerInfo -> Platform -> PathTemplateEnv@@ -425,7 +391,7 @@ ++ platformTemplateEnv platform ++ abiTemplateEnv compiler platform -packageTemplateEnv :: PackageIdentifier -> LibraryName -> PathTemplateEnv+packageTemplateEnv :: PackageIdentifier -> UnitId -> PathTemplateEnv packageTemplateEnv pkgId libname = [(PkgNameVar, PathTemplate [Ordinary $ display (packageName pkgId)]) ,(PkgVerVar, PathTemplate [Ordinary $ display (packageVersion pkgId)])@@ -478,7 +444,7 @@ instance Show PathTemplateVariable where show PrefixVar = "prefix"- show LibNameVar = "libname"+ show LibNameVar = "libname" show BindirVar = "bindir" show LibdirVar = "libdir" show LibsubdirVar = "libsubdir"@@ -515,8 +481,8 @@ ,("docdir", DocdirVar) ,("htmldir", HtmldirVar) ,("pkgid", PkgIdVar)- ,("pkgkey", LibNameVar) -- backwards compatibility ,("libname", LibNameVar)+ ,("pkgkey", LibNameVar) -- backwards compatibility ,("pkg", PkgNameVar) ,("version", PkgVerVar) ,("compiler", CompilerVar)
cabal/Cabal/Distribution/Simple/JHC.hs view
@@ -16,40 +16,23 @@ installLib, installExe ) where -import Distribution.PackageDescription as PD- ( PackageDescription(..), BuildInfo(..), Executable(..)- , Library(..), libModules, hcOptions, usedExtensions )+import Distribution.PackageDescription as PD hiding (Flag) import Distribution.InstalledPackageInfo- ( emptyInstalledPackageInfo, ) import qualified Distribution.InstalledPackageInfo as InstalledPackageInfo import Distribution.Simple.PackageIndex (InstalledPackageIndex) import qualified Distribution.Simple.PackageIndex as PackageIndex import Distribution.Simple.LocalBuildInfo- ( LocalBuildInfo(..), ComponentLocalBuildInfo(..) ) import Distribution.Simple.BuildPaths- ( autogenModulesDir, exeExtension ) import Distribution.Simple.Compiler- ( CompilerFlavor(..), CompilerId(..), Compiler(..), AbiTag(..)- , PackageDBStack, Flag, languageToFlags, extensionsToFlags ) import Language.Haskell.Extension- ( Language(Haskell98), Extension(..), KnownExtension(..)) import Distribution.Simple.Program- ( ConfiguredProgram(..), jhcProgram, ProgramConfiguration- , userMaybeSpecifyPath, requireProgramVersion, lookupProgram- , rawSystemProgram, rawSystemProgramStdoutConf ) import Distribution.Version- ( Version(..), orLaterVersion ) import Distribution.Package- ( Package(..), InstalledPackageId(InstalledPackageId),- pkgName, pkgVersion, ) import Distribution.Simple.Utils- ( createDirectoryIfMissingVerbose, writeFileAtomic- , installOrdinaryFile, installExecutableFile- , intercalate )-import System.FilePath ( (</>) ) import Distribution.Verbosity import Distribution.Text- ( Text(parse), display )++import System.FilePath ( (</>) ) import Distribution.Compat.ReadP ( readP_to_S, string, skipSpaces ) import Distribution.System ( Platform )@@ -57,7 +40,6 @@ import Data.List ( nub ) import Data.Char ( isSpace ) import qualified Data.Map as M ( empty )-import Data.Maybe ( fromMaybe ) import qualified Data.ByteString.Lazy.Char8 as BS.Char8 @@ -116,8 +98,7 @@ return $ PackageIndex.fromList $ map (\p -> emptyInstalledPackageInfo {- InstalledPackageInfo.installedPackageId =- InstalledPackageId (display p),+ InstalledPackageInfo.installedUnitId = mkLegacyUnitId p, InstalledPackageInfo.sourcePackageId = p }) $ concatMap parseLine $@@ -162,7 +143,7 @@ ++ extensionsToFlags (compiler lbi) (usedExtensions bi) ++ ["--noauto","-i-"] ++ concat [["-i", l] | l <- nub (hsSourceDirs bi)]- ++ ["-i", autogenModulesDir lbi]+ ++ ["-i", autogenModulesDir lbi clbi] ++ ["-optc" ++ opt | opt <- PD.ccOptions bi] -- It would be better if JHC would accept package names with versions, -- but JHC-0.7.2 doesn't accept this.@@ -173,7 +154,10 @@ jhcPkgConf :: PackageDescription -> String jhcPkgConf pd = let sline name sel = name ++ ": "++sel pd- lib = fromMaybe (error "no library available") . library+ lib pd' = case libraries pd' of+ [lib'] -> lib'+ [] -> error "no library available"+ _ -> error "JHC does not support multiple libraries (yet)" comma = intercalate "," . map display in unlines [sline "name" (display . pkgName . packageId) ,sline "version" (display . pkgVersion . packageId)@@ -181,8 +165,16 @@ ,sline "hidden-modules" (comma . otherModules . libBuildInfo . lib) ] -installLib :: Verbosity -> FilePath -> FilePath -> PackageDescription -> Library -> IO ()-installLib verb dest build_dir pkg_descr _ = do+installLib :: Verbosity+ -> LocalBuildInfo+ -> FilePath+ -> FilePath+ -> FilePath+ -> PackageDescription+ -> Library+ -> ComponentLocalBuildInfo+ -> IO ()+installLib verb _lbi dest _dyn_dest build_dir pkg_descr _lib _clbi = do let p = display (packageId pkg_descr)++".hl" createDirectoryIfMissingVerbose verb True dest installOrdinaryFile verb (build_dir </> p) (dest </> p)
cabal/Cabal/Distribution/Simple/LHC.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE CPP #-} ----------------------------------------------------------------------------- -- | -- Module : Distribution.Simple.LHC@@ -41,66 +40,37 @@ ghcVerbosityOptions ) where -import Distribution.PackageDescription as PD- ( PackageDescription(..), BuildInfo(..), Executable(..)- , Library(..), libModules, hcOptions, hcProfOptions, hcSharedOptions- , usedExtensions, allExtensions )+import Distribution.PackageDescription as PD hiding (Flag) import Distribution.InstalledPackageInfo- ( InstalledPackageInfo- , parseInstalledPackageInfo ) import qualified Distribution.InstalledPackageInfo as InstalledPackageInfo- ( InstalledPackageInfo(..) ) import Distribution.Simple.PackageIndex import qualified Distribution.Simple.PackageIndex as PackageIndex-import Distribution.ParseUtils ( ParseResult(..) ) import Distribution.Simple.LocalBuildInfo- ( LocalBuildInfo(..), ComponentLocalBuildInfo(..) )-import Distribution.Simple.InstallDirs import Distribution.Simple.BuildPaths import Distribution.Simple.Utils import Distribution.Package- ( Package(..), LibraryName, getHSLibraryName ) import qualified Distribution.ModuleName as ModuleName import Distribution.Simple.Program- ( Program(..), ConfiguredProgram(..), ProgramConfiguration- , ProgramSearchPath, ProgramLocation(..)- , rawSystemProgram, rawSystemProgramConf- , rawSystemProgramStdout, rawSystemProgramStdoutConf- , requireProgramVersion- , userMaybeSpecifyPath, programPath, lookupProgram, addKnownProgram- , arProgram, ldProgram- , gccProgram, stripProgram- , lhcProgram, lhcPkgProgram ) import qualified Distribution.Simple.Program.HcPkg as HcPkg import Distribution.Simple.Compiler- ( CompilerFlavor(..), CompilerId(..), Compiler(..), compilerVersion- , OptimisationLevel(..), PackageDB(..), PackageDBStack, AbiTag(..)- , Flag, languageToFlags, extensionsToFlags ) import Distribution.Version- ( Version(..), orLaterVersion )-import Distribution.System- ( OS(..), buildOS ) import Distribution.Verbosity import Distribution.Text- ( display, simpleParse )+import Distribution.Compat.Exception+import Distribution.System import Language.Haskell.Extension- ( Language(Haskell98), Extension(..), KnownExtension(..) ) import Control.Monad ( unless, when )+import Data.Monoid as Mon import Data.List import qualified Data.Map as M ( empty ) import Data.Maybe ( catMaybes )-#if __GLASGOW_HASKELL__ < 710-import Data.Monoid ( Monoid(..) )-#endif import System.Directory ( removeFile, renameFile, getDirectoryContents, doesFileExist, getTemporaryDirectory ) import System.FilePath ( (</>), (<.>), takeExtension, takeDirectory, replaceExtension ) import System.IO (hClose, hPutStrLn)-import Distribution.Compat.Exception (catchExit, catchIO)-import Distribution.System ( Platform ) -- ----------------------------------------------------------------------------- -- Configuring@@ -149,22 +119,23 @@ programPostConf = configureGcc } . addKnownProgram ldProgram {- programFindLocation = findProg ldProgram (libDir </> "ld.exe"),+ programFindLocation = findProg ldProgram (gccLibDir </> "ld.exe"), programPostConf = configureLd } where compilerDir = takeDirectory (programPath lhcProg) baseDir = takeDirectory compilerDir- libDir = baseDir </> "gcc-lib"+ gccLibDir = baseDir </> "gcc-lib" includeDir = baseDir </> "include" </> "mingw" isWindows = case buildOS of Windows -> True; _ -> False -- on Windows finding and configuring ghc's gcc and ld is a bit special findProg :: Program -> FilePath- -> Verbosity -> ProgramSearchPath -> IO (Maybe FilePath)+ -> Verbosity -> ProgramSearchPath+ -> IO (Maybe (FilePath, [FilePath])) findProg prog location | isWindows = \verbosity searchpath -> do exists <- doesFileExist location- if exists then return (Just location)+ if exists then return (Just (location, [])) else do warn verbosity ("Couldn't find " ++ programName prog ++ " where I expected it. Trying the search path.") programFindLocation prog verbosity searchpath | otherwise = programFindLocation prog@@ -177,7 +148,7 @@ -- that means we should add this extra flag to tell ghc's gcc -- where it lives and thus where gcc can find its various files: FoundOnSystem {} -> return gccProg {- programDefaultArgs = ["-B" ++ libDir,+ programDefaultArgs = ["-B" ++ gccLibDir, "-I" ++ includeDir] } UserSpecified {} -> return gccProg@@ -230,7 +201,7 @@ pkgss <- getInstalledPackages' lhcPkg verbosity packagedbs conf let indexes = [ PackageIndex.fromList (map (substTopDir topDir) pkgs) | (_, pkgs) <- pkgss ]- return $! (mconcat indexes)+ return $! (Mon.mconcat indexes) where -- On Windows, various fields have $topdir/foo rather than full@@ -318,8 +289,8 @@ buildLib :: Verbosity -> PackageDescription -> LocalBuildInfo -> Library -> ComponentLocalBuildInfo -> IO () buildLib verbosity pkg_descr lbi lib clbi = do- let libName = componentLibraryName clbi- pref = buildDir lbi+ let lib_name = componentUnitId clbi+ pref = componentBuildDir lbi clbi pkgid = packageId pkg_descr runGhcProg = rawSystemProgramConf verbosity lhcProgram (withPrograms lbi) ifVanillaLib forceVanilla = when (forceVanilla || withVanillaLib lbi)@@ -373,10 +344,10 @@ let cObjs = map (`replaceExtension` objExtension) (cSources libBi) cSharedObjs = map (`replaceExtension` ("dyn_" ++ objExtension)) (cSources libBi) cid = compilerId (compiler lbi)- vanillaLibFilePath = libTargetDir </> mkLibName libName- profileLibFilePath = libTargetDir </> mkProfLibName libName- sharedLibFilePath = libTargetDir </> mkSharedLibName cid libName- ghciLibFilePath = libTargetDir </> mkGHCiLibName libName+ vanillaLibFilePath = libTargetDir </> mkLibName lib_name+ profileLibFilePath = libTargetDir </> mkProfLibName lib_name+ sharedLibFilePath = libTargetDir </> mkSharedLibName cid lib_name+ ghciLibFilePath = libTargetDir </> mkGHCiLibName lib_name stubObjs <- fmap catMaybes $ sequence [ findFileWithExtension [objExtension] [libTargetDir]@@ -455,7 +426,7 @@ -- This method is called iteratively by xargs. The -- output goes to <ldLibName>.tmp, and any existing file -- named <ldLibName> is included when linking. The- -- output is renamed to <libName>.+ -- output is renamed to <lib_name>. rawSystemProgramConf verbosity ldProgram (withPrograms lbi) (args ++ if exists then [ldLibName] else []) renameFile (ldLibName <.> "tmp") ldLibName@@ -525,7 +496,7 @@ ++ [srcMainFile] ++ ["-optl" ++ opt | opt <- PD.ldOptions exeBi] ++ ["-l"++lib | lib <- extraLibs exeBi]- ++ ["-L"++libDir | libDir <- extraLibDirs exeBi]+ ++ ["-L"++extraLibDir | extraLibDir <- extraLibDirs exeBi] ++ concat [["-framework", f] | f <- PD.frameworks exeBi] ++ if profExe then ["-prof",@@ -607,12 +578,12 @@ ++ ["-i"] ++ ["-i" ++ odir] ++ ["-i" ++ l | l <- nub (hsSourceDirs bi)]- ++ ["-i" ++ autogenModulesDir lbi]- ++ ["-I" ++ autogenModulesDir lbi]+ ++ ["-i" ++ autogenModulesDir lbi clbi]+ ++ ["-I" ++ autogenModulesDir lbi clbi] ++ ["-I" ++ odir] ++ ["-I" ++ dir | dir <- PD.includeDirs bi] ++ ["-optP" ++ opt | opt <- cppOptions bi]- ++ [ "-optP-include", "-optP"++ (autogenModulesDir lbi </> cppHeaderName) ]+ ++ [ "-optP-include", "-optP"++ (autogenModulesDir lbi clbi </> cppHeaderName) ] ++ [ "-#include \"" ++ inc ++ "\"" | inc <- PD.includes bi ] ++ [ "-odir", odir, "-hidir", odir ] ++ (if compilerVersion c >= Version [6,8] []@@ -682,7 +653,7 @@ _ -> ["-optc-O2"]) ++ ["-odir", odir] -mkGHCiLibName :: LibraryName -> String+mkGHCiLibName :: UnitId -> String mkGHCiLibName lib = getHSLibraryName lib <.> "o" -- -----------------------------------------------------------------------------@@ -757,11 +728,11 @@ where cid = compilerId (compiler lbi)- libName = componentLibraryName clbi- vanillaLibName = mkLibName libName- profileLibName = mkProfLibName libName- ghciLibName = mkGHCiLibName libName- sharedLibName = mkSharedLibName cid libName+ lib_name = componentUnitId clbi+ vanillaLibName = mkLibName lib_name+ profileLibName = mkProfLibName lib_name+ ghciLibName = mkGHCiLibName lib_name+ sharedLibName = mkSharedLibName cid lib_name hasLib = not $ null (libModules lib) && null (cSources (libBuildInfo lib))@@ -777,14 +748,12 @@ registerPackage :: Verbosity- -> InstalledPackageInfo- -> PackageDescription- -> LocalBuildInfo- -> Bool+ -> ProgramConfiguration -> PackageDBStack+ -> InstalledPackageInfo -> IO ()-registerPackage verbosity installedPkgInfo _pkg lbi _inplace packageDbs =- HcPkg.reregister (hcPkgInfo $ withPrograms lbi) verbosity packageDbs+registerPackage verbosity progdb packageDbs installedPkgInfo =+ HcPkg.reregister (hcPkgInfo progdb) verbosity packageDbs (Right installedPkgInfo) hcPkgInfo :: ProgramConfiguration -> HcPkg.HcPkgInfo@@ -792,7 +761,10 @@ , HcPkg.noPkgDbStack = False , HcPkg.noVerboseFlag = False , HcPkg.flagPackageConf = False- , HcPkg.useSingleFileDb = True+ , HcPkg.supportsDirDbs = True+ , HcPkg.requiresDirDbs = True+ , HcPkg.nativeMultiInstance = False -- ?+ , HcPkg.recacheMultiInstance = False -- ? } where Just lhcPkgProg = lookupProgram lhcPkgProgram conf
cabal/Cabal/Distribution/Simple/LocalBuildInfo.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE PatternGuards #-} ----------------------------------------------------------------------------- -- |@@ -20,15 +21,20 @@ module Distribution.Simple.LocalBuildInfo ( LocalBuildInfo(..), externalPackageDeps,- inplacePackageId,- localPackageKey,- localLibraryName,+ localComponentId,+ localUnitId,+ localCompatPackageKey, -- * Buildable package components Component(..), ComponentName(..),+ defaultLibName, showComponentName,+ componentNameString, ComponentLocalBuildInfo(..),+ getLocalComponent,+ componentComponentId,+ componentBuildDir, foldComponent, componentName, componentBuildInfo,@@ -39,6 +45,8 @@ pkgEnabledComponents, lookupComponent, getComponent,+ maybeGetDefaultLibraryLocalBuildInfo,+ maybeGetComponentLocalBuildInfo, getComponentLocalBuildInfo, allComponentsInBuildOrder, componentsInBuildOrder,@@ -55,6 +63,7 @@ -- * Installation directories module Distribution.Simple.InstallDirs, absoluteInstallDirs, prefixRelativeInstallDirs,+ absoluteComponentInstallDirs, prefixRelativeComponentInstallDirs, substPathTemplate ) where @@ -63,30 +72,16 @@ prefixRelativeInstallDirs, substPathTemplate, ) import qualified Distribution.Simple.InstallDirs as InstallDirs-import Distribution.Simple.Program (ProgramConfiguration)-import Distribution.InstalledPackageInfo (InstalledPackageInfo)+import Distribution.Simple.Program import Distribution.PackageDescription- ( PackageDescription(..), withLib, Library(libBuildInfo), withExe- , Executable(exeName, buildInfo), withTest, TestSuite(..)- , BuildInfo(buildable), Benchmark(..), ModuleRenaming(..) ) import qualified Distribution.InstalledPackageInfo as Installed import Distribution.Package- ( PackageId, Package(..), InstalledPackageId(..)- , PackageName, LibraryName(..), PackageKey(..) ) import Distribution.Simple.Compiler- ( Compiler, compilerInfo, PackageDBStack, DebugInfoLevel- , OptimisationLevel, ProfDetailLevel ) import Distribution.Simple.PackageIndex- ( InstalledPackageIndex, allPackages )-import Distribution.ModuleName ( ModuleName ) import Distribution.Simple.Setup- ( ConfigFlags ) import Distribution.Simple.Utils- ( shortRelativePath ) import Distribution.Text- ( display ) import Distribution.System- ( Platform (..) ) import Data.Array ((!)) import Distribution.Compat.Binary (Binary)@@ -95,7 +90,7 @@ import Data.Maybe import Data.Tree (flatten) import GHC.Generics (Generic)-import Data.Map (Map)+import System.FilePath import System.Directory (doesDirectoryExist, canonicalizePath) @@ -105,6 +100,8 @@ configFlags :: ConfigFlags, -- ^ Options passed to the configuration step. -- Needed to re-run configuration when .cabal is out of date+ flagAssignment :: FlagAssignment,+ -- ^ The final set of flags which were picked for this package extraConfigArgs :: [String], -- ^ Extra args on the command line for the configuration step. -- Needed to re-run configuration when .cabal is out of date@@ -118,18 +115,21 @@ -- ^ The platform we're building for buildDir :: FilePath, -- ^ Where to build the package.- componentsConfigs :: [(ComponentName, ComponentLocalBuildInfo, [ComponentName])],- -- ^ All the components to build, ordered by topological sort, and with their dependencies- -- over the intrapackage dependency graph+ componentsConfigs :: [(ComponentLocalBuildInfo, [UnitId])],+ -- ^ All the components to build, ordered by topological+ -- sort, and with their INTERNAL dependencies over the+ -- intrapackage dependency graph.+ -- TODO: this is assumed to be short; otherwise we want+ -- some sort of ordered map. installedPkgs :: InstalledPackageIndex, -- ^ All the info about the installed packages that the -- current package depends on (directly or indirectly).+ -- Does NOT include internal dependencies. pkgDescrFile :: Maybe FilePath, -- ^ the filename containing the .cabal file, if available localPkgDescr :: PackageDescription, -- ^ The resolved package description, that does not contain -- any conditionals.- instantiatedWith :: [(ModuleName, (InstalledPackageInfo, ModuleName))], withPrograms :: ProgramConfiguration, -- ^Location and args for all programs withPackageDB :: PackageDBStack, -- ^What package database to use, global\/user withVanillaLib:: Bool, -- ^Whether to build normal libs.@@ -152,48 +152,53 @@ instance Binary LocalBuildInfo --- | Extract the 'PackageKey' from the library component of a--- 'LocalBuildInfo' if it exists, or make a fake package key based+-- TODO: Get rid of these functions, as much as possible. They are+-- a bit useful in some cases, but you should be very careful!++-- | Extract the 'ComponentId' from the public library component of a+-- 'LocalBuildInfo' if it exists, or make a fake component ID based -- on the package ID.-localPackageKey :: LocalBuildInfo -> PackageKey-localPackageKey lbi =- foldr go (OldPackageKey (package (localPkgDescr lbi))) (componentsConfigs lbi)- where go (_, clbi, _) old_pk = case clbi of- LibComponentLocalBuildInfo { componentPackageKey = pk } -> pk- _ -> old_pk+localComponentId :: LocalBuildInfo -> ComponentId+localComponentId lbi+ = case localUnitId lbi of+ SimpleUnitId cid -> cid --- | Extract the 'LibraryName' from the library component of a--- 'LocalBuildInfo' if it exists, or make a library name based+-- | Extract the 'UnitId' from the library component of a+-- 'LocalBuildInfo' if it exists, or make a fake unit ID based on+-- the package ID.+localUnitId :: LocalBuildInfo -> UnitId+localUnitId lbi+ = case maybeGetDefaultLibraryLocalBuildInfo lbi of+ Just LibComponentLocalBuildInfo { componentUnitId = uid } -> uid+ -- Something fake:+ _ -> mkLegacyUnitId (package (localPkgDescr lbi))++-- | Extract the compatibility package key from the public library component of a+-- 'LocalBuildInfo' if it exists, or make a fake package key based -- on the package ID.-localLibraryName :: LocalBuildInfo -> LibraryName-localLibraryName lbi =- foldr go (LibraryName (display (package (localPkgDescr lbi)))) (componentsConfigs lbi)- where go (_, clbi, _) old_n = case clbi of- LibComponentLocalBuildInfo { componentLibraryName = n } -> n- _ -> old_n+localCompatPackageKey :: LocalBuildInfo -> String+localCompatPackageKey lbi =+ case maybeGetDefaultLibraryLocalBuildInfo lbi of+ Just LibComponentLocalBuildInfo { componentCompatPackageKey = pk } -> pk+ -- Something fake:+ _ -> display (package (localPkgDescr lbi)) -- | External package dependencies for the package as a whole. This is the -- union of the individual 'componentPackageDeps', less any internal deps.-externalPackageDeps :: LocalBuildInfo -> [(InstalledPackageId, PackageId)]+externalPackageDeps :: LocalBuildInfo -> [(UnitId, PackageId)] externalPackageDeps lbi = -- TODO: what about non-buildable components? nub [ (ipkgid, pkgid)- | (_,clbi,_) <- componentsConfigs lbi+ | (clbi,_) <- componentsConfigs lbi , (ipkgid, pkgid) <- componentPackageDeps clbi- , not (internal pkgid) ]+ , not (internal ipkgid) ] where -- True if this dependency is an internal one (depends on the library -- defined in the same package).- internal pkgid = pkgid == packageId (localPkgDescr lbi)---- | The installed package Id we use for local packages registered in the local--- package db. This is what is used for intra-package deps between components.----inplacePackageId :: PackageId -> InstalledPackageId-inplacePackageId pkgid = InstalledPackageId (display pkgid ++ "-inplace")+ internal ipkgid = any ((==ipkgid) . componentUnitId . fst) (componentsConfigs lbi) -- -------------------------------------------------------------------------------- Buildable components+-- Source-representation of buildable components data Component = CLib Library | CExe Executable@@ -201,48 +206,23 @@ | CBench Benchmark deriving (Show, Eq, Read) -data ComponentName = CLibName -- currently only a single lib- | CExeName String- | CTestName String- | CBenchName String- deriving (Eq, Generic, Ord, Read, Show)--instance Binary ComponentName+-- | This gets the 'String' component name. In fact, it is+-- guaranteed to uniquely identify a component, returning+-- @Nothing@ if the 'ComponentName' was for the public+-- library (which CAN conflict with an executable name.)+componentNameString :: PackageName -> ComponentName -> Maybe String+componentNameString (PackageName pkg_name) (CLibName n) | pkg_name == n = Nothing+componentNameString _ (CLibName n) = Just n+componentNameString _ (CExeName n) = Just n+componentNameString _ (CTestName n) = Just n+componentNameString _ (CBenchName n) = Just n showComponentName :: ComponentName -> String-showComponentName CLibName = "library"+showComponentName (CLibName name) = "library '" ++ name ++ "'" showComponentName (CExeName name) = "executable '" ++ name ++ "'" showComponentName (CTestName name) = "test suite '" ++ name ++ "'" showComponentName (CBenchName name) = "benchmark '" ++ name ++ "'" -data ComponentLocalBuildInfo- = LibComponentLocalBuildInfo {- -- | Resolved internal and external package dependencies for this component.- -- The 'BuildInfo' specifies a set of build dependencies that must be- -- satisfied in terms of version ranges. This field fixes those dependencies- -- to the specific versions available on this machine for this compiler.- componentPackageDeps :: [(InstalledPackageId, PackageId)],- componentPackageKey :: PackageKey,- componentLibraryName :: LibraryName,- componentExposedModules :: [Installed.ExposedModule],- componentPackageRenaming :: Map PackageName ModuleRenaming- }- | ExeComponentLocalBuildInfo {- componentPackageDeps :: [(InstalledPackageId, PackageId)],- componentPackageRenaming :: Map PackageName ModuleRenaming- }- | TestComponentLocalBuildInfo {- componentPackageDeps :: [(InstalledPackageId, PackageId)],- componentPackageRenaming :: Map PackageName ModuleRenaming- }- | BenchComponentLocalBuildInfo {- componentPackageDeps :: [(InstalledPackageId, PackageId)],- componentPackageRenaming :: Map PackageName ModuleRenaming- }- deriving (Generic, Read, Show)--instance Binary ComponentLocalBuildInfo- foldComponent :: (Library -> a) -> (Executable -> a) -> (TestSuite -> a)@@ -260,7 +240,7 @@ componentName :: Component -> ComponentName componentName =- foldComponent (const CLibName)+ foldComponent (CLibName . libName) (CExeName . exeName) (CTestName . testName) (CBenchName . benchmarkName)@@ -269,7 +249,7 @@ -- pkgComponents :: PackageDescription -> [Component] pkgComponents pkg =- [ CLib lib | Just lib <- [library pkg] ]+ [ CLib lib | lib <- libraries pkg ] ++ [ CExe exe | exe <- executables pkg ] ++ [ CTest tst | tst <- testSuites pkg ] ++ [ CBench bm | bm <- benchmarks pkg ]@@ -302,8 +282,9 @@ componentDisabledReason _ = Nothing lookupComponent :: PackageDescription -> ComponentName -> Maybe Component-lookupComponent pkg CLibName =- fmap CLib $ library pkg+lookupComponent pkg (CLibName "") = lookupComponent pkg (defaultLibName (package pkg))+lookupComponent pkg (CLibName name) =+ fmap CLib $ find ((name ==) . libName) (libraries pkg) lookupComponent pkg (CExeName name) = fmap CExe $ find ((name ==) . exeName) (executables pkg) lookupComponent pkg (CTestName name) =@@ -321,43 +302,149 @@ error $ "internal error: the package description contains no " ++ "component corresponding to " ++ show cname +-- -----------------------------------------------------------------------------+-- Configuration information of buildable components +data ComponentLocalBuildInfo+ = LibComponentLocalBuildInfo {+ -- | It would be very convenient to store the literal Library here,+ -- but if we do that, it will get serialized (via the Binary)+ -- instance twice. So instead we just provide the ComponentName,+ -- which can be used to find the Component in the+ -- PackageDescription. NB: eventually, this will NOT uniquely+ -- identify the ComponentLocalBuildInfo.+ componentLocalName :: ComponentName,+ -- | Resolved internal and external package dependencies for this component.+ -- The 'BuildInfo' specifies a set of build dependencies that must be+ -- satisfied in terms of version ranges. This field fixes those dependencies+ -- to the specific versions available on this machine for this compiler.+ componentPackageDeps :: [(UnitId, PackageId)],+ -- | The computed 'UnitId' which uniquely identifies this+ -- component.+ componentUnitId :: UnitId,+ -- | Compatibility "package key" that we pass to older versions of GHC.+ componentCompatPackageKey :: String,+ -- | Compatability "package name" that we register this component as.+ componentCompatPackageName :: PackageName,+ -- | A list of exposed modules (either defined in this component,+ -- or reexported from another component.)+ componentExposedModules :: [Installed.ExposedModule],+ -- | Convenience field, specifying whether or not this is the+ -- "public library" that has the same name as the package.+ componentIsPublic :: Bool,+ -- | The set of packages that are brought into scope during+ -- compilation, including a 'ModuleRenaming' which may used+ -- to hide or rename modules. This is what gets translated into+ -- @-package-id@ arguments. This is a modernized version of+ -- 'componentPackageDeps', which is kept around for BC purposes.+ componentIncludes :: [(UnitId, ModuleRenaming)]+ }+ | ExeComponentLocalBuildInfo {+ componentLocalName :: ComponentName,+ componentUnitId :: UnitId,+ componentPackageDeps :: [(UnitId, PackageId)],+ componentIncludes :: [(UnitId, ModuleRenaming)]+ }+ | TestComponentLocalBuildInfo {+ componentLocalName :: ComponentName,+ componentUnitId :: UnitId,+ componentPackageDeps :: [(UnitId, PackageId)],+ componentIncludes :: [(UnitId, ModuleRenaming)]+ }+ | BenchComponentLocalBuildInfo {+ componentLocalName :: ComponentName,+ componentUnitId :: UnitId,+ componentPackageDeps :: [(UnitId, PackageId)],+ componentIncludes :: [(UnitId, ModuleRenaming)]+ }+ deriving (Generic, Read, Show)++instance Binary ComponentLocalBuildInfo++getLocalComponent :: PackageDescription -> ComponentLocalBuildInfo -> Component+getLocalComponent pkg_descr clbi = getComponent pkg_descr (componentLocalName clbi)++componentComponentId :: ComponentLocalBuildInfo -> ComponentId+componentComponentId clbi = case componentUnitId clbi of+ SimpleUnitId cid -> cid++componentBuildDir :: LocalBuildInfo -> ComponentLocalBuildInfo -> FilePath+componentBuildDir lbi LibComponentLocalBuildInfo{ componentIsPublic = True }+ = buildDir lbi+-- For now, we assume that libraries/executables/test-suites/benchmarks+-- are only ever built once. With Backpack, we need a special case for+-- libraries so that we can handle building them multiple times.+componentBuildDir lbi clbi+ = buildDir lbi </> case componentLocalName clbi of+ CLibName s -> s+ CExeName s -> s+ CTestName s -> s+ CBenchName s -> s+ getComponentLocalBuildInfo :: LocalBuildInfo -> ComponentName -> ComponentLocalBuildInfo getComponentLocalBuildInfo lbi cname =+ case maybeGetComponentLocalBuildInfo lbi cname of+ Just clbi -> clbi+ Nothing ->+ error $ "internal error: there is no configuration data "+ ++ "for component " ++ show cname++maybeGetComponentLocalBuildInfo+ :: LocalBuildInfo -> ComponentName -> Maybe ComponentLocalBuildInfo+maybeGetComponentLocalBuildInfo lbi cname = case [ clbi- | (cname', clbi, _) <- componentsConfigs lbi- , cname == cname' ] of- [clbi] -> clbi- _ -> missingComponent- where- missingComponent =- error $ "internal error: there is no configuration data "- ++ "for component " ++ show cname+ | (clbi, _) <- componentsConfigs lbi+ , cid <- componentNameToUnitIds lbi cname+ , cid == componentUnitId clbi ] of+ [clbi] -> Just clbi+ _ -> Nothing +maybeGetDefaultLibraryLocalBuildInfo+ :: LocalBuildInfo+ -> Maybe ComponentLocalBuildInfo+maybeGetDefaultLibraryLocalBuildInfo lbi =+ case [ clbi+ | (clbi@LibComponentLocalBuildInfo{}, _) <- componentsConfigs lbi+ , componentIsPublic clbi+ ] of+ [clbi] -> Just clbi+ _ -> Nothing --- |If the package description has a library section, call the given--- function with the library build info as argument. Extended version of--- 'withLib' that also gives corresponding build info.+componentNameToUnitIds :: LocalBuildInfo -> ComponentName -> [UnitId]+componentNameToUnitIds lbi cname =+ [ componentUnitId clbi+ | (clbi, _) <- componentsConfigs lbi+ , componentName (getLocalComponent (localPkgDescr lbi) clbi) == cname ]++-- | Perform the action on each buildable 'library' in the package+-- description. Extended version of 'withLib' that also gives+-- corresponding build info. withLibLBI :: PackageDescription -> LocalBuildInfo -> (Library -> ComponentLocalBuildInfo -> IO ()) -> IO ()-withLibLBI pkg_descr lbi f =- withLib pkg_descr $ \lib ->- f lib (getComponentLocalBuildInfo lbi CLibName)+withLibLBI pkg lbi f =+ sequence_+ [ f lib clbi+ | (clbi@LibComponentLocalBuildInfo{}, _) <- componentsConfigs lbi+ , CLib lib <- [getComponent pkg (componentLocalName clbi)] ] -- | Perform the action on each buildable 'Executable' in the package -- description. Extended version of 'withExe' that also gives corresponding -- build info. withExeLBI :: PackageDescription -> LocalBuildInfo -> (Executable -> ComponentLocalBuildInfo -> IO ()) -> IO ()-withExeLBI pkg_descr lbi f =- withExe pkg_descr $ \exe ->- f exe (getComponentLocalBuildInfo lbi (CExeName (exeName exe)))+withExeLBI pkg lbi f =+ sequence_+ [ f exe clbi+ | (clbi@ExeComponentLocalBuildInfo{}, _) <- componentsConfigs lbi+ , CExe exe <- [getComponent pkg (componentLocalName clbi)] ] withTestLBI :: PackageDescription -> LocalBuildInfo -> (TestSuite -> ComponentLocalBuildInfo -> IO ()) -> IO ()-withTestLBI pkg_descr lbi f =- withTest pkg_descr $ \test ->- f test (getComponentLocalBuildInfo lbi (CTestName (testName test)))+withTestLBI pkg lbi f =+ sequence_+ [ f test clbi+ | (clbi@TestComponentLocalBuildInfo{}, _) <- componentsConfigs lbi+ , CTest test <- [getComponent pkg (componentLocalName clbi)] ] {-# DEPRECATED withComponentsLBI "Use withAllComponentsInBuildOrder" #-} withComponentsLBI :: PackageDescription -> LocalBuildInfo@@ -373,37 +460,44 @@ -> IO () withAllComponentsInBuildOrder pkg lbi f = sequence_- [ f (getComponent pkg cname) clbi- | (cname, clbi) <- allComponentsInBuildOrder lbi ]+ [ f (getLocalComponent pkg clbi) clbi+ | clbi <- allComponentsInBuildOrder lbi ] withComponentsInBuildOrder :: PackageDescription -> LocalBuildInfo- -> [ComponentName]- -> (Component -> ComponentLocalBuildInfo -> IO ())- -> IO ()+ -> [ComponentName]+ -> (Component -> ComponentLocalBuildInfo -> IO ())+ -> IO () withComponentsInBuildOrder pkg lbi cnames f = sequence_- [ f (getComponent pkg cname') clbi- | (cname', clbi) <- componentsInBuildOrder lbi cnames ]+ [ f (getLocalComponent pkg clbi) clbi+ | clbi <- componentsInBuildOrder lbi cnames ] allComponentsInBuildOrder :: LocalBuildInfo- -> [(ComponentName, ComponentLocalBuildInfo)]+ -> [ComponentLocalBuildInfo] allComponentsInBuildOrder lbi =- componentsInBuildOrder lbi- [ cname | (cname, _, _) <- componentsConfigs lbi ]+ componentsInBuildOrder' lbi+ [ componentUnitId clbi | (clbi, _) <- componentsConfigs lbi ] componentsInBuildOrder :: LocalBuildInfo -> [ComponentName]- -> [(ComponentName, ComponentLocalBuildInfo)]+ -> [ComponentLocalBuildInfo] componentsInBuildOrder lbi cnames =- map ((\(clbi,cname,_) -> (cname,clbi)) . vertexToNode)+ componentsInBuildOrder' lbi+ (concatMap (componentNameToUnitIds lbi) cnames)++componentsInBuildOrder' :: LocalBuildInfo -> [UnitId]+ -> [ComponentLocalBuildInfo]+componentsInBuildOrder' lbi cids =+ map ((\(clbi,_,_) -> clbi) . vertexToNode) . postOrder graph- . map (\cname -> fromMaybe (noSuchComp cname) (keyToVertex cname))- $ cnames+ . map (\cid -> fromMaybe (noSuchComp cid) (keyToVertex cid))+ $ cids where (graph, vertexToNode, keyToVertex) =- graphFromEdges (map (\(a,b,c) -> (b,a,c)) (componentsConfigs lbi))+ graphFromEdges (map (\(clbi,internal_deps) ->+ (clbi,componentUnitId clbi,internal_deps)) (componentsConfigs lbi)) - noSuchComp cname = error $ "internal error: componentsInBuildOrder: "- ++ "no such component: " ++ show cname+ noSuchComp cid = error $ "internal error: componentsInBuildOrder: "+ ++ "no such component: " ++ show cid postOrder :: Graph -> [Vertex] -> [Vertex] postOrder g vs = postorderF (dfs g vs) []@@ -437,27 +531,30 @@ -> IO [FilePath] depLibraryPaths inplace relative lbi clbi = do let pkgDescr = localPkgDescr lbi- installDirs = absoluteInstallDirs pkgDescr lbi NoCopyDest+ installDirs = absoluteComponentInstallDirs pkgDescr lbi (componentUnitId clbi) NoCopyDest executable = case clbi of ExeComponentLocalBuildInfo {} -> True _ -> False relDir | executable = bindir installDirs | otherwise = libdir installDirs - let hasInternalDeps = not $ null- $ [ pkgid- | (_,pkgid) <- componentPackageDeps clbi- , internal pkgid- ]+ let -- TODO: this is kind of inefficient+ internalDeps = [ cid+ | (cid, _) <- componentPackageDeps clbi+ -- Test that it's internal+ , (sub_clbi, _) <- componentsConfigs lbi+ , componentUnitId sub_clbi == cid ]+ internalLibs = [ getLibDir sub_clbi+ | sub_clbi <- componentsInBuildOrder'+ lbi internalDeps ]+ getLibDir sub_clbi+ | inplace = componentBuildDir lbi sub_clbi+ | otherwise = libdir (absoluteComponentInstallDirs pkgDescr lbi (componentUnitId sub_clbi) NoCopyDest) let ipkgs = allPackages (installedPkgs lbi) allDepLibDirs = concatMap Installed.libraryDirs ipkgs- internalLib- | inplace = buildDir lbi- | otherwise = libdir installDirs- allDepLibDirs' = if hasInternalDeps- then internalLib : allDepLibDirs- else allDepLibDirs++ allDepLibDirs' = internalLibs ++ allDepLibDirs allDepLibDirsC <- mapM canonicalizePathNoFail allDepLibDirs' let p = prefix installDirs@@ -473,7 +570,6 @@ return libPaths where- internal pkgid = pkgid == packageId (localPkgDescr lbi) -- 'canonicalizePath' fails on UNIX when the directory does not exists. -- So just don't canonicalize when it doesn't exist. canonicalizePathNoFail p = do@@ -486,35 +582,58 @@ -- ----------------------------------------------------------------------------- -- Wrappers for a couple functions from InstallDirs --- |See 'InstallDirs.absoluteInstallDirs'-absoluteInstallDirs :: PackageDescription -> LocalBuildInfo -> CopyDest+-- | Backwards compatibility function which computes the InstallDirs+-- assuming that @$libname@ points to the public library (or some fake+-- package identifier if there is no public library.) IF AT ALL+-- POSSIBLE, please use 'absoluteComponentInstallDirs' instead.+absoluteInstallDirs :: PackageDescription -> LocalBuildInfo+ -> CopyDest -> InstallDirs FilePath absoluteInstallDirs pkg lbi copydest =+ absoluteComponentInstallDirs pkg lbi (localUnitId lbi) copydest++-- | See 'InstallDirs.absoluteInstallDirs'.+absoluteComponentInstallDirs :: PackageDescription -> LocalBuildInfo+ -> UnitId+ -> CopyDest+ -> InstallDirs FilePath+absoluteComponentInstallDirs pkg lbi uid copydest = InstallDirs.absoluteInstallDirs (packageId pkg)- (localLibraryName lbi)+ uid (compilerInfo (compiler lbi)) copydest (hostPlatform lbi) (installDirTemplates lbi) --- |See 'InstallDirs.prefixRelativeInstallDirs'+-- | Backwards compatibility function which computes the InstallDirs+-- assuming that @$libname@ points to the public library (or some fake+-- package identifier if there is no public library.) IF AT ALL+-- POSSIBLE, please use 'prefixRelativeComponentInstallDirs' instead. prefixRelativeInstallDirs :: PackageId -> LocalBuildInfo -> InstallDirs (Maybe FilePath) prefixRelativeInstallDirs pkg_descr lbi =+ prefixRelativeComponentInstallDirs pkg_descr lbi (localUnitId lbi)++-- |See 'InstallDirs.prefixRelativeInstallDirs'+prefixRelativeComponentInstallDirs :: PackageId -> LocalBuildInfo+ -> UnitId+ -> InstallDirs (Maybe FilePath)+prefixRelativeComponentInstallDirs pkg_descr lbi uid = InstallDirs.prefixRelativeInstallDirs (packageId pkg_descr)- (localLibraryName lbi)+ uid (compilerInfo (compiler lbi)) (hostPlatform lbi) (installDirTemplates lbi) substPathTemplate :: PackageId -> LocalBuildInfo+ -> UnitId -> PathTemplate -> FilePath-substPathTemplate pkgid lbi = fromPathTemplate- . ( InstallDirs.substPathTemplate env )+substPathTemplate pkgid lbi uid = fromPathTemplate+ . ( InstallDirs.substPathTemplate env ) where env = initialPathTemplateEnv pkgid- (localLibraryName lbi)+ uid (compilerInfo (compiler lbi)) (hostPlatform lbi)
cabal/Cabal/Distribution/Simple/PackageIndex.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE CPP #-} {-# LANGUAGE DeriveGeneric #-} -----------------------------------------------------------------------------@@ -11,8 +10,51 @@ -- Maintainer : cabal-devel@haskell.org -- Portability : portable ----- An index of packages.+-- An index of packages whose primary key is 'UnitId'. Public libraries+-- are additionally indexed by 'PackageName' and 'Version'.+-- Technically, these are an index of *units* (so we should eventually+-- rename it to 'UnitIndex'); but in the absence of internal libraries+-- or Backpack each unit is equivalent to a package. --+-- 'PackageIndex' is parametric over what it actually records, and it+-- is used in two ways:+--+-- * The 'InstalledPackageIndex' (defined here) contains a graph of+-- 'InstalledPackageInfo's representing the packages in a+-- package database stack. It is used in a variety of ways:+--+-- * The primary use to let Cabal access the same installed+-- package database which is used by GHC during compilation.+-- For example, this data structure is used by 'ghc-pkg'+-- and 'Cabal' to do consistency checks on the database+-- (are the references closed).+--+-- * Given a set of dependencies, we can compute the transitive+-- closure of dependencies. This is to check if the versions+-- of packages are consistent, and also needed by multiple+-- tools (Haddock must be explicitly told about the every+-- transitive package to do cross-package linking;+-- preprocessors must know about the include paths of all+-- transitive dependencies.)+--+-- * The 'PlanIndex' (defined in 'Distribution.Client.InstallPlan'),+-- contains a graph of 'GenericPlanPackage'. Ignoring its type+-- parameters for a moment, a 'PlanIndex' is an extension of the+-- 'InstalledPackageIndex' to also record nodes for packages+-- which are *planned* to be installed, but not actually+-- installed yet. A 'PlanIndex' containing only 'PreExisting'+-- packages is essentially a 'PackageIndex'.+--+-- 'PlanIndex'es actually require some auxiliary information, so+-- most users interact with a 'GenericInstallPlan'. This type is+-- specialized as an 'ElaboratedInstallPlan' (for @cabal+-- new-build@) or an 'InstallPlan' (for @cabal install@).+--+-- This 'PackageIndex' is NOT to be confused with+-- 'Distribution.Client.PackageIndex', which indexes packages only by+-- 'PackageName' (this makes it suitable for indexing source packages,+-- for which we don't know 'UnitId's.)+-- module Distribution.Simple.PackageIndex ( -- * Package index data type InstalledPackageIndex,@@ -26,7 +68,7 @@ insert, - deleteInstalledPackageId,+ deleteUnitId, deleteSourcePackageId, deletePackageName, -- deleteDependency,@@ -34,7 +76,7 @@ -- * Queries -- ** Precise lookups- lookupInstalledPackageId,+ lookupUnitId, lookupSourcePackageId, lookupPackageId, lookupPackageName,@@ -60,19 +102,27 @@ dependencyCycles, dependencyGraph, moduleNameIndex,++ -- * Backwards compatibility+ deleteInstalledPackageId,+ lookupInstalledPackageId, ) where +import Distribution.Compat.Binary+import Distribution.Compat.Semigroup as Semi+import Distribution.Package+import Distribution.ModuleName+import qualified Distribution.InstalledPackageInfo as IPI+import Distribution.Version+import Distribution.Simple.Utils+ import Control.Exception (assert) import Data.Array ((!)) import qualified Data.Array as Array-import Distribution.Compat.Binary (Binary) import qualified Data.Graph as Graph import Data.List as List ( null, foldl', sort- , groupBy, sortBy, find, isInfixOf, nubBy, deleteBy, deleteFirstsBy )-#if __GLASGOW_HASKELL__ < 710-import Data.Monoid (Monoid(..))-#endif+ , groupBy, sortBy, find, nubBy, deleteBy, deleteFirstsBy ) import Data.Map (Map) import qualified Data.Map as Map import Data.Maybe (isNothing, fromMaybe)@@ -80,32 +130,17 @@ import GHC.Generics (Generic) import Prelude hiding (lookup) -import Distribution.Package- ( PackageName(..), PackageId- , Package(..), packageName, packageVersion- , Dependency(Dependency)--, --PackageFixedDeps(..)- , InstalledPackageId(..)- , HasInstalledPackageId(..), PackageInstalled(..) )-import Distribution.ModuleName- ( ModuleName )-import Distribution.InstalledPackageInfo- ( InstalledPackageInfo )-import qualified Distribution.InstalledPackageInfo as IPI-import Distribution.Version- ( Version, withinRange )-import Distribution.Simple.Utils (lowercase, comparing, equating)- -- | The collection of information about packages from one or more 'PackageDB's. -- These packages generally should have an instance of 'PackageInstalled' ----- Packages are uniquely identified in by their 'InstalledPackageId', they can+-- Packages are uniquely identified in by their 'UnitId', they can -- also be efficiently looked up by package name or by name and version. -- data PackageIndex a = PackageIndex -- The primary index. Each InstalledPackageInfo record is uniquely identified- -- by its InstalledPackageId.+ -- by its UnitId. --- !(Map InstalledPackageId a)+ !(Map UnitId a) -- This auxiliary index maps package names (case-sensitively) to all the -- versions and instances of that package. This allows us to find all@@ -113,39 +148,42 @@ -- -- It is a three-level index. The first level is the package name, -- the second is the package version and the final level is instances- -- of the same package version. These are unique by InstalledPackageId+ -- of the same package version. These are unique by UnitId -- and are kept in preference order. -- -- FIXME: Clarify what "preference order" means. Check that this invariant is -- preserved. See #1463 for discussion. !(Map PackageName (Map Version [a])) - deriving (Generic, Show, Read)+ deriving (Eq, Generic, Show, Read) instance Binary a => Binary (PackageIndex a) -- | The default package index which contains 'InstalledPackageInfo'. Normally -- use this.-type InstalledPackageIndex = PackageIndex InstalledPackageInfo+type InstalledPackageIndex = PackageIndex IPI.InstalledPackageInfo -instance HasInstalledPackageId a => Monoid (PackageIndex a) where+instance HasUnitId a => Monoid (PackageIndex a) where mempty = PackageIndex Map.empty Map.empty- mappend = merge+ mappend = (Semi.<>) --save one mappend with empty in the common case: mconcat [] = mempty mconcat xs = foldr1 mappend xs -invariant :: HasInstalledPackageId a => PackageIndex a -> Bool+instance HasUnitId a => Semigroup (PackageIndex a) where+ (<>) = merge++invariant :: HasUnitId a => PackageIndex a -> Bool invariant (PackageIndex pids pnames) =- map installedPackageId (Map.elems pids)+ map installedUnitId (Map.elems pids) == sort- [ assert pinstOk (installedPackageId pinst)+ [ assert pinstOk (installedUnitId pinst) | (pname, pvers) <- Map.toList pnames , let pversOk = not (Map.null pvers) , (pver, pinsts) <- assert pversOk $ Map.toList pvers- , let pinsts' = sortBy (comparing installedPackageId) pinsts+ , let pinsts' = sortBy (comparing installedUnitId) pinsts pinstsOk = all (\g -> length g == 1)- (groupBy (equating installedPackageId) pinsts')+ (groupBy (equating installedUnitId) pinsts') , pinst <- assert pinstsOk $ pinsts' , let pinstOk = packageName pinst == pname && packageVersion pinst == pver@@ -156,8 +194,8 @@ -- * Internal helpers -- -mkPackageIndex :: HasInstalledPackageId a- => Map InstalledPackageId a+mkPackageIndex :: HasUnitId a+ => Map UnitId a -> Map PackageName (Map Version [a]) -> PackageIndex a mkPackageIndex pids pnames = assert (invariant index) index@@ -170,13 +208,13 @@ -- | Build an index out of a bunch of packages. ----- If there are duplicates by 'InstalledPackageId' then later ones mask earlier+-- If there are duplicates by 'UnitId' then later ones mask earlier -- ones. ---fromList :: HasInstalledPackageId a => [a] -> PackageIndex a+fromList :: HasUnitId a => [a] -> PackageIndex a fromList pkgs = mkPackageIndex pids pnames where- pids = Map.fromList [ (installedPackageId pkg, pkg) | pkg <- pkgs ]+ pids = Map.fromList [ (installedUnitId pkg, pkg) | pkg <- pkgs ] pnames = Map.fromList [ (packageName (head pkgsN), pvers)@@ -186,7 +224,7 @@ , let pvers = Map.fromList [ (packageVersion (head pkgsNV),- nubBy (equating installedPackageId) (reverse pkgsNV))+ nubBy (equating installedUnitId) (reverse pkgsNV)) | pkgsNV <- groupBy (equating packageVersion) pkgsN ] ]@@ -198,14 +236,14 @@ -- | Merge two indexes. -- -- Packages from the second mask packages from the first if they have the exact--- same 'InstalledPackageId'.+-- same 'UnitId'. -- -- For packages with the same source 'PackageId', packages from the second are -- \"preferred\" over those from the first. Being preferred means they are top -- result when we do a lookup by source 'PackageId'. This is the mechanism we -- use to prefer user packages over global packages. ---merge :: HasInstalledPackageId a => PackageIndex a -> PackageIndex a+merge :: HasUnitId a => PackageIndex a -> PackageIndex a -> PackageIndex a merge (PackageIndex pids1 pnames1) (PackageIndex pids2 pnames2) = mkPackageIndex (Map.unionWith (\_ y -> y) pids1 pids2)@@ -214,7 +252,7 @@ -- Packages in the second list mask those in the first, however preferred -- packages go first in the list. mergeBuckets xs ys = ys ++ (xs \\ ys)- (\\) = deleteFirstsBy (equating installedPackageId)+ (\\) = deleteFirstsBy (equating installedUnitId) -- | Inserts a single package into the index.@@ -222,12 +260,12 @@ -- This is equivalent to (but slightly quicker than) using 'mappend' or -- 'merge' with a singleton index. ---insert :: HasInstalledPackageId a => a -> PackageIndex a -> PackageIndex a+insert :: HasUnitId a => a -> PackageIndex a -> PackageIndex a insert pkg (PackageIndex pids pnames) = mkPackageIndex pids' pnames' where- pids' = Map.insert (installedPackageId pkg) pkg pids+ pids' = Map.insert (installedUnitId pkg) pkg pids pnames' = insertPackageName pnames insertPackageName = Map.insertWith' (\_ -> insertPackageVersion)@@ -239,15 +277,15 @@ (packageVersion pkg) [pkg] insertPackageInstance pkgs =- pkg : deleteBy (equating installedPackageId) pkg pkgs+ pkg : deleteBy (equating installedUnitId) pkg pkgs -- | Removes a single installed package from the index. ---deleteInstalledPackageId :: HasInstalledPackageId a- => InstalledPackageId -> PackageIndex a- -> PackageIndex a-deleteInstalledPackageId ipkgid original@(PackageIndex pids pnames) =+deleteUnitId :: HasUnitId a+ => UnitId -> PackageIndex a+ -> PackageIndex a+deleteUnitId ipkgid original@(PackageIndex pids pnames) = case Map.updateLookupWithKey (\_ _ -> Nothing) ipkgid pids of (Nothing, _) -> original (Just spkgid, pids') -> mkPackageIndex pids'@@ -263,12 +301,18 @@ deletePkgInstance = (\xs -> if List.null xs then Nothing else Just xs)- . List.deleteBy (\_ pkg -> installedPackageId pkg == ipkgid) undefined+ . List.deleteBy (\_ pkg -> installedUnitId pkg == ipkgid) undefined +-- | Backwards compatibility wrapper for Cabal pre-1.24.+{-# DEPRECATED deleteInstalledPackageId "Use deleteUnitId instead" #-}+deleteInstalledPackageId :: HasUnitId a+ => UnitId -> PackageIndex a+ -> PackageIndex a+deleteInstalledPackageId = deleteUnitId -- | Removes all packages with this source 'PackageId' from the index. ---deleteSourcePackageId :: HasInstalledPackageId a => PackageId -> PackageIndex a+deleteSourcePackageId :: HasUnitId a => PackageId -> PackageIndex a -> PackageIndex a deleteSourcePackageId pkgid original@(PackageIndex pids pnames) = case Map.lookup (packageName pkgid) pnames of@@ -276,7 +320,7 @@ Just pvers -> case Map.lookup (packageVersion pkgid) pvers of Nothing -> original Just pkgs -> mkPackageIndex- (foldl' (flip (Map.delete . installedPackageId)) pids pkgs)+ (foldl' (flip (Map.delete . installedUnitId)) pids pkgs) (deletePkgName pnames) where deletePkgName =@@ -289,13 +333,13 @@ -- | Removes all packages with this (case-sensitive) name from the index. ---deletePackageName :: HasInstalledPackageId a => PackageName -> PackageIndex a+deletePackageName :: HasUnitId a => PackageName -> PackageIndex a -> PackageIndex a deletePackageName name original@(PackageIndex pids pnames) = case Map.lookup name pnames of Nothing -> original Just pvers -> mkPackageIndex- (foldl' (flip (Map.delete . installedPackageId)) pids+ (foldl' (flip (Map.delete . installedUnitId)) pids (concat (Map.elems pvers))) (Map.delete name pnames) @@ -329,7 +373,7 @@ -- -- They are grouped by source package id (package name and version). ---allPackagesBySourcePackageId :: HasInstalledPackageId a => PackageIndex a+allPackagesBySourcePackageId :: HasUnitId a => PackageIndex a -> [(PackageId, [a])] allPackagesBySourcePackageId (PackageIndex _ pnames) = [ (packageId ipkg, ipkgs)@@ -342,18 +386,24 @@ -- | Does a lookup by source package id (name & version). ----- Since multiple package DBs mask each other by 'InstalledPackageId',+-- Since multiple package DBs mask each other by 'UnitId', -- then we get back at most one package. ---lookupInstalledPackageId :: PackageIndex a -> InstalledPackageId+lookupUnitId :: PackageIndex a -> UnitId+ -> Maybe a+lookupUnitId (PackageIndex pids _) pid = Map.lookup pid pids++-- | Backwards compatibility for Cabal pre-1.24.+{-# DEPRECATED lookupInstalledPackageId "Use lookupUnitId instead" #-}+lookupInstalledPackageId :: PackageIndex a -> UnitId -> Maybe a-lookupInstalledPackageId (PackageIndex pids _) pid = Map.lookup pid pids+lookupInstalledPackageId = lookupUnitId -- | Does a lookup by source package id (name & version). -- -- There can be multiple installed packages with the same source 'PackageId'--- but different 'InstalledPackageId'. They are returned in order of+-- but different 'UnitId'. They are returned in order of -- preference, with the most preferred first. -- lookupSourcePackageId :: PackageIndex a -> PackageId -> [a]@@ -457,7 +507,7 @@ dependencyCycles index = [ vs | Graph.CyclicSCC vs <- Graph.stronglyConnComp adjacencyList ] where- adjacencyList = [ (pkg, installedPackageId pkg, installedDepends pkg)+ adjacencyList = [ (pkg, installedUnitId pkg, installedDepends pkg) | pkg <- allPackages index ] @@ -466,12 +516,12 @@ -- Returns such packages along with the dependencies that they're missing. -- brokenPackages :: PackageInstalled a => PackageIndex a- -> [(a, [InstalledPackageId])]+ -> [(a, [UnitId])] brokenPackages index = [ (pkg, missing) | pkg <- allPackages index , let missing = [ pkg' | pkg' <- installedDepends pkg- , isNothing (lookupInstalledPackageId index pkg') ]+ , isNothing (lookupUnitId index pkg') ] , not (null missing) ] -- | Tries to take the transitive closure of the package dependencies.@@ -483,17 +533,17 @@ -- the original given 'PackageId's do not occur in the index. -- dependencyClosure :: PackageInstalled a => PackageIndex a- -> [InstalledPackageId]+ -> [UnitId] -> Either (PackageIndex a)- [(a, [InstalledPackageId])]+ [(a, [UnitId])] dependencyClosure index pkgids0 = case closure mempty [] pkgids0 of (completed, []) -> Left completed (completed, _) -> Right (brokenPackages completed) where closure completed failed [] = (completed, failed)- closure completed failed (pkgid:pkgids) = case lookupInstalledPackageId index pkgid of+ closure completed failed (pkgid:pkgids) = case lookupUnitId index pkgid of Nothing -> closure completed (pkgid:failed) pkgids- Just pkg -> case lookupInstalledPackageId completed (installedPackageId pkg) of+ Just pkg -> case lookupUnitId completed (installedUnitId pkg) of Just _ -> closure completed failed pkgids Nothing -> closure completed' failed pkgids' where completed' = insert pkg completed@@ -504,7 +554,7 @@ -- * The given 'PackageId's must be in the index. -- reverseDependencyClosure :: PackageInstalled a => PackageIndex a- -> [InstalledPackageId]+ -> [UnitId] -> [a] reverseDependencyClosure index = map vertexToPkg@@ -538,7 +588,7 @@ dependencyGraph :: PackageInstalled a => PackageIndex a -> (Graph.Graph, Graph.Vertex -> a,- InstalledPackageId -> Maybe Graph.Vertex)+ UnitId -> Maybe Graph.Vertex) dependencyGraph index = (graph, vertex_to_pkg, id_to_vertex) where graph = Array.listArray bounds@@ -546,7 +596,7 @@ | pkg <- pkgs ] pkgs = sortBy (comparing packageId) (allPackages index)- vertices = zip (map installedPackageId pkgs) [0..]+ vertices = zip (map installedUnitId pkgs) [0..] vertex_map = Map.fromList vertices id_to_vertex pid = Map.lookup pid vertex_map @@ -583,15 +633,20 @@ Map.fromList [(ipid,(dep,[packageId pkg]))]) | pkg <- allPackages index , ipid <- installedDepends pkg- , Just dep <- [lookupInstalledPackageId index ipid]+ , Just dep <- [lookupUnitId index ipid] ] + -- Added in 991e52a474e2b8280432257c1771dc474a320a30,+ -- this is a special case to handle the base 3 compatibility+ -- package which shipped with GHC 6.10 and GHC 6.12+ -- (it was removed in GHC 7.0). Remove this when GHC 6.12+ -- goes out of our support window. reallyIsInconsistent :: PackageInstalled a => [a] -> Bool reallyIsInconsistent [] = False reallyIsInconsistent [_p] = False reallyIsInconsistent [p1, p2] =- let pid1 = installedPackageId p1- pid2 = installedPackageId p2+ let pid1 = installedUnitId p1+ pid2 = installedUnitId p2 in pid1 `notElem` installedDepends p2 && pid2 `notElem` installedDepends p1 reallyIsInconsistent _ = True@@ -600,15 +655,15 @@ -- 'InstalledPackageIndex' and turns it into a map from module names to their -- source packages. It's used to initialize the @build-deps@ field in @cabal -- init@.-moduleNameIndex :: InstalledPackageIndex -> Map ModuleName [InstalledPackageInfo]+moduleNameIndex :: InstalledPackageIndex -> Map ModuleName [IPI.InstalledPackageInfo] moduleNameIndex index = Map.fromListWith (++) $ do pkg <- allPackages index- IPI.ExposedModule m reexport _ <- IPI.exposedModules pkg+ IPI.ExposedModule m reexport <- IPI.exposedModules pkg case reexport of Nothing -> return (m, [pkg])- Just (IPI.OriginalModule _ m') | m == m' -> []- | otherwise -> return (m', [pkg])+ Just (Module _ m') | m == m' -> []+ | otherwise -> return (m', [pkg]) -- The heuristic is this: we want to prefer the original package -- which originally exported a module. However, if a reexport -- also *renamed* the module (m /= m'), then we have to use the
cabal/Cabal/Distribution/Simple/PreProcess.hs view
@@ -27,49 +27,25 @@ where -import Control.Monad-import Distribution.Simple.PreProcess.Unlit (unlit)+import Distribution.Simple.PreProcess.Unlit import Distribution.Package- ( Package(..), PackageName(..) ) import qualified Distribution.ModuleName as ModuleName import Distribution.PackageDescription as PD- ( PackageDescription(..), BuildInfo(..)- , Executable(..)- , Library(..), libModules- , TestSuite(..), testModules- , TestSuiteInterface(..)- , Benchmark(..), benchmarkModules, BenchmarkInterface(..) ) import qualified Distribution.InstalledPackageInfo as Installed- ( InstalledPackageInfo(..) ) import qualified Distribution.Simple.PackageIndex as PackageIndex import Distribution.Simple.CCompiler- ( cSourceExtensions ) import Distribution.Simple.Compiler- ( CompilerFlavor(..)- , compilerFlavor, compilerCompatVersion, compilerVersion ) import Distribution.Simple.LocalBuildInfo- ( LocalBuildInfo(..), Component(..) )-import Distribution.Simple.BuildPaths (autogenModulesDir,cppHeaderName)+import Distribution.Simple.BuildPaths import Distribution.Simple.Utils- ( createDirectoryIfMissingVerbose, withUTF8FileContents, writeUTF8File- , die, setupMessage, intercalate, copyFileVerbose, moreRecentFile- , findFileWithExtension, findFileWithExtension'- , getDirectoryContentsRecursive ) import Distribution.Simple.Program- ( Program(..), ConfiguredProgram(..), programPath- , requireProgram, requireProgramVersion- , rawSystemProgramConf, rawSystemProgram- , greencardProgram, cpphsProgram, hsc2hsProgram, c2hsProgram- , happyProgram, alexProgram, ghcProgram, ghcjsProgram, gccProgram ) import Distribution.Simple.Test.LibV09- ( writeSimpleTestStub, stubFilePath, stubName ) import Distribution.System- ( OS(..), buildOS, Arch(..), Platform(..) ) import Distribution.Text import Distribution.Version- ( Version(..), anyVersion, orLaterVersion ) import Distribution.Verbosity +import Control.Monad import Data.Maybe (fromMaybe) import Data.List (nub, isSuffixOf) import System.Directory (doesFileExist)@@ -156,26 +132,27 @@ -- |A preprocessor for turning non-Haskell files with the given extension -- into plain Haskell source files. type PPSuffixHandler- = (String, BuildInfo -> LocalBuildInfo -> PreProcessor)+ = (String, BuildInfo -> LocalBuildInfo -> ComponentLocalBuildInfo -> PreProcessor) -- | Apply preprocessors to the sources from 'hsSourceDirs' for a given -- component (lib, exe, or test suite). preprocessComponent :: PackageDescription -> Component -> LocalBuildInfo+ -> ComponentLocalBuildInfo -> Bool -> Verbosity -> [PPSuffixHandler] -> IO ()-preprocessComponent pd comp lbi isSrcDist verbosity handlers = case comp of+preprocessComponent pd comp lbi clbi isSrcDist verbosity handlers = case comp of (CLib lib@Library{ libBuildInfo = bi }) -> do- let dirs = hsSourceDirs bi ++ [autogenModulesDir lbi]+ let dirs = hsSourceDirs bi ++ [autogenModulesDir lbi clbi] setupMessage verbosity "Preprocessing library" (packageId pd) forM_ (map ModuleName.toFilePath $ libModules lib) $ pre dirs (buildDir lbi) (localHandlers bi) (CExe exe@Executable { buildInfo = bi, exeName = nm }) -> do let exeDir = buildDir lbi </> nm </> nm ++ "-tmp"- dirs = hsSourceDirs bi ++ [autogenModulesDir lbi]+ dirs = hsSourceDirs bi ++ [autogenModulesDir lbi clbi] setupMessage verbosity ("Preprocessing executable '" ++ nm ++ "' for") (packageId pd) forM_ (map ModuleName.toFilePath $ otherModules bi) $ pre dirs exeDir (localHandlers bi)@@ -206,7 +183,7 @@ builtinHaskellSuffixes = ["hs", "lhs", "hsig", "lhsig"] builtinCSuffixes = cSourceExtensions builtinSuffixes = builtinHaskellSuffixes ++ builtinCSuffixes- localHandlers bi = [(ext, h bi lbi) | (ext, h) <- handlers]+ localHandlers bi = [(ext, h bi lbi clbi) | (ext, h) <- handlers] pre dirs dir lhndlrs fp = preprocessFile dirs dir isSrcDist fp verbosity builtinSuffixes lhndlrs preProcessTest test = preProcessComponent (testBuildInfo test)@@ -215,7 +192,7 @@ (benchmarkModules bm) preProcessComponent bi modules exePath dir = do let biHandlers = localHandlers bi- sourceDirs = hsSourceDirs bi ++ [ autogenModulesDir lbi ]+ sourceDirs = hsSourceDirs bi ++ [ autogenModulesDir lbi clbi ] sequence_ [ preprocessFile sourceDirs dir isSrcDist (ModuleName.toFilePath modu) verbosity builtinSuffixes biHandlers@@ -318,8 +295,8 @@ -- * known preprocessors -- ------------------------------------------------------------ -ppGreenCard :: BuildInfo -> LocalBuildInfo -> PreProcessor-ppGreenCard _ lbi+ppGreenCard :: BuildInfo -> LocalBuildInfo -> ComponentLocalBuildInfo -> PreProcessor+ppGreenCard _ lbi _ = PreProcessor { platformIndependent = False, runPreProcessor = mkSimplePreProcessor $ \inFile outFile verbosity ->@@ -338,21 +315,21 @@ either (writeUTF8File outFile) die (unlit inFile contents) } -ppCpp :: BuildInfo -> LocalBuildInfo -> PreProcessor+ppCpp :: BuildInfo -> LocalBuildInfo -> ComponentLocalBuildInfo -> PreProcessor ppCpp = ppCpp' [] -ppCpp' :: [String] -> BuildInfo -> LocalBuildInfo -> PreProcessor-ppCpp' extraArgs bi lbi =+ppCpp' :: [String] -> BuildInfo -> LocalBuildInfo -> ComponentLocalBuildInfo -> PreProcessor+ppCpp' extraArgs bi lbi clbi = case compilerFlavor (compiler lbi) of- GHC -> ppGhcCpp ghcProgram (>= Version [6,6] []) args bi lbi- GHCJS -> ppGhcCpp ghcjsProgram (const True) args bi lbi- _ -> ppCpphs args bi lbi+ GHC -> ppGhcCpp ghcProgram (>= Version [6,6] []) args bi lbi clbi+ GHCJS -> ppGhcCpp ghcjsProgram (const True) args bi lbi clbi+ _ -> ppCpphs args bi lbi clbi where cppArgs = getCppOptions bi lbi args = cppArgs ++ extraArgs ppGhcCpp :: Program -> (Version -> Bool)- -> [String] -> BuildInfo -> LocalBuildInfo -> PreProcessor-ppGhcCpp program xHs extraArgs _bi lbi =+ -> [String] -> BuildInfo -> LocalBuildInfo -> ComponentLocalBuildInfo -> PreProcessor+ppGhcCpp program xHs extraArgs _bi lbi clbi = PreProcessor { platformIndependent = False, runPreProcessor = mkSimplePreProcessor $ \inFile outFile verbosity -> do@@ -366,13 +343,13 @@ -- double-unlitted. In the future we might switch to -- using cpphs --unlit instead. ++ (if xHs version then ["-x", "hs"] else [])- ++ [ "-optP-include", "-optP"++ (autogenModulesDir lbi </> cppHeaderName) ]+ ++ [ "-optP-include", "-optP"++ (autogenModulesDir lbi clbi </> cppHeaderName) ] ++ ["-o", outFile, inFile] ++ extraArgs } -ppCpphs :: [String] -> BuildInfo -> LocalBuildInfo -> PreProcessor-ppCpphs extraArgs _bi lbi =+ppCpphs :: [String] -> BuildInfo -> LocalBuildInfo -> ComponentLocalBuildInfo -> PreProcessor+ppCpphs extraArgs _bi lbi clbi = PreProcessor { platformIndependent = False, runPreProcessor = mkSimplePreProcessor $ \inFile outFile verbosity -> do@@ -382,13 +359,13 @@ ("-O" ++ outFile) : inFile : "--noline" : "--strip" : (if cpphsVersion >= Version [1,6] []- then ["--include="++ (autogenModulesDir lbi </> cppHeaderName)]+ then ["--include="++ (autogenModulesDir lbi clbi </> cppHeaderName)] else []) ++ extraArgs } -ppHsc2hs :: BuildInfo -> LocalBuildInfo -> PreProcessor-ppHsc2hs bi lbi =+ppHsc2hs :: BuildInfo -> LocalBuildInfo -> ComponentLocalBuildInfo -> PreProcessor+ppHsc2hs bi lbi clbi = PreProcessor { platformIndependent = False, runPreProcessor = mkSimplePreProcessor $ \inFile outFile verbosity -> do@@ -425,8 +402,8 @@ ++ [ "--cflag=" ++ opt | opt <- PD.ccOptions bi ++ PD.cppOptions bi ] ++ [ "--cflag=" ++ opt | opt <-- [ "-I" ++ autogenModulesDir lbi,- "-include", autogenModulesDir lbi </> cppHeaderName ] ]+ [ "-I" ++ autogenModulesDir lbi clbi,+ "-include", autogenModulesDir lbi clbi </> cppHeaderName ] ] ++ [ "--lflag=-L" ++ opt | opt <- PD.extraLibDirs bi ] ++ [ "--lflag=-Wl,-R," ++ opt | isELF , opt <- PD.extraLibDirs bi ]@@ -448,9 +425,15 @@ ++ ["-o", outFile, inFile] } where+ -- TODO: installedPkgs contains ALL dependencies associated with+ -- the package, but we really only want to look at packages for the+ -- *current* dependency. We should use PackageIndex.dependencyClosure+ -- on the direct depends of the component. The signature of this+ -- function was recently refactored, so this should be fixable+ -- now. Tracked with #2971 (which has a test case.) pkgs = PackageIndex.topologicalOrder (packageHacks (installedPkgs lbi)) isOSX = case buildOS of OSX -> True; _ -> False- isELF = case buildOS of OSX -> False; Windows -> False; _ -> True;+ isELF = case buildOS of OSX -> False; Windows -> False; AIX -> False; _ -> True; packageHacks = case compilerFlavor (compiler lbi) of GHC -> hackRtsPackage GHCJS -> hackRtsPackage@@ -469,8 +452,8 @@ ppHsc2hsExtras buildBaseDir = filter ("_hsc.c" `isSuffixOf`) `fmap` getDirectoryContentsRecursive buildBaseDir -ppC2hs :: BuildInfo -> LocalBuildInfo -> PreProcessor-ppC2hs bi lbi =+ppC2hs :: BuildInfo -> LocalBuildInfo -> ComponentLocalBuildInfo -> PreProcessor+ppC2hs bi lbi clbi = PreProcessor { platformIndependent = False, runPreProcessor = \(inBaseDir, inRelativeFile)@@ -484,7 +467,7 @@ -- Options from the current package: [ "--cpp=" ++ programPath gccProg, "--cppopts=-E" ] ++ [ "--cppopts=" ++ opt | opt <- getCppOptions bi lbi ]- ++ [ "--cppopts=-include" ++ (autogenModulesDir lbi </> cppHeaderName) ]+ ++ [ "--cppopts=-include" ++ (autogenModulesDir lbi clbi </> cppHeaderName) ] ++ [ "--include=" ++ outBaseDir ] -- Options from dependent packages@@ -598,16 +581,16 @@ JavaScript -> ["javascript"] OtherArch _ -> [] -ppHappy :: BuildInfo -> LocalBuildInfo -> PreProcessor-ppHappy _ lbi = pp { platformIndependent = True }+ppHappy :: BuildInfo -> LocalBuildInfo -> ComponentLocalBuildInfo -> PreProcessor+ppHappy _ lbi _ = pp { platformIndependent = True } where pp = standardPP lbi happyProgram (hcFlags hc) hc = compilerFlavor (compiler lbi) hcFlags GHC = ["-agc"] hcFlags GHCJS = ["-agc"] hcFlags _ = [] -ppAlex :: BuildInfo -> LocalBuildInfo -> PreProcessor-ppAlex _ lbi = pp { platformIndependent = True }+ppAlex :: BuildInfo -> LocalBuildInfo -> ComponentLocalBuildInfo -> PreProcessor+ppAlex _ lbi _ = pp { platformIndependent = True } where pp = standardPP lbi alexProgram (hcFlags hc) hc = compilerFlavor (compiler lbi) hcFlags GHC = ["-g"]
cabal/Cabal/Distribution/Simple/Program.hs view
@@ -38,7 +38,8 @@ , ProgramSearchPath , ProgramSearchPathEntry(..) , simpleProgram- , findProgramLocation+ , findProgramOnSearchPath+ , defaultProgramSearchPath , findProgramVersion -- * Configured program and related functions@@ -122,6 +123,7 @@ , rawSystemProgramConf , rawSystemProgramStdoutConf , findProgramOnPath+ , findProgramLocation ) where @@ -129,11 +131,9 @@ import Distribution.Simple.Program.Run import Distribution.Simple.Program.Db import Distribution.Simple.Program.Builtin-+import Distribution.Simple.Program.Find import Distribution.Simple.Utils- ( die, findProgramLocation, findProgramVersion ) import Distribution.Verbosity- ( Verbosity ) -- | Runs the given configured program.@@ -217,6 +217,8 @@ -> ProgramConfiguration restoreProgramConfiguration = restoreProgramDb -{-# DEPRECATED findProgramOnPath "use findProgramLocation instead" #-}+{-# DEPRECATED findProgramOnPath "use findProgramOnSearchPath instead" #-} findProgramOnPath :: String -> Verbosity -> IO (Maybe FilePath)-findProgramOnPath = flip findProgramLocation+findProgramOnPath name verbosity =+ fmap (fmap fst) $+ findProgramOnSearchPath verbosity defaultProgramSearchPath name
cabal/Cabal/Distribution/Simple/Program/Ar.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE NoMonoLocalBinds #-} ----------------------------------------------------------------------------- -- |@@ -84,7 +85,8 @@ | inv <- multiStageProgramInvocation simple (initial, middle, final) files ] - unless (hostArch == Arm) $ -- See #1537+ unless (hostArch == Arm -- See #1537+ || hostOS == AIX) $ -- AIX uses its own "ar" format variant wipeMetadata tmpPath equal <- filesEqual tmpPath targetPath unless equal $ renameFile tmpPath targetPath
cabal/Cabal/Distribution/Simple/Program/Builtin.hs view
@@ -46,27 +46,16 @@ ) where import Distribution.Simple.Program.Find- ( findProgramOnSearchPath ) import Distribution.Simple.Program.Internal- ( stripExtractVersion ) import Distribution.Simple.Program.Run- ( getProgramInvocationOutput, programInvocation ) import Distribution.Simple.Program.Types- ( Program(..), ConfiguredProgram(..), simpleProgram ) import Distribution.Simple.Utils- ( findProgramVersion ) import Distribution.Compat.Exception- ( catchIO ) import Distribution.Verbosity- ( lessVerbose ) import Distribution.Version- ( Version(..), withinRange, earlierVersion, laterVersion- , intersectVersionRanges )+ import Data.Char ( isDigit )--import Data.List- ( isInfixOf ) import qualified Data.Map as Map -- ------------------------------------------------------------@@ -227,16 +216,16 @@ haskellSuiteProgram = (simpleProgram "haskell-suite") { -- pretend that the program exists, otherwise it won't be in the -- "configured" state- programFindLocation =- \_verbosity _searchPath -> return $ Just "haskell-suite-dummy-location"+ programFindLocation = \_verbosity _searchPath ->+ return $ Just ("haskell-suite-dummy-location", []) } -- This represent a haskell-suite package manager. See the comments for -- haskellSuiteProgram. haskellSuitePkgProgram :: Program haskellSuitePkgProgram = (simpleProgram "haskell-suite-pkg") {- programFindLocation =- \_verbosity _searchPath -> return $ Just "haskell-suite-pkg-dummy-location"+ programFindLocation = \_verbosity _searchPath ->+ return $ Just ("haskell-suite-pkg-dummy-location", []) }
cabal/Cabal/Distribution/Simple/Program/Db.hs view
@@ -1,6 +1,3 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE DeriveGeneric #-}- ----------------------------------------------------------------------------- -- | -- Module : Distribution.Simple.Program.Db@@ -59,29 +56,19 @@ ) where import Distribution.Simple.Program.Types- ( Program(..), ProgArg, ConfiguredProgram(..), ProgramLocation(..) ) import Distribution.Simple.Program.Find- ( ProgramSearchPath, defaultProgramSearchPath- , findProgramOnSearchPath, programSearchPathAsPATHVar ) import Distribution.Simple.Program.Builtin- ( builtinPrograms ) import Distribution.Simple.Utils- ( die, doesExecutableExist ) import Distribution.Version- ( Version, VersionRange, isAnyVersion, withinRange ) import Distribution.Text- ( display ) import Distribution.Verbosity- ( Verbosity )+import Distribution.Compat.Binary -import Distribution.Compat.Binary (Binary(..))-#if __GLASGOW_HASKELL__ < 710-import Data.Functor ((<$>))-#endif import Data.List ( foldl' ) import Data.Maybe ( catMaybes )+import Data.Tuple (swap) import qualified Data.Map as Map import Control.Monad ( join, foldM )@@ -129,28 +116,42 @@ -- Read & Show instances are based on listToFM--- Note that we only serialise the configured part of the database, this is--- because we don't need the unconfigured part after the configure stage, and--- additionally because we cannot read/show 'Program' as it contains functions.++-- | Note that this instance does not preserve the known 'Program's.+-- See 'restoreProgramDb' for details.+-- instance Show ProgramDb where show = show . Map.toAscList . configuredProgs +-- | Note that this instance does not preserve the known 'Program's.+-- See 'restoreProgramDb' for details.+-- instance Read ProgramDb where readsPrec p s = [ (emptyProgramDb { configuredProgs = Map.fromList s' }, r) | (s', r) <- readsPrec p s ] +-- | Note that this instance does not preserve the known 'Program's.+-- See 'restoreProgramDb' for details.+-- instance Binary ProgramDb where- put = put . configuredProgs+ put db = do+ put (progSearchPath db)+ put (configuredProgs db)+ get = do- progs <- get- return $! emptyProgramDb { configuredProgs = progs }+ searchpath <- get+ progs <- get+ return $! emptyProgramDb {+ progSearchPath = searchpath,+ configuredProgs = progs+ } --- | The Read\/Show instance does not preserve all the unconfigured 'Programs'--- because 'Program' is not in Read\/Show because it contains functions. So to--- fully restore a deserialised 'ProgramDb' use this function to add--- back all the known 'Program's.+-- | The 'Read'\/'Show' and 'Binary' instances do not preserve all the+-- unconfigured 'Programs' because 'Program' is not in 'Read'\/'Show' because+-- it contains functions. So to fully restore a deserialised 'ProgramDb' use+-- this function to add back all the known 'Program's. -- -- * It does not add the default programs, but you probably want them, use -- 'builtinPrograms' in addition to any extra you might need.@@ -319,21 +320,23 @@ configureProgram verbosity prog conf = do let name = programName prog maybeLocation <- case userSpecifiedPath prog conf of- Nothing -> programFindLocation prog verbosity (progSearchPath conf)- >>= return . fmap FoundOnSystem+ Nothing ->+ programFindLocation prog verbosity (progSearchPath conf)+ >>= return . fmap (swap . fmap FoundOnSystem . swap) Just path -> do absolute <- doesExecutableExist path if absolute- then return (Just (UserSpecified path))+ then return (Just (UserSpecified path, [])) else findProgramOnSearchPath verbosity (progSearchPath conf) path- >>= maybe (die notFound) (return . Just . UserSpecified)+ >>= maybe (die notFound)+ (return . Just . swap . fmap UserSpecified . swap) where notFound = "Cannot find the program '" ++ name ++ "'. User-specified path '" ++ path ++ "' does not refer to an executable and " ++ "the program is not on the system path." case maybeLocation of Nothing -> return conf- Just location -> do+ Just (location, triedLocations) -> do version <- programFindVersion prog verbosity (locationPath location) newPath <- programSearchPathAsPATHVar (progSearchPath conf) let configuredProg = ConfiguredProgram {@@ -343,7 +346,8 @@ programOverrideArgs = userSpecifiedArgs prog conf, programOverrideEnv = [("PATH", Just newPath)], programProperties = Map.empty,- programLocation = location+ programLocation = location,+ programMonitorFiles = triedLocations } configuredProg' <- programPostConf prog verbosity configuredProg return (updateConfiguredProgs (Map.insert name configuredProg') conf)@@ -443,7 +447,7 @@ | otherwise -> return $! Left (badVersion version location) Nothing ->- return $! Left (noVersion location)+ return $! Left (unknownVersion location) where notFound = "The program '" ++ programName prog ++ "'" ++ versionRequirement@@ -452,7 +456,7 @@ ++ programName prog ++ "'" ++ versionRequirement ++ " is required but the version found at " ++ locationPath l ++ " is version " ++ display v- noVersion l = "The program '"+ unknownVersion l = "The program '" ++ programName prog ++ "'" ++ versionRequirement ++ " is required but the version of " ++ locationPath l ++ " could not be determined."@@ -467,5 +471,5 @@ -> ProgramDb -> IO (ConfiguredProgram, Version, ProgramDb) requireProgramVersion verbosity prog range programDb =- join $ either die return <$>+ join $ either die return `fmap` lookupProgramVersion verbosity prog range programDb
cabal/Cabal/Distribution/Simple/Program/Find.hs view
@@ -1,6 +1,8 @@+{-# LANGUAGE CPP, DeriveGeneric #-}+ ----------------------------------------------------------------------------- -- |--- Module : Distribution.Simple.Program.Types+-- Module : Distribution.Simple.Program.Find -- Copyright : Duncan Coutts 2013 -- -- Maintainer : cabal-devel@haskell.org@@ -26,23 +28,26 @@ defaultProgramSearchPath, findProgramOnSearchPath, programSearchPathAsPATHVar,+ getSystemSearchPath, ) where import Distribution.Verbosity- ( Verbosity ) import Distribution.Simple.Utils- ( debug, doesExecutableExist ) import Distribution.System- ( OS(..), buildOS )-import System.Directory- ( findExecutable ) import Distribution.Compat.Environment- ( getEnvironment )-import System.FilePath- ( (</>), (<.>), splitSearchPath, searchPathSeparator )-import Data.List- ( intercalate )+import Distribution.Compat.Binary +import qualified System.Directory as Directory+ ( findExecutable )+import System.FilePath as FilePath+ ( (</>), (<.>), splitSearchPath, searchPathSeparator, getSearchPath+ , takeDirectory )+import Data.List+ ( nub )+import GHC.Generics+#if defined(mingw32_HOST_OS)+import qualified System.Win32 as Win32+#endif -- | A search path to use when locating executables. This is analogous -- to the unix @$PATH@ or win32 @%PATH%@ but with the ability to use@@ -60,58 +65,69 @@ data ProgramSearchPathEntry = ProgramSearchPathDir FilePath -- ^ A specific dir | ProgramSearchPathDefault -- ^ The system default+ deriving (Eq, Generic) +instance Binary ProgramSearchPathEntry+ defaultProgramSearchPath :: ProgramSearchPath defaultProgramSearchPath = [ProgramSearchPathDefault] findProgramOnSearchPath :: Verbosity -> ProgramSearchPath- -> FilePath -> IO (Maybe FilePath)+ -> FilePath -> IO (Maybe (FilePath, [FilePath])) findProgramOnSearchPath verbosity searchpath prog = do debug verbosity $ "Searching for " ++ prog ++ " in path."- res <- tryPathElems searchpath+ res <- tryPathElems [] searchpath case res of Nothing -> debug verbosity ("Cannot find " ++ prog ++ " on the path")- Just path -> debug verbosity ("Found " ++ prog ++ " at "++ path)+ Just (path, _) -> debug verbosity ("Found " ++ prog ++ " at "++ path) return res where- tryPathElems [] = return Nothing- tryPathElems (pe:pes) = do+ tryPathElems :: [[FilePath]] -> [ProgramSearchPathEntry]+ -> IO (Maybe (FilePath, [FilePath]))+ tryPathElems _ [] = return Nothing+ tryPathElems tried (pe:pes) = do res <- tryPathElem pe case res of- Nothing -> tryPathElems pes- Just _ -> return res+ (Nothing, notfoundat) -> tryPathElems (notfoundat : tried) pes+ (Just foundat, notfoundat) -> return (Just (foundat, alltried))+ where+ alltried = concat (reverse (notfoundat : tried)) + tryPathElem :: ProgramSearchPathEntry -> IO (Maybe FilePath, [FilePath]) tryPathElem (ProgramSearchPathDir dir) =- findFirstExe [ dir </> prog <.> ext | ext <- extensions ]- where- -- Possible improvement: on Windows, read the list of extensions from- -- the PATHEXT environment variable. By default PATHEXT is ".com; .exe;- -- .bat; .cmd".- extensions = case buildOS of- Windows -> ["", "exe"]- Ghcjs -> ["", "exe"]- _ -> [""]+ findFirstExe [ dir </> prog <.> ext | ext <- exeExtensions ] - tryPathElem ProgramSearchPathDefault = do- -- 'findExecutable' doesn't check that the path really refers to an- -- executable on Windows (at least with GHC < 7.8). See- -- https://ghc.haskell.org/trac/ghc/ticket/2184- mExe <- findExecutable prog+ -- On windows, getSystemSearchPath is not guaranteed 100% correct so we+ -- use findExecutable and then approximate the not-found-at locations.+ tryPathElem ProgramSearchPathDefault | buildOS == Windows = do+ mExe <- findExecutable prog+ syspath <- getSystemSearchPath case mExe of- Just exe -> do- exeExists <- doesExecutableExist exe- if exeExists- then return mExe- else return Nothing- _ -> return mExe+ Nothing ->+ let notfoundat = [ dir </> prog | dir <- syspath ] in+ return (Nothing, notfoundat) - findFirstExe [] = return Nothing- findFirstExe (f:fs) = do- isExe <- doesExecutableExist f- if isExe- then return (Just f)- else findFirstExe fs+ Just foundat -> do+ let founddir = takeDirectory foundat+ notfoundat = [ dir </> prog+ | dir <- takeWhile (/= founddir) syspath ]+ return (Just foundat, notfoundat) + -- On other OSs we can just do the simple thing+ tryPathElem ProgramSearchPathDefault = do+ dirs <- getSystemSearchPath+ findFirstExe [ dir </> prog <.> ext | dir <- dirs, ext <- exeExtensions ]++ findFirstExe :: [FilePath] -> IO (Maybe FilePath, [FilePath])+ findFirstExe = go []+ where+ go fs' [] = return (Nothing, reverse fs')+ go fs' (f:fs) = do+ isExe <- doesExecutableExist f+ if isExe+ then return (Just f, reverse fs')+ else go (f:fs') fs+ -- | Interpret a 'ProgramSearchPath' to construct a new @$PATH@ env var. -- Note that this is close but not perfect because on Windows the search -- algorithm looks at more than just the @%PATH%@.@@ -124,3 +140,46 @@ getEntries ProgramSearchPathDefault = do env <- getEnvironment return (maybe [] splitSearchPath (lookup "PATH" env))++-- | Get the system search path. On Unix systems this is just the @$PATH@ env+-- var, but on windows it's a bit more complicated.+--+getSystemSearchPath :: IO [FilePath]+getSystemSearchPath = fmap nub $ do+#if defined(mingw32_HOST_OS)+ processdir <- takeDirectory `fmap` Win32.getModuleFileName Win32.nullHANDLE+ currentdir <- Win32.getCurrentDirectory+ systemdir <- Win32.getSystemDirectory+ windowsdir <- Win32.getWindowsDirectory+ pathdirs <- FilePath.getSearchPath+ let path = processdir : currentdir+ : systemdir : windowsdir+ : pathdirs+ return path+#else+ FilePath.getSearchPath+#endif++#ifdef MIN_VERSION_directory+#if MIN_VERSION_directory(1,2,1)+#define HAVE_directory_121+#endif+#endif++findExecutable :: FilePath -> IO (Maybe FilePath)+#ifdef HAVE_directory_121+findExecutable = Directory.findExecutable+#else+findExecutable prog = do+ -- With directory < 1.2.1 'findExecutable' doesn't check that the path+ -- really refers to an executable.+ mExe <- Directory.findExecutable prog+ case mExe of+ Just exe -> do+ exeExists <- doesExecutableExist exe+ if exeExists+ then return mExe+ else return Nothing+ _ -> return mExe+#endif+
cabal/Cabal/Distribution/Simple/Program/GHC.hs view
@@ -1,4 +1,6 @@-{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE NoMonoLocalBinds #-}+ module Distribution.Simple.Program.GHC ( GhcOptions(..), GhcMode(..),@@ -13,25 +15,23 @@ ) where -import Distribution.Simple.GHC.ImplInfo ( getImplInfo, GhcImplInfo(..) )+import Distribution.Compat.Semigroup as Semi+import Distribution.Simple.GHC.ImplInfo import Distribution.Package import Distribution.PackageDescription hiding (Flag) import Distribution.ModuleName import Distribution.Simple.Compiler hiding (Flag)-import Distribution.Simple.Setup ( Flag(..), flagToMaybe, fromFlagOrDefault,- flagToList )+import Distribution.Simple.Setup import Distribution.Simple.Program.Types import Distribution.Simple.Program.Run+import Distribution.System import Distribution.Text import Distribution.Verbosity-import Distribution.Utils.NubList ( NubListR, fromNubListR )-import Language.Haskell.Extension ( Language(..), Extension(..) )+import Distribution.Utils.NubList+import Language.Haskell.Extension +import GHC.Generics (Generic) import qualified Data.Map as M-#if __GLASGOW_HASKELL__ < 710-import Data.Monoid-#endif-import Data.List ( intercalate ) -- | A structured set of GHC options/flags --@@ -74,18 +74,20 @@ ------------- -- Packages - -- | The package key the modules will belong to; the @ghc -this-package-key@- -- flag.- ghcOptPackageKey :: Flag PackageKey,+ -- | The unit ID the modules will belong to; the @ghc -this-unit-id@+ -- flag (or @-this-package-key@ or @-package-name@ on older+ -- versions of GHC). This is a 'String' because we assume you've+ -- already figured out what the correct format for this string is+ -- (we need to handle backwards compatibility.)+ ghcOptThisUnitId :: Flag String, -- | GHC package databases to use, the @ghc -package-conf@ flag. ghcOptPackageDBs :: PackageDBStack, - -- | The GHC packages to use. For compatability with old and new ghc, this- -- requires both the short and long form of the package id;- -- the @ghc -package@ or @ghc -package-id@ flags.+ -- | The GHC packages to bring into scope when compiling,+ -- the @ghc -package-id@ flags. ghcOptPackages ::- NubListR (InstalledPackageId, PackageId, ModuleRenaming),+ NubListR (UnitId, ModuleRenaming), -- | Start with a clean package set; the @ghc -hide-all-packages@ flag ghcOptHideAllPackages :: Flag Bool,@@ -94,9 +96,6 @@ -- -no-auto-link-packages@ flag. ghcOptNoAutoLinkPackages :: Flag Bool, - -- | What packages are implementing the signatures- ghcOptSigOf :: [(ModuleName, (PackageKey, ModuleName))],- ----------------- -- Linker stuff @@ -112,6 +111,10 @@ -- | OSX only: frameworks to link in; the @ghc -framework@ flag. ghcOptLinkFrameworks :: NubListR String, + -- | OSX only: Search path for frameworks to link in; the+ -- @ghc -framework-path@ flag.+ ghcOptLinkFrameworkDirs :: NubListR String,+ -- | Don't do the link step, useful in make mode; the @ghc -no-link@ flag. ghcOptNoLink :: Flag Bool, @@ -211,7 +214,7 @@ -- Modifies some of the GHC error messages. ghcOptCabal :: Flag Bool -} deriving Show+} deriving (Show, Generic) data GhcMode = GhcModeCompile -- ^ @ghc -c@@@ -239,17 +242,19 @@ | GhcProfAutoExported -- ^ @-fprof-auto-exported@ deriving (Show, Eq) -runGHC :: Verbosity -> ConfiguredProgram -> Compiler -> GhcOptions -> IO ()-runGHC verbosity ghcProg comp opts = do- runProgramInvocation verbosity (ghcInvocation ghcProg comp opts)+runGHC :: Verbosity -> ConfiguredProgram -> Compiler -> Platform -> GhcOptions+ -> IO ()+runGHC verbosity ghcProg comp platform opts = do+ runProgramInvocation verbosity (ghcInvocation ghcProg comp platform opts) -ghcInvocation :: ConfiguredProgram -> Compiler -> GhcOptions -> ProgramInvocation-ghcInvocation prog comp opts =- programInvocation prog (renderGhcOptions comp opts)+ghcInvocation :: ConfiguredProgram -> Compiler -> Platform -> GhcOptions+ -> ProgramInvocation+ghcInvocation prog comp platform opts =+ programInvocation prog (renderGhcOptions comp platform opts) -renderGhcOptions :: Compiler -> GhcOptions -> [String]-renderGhcOptions comp opts+renderGhcOptions :: Compiler -> Platform -> GhcOptions -> [String]+renderGhcOptions comp _platform@(Platform _arch os) opts | compilerFlavor comp `notElem` [GHC, GHCJS] = error $ "Distribution.Simple.Program.GHC.renderGhcOptions: " ++ "compiler flavor must be 'GHC' or 'GHCJS'!"@@ -274,8 +279,7 @@ , maybe [] verbosityOpts (flagToMaybe (ghcOptVerbosity opts)) - , [ "-fbuilding-cabal-package" | flagBool ghcOptCabal- , flagBuildingCabalPkg implInfo ]+ , [ "-fbuilding-cabal-package" | flagBool ghcOptCabal ] ---------------- -- Compilation@@ -337,12 +341,10 @@ , concat [ ["-hisuf", suf] | suf <- flag ghcOptHiSuffix ] , concat [ ["-dynosuf", suf] | suf <- flag ghcOptDynObjSuffix ] , concat [ ["-dynhisuf",suf] | suf <- flag ghcOptDynHiSuffix ]- , concat [ ["-outputdir", dir] | dir <- flag ghcOptOutputDir- , flagOutputDir implInfo ]+ , concat [ ["-outputdir", dir] | dir <- flag ghcOptOutputDir ] , concat [ ["-odir", dir] | dir <- flag ghcOptObjDir ] , concat [ ["-hidir", dir] | dir <- flag ghcOptHiDir ]- , concat [ ["-stubdir", dir] | dir <- flag ghcOptStubDir- , flagStubdir implInfo ]+ , concat [ ["-stubdir", dir] | dir <- flag ghcOptStubDir ] ----------------------- -- Source search path@@ -357,8 +359,6 @@ , [ "-optP" ++ opt | opt <- flags ghcOptCppOptions ] , concat [ [ "-optP-include", "-optP" ++ inc] | inc <- flags ghcOptCppIncludes ]- , [ "-#include \"" ++ inc ++ "\""- | inc <- flags ghcOptFfiIncludes, flagFfiIncludes implInfo ] , [ "-optc" ++ opt | opt <- flags ghcOptCcOptions ] -----------------@@ -367,7 +367,14 @@ , [ "-optl" ++ opt | opt <- flags ghcOptLinkOptions ] , ["-l" ++ lib | lib <- flags ghcOptLinkLibs ] , ["-L" ++ dir | dir <- flags ghcOptLinkLibPath ]- , concat [ ["-framework", fmwk] | fmwk <- flags ghcOptLinkFrameworks ]+ , if isOSX+ then concat [ ["-framework", fmwk]+ | fmwk <- flags ghcOptLinkFrameworks ]+ else []+ , if isOSX+ then concat [ ["-framework-path", path]+ | path <- flags ghcOptLinkFrameworkDirs ]+ else [] , [ "-no-hs-main" | flagBool ghcOptLinkNoHsMain ] , [ "-dynload deploy" | not (null (flags ghcOptRPaths)) ] , concat [ [ "-optl-Wl,-rpath," ++ dir]@@ -376,32 +383,22 @@ ------------- -- Packages - , concat [ [if packageKeySupported comp- then "-this-package-key"- else "-package-name", display pkgid]- | pkgid <- flag ghcOptPackageKey ]+ , concat [ [ case () of+ _ | unitIdSupported comp -> "-this-unit-id"+ | packageKeySupported comp -> "-this-package-key"+ | otherwise -> "-package-name"+ , this_arg ]+ | this_arg <- flag ghcOptThisUnitId ] , [ "-hide-all-packages" | flagBool ghcOptHideAllPackages ] , [ "-no-auto-link-packages" | flagBool ghcOptNoAutoLinkPackages ] , packageDbArgs implInfo (ghcOptPackageDBs opts) - , if null (ghcOptSigOf opts)- then []- else "-sig-of"- : intercalate "," (map (\(n,(p,m)) -> display n ++ " is "- ++ display p ++ ":"- ++ display m)- (ghcOptSigOf opts))- : []-- , concat $ if flagPackageId implInfo- then let space "" = ""- space xs = ' ' : xs- in [ ["-package-id", display ipkgid ++ space (display rns)]- | (ipkgid,_,rns) <- flags ghcOptPackages ]- else [ ["-package", display pkgid]- | (_,pkgid,_) <- flags ghcOptPackages ]+ , concat $ let space "" = ""+ space xs = ' ' : xs+ in [ ["-package-id", display ipkgid ++ space (display rns)]+ | (ipkgid,rns) <- flags ghcOptPackages ] ---------------------------- -- Language and extensions@@ -441,6 +438,7 @@ where implInfo = getImplInfo comp+ isOSX = os == OSX flag flg = flagToList (flg opts) flags flg = fromNubListR . flg $ opts flagBool flg = fromFlagOrDefault False (flg opts)@@ -493,113 +491,8 @@ -- Boilerplate Monoid instance for GhcOptions instance Monoid GhcOptions where- mempty = GhcOptions {- ghcOptMode = mempty,- ghcOptExtra = mempty,- ghcOptExtraDefault = mempty,- ghcOptInputFiles = mempty,- ghcOptInputModules = mempty,- ghcOptOutputFile = mempty,- ghcOptOutputDynFile = mempty,- ghcOptSourcePathClear = mempty,- ghcOptSourcePath = mempty,- ghcOptPackageKey = mempty,- ghcOptPackageDBs = mempty,- ghcOptPackages = mempty,- ghcOptHideAllPackages = mempty,- ghcOptNoAutoLinkPackages = mempty,- ghcOptSigOf = mempty,- ghcOptLinkLibs = mempty,- ghcOptLinkLibPath = mempty,- ghcOptLinkOptions = mempty,- ghcOptLinkFrameworks = mempty,- ghcOptNoLink = mempty,- ghcOptLinkNoHsMain = mempty,- ghcOptCcOptions = mempty,- ghcOptCppOptions = mempty,- ghcOptCppIncludePath = mempty,- ghcOptCppIncludes = mempty,- ghcOptFfiIncludes = mempty,- ghcOptLanguage = mempty,- ghcOptExtensions = mempty,- ghcOptExtensionMap = mempty,- ghcOptOptimisation = mempty,- ghcOptDebugInfo = mempty,- ghcOptProfilingMode = mempty,- ghcOptProfilingAuto = mempty,- ghcOptSplitObjs = mempty,- ghcOptNumJobs = mempty,- ghcOptHPCDir = mempty,- ghcOptGHCiScripts = mempty,- ghcOptHiSuffix = mempty,- ghcOptObjSuffix = mempty,- ghcOptDynHiSuffix = mempty,- ghcOptDynObjSuffix = mempty,- ghcOptHiDir = mempty,- ghcOptObjDir = mempty,- ghcOptOutputDir = mempty,- ghcOptStubDir = mempty,- ghcOptDynLinkMode = mempty,- ghcOptShared = mempty,- ghcOptFPic = mempty,- ghcOptDylibName = mempty,- ghcOptRPaths = mempty,- ghcOptVerbosity = mempty,- ghcOptCabal = mempty- }- mappend a b = GhcOptions {- ghcOptMode = combine ghcOptMode,- ghcOptExtra = combine ghcOptExtra,- ghcOptExtraDefault = combine ghcOptExtraDefault,- ghcOptInputFiles = combine ghcOptInputFiles,- ghcOptInputModules = combine ghcOptInputModules,- ghcOptOutputFile = combine ghcOptOutputFile,- ghcOptOutputDynFile = combine ghcOptOutputDynFile,- ghcOptSourcePathClear = combine ghcOptSourcePathClear,- ghcOptSourcePath = combine ghcOptSourcePath,- ghcOptPackageKey = combine ghcOptPackageKey,- ghcOptPackageDBs = combine ghcOptPackageDBs,- ghcOptPackages = combine ghcOptPackages,- ghcOptHideAllPackages = combine ghcOptHideAllPackages,- ghcOptNoAutoLinkPackages = combine ghcOptNoAutoLinkPackages,- ghcOptSigOf = combine ghcOptSigOf,- ghcOptLinkLibs = combine ghcOptLinkLibs,- ghcOptLinkLibPath = combine ghcOptLinkLibPath,- ghcOptLinkOptions = combine ghcOptLinkOptions,- ghcOptLinkFrameworks = combine ghcOptLinkFrameworks,- ghcOptNoLink = combine ghcOptNoLink,- ghcOptLinkNoHsMain = combine ghcOptLinkNoHsMain,- ghcOptCcOptions = combine ghcOptCcOptions,- ghcOptCppOptions = combine ghcOptCppOptions,- ghcOptCppIncludePath = combine ghcOptCppIncludePath,- ghcOptCppIncludes = combine ghcOptCppIncludes,- ghcOptFfiIncludes = combine ghcOptFfiIncludes,- ghcOptLanguage = combine ghcOptLanguage,- ghcOptExtensions = combine ghcOptExtensions,- ghcOptExtensionMap = combine ghcOptExtensionMap,- ghcOptOptimisation = combine ghcOptOptimisation,- ghcOptDebugInfo = combine ghcOptDebugInfo,- ghcOptProfilingMode = combine ghcOptProfilingMode,- ghcOptProfilingAuto = combine ghcOptProfilingAuto,- ghcOptSplitObjs = combine ghcOptSplitObjs,- ghcOptNumJobs = combine ghcOptNumJobs,- ghcOptHPCDir = combine ghcOptHPCDir,- ghcOptGHCiScripts = combine ghcOptGHCiScripts,- ghcOptHiSuffix = combine ghcOptHiSuffix,- ghcOptObjSuffix = combine ghcOptObjSuffix,- ghcOptDynHiSuffix = combine ghcOptDynHiSuffix,- ghcOptDynObjSuffix = combine ghcOptDynObjSuffix,- ghcOptHiDir = combine ghcOptHiDir,- ghcOptObjDir = combine ghcOptObjDir,- ghcOptOutputDir = combine ghcOptOutputDir,- ghcOptStubDir = combine ghcOptStubDir,- ghcOptDynLinkMode = combine ghcOptDynLinkMode,- ghcOptShared = combine ghcOptShared,- ghcOptFPic = combine ghcOptFPic,- ghcOptDylibName = combine ghcOptDylibName,- ghcOptRPaths = combine ghcOptRPaths,- ghcOptVerbosity = combine ghcOptVerbosity,- ghcOptCabal = combine ghcOptCabal- }- where- combine field = field a `mappend` field b+ mempty = gmempty+ mappend = (Semi.<>)++instance Semigroup GhcOptions where+ (<>) = gmappend
cabal/Cabal/Distribution/Simple/Program/HcPkg.hs view
@@ -11,58 +11,54 @@ module Distribution.Simple.Program.HcPkg ( HcPkgInfo(..),+ MultiInstance(..), init, invoke, register, reregister,+ registerMultiInstance, unregister,+ recache, expose, hide, dump,+ describe, list, -- * Program invocations initInvocation, registerInvocation, reregisterInvocation,+ registerMultiInstanceInvocation, unregisterInvocation,+ recacheInvocation, exposeInvocation, hideInvocation, dumpInvocation,+ describeInvocation, listInvocation, ) where -import Prelude hiding (init)-import Distribution.Package- ( PackageId, InstalledPackageId(..) )+import Distribution.Package hiding (installedUnitId) import Distribution.InstalledPackageInfo- ( InstalledPackageInfo, InstalledPackageInfo(..)- , showInstalledPackageInfo- , emptyInstalledPackageInfo, fieldsInstalledPackageInfo ) import Distribution.ParseUtils import Distribution.Simple.Compiler- ( PackageDB(..), PackageDBStack ) import Distribution.Simple.Program.Types- ( ConfiguredProgram(programId) ) import Distribution.Simple.Program.Run- ( ProgramInvocation(..), IOEncoding(..), programInvocation- , runProgramInvocation, getProgramInvocationOutput ) import Distribution.Text- ( display, simpleParse ) import Distribution.Simple.Utils- ( die ) import Distribution.Verbosity- ( Verbosity, deafening, silent ) import Distribution.Compat.Exception- ( catchExit ) +import Prelude hiding (init) import Data.Char ( isSpace ) import Data.List ( stripPrefix ) import System.FilePath as FilePath- ( (</>), splitPath, splitDirectories, joinPath, isPathSeparator )+ ( (</>), (<.>)+ , splitPath, splitDirectories, joinPath, isPathSeparator ) import qualified System.FilePath.Posix as FilePath.Posix -- | Information about the features and capabilities of an @hc-pkg@@@ -73,17 +69,29 @@ , noPkgDbStack :: Bool -- ^ no package DB stack supported , noVerboseFlag :: Bool -- ^ hc-pkg does not support verbosity flags , flagPackageConf :: Bool -- ^ use package-conf option instead of package-db- , useSingleFileDb :: Bool -- ^ requires single file package database+ , supportsDirDbs :: Bool -- ^ supports directory style package databases+ , requiresDirDbs :: Bool -- ^ requires directory style package databases+ , nativeMultiInstance :: Bool -- ^ supports --enable-multi-instance flag+ , recacheMultiInstance :: Bool -- ^ supports multi-instance via recache } +-- | Whether or not use multi-instance functionality.+data MultiInstance = MultiInstance | NoMultiInstance+ deriving (Show, Read, Eq, Ord)+ -- | Call @hc-pkg@ to initialise a package database at the location {path}. -- -- > hc-pkg init {path} ---init :: HcPkgInfo -> Verbosity -> FilePath -> IO ()-init hpi verbosity path =- runProgramInvocation verbosity (initInvocation hpi verbosity path)+init :: HcPkgInfo -> Verbosity -> Bool -> FilePath -> IO ()+init hpi verbosity preferCompat path+ | not (supportsDirDbs hpi)+ || (not (requiresDirDbs hpi) && preferCompat)+ = writeFile path "[]" + | otherwise+ = runProgramInvocation verbosity (initInvocation hpi verbosity path)+ -- | Run @hc-pkg@ using a given package DB stack, directly forwarding the -- provided command-line arguments to it. invoke :: HcPkgInfo -> Verbosity -> PackageDBStack -> [String] -> IO ()@@ -118,7 +126,51 @@ runProgramInvocation verbosity (reregisterInvocation hpi verbosity packagedb pkgFile) +registerMultiInstance :: HcPkgInfo -> Verbosity+ -> PackageDBStack+ -> InstalledPackageInfo+ -> IO ()+registerMultiInstance hpi verbosity packagedbs pkgInfo+ | nativeMultiInstance hpi+ = runProgramInvocation verbosity+ (registerMultiInstanceInvocation hpi verbosity packagedbs (Right pkgInfo)) + -- This is a trick. Older versions of GHC do not support the+ -- --enable-multi-instance flag for ghc-pkg register but it turns out that+ -- the same ability is available by using ghc-pkg recache. The recache+ -- command is there to support distro package managers that like to work+ -- by just installing files and running update commands, rather than+ -- special add/remove commands. So the way to register by this method is+ -- to write the package registration file directly into the package db and+ -- then call hc-pkg recache.+ --+ | recacheMultiInstance hpi+ = do let pkgdb = last packagedbs+ writeRegistrationFileDirectly hpi pkgdb pkgInfo+ recache hpi verbosity pkgdb++ | otherwise+ = die $ "HcPkg.registerMultiInstance: the compiler does not support "+ ++ "registering multiple instances of packages."++writeRegistrationFileDirectly :: HcPkgInfo+ -> PackageDB+ -> InstalledPackageInfo+ -> IO ()+writeRegistrationFileDirectly hpi (SpecificPackageDB dir) pkgInfo+ | supportsDirDbs hpi+ = do let pkgfile = dir </> display (installedUnitId pkgInfo) <.> "conf"+ writeUTF8File pkgfile (showInstalledPackageInfo pkgInfo)++ | otherwise+ = die $ "HcPkg.writeRegistrationFileDirectly: compiler does not support dir style package dbs"++writeRegistrationFileDirectly _ _ _ =+ -- We don't know here what the dir for the global or user dbs are,+ -- if that's needed it'll require a bit more plumbing to support.+ die $ "HcPkg.writeRegistrationFileDirectly: only supports SpecificPackageDB for now"++ -- | Call @hc-pkg@ to unregister a package -- -- > hc-pkg unregister [pkgid] [--user | --global | --package-db]@@ -129,6 +181,16 @@ (unregisterInvocation hpi verbosity packagedb pkgid) +-- | Call @hc-pkg@ to recache the registered packages.+--+-- > hc-pkg recache [--user | --global | --package-db]+--+recache :: HcPkgInfo -> Verbosity -> PackageDB -> IO ()+recache hpi verbosity packagedb =+ runProgramInvocation verbosity+ (recacheInvocation hpi verbosity packagedb)++ -- | Call @hc-pkg@ to expose a package. -- -- > hc-pkg expose [pkgid] [--user | --global | --package-db]@@ -138,7 +200,22 @@ runProgramInvocation verbosity (exposeInvocation hpi verbosity packagedb pkgid) +-- | Call @hc-pkg@ to retrieve a specific package+--+-- > hc-pkg describe [pkgid] [--user | --global | --package-db]+--+describe :: HcPkgInfo -> Verbosity -> PackageDBStack -> PackageId -> IO [InstalledPackageInfo]+describe hpi verbosity packagedb pid = do + output <- getProgramInvocationOutput verbosity+ (describeInvocation hpi verbosity packagedb pid)+ `catchIO` \_ -> return ""++ case parsePackages output of+ Left ok -> return ok+ _ -> die $ "failed to parse output of '"+ ++ programId (hcPkgProgram hpi) ++ " describe " ++ display pid ++ "'"+ -- | Call @hc-pkg@ to hide a package. -- -- > hc-pkg hide [pkgid] [--user | --global | --package-db]@@ -157,40 +234,40 @@ output <- getProgramInvocationOutput verbosity (dumpInvocation hpi verbosity packagedb)- `catchExit` \_ -> die $ programId (hcPkgProgram hpi) ++ " dump failed"+ `catchIO` \_ -> die $ programId (hcPkgProgram hpi) ++ " dump failed" case parsePackages output of Left ok -> return ok _ -> die $ "failed to parse output of '" ++ programId (hcPkgProgram hpi) ++ " dump'" +parsePackages :: String -> Either [InstalledPackageInfo] [PError]+parsePackages str =+ let parsed = map parseInstalledPackageInfo' (splitPkgs str)+ in case [ msg | ParseFailed msg <- parsed ] of+ [] -> Left [ setUnitId+ . maybe id mungePackagePaths (pkgRoot pkg)+ $ pkg+ | ParseOk _ pkg <- parsed ]+ msgs -> Right msgs where- parsePackages str =- let parsed = map parseInstalledPackageInfo' (splitPkgs str)- in case [ msg | ParseFailed msg <- parsed ] of- [] -> Left [ setInstalledPackageId- . maybe id mungePackagePaths (pkgRoot pkg)- $ pkg- | ParseOk _ pkg <- parsed ]- msgs -> Right msgs- parseInstalledPackageInfo' = parseFieldsFlat fieldsInstalledPackageInfo emptyInstalledPackageInfo - --TODO: this could be a lot faster. We're doing normaliseLineEndings twice- -- and converting back and forth with lines/unlines.- splitPkgs :: String -> [String]- splitPkgs = checkEmpty . map unlines . splitWith ("---" ==) . lines- where- -- Handle the case of there being no packages at all.- checkEmpty [s] | all isSpace s = []- checkEmpty ss = ss+--TODO: this could be a lot faster. We're doing normaliseLineEndings twice+-- and converting back and forth with lines/unlines.+splitPkgs :: String -> [String]+splitPkgs = checkEmpty . map unlines . splitWith ("---" ==) . lines+ where+ -- Handle the case of there being no packages at all.+ checkEmpty [s] | all isSpace s = []+ checkEmpty ss = ss - splitWith :: (a -> Bool) -> [a] -> [[a]]- splitWith p xs = ys : case zs of- [] -> []- _:ws -> splitWith p ws- where (ys,zs) = break p xs+ splitWith :: (a -> Bool) -> [a] -> [[a]]+ splitWith p xs = ys : case zs of+ [] -> []+ _:ws -> splitWith p ws+ where (ys,zs) = break p xs mungePackagePaths :: FilePath -> InstalledPackageInfo -> InstalledPackageInfo -- Perform path/URL variable substitution as per the Cabal ${pkgroot} spec@@ -230,26 +307,24 @@ _ -> Nothing --- Older installed package info files did not have the installedPackageId+-- Older installed package info files did not have the installedUnitId -- field, so if it is missing then we fill it as the source package ID.-setInstalledPackageId :: InstalledPackageInfo -> InstalledPackageInfo-setInstalledPackageId pkginfo@InstalledPackageInfo {- installedPackageId = InstalledPackageId "",- sourcePackageId = pkgid+setUnitId :: InstalledPackageInfo -> InstalledPackageInfo+setUnitId pkginfo@InstalledPackageInfo {+ installedUnitId = SimpleUnitId (ComponentId ""),+ sourcePackageId = pkgid } = pkginfo {- --TODO use a proper named function for the conversion- -- from source package id to installed package id- installedPackageId = InstalledPackageId (display pkgid)+ installedUnitId = mkLegacyUnitId pkgid }-setInstalledPackageId pkginfo = pkginfo+setUnitId pkginfo = pkginfo -- | Call @hc-pkg@ to get the source package Id of all the packages in the -- given package database. -- -- This is much less information than with 'dump', but also rather quicker.--- Note in particular that it does not include the 'InstalledPackageId', just+-- Note in particular that it does not include the 'UnitId', just -- the source 'PackageId' which is not necessarily unique in any package db. -- list :: HcPkgInfo -> Verbosity -> PackageDB@@ -258,7 +333,7 @@ output <- getProgramInvocationOutput verbosity (listInvocation hpi verbosity packagedb)- `catchExit` \_ -> die $ programId (hcPkgProgram hpi) ++ " list failed"+ `catchIO` \_ -> die $ programId (hcPkgProgram hpi) ++ " list failed" case parsePackageIds output of Just ok -> return ok@@ -279,38 +354,36 @@ args = ["init", path] ++ verbosityOpts hpi verbosity -registerInvocation, reregisterInvocation+registerInvocation, reregisterInvocation, registerMultiInstanceInvocation :: HcPkgInfo -> Verbosity -> PackageDBStack -> Either FilePath InstalledPackageInfo -> ProgramInvocation-registerInvocation = registerInvocation' "register"-reregisterInvocation = registerInvocation' "update"-+registerInvocation = registerInvocation' "register" NoMultiInstance+reregisterInvocation = registerInvocation' "update" NoMultiInstance+registerMultiInstanceInvocation = registerInvocation' "update" MultiInstance -registerInvocation' :: String -> HcPkgInfo -> Verbosity -> PackageDBStack+registerInvocation' :: String -> MultiInstance+ -> HcPkgInfo -> Verbosity -> PackageDBStack -> Either FilePath InstalledPackageInfo -> ProgramInvocation-registerInvocation' cmdname hpi verbosity packagedbs (Left pkgFile) =- programInvocation (hcPkgProgram hpi) args- where- args = [cmdname, pkgFile]- ++ (if noPkgDbStack hpi- then [packageDbOpts hpi (last packagedbs)]- else packageDbStackOpts hpi packagedbs)- ++ verbosityOpts hpi verbosity+registerInvocation' cmdname multiInstance hpi+ verbosity packagedbs pkgFileOrInfo =+ case pkgFileOrInfo of+ Left pkgFile ->+ programInvocation (hcPkgProgram hpi) (args pkgFile) -registerInvocation' cmdname hpi verbosity packagedbs (Right pkgInfo) =- (programInvocation (hcPkgProgram hpi) args) {- progInvokeInput = Just (showInstalledPackageInfo pkgInfo),- progInvokeInputEncoding = IOEncodingUTF8- }+ Right pkgInfo ->+ (programInvocation (hcPkgProgram hpi) (args "-")) {+ progInvokeInput = Just (showInstalledPackageInfo pkgInfo),+ progInvokeInputEncoding = IOEncodingUTF8+ } where- args = [cmdname, "-"]- ++ (if noPkgDbStack hpi- then [packageDbOpts hpi (last packagedbs)]- else packageDbStackOpts hpi packagedbs)- ++ verbosityOpts hpi verbosity-+ args file = [cmdname, file]+ ++ (if noPkgDbStack hpi+ then [packageDbOpts hpi (last packagedbs)]+ else packageDbStackOpts hpi packagedbs)+ ++ [ "--enable-multi-instance" | multiInstance == MultiInstance ]+ ++ verbosityOpts hpi verbosity unregisterInvocation :: HcPkgInfo -> Verbosity -> PackageDB -> PackageId -> ProgramInvocation@@ -320,6 +393,14 @@ ++ verbosityOpts hpi verbosity +recacheInvocation :: HcPkgInfo -> Verbosity -> PackageDB+ -> ProgramInvocation+recacheInvocation hpi verbosity packagedb =+ programInvocation (hcPkgProgram hpi) $+ ["recache", packageDbOpts hpi packagedb]+ ++ verbosityOpts hpi verbosity++ exposeInvocation :: HcPkgInfo -> Verbosity -> PackageDB -> PackageId -> ProgramInvocation exposeInvocation hpi verbosity packagedb pkgid =@@ -327,6 +408,15 @@ ["expose", packageDbOpts hpi packagedb, display pkgid] ++ verbosityOpts hpi verbosity +describeInvocation :: HcPkgInfo -> Verbosity -> PackageDBStack -> PackageId+ -> ProgramInvocation+describeInvocation hpi verbosity packagedbs pkgid =+ programInvocation (hcPkgProgram hpi) $+ ["describe", display pkgid]+ ++ (if noPkgDbStack hpi+ then [packageDbOpts hpi (last packagedbs)]+ else packageDbStackOpts hpi packagedbs)+ ++ verbosityOpts hpi verbosity hideInvocation :: HcPkgInfo -> Verbosity -> PackageDB -> PackageId -> ProgramInvocation
cabal/Cabal/Distribution/Simple/Program/Hpc.hs view
@@ -13,14 +13,13 @@ , union ) where -import Distribution.ModuleName ( ModuleName )+import Distribution.ModuleName import Distribution.Simple.Program.Run- ( ProgramInvocation, programInvocation, runProgramInvocation )-import Distribution.Simple.Program.Types ( ConfiguredProgram(..) )-import Distribution.Text ( display )-import Distribution.Simple.Utils ( warn )-import Distribution.Verbosity ( Verbosity )-import Distribution.Version ( Version(..), orLaterVersion, withinRange )+import Distribution.Simple.Program.Types+import Distribution.Text+import Distribution.Simple.Utils+import Distribution.Verbosity+import Distribution.Version -- | Invoke hpc with the given parameters. --
cabal/Cabal/Distribution/Simple/Program/Run.hs view
@@ -24,12 +24,9 @@ ) where import Distribution.Simple.Program.Types- ( ConfiguredProgram(..), programPath ) import Distribution.Simple.Utils- ( die, rawSystemExit, rawSystemIOWithEnv, rawSystemStdInOut- , toUTF8, fromUTF8, normaliseLineEndings ) import Distribution.Verbosity- ( Verbosity )+import Distribution.Compat.Environment import Data.List ( foldl', unfoldr )@@ -38,8 +35,6 @@ ( when ) import System.Exit ( ExitCode(..), exitWith )-import Distribution.Compat.Environment- ( getEnvironment ) -- | Represents a specific invocation of a specific program. --
cabal/Cabal/Distribution/Simple/Program/Script.hs view
@@ -17,9 +17,7 @@ ) where import Distribution.Simple.Program.Run- ( ProgramInvocation(..) ) import Distribution.System- ( OS(..) ) import Data.Maybe ( maybeToList )
cabal/Cabal/Distribution/Simple/Program/Strip.hs view
@@ -10,16 +10,13 @@ module Distribution.Simple.Program.Strip (stripLib, stripExe) where -import Distribution.Simple.Program (ProgramConfiguration, lookupProgram- , programVersion, rawSystemProgram- , stripProgram)-import Distribution.Simple.Utils (warn)-import Distribution.System (Arch(..), Platform(..), OS (..), buildOS)-import Distribution.Verbosity (Verbosity)-import Distribution.Version (orLaterVersion, withinRange)+import Distribution.Simple.Program+import Distribution.Simple.Utils+import Distribution.System+import Distribution.Verbosity+import Distribution.Version import Control.Monad (unless)-import Data.Version (Version(..)) import System.FilePath (takeBaseName) runStrip :: Verbosity -> ProgramConfiguration -> FilePath -> [String] -> IO ()@@ -47,10 +44,11 @@ stripLib :: Verbosity -> Platform -> ProgramConfiguration -> FilePath -> IO () stripLib verbosity (Platform arch os) conf path = do case os of- OSX -> -- '--strip-unneeded' is not supported on OS X, iOS or+ OSX -> -- '--strip-unneeded' is not supported on OS X, iOS, AIX, or -- Solaris. See #1630. return () IOS -> return ()+ AIX -> return () Solaris -> return () Windows -> -- Stripping triggers a bug in 'strip.exe' for -- libraries with lots identically named modules. See
cabal/Cabal/Distribution/Simple/Program/Types.hs view
@@ -33,14 +33,10 @@ ) where import Distribution.Simple.Program.Find- ( ProgramSearchPath, ProgramSearchPathEntry(..)- , findProgramOnSearchPath ) import Distribution.Version- ( Version ) import Distribution.Verbosity- ( Verbosity )+import Distribution.Compat.Binary -import Distribution.Compat.Binary (Binary) import qualified Data.Map as Map import GHC.Generics (Generic) @@ -59,8 +55,13 @@ -- -- It is supplied with the prevailing search path which will typically -- just be used as-is, but can be extended or ignored as needed.+ --+ -- For the purpose of change monitoring, in addition to the location+ -- where the program was found, it returns all the other places that+ -- were tried.+ -- programFindLocation :: Verbosity -> ProgramSearchPath- -> IO (Maybe FilePath),+ -> IO (Maybe (FilePath, [FilePath])), -- | Try to find the version of the program. For many programs this is -- not possible or is not necessary so it's OK to return Nothing.@@ -108,9 +109,18 @@ programProperties :: Map.Map String String, -- | Location of the program. eg. @\/usr\/bin\/ghc-6.4@- programLocation :: ProgramLocation- } deriving (Eq, Generic, Read, Show)+ programLocation :: ProgramLocation, + -- | In addition to the 'programLocation' where the program was found,+ -- these are additional locations that were looked at. The combination+ -- of ths found location and these not-found locations can be used to+ -- monitor to detect when the re-configuring the program might give a+ -- different result (e.g. found in a different location).+ --+ programMonitorFiles :: [FilePath]+ }+ deriving (Eq, Generic, Read, Show)+ instance Binary ConfiguredProgram -- | Where a program was found. Also tells us whether it's specified by user or@@ -160,5 +170,6 @@ programOverrideArgs = [], programOverrideEnv = [], programProperties = Map.empty,- programLocation = loc+ programLocation = loc,+ programMonitorFiles = [] -- did not look in any other locations }
cabal/Cabal/Distribution/Simple/Register.hs view
@@ -28,6 +28,10 @@ unregister, initPackageDB,+ doesPackageDBExist,+ createPackageDB,+ deletePackageDB,+ invokeHcPkg, registerPackage, generateRegistrationInfo,@@ -37,10 +41,7 @@ ) where import Distribution.Simple.LocalBuildInfo- ( LocalBuildInfo(..), ComponentLocalBuildInfo(..)- , ComponentName(..), getComponentLocalBuildInfo- , InstallDirs(..), absoluteInstallDirs )-import Distribution.Simple.BuildPaths (haddockName)+import Distribution.Simple.BuildPaths import qualified Distribution.Simple.GHC as GHC import qualified Distribution.Simple.GHCJS as GHCJS@@ -49,42 +50,25 @@ import qualified Distribution.Simple.HaskellSuite as HaskellSuite import Distribution.Simple.Compiler- ( compilerVersion, Compiler, CompilerFlavor(..), compilerFlavor- , PackageDB, PackageDBStack, absolutePackageDBPaths- , registrationPackageDB ) import Distribution.Simple.Program- ( ProgramConfiguration, runProgramInvocation ) import Distribution.Simple.Program.Script- ( invocationAsSystemScript )-import Distribution.Simple.Program.HcPkg (HcPkgInfo) import qualified Distribution.Simple.Program.HcPkg as HcPkg import Distribution.Simple.Setup- ( RegisterFlags(..), CopyDest(..)- , fromFlag, fromFlagOrDefault, flagToMaybe ) import Distribution.PackageDescription- ( PackageDescription(..), Library(..), BuildInfo(..), libModules ) import Distribution.Package- ( Package(..), packageName, InstalledPackageId(..)- , getHSLibraryName )-import Distribution.InstalledPackageInfo- ( InstalledPackageInfo, InstalledPackageInfo(InstalledPackageInfo)- , showInstalledPackageInfo ) import qualified Distribution.InstalledPackageInfo as IPI+import Distribution.InstalledPackageInfo (InstalledPackageInfo) import Distribution.Simple.Utils- ( writeUTF8File, writeFileAtomic, setFileExecutable- , die, notice, setupMessage, shortRelativePath ) import Distribution.System- ( OS(..), buildOS ) import Distribution.Text- ( display )-import Distribution.Version ( Version(..) ) import Distribution.Verbosity as Verbosity- ( Verbosity, normal ) import System.FilePath ((</>), (<.>), isAbsolute) import System.Directory- ( getCurrentDirectory )+ ( getCurrentDirectory, removeDirectoryRecursive, removeFile+ , doesDirectoryExist, doesFileExist ) +import Data.Version import Control.Monad (when) import Data.Maybe ( isJust, fromMaybe, maybeToList )@@ -98,24 +82,43 @@ register :: PackageDescription -> LocalBuildInfo -> RegisterFlags -- ^Install in the user's database?; verbose -> IO ()-register pkg@PackageDescription { library = Just lib } lbi regFlags+register pkg lbi regFlags =+ -- We do NOT register libraries outside of the inplace database+ -- if there is no public library, since no one else can use it+ -- usefully (they're not public.) If we start supporting scoped+ -- packages, we'll have to relax this.+ when (hasPublicLib pkg) $+ let maybeRegister (CLib lib) _clbi =+ registerOne pkg lbi regFlags lib+ maybeRegister _comp _clbi = return ()+ in withAllComponentsInBuildOrder pkg lbi maybeRegister++registerOne :: PackageDescription -> LocalBuildInfo -> RegisterFlags+ -> Library+ -> IO ()+registerOne pkg lbi regFlags lib = do- let clbi = getComponentLocalBuildInfo lbi CLibName+ let clbi = getComponentLocalBuildInfo lbi (CLibName (libName lib)) absPackageDBs <- absolutePackageDBPaths packageDbs+ -- TODO: registration info named base on LIBNAME!!! installedPkgInfo <- generateRegistrationInfo verbosity pkg lib lbi clbi inplace reloc distPref (registrationPackageDB absPackageDBs) + info verbosity (IPI.showInstalledPackageInfo installedPkgInfo)+ when (fromFlag (regPrintId regFlags)) $ do- putStrLn (display (IPI.installedPackageId installedPkgInfo))+ putStrLn (display (IPI.installedUnitId installedPkgInfo)) -- Three different modes: case () of _ | modeGenerateRegFile -> writeRegistrationFile installedPkgInfo | modeGenerateRegScript -> writeRegisterScript installedPkgInfo- | otherwise -> registerPackage verbosity- installedPkgInfo pkg lbi inplace packageDbs+ | otherwise -> do+ setupMessage verbosity "Registering" (packageId pkg)+ registerPackage verbosity (compiler lbi) (withPrograms lbi) HcPkg.NoMultiInstance+ packageDbs installedPkgInfo where modeGenerateRegFile = isJust (flagToMaybe (regGenPkgConf regFlags))@@ -136,7 +139,7 @@ writeRegistrationFile installedPkgInfo = do notice verbosity ("Creating package registration file: " ++ regFile)- writeUTF8File regFile (showInstalledPackageInfo installedPkgInfo)+ writeUTF8File regFile (IPI.showInstalledPackageInfo installedPkgInfo) writeRegisterScript installedPkgInfo = case compilerFlavor (compiler lbi) of@@ -147,11 +150,7 @@ (compiler lbi) (withPrograms lbi) (writeHcPkgRegisterScript verbosity installedPkgInfo packageDbs) -register _ _ regFlags = notice verbosity "No package to register"- where- verbosity = fromFlag (regVerbosity regFlags) - generateRegistrationInfo :: Verbosity -> PackageDescription -> Library@@ -166,59 +165,80 @@ --TODO: eliminate pwd! pwd <- getCurrentDirectory - --TODO: the method of setting the InstalledPackageId is compiler specific+ --TODO: the method of setting the UnitId is compiler specific -- this aspect should be delegated to a per-compiler helper. let comp = compiler lbi- ipid <-+ abi_hash <- case compilerFlavor comp of GHC | compilerVersion comp >= Version [6,11] [] -> do- s <- GHC.libAbiHash verbosity pkg lbi lib clbi- return (InstalledPackageId (display (packageId pkg) ++ '-':s))+ fmap AbiHash $ GHC.libAbiHash verbosity pkg lbi lib clbi GHCJS -> do- s <- GHCJS.libAbiHash verbosity pkg lbi lib clbi- return (InstalledPackageId (display (packageId pkg) ++ '-':s))- _other -> do- return (InstalledPackageId (display (packageId pkg)))+ fmap AbiHash $ GHCJS.libAbiHash verbosity pkg lbi lib clbi+ _ -> return (AbiHash "") installedPkgInfo <- if inplace then return (inplaceInstalledPackageInfo pwd distPref- pkg ipid lib lbi clbi)+ pkg abi_hash lib lbi clbi) else if reloc then relocRegistrationInfo verbosity- pkg lib lbi clbi ipid packageDb+ pkg lib lbi clbi abi_hash packageDb else return (absoluteInstalledPackageInfo- pkg ipid lib lbi clbi)+ pkg abi_hash lib lbi clbi) - return installedPkgInfo{ IPI.installedPackageId = ipid }+ return installedPkgInfo{ IPI.abiHash = abi_hash } relocRegistrationInfo :: Verbosity -> PackageDescription -> Library -> LocalBuildInfo -> ComponentLocalBuildInfo- -> InstalledPackageId+ -> AbiHash -> PackageDB -> IO InstalledPackageInfo-relocRegistrationInfo verbosity pkg lib lbi clbi ipid packageDb =+relocRegistrationInfo verbosity pkg lib lbi clbi abi_hash packageDb = case (compilerFlavor (compiler lbi)) of GHC -> do fs <- GHC.pkgRoot verbosity lbi packageDb return (relocatableInstalledPackageInfo- pkg ipid lib lbi clbi fs)+ pkg abi_hash lib lbi clbi fs) _ -> die "Distribution.Simple.Register.relocRegistrationInfo: \ \not implemented for this compiler" +initPackageDB :: Verbosity -> Compiler -> ProgramConfiguration -> FilePath -> IO ()+initPackageDB verbosity comp progdb dbPath =+ createPackageDB verbosity comp progdb False dbPath+ -- | Create an empty package DB at the specified location.-initPackageDB :: Verbosity -> Compiler -> ProgramConfiguration -> FilePath- -> IO ()-initPackageDB verbosity comp conf dbPath =- case compilerFlavor comp of- HaskellSuite {} -> HaskellSuite.initPackageDB verbosity conf dbPath- _ -> withHcPkg "Distribution.Simple.Register.initPackageDB: \- \not implemented for this compiler" comp conf- (\hpi -> HcPkg.init hpi verbosity dbPath)+createPackageDB :: Verbosity -> Compiler -> ProgramConfiguration -> Bool+ -> FilePath -> IO ()+createPackageDB verbosity comp progdb preferCompat dbPath =+ case compilerFlavor comp of+ GHC -> HcPkg.init (GHC.hcPkgInfo progdb) verbosity preferCompat dbPath+ GHCJS -> HcPkg.init (GHCJS.hcPkgInfo progdb) verbosity False dbPath+ LHC -> HcPkg.init (LHC.hcPkgInfo progdb) verbosity False dbPath+ UHC -> return ()+ HaskellSuite _ -> HaskellSuite.initPackageDB verbosity progdb dbPath+ _ -> die $ "Distribution.Simple.Register.createPackageDB: "+ ++ "not implemented for this compiler" +doesPackageDBExist :: FilePath -> IO Bool+doesPackageDBExist dbPath = do+ -- currently one impl for all compiler flavours, but could change if needed+ dir_exists <- doesDirectoryExist dbPath+ if dir_exists+ then return True+ else doesFileExist dbPath++deletePackageDB :: FilePath -> IO ()+deletePackageDB dbPath = do+ -- currently one impl for all compiler flavours, but could change if needed+ dir_exists <- doesDirectoryExist dbPath+ if dir_exists+ then removeDirectoryRecursive dbPath+ else do file_exists <- doesFileExist dbPath+ when file_exists $ removeFile dbPath+ -- | Run @hc-pkg@ using a given package DB stack, directly forwarding the -- provided command-line arguments to it. invokeHcPkg :: Verbosity -> Compiler -> ProgramConfiguration -> PackageDBStack@@ -228,7 +248,7 @@ (\hpi -> HcPkg.invoke hpi verbosity dbStack extraArgs) withHcPkg :: String -> Compiler -> ProgramConfiguration- -> (HcPkgInfo -> IO a) -> IO a+ -> (HcPkg.HcPkgInfo -> IO a) -> IO a withHcPkg name comp conf f = case compilerFlavor comp of GHC -> f (GHC.hcPkgInfo conf)@@ -238,31 +258,29 @@ \not implemented for this compiler") registerPackage :: Verbosity- -> InstalledPackageInfo- -> PackageDescription- -> LocalBuildInfo- -> Bool+ -> Compiler+ -> ProgramConfiguration+ -> HcPkg.MultiInstance -> PackageDBStack+ -> InstalledPackageInfo -> IO ()-registerPackage verbosity installedPkgInfo pkg lbi inplace packageDbs = do- let msg = if inplace- then "In-place registering"- else "Registering"- setupMessage verbosity msg (packageId pkg)- case compilerFlavor (compiler lbi) of- GHC -> GHC.registerPackage verbosity installedPkgInfo pkg lbi inplace packageDbs- GHCJS -> GHCJS.registerPackage verbosity installedPkgInfo pkg lbi inplace packageDbs- LHC -> LHC.registerPackage verbosity installedPkgInfo pkg lbi inplace packageDbs- UHC -> UHC.registerPackage verbosity installedPkgInfo pkg lbi inplace packageDbs+registerPackage verbosity comp progdb multiInstance packageDbs installedPkgInfo =+ case compilerFlavor comp of+ GHC -> GHC.registerPackage verbosity progdb multiInstance packageDbs installedPkgInfo+ GHCJS -> GHCJS.registerPackage verbosity progdb multiInstance packageDbs installedPkgInfo+ _ | HcPkg.MultiInstance == multiInstance+ -> die "Registering multiple package instances is not yet supported for this compiler"+ LHC -> LHC.registerPackage verbosity progdb packageDbs installedPkgInfo+ UHC -> UHC.registerPackage verbosity comp progdb packageDbs installedPkgInfo JHC -> notice verbosity "Registering for jhc (nothing to do)" HaskellSuite {} ->- HaskellSuite.registerPackage verbosity installedPkgInfo pkg lbi inplace packageDbs+ HaskellSuite.registerPackage verbosity progdb packageDbs installedPkgInfo _ -> die "Registering is not implemented for this compiler" writeHcPkgRegisterScript :: Verbosity -> InstalledPackageInfo -> PackageDBStack- -> HcPkgInfo+ -> HcPkg.HcPkgInfo -> IO () writeHcPkgRegisterScript verbosity installedPkgInfo packageDbs hpi = do let invocation = HcPkg.reregisterInvocation hpi Verbosity.normal@@ -289,17 +307,19 @@ :: ([FilePath] -> [FilePath]) -- ^ Translate relative include dir paths to -- absolute paths. -> PackageDescription- -> InstalledPackageId+ -> AbiHash -> Library -> LocalBuildInfo -> ComponentLocalBuildInfo -> InstallDirs FilePath -> InstalledPackageInfo-generalInstalledPackageInfo adjustRelIncDirs pkg ipid lib lbi clbi installDirs =- InstalledPackageInfo {- IPI.installedPackageId = ipid,- IPI.sourcePackageId = packageId pkg,- IPI.packageKey = componentPackageKey clbi,+generalInstalledPackageInfo adjustRelIncDirs pkg abi_hash lib lbi clbi installDirs =+ IPI.InstalledPackageInfo {+ IPI.sourcePackageId = (packageId pkg) {+ pkgName = componentCompatPackageName clbi+ },+ IPI.installedUnitId = componentUnitId clbi,+ IPI.compatPackageKey = componentCompatPackageKey clbi, IPI.license = license pkg, IPI.copyright = copyright pkg, IPI.maintainer = maintainer pkg,@@ -310,12 +330,10 @@ IPI.synopsis = synopsis pkg, IPI.description = description pkg, IPI.category = category pkg,+ IPI.abiHash = abi_hash, IPI.exposed = libExposed lib,- IPI.exposedModules = map fixupSelf (componentExposedModules clbi),+ IPI.exposedModules = componentExposedModules clbi, IPI.hiddenModules = otherModules bi,- IPI.instantiatedWith = map (\(k,(p,n)) ->- (k,IPI.OriginalModule (IPI.installedPackageId p) n))- (instantiatedWith lbi), IPI.trusted = IPI.trusted IPI.emptyInstalledPackageInfo, IPI.importDirs = [ libdir installDirs | hasModules ], -- Note. the libsubdir and datasubdir templates have already been expanded@@ -325,7 +343,7 @@ else extraLibDirs bi, IPI.dataDir = datadir installDirs, IPI.hsLibraries = if hasLibrary- then [getHSLibraryName (componentLibraryName clbi)]+ then [getHSLibraryName (componentUnitId clbi)] else [], IPI.extraLibraries = extraLibs bi, IPI.extraGHCiLibraries = extraGHCiLibs bi,@@ -336,8 +354,8 @@ -- We don't want cc-options to be propagated -- to C compilations in other packages. IPI.ldOptions = ldOptions bi,- IPI.frameworkDirs = [], IPI.frameworks = frameworks bi,+ IPI.frameworkDirs = extraFrameworkDirs bi, IPI.haddockInterfaces = [haddockdir installDirs </> haddockName pkg], IPI.haddockHTMLs = [htmldir installDirs], IPI.pkgRoot = Nothing@@ -350,18 +368,6 @@ || (not (null (jsSources bi)) && compilerFlavor (compiler lbi) == GHCJS) - -- Since we currently don't decide the InstalledPackageId of our package- -- until just before we register, we didn't have one for the re-exports- -- of modules defined within this package, so we used an empty one that- -- we fill in here now that we know what it is. It's a bit of a hack,- -- we ought really to decide the InstalledPackageId ahead of time.- fixupSelf (IPI.ExposedModule n o o') =- IPI.ExposedModule n (fmap fixupOriginalModule o)- (fmap fixupOriginalModule o')- fixupOriginalModule (IPI.OriginalModule i m) = IPI.OriginalModule (fixupIpid i) m- fixupIpid (InstalledPackageId []) = ipid- fixupIpid x = x- -- | Construct 'InstalledPackageInfo' for a library that is in place in the -- build tree. --@@ -370,19 +376,20 @@ inplaceInstalledPackageInfo :: FilePath -- ^ top of the build tree -> FilePath -- ^ location of the dist tree -> PackageDescription- -> InstalledPackageId+ -> AbiHash -> Library -> LocalBuildInfo -> ComponentLocalBuildInfo -> InstalledPackageInfo-inplaceInstalledPackageInfo inplaceDir distPref pkg ipid lib lbi clbi =+inplaceInstalledPackageInfo inplaceDir distPref pkg abi_hash lib lbi clbi = generalInstalledPackageInfo adjustRelativeIncludeDirs- pkg ipid lib lbi clbi installDirs+ pkg abi_hash lib lbi clbi installDirs where adjustRelativeIncludeDirs = map (inplaceDir </>)+ libTargetDir = componentBuildDir lbi clbi installDirs =- (absoluteInstallDirs pkg lbi NoCopyDest) {- libdir = inplaceDir </> buildDir lbi,+ (absoluteComponentInstallDirs pkg lbi (componentUnitId clbi) NoCopyDest) {+ libdir = inplaceDir </> libTargetDir, datadir = inplaceDir </> dataDir pkg, docdir = inplaceDocdir, htmldir = inplaceHtmldir,@@ -398,14 +405,14 @@ -- This function knows about the layout of installed packages. -- absoluteInstalledPackageInfo :: PackageDescription- -> InstalledPackageId+ -> AbiHash -> Library -> LocalBuildInfo -> ComponentLocalBuildInfo -> InstalledPackageInfo-absoluteInstalledPackageInfo pkg ipid lib lbi clbi =+absoluteInstalledPackageInfo pkg abi_hash lib lbi clbi = generalInstalledPackageInfo adjustReativeIncludeDirs- pkg ipid lib lbi clbi installDirs+ pkg abi_hash lib lbi clbi installDirs where -- For installed packages we install all include files into one dir, -- whereas in the build tree they may live in multiple local dirs.@@ -413,19 +420,19 @@ | null (installIncludes bi) = [] | otherwise = [includedir installDirs] bi = libBuildInfo lib- installDirs = absoluteInstallDirs pkg lbi NoCopyDest+ installDirs = absoluteComponentInstallDirs pkg lbi (componentUnitId clbi) NoCopyDest relocatableInstalledPackageInfo :: PackageDescription- -> InstalledPackageId+ -> AbiHash -> Library -> LocalBuildInfo -> ComponentLocalBuildInfo -> FilePath -> InstalledPackageInfo-relocatableInstalledPackageInfo pkg ipid lib lbi clbi pkgroot =+relocatableInstalledPackageInfo pkg abi_hash lib lbi clbi pkgroot = generalInstalledPackageInfo adjustReativeIncludeDirs- pkg ipid lib lbi clbi installDirs+ pkg abi_hash lib lbi clbi installDirs where -- For installed packages we install all include files into one dir, -- whereas in the build tree they may live in multiple local dirs.@@ -435,7 +442,7 @@ bi = libBuildInfo lib installDirs = fmap (("${pkgroot}" </>) . shortRelativePath pkgroot)- $ absoluteInstallDirs pkg lbi NoCopyDest+ $ absoluteComponentInstallDirs pkg lbi (componentUnitId clbi) NoCopyDest -- ----------------------------------------------------------------------------- -- Unregistration
cabal/Cabal/Distribution/Simple/Setup.hs view
@@ -30,12 +30,12 @@ -- read and written from files. This would allow us to save configure flags in -- config files. -{-# LANGUAGE CPP #-}- module Distribution.Simple.Setup ( GlobalFlags(..), emptyGlobalFlags, defaultGlobalFlags, globalCommand, ConfigFlags(..), emptyConfigFlags, defaultConfigFlags, configureCommand,+ configPrograms,+ AllowNewer(..), AllowNewerDep(..), isAllowNewer, configAbsolutePaths, readPackageDbList, showPackageDbList, CopyFlags(..), emptyCopyFlags, defaultCopyFlags, copyCommand, InstallFlags(..), emptyInstallFlags, defaultInstallFlags, installCommand,@@ -56,6 +56,7 @@ configureArgs, configureOptions, configureCCompiler, configureLinker, buildOptions, haddockOptions, installDirsOptions, programConfigurationOptions, programConfigurationPaths',+ splitArgs, defaultDistPref, optionDistPref, @@ -65,51 +66,34 @@ fromFlagOrDefault, flagToMaybe, flagToList,- boolOpt, boolOpt', trueArg, falseArg, optionVerbosity, optionNumJobs ) where+ boolOpt, boolOpt', trueArg, falseArg,+ optionVerbosity, optionNumJobs, readPToMaybe ) where -import Distribution.Compiler ()+import Distribution.Compiler import Distribution.ReadE import Distribution.Text- ( Text(..), display ) import qualified Distribution.Compat.ReadP as Parse import qualified Text.PrettyPrint as Disp-import Distribution.ModuleName-import Distribution.Package ( Dependency(..)- , PackageName- , InstalledPackageId )-import Distribution.PackageDescription- ( FlagName(..), FlagAssignment )+import Distribution.Package+import Distribution.PackageDescription hiding (Flag) import Distribution.Simple.Command hiding (boolOpt, boolOpt') import qualified Distribution.Simple.Command as Command-import Distribution.Simple.Compiler- ( CompilerFlavor(..), defaultCompilerFlavor, PackageDB(..)- , DebugInfoLevel(..), flagToDebugInfoLevel- , OptimisationLevel(..), flagToOptimisationLevel- , ProfDetailLevel(..), flagToProfDetailLevel- , absolutePackageDBPath )+import Distribution.Simple.Compiler hiding (Flag) import Distribution.Simple.Utils- ( wrapText, wrapLine, lowercase, intercalate )-import Distribution.Simple.Program (Program(..), ProgramConfiguration,- requireProgram,- programInvocation, progInvokePath, progInvokeArgs,- knownPrograms,- addKnownProgram, emptyProgramConfiguration,- haddockProgram, ghcProgram, gccProgram, ldProgram)+import Distribution.Simple.Program import Distribution.Simple.InstallDirs- ( InstallDirs(..), CopyDest(..),- PathTemplate, toPathTemplate, fromPathTemplate ) import Distribution.Verbosity import Distribution.Utils.NubList--import Control.Monad (liftM) import Distribution.Compat.Binary (Binary)-import Data.List ( sort )-import Data.Char ( isSpace, isAlpha )-#if __GLASGOW_HASKELL__ < 710-import Data.Monoid ( Monoid(..) )-#endif-import GHC.Generics (Generic)+import Distribution.Compat.Semigroup as Semi +import Control.Applicative as A ( Applicative(..), (<*) )+import Control.Monad ( liftM )+import Data.List ( sort )+import Data.Maybe ( listToMaybe )+import Data.Char ( isSpace, isAlpha )+import GHC.Generics ( Generic )+ -- FIXME Not sure where this should live defaultDistPref :: FilePath defaultDistPref = "dist"@@ -145,9 +129,12 @@ instance Monoid (Flag a) where mempty = NoFlag- _ `mappend` f@(Flag _) = f- f `mappend` NoFlag = f+ mappend = (Semi.<>) +instance Semigroup (Flag a) where+ _ <> f@(Flag _) = f+ f <> NoFlag = f+ instance Bounded a => Bounded (Flag a) where minBound = toFlag minBound maxBound = toFlag maxBound@@ -202,7 +189,7 @@ data GlobalFlags = GlobalFlags { globalVersion :: Flag Bool, globalNumericVersion :: Flag Bool- }+ } deriving (Generic) defaultGlobalFlags :: GlobalFlags defaultGlobalFlags = GlobalFlags {@@ -227,8 +214,8 @@ align str = str ++ replicate (maxlen - length str) ' ' in "Commands:\n"- ++ unlines [ " " ++ align name ++ " " ++ description- | (name, description) <- cmdDescs ]+ ++ unlines [ " " ++ align name ++ " " ++ descr+ | (name, descr) <- cmdDescs ] ++ "\n" ++ "For more information about a command use\n" ++ " " ++ pname ++ " COMMAND --help\n\n"@@ -253,20 +240,78 @@ emptyGlobalFlags = mempty instance Monoid GlobalFlags where- mempty = GlobalFlags {- globalVersion = mempty,- globalNumericVersion = mempty- }- mappend a b = GlobalFlags {- globalVersion = combine globalVersion,- globalNumericVersion = combine globalNumericVersion- }- where combine field = field a `mappend` field b+ mempty = gmempty+ mappend = (Semi.<>) +instance Semigroup GlobalFlags where+ (<>) = gmappend+ -- ------------------------------------------------------------ -- * Config flags -- ------------------------------------------------------------ +-- | Policy for relaxing upper bounds in dependencies. For example, given+-- 'build-depends: array >= 0.3 && < 0.5', are we allowed to relax the upper+-- bound and choose a version of 'array' that is greater or equal to 0.5? By+-- default the upper bounds are always strictly honored.+data AllowNewer =++ -- | Default: honor the upper bounds in all dependencies, never choose+ -- versions newer than allowed.+ AllowNewerNone++ -- | Ignore upper bounds in dependencies on the given packages.+ | AllowNewerSome [AllowNewerDep]++ -- | Ignore upper bounds in dependencies on all packages.+ | AllowNewerAll+ deriving (Eq, Read, Show, Generic)++-- | Dependencies can be relaxed either for all packages in the install plan, or+-- only for some packages.+data AllowNewerDep = AllowNewerDep PackageName+ | AllowNewerDepScoped PackageName PackageName+ deriving (Eq, Read, Show, Generic)++instance Text AllowNewerDep where+ disp (AllowNewerDep p0) = disp p0+ disp (AllowNewerDepScoped p0 p1) = disp p0 Disp.<> Disp.colon Disp.<> disp p1++ parse = scopedP Parse.<++ normalP+ where+ scopedP = AllowNewerDepScoped `fmap` parse A.<* Parse.char ':' A.<*> parse+ normalP = AllowNewerDep `fmap` parse++instance Binary AllowNewer+instance Binary AllowNewerDep++instance Semigroup AllowNewer where+ AllowNewerNone <> r = r+ l@AllowNewerAll <> _ = l+ l@(AllowNewerSome _) <> AllowNewerNone = l+ (AllowNewerSome _) <> r@AllowNewerAll = r+ (AllowNewerSome a) <> (AllowNewerSome b) = AllowNewerSome (a ++ b)++instance Monoid AllowNewer where+ mempty = AllowNewerNone+ mappend = (Semi.<>)++-- | Convert 'AllowNewer' to a boolean.+isAllowNewer :: AllowNewer -> Bool+isAllowNewer AllowNewerNone = False+isAllowNewer (AllowNewerSome _) = True+isAllowNewer AllowNewerAll = True++allowNewerParser :: Parse.ReadP r (Maybe AllowNewer)+allowNewerParser =+ (Just . AllowNewerSome) `fmap` Parse.sepBy1 parse (Parse.char ',')++allowNewerPrinter :: (Maybe AllowNewer) -> [Maybe String]+allowNewerPrinter Nothing = []+allowNewerPrinter (Just AllowNewerNone) = []+allowNewerPrinter (Just AllowNewerAll) = [Nothing]+allowNewerPrinter (Just (AllowNewerSome pkgs)) = map (Just . display) $ pkgs+ -- | Flags to @configure@ command. -- -- IMPORTANT: every time a new flag is added, 'D.C.Setup.filterConfigureFlags'@@ -276,8 +321,8 @@ -- because the type of configure is constrained by the UserHooks. -- when we change UserHooks next we should pass the initial -- ProgramConfiguration directly and not via ConfigFlags- configPrograms :: ProgramConfiguration, -- ^All programs that cabal may- -- run+ configPrograms_ :: Last' ProgramConfiguration, -- ^All programs that+ -- @cabal@ may run configProgramPaths :: [(String, FilePath)], -- ^user specified programs paths configProgramArgs :: [(String, [String])], -- ^user specified programs args@@ -308,7 +353,10 @@ -- paths configScratchDir :: Flag FilePath, configExtraLibDirs :: [FilePath], -- ^ path to search for extra libraries+ configExtraFrameworkDirs :: [FilePath], -- ^ path to search for extra+ -- frameworks (OS X only) configExtraIncludeDirs :: [FilePath], -- ^ path to search for header files+ configIPID :: Flag String, -- ^ explicit IPID to be used configDistPref :: Flag FilePath, -- ^"dist" prefix configVerbosity :: Flag Verbosity, -- ^verbosity level@@ -320,8 +368,7 @@ configStripLibs :: Flag Bool, -- ^Enable library stripping configConstraints :: [Dependency], -- ^Additional constraints for -- dependencies.- configDependencies :: [(PackageName, InstalledPackageId)],- configInstantiateWith :: [(ModuleName, (InstalledPackageId, ModuleName))],+ configDependencies :: [(PackageName, UnitId)], -- ^The packages depended on. configConfigurationsFlags :: FlagAssignment, configTests :: Flag Bool, -- ^Enable test suite compilation@@ -334,12 +381,20 @@ configFlagError :: Flag String, -- ^Halt and show an error message indicating an error in flag assignment configRelocatable :: Flag Bool, -- ^ Enable relocatable package built- configDebugInfo :: Flag DebugInfoLevel -- ^ Emit debug info.+ configDebugInfo :: Flag DebugInfoLevel, -- ^ Emit debug info.+ configAllowNewer :: Maybe AllowNewer+ -- ^ Ignore upper bounds on all or some dependencies. Wrapped in 'Maybe' to+ -- distinguish between "default" and "explicitly disabled". } deriving (Generic, Read, Show) instance Binary ConfigFlags +-- | More convenient version of 'configPrograms'. Results in an+-- 'error' if internal invariant is violated.+configPrograms :: ConfigFlags -> ProgramConfiguration+configPrograms = maybe (error "FIXME: remove configPrograms") id . getLast' . configPrograms_+ configAbsolutePaths :: ConfigFlags -> IO ConfigFlags configAbsolutePaths f = (\v -> f { configPackageDBs = v })@@ -348,7 +403,7 @@ defaultConfigFlags :: ProgramConfiguration -> ConfigFlags defaultConfigFlags progConf = emptyConfigFlags {- configPrograms = progConf,+ configPrograms_ = pure progConf, configHcFlavor = maybe NoFlag Flag defaultCompilerFlavor, configVanillaLib = Flag True, configProfLib = NoFlag,@@ -380,7 +435,8 @@ configExactConfiguration = Flag False, configFlagError = NoFlag, configRelocatable = Flag False,- configDebugInfo = Flag NoDebugInfo+ configDebugInfo = Flag NoDebugInfo,+ configAllowNewer = Nothing } configureCommand :: ProgramConfiguration -> CommandUI ConfigFlags@@ -573,11 +629,22 @@ configExtraIncludeDirs (\v flags -> flags {configExtraIncludeDirs = v}) (reqArg' "PATH" (\x -> [x]) id) + ,option "" ["ipid"]+ "Installed package ID to compile this package as"+ configIPID (\v flags -> flags {configIPID = v})+ (reqArgFlag "IPID")+ ,option "" ["extra-lib-dirs"] "A list of directories to search for external libraries" configExtraLibDirs (\v flags -> flags {configExtraLibDirs = v}) (reqArg' "PATH" (\x -> [x]) id) + ,option "" ["extra-framework-dirs"]+ "A list of directories to search for external frameworks (OS X only)"+ configExtraFrameworkDirs+ (\v flags -> flags {configExtraFrameworkDirs = v})+ (reqArg' "PATH" (\x -> [x]) id)+ ,option "" ["extra-prog-path"] "A list of directories to search for required programs (in addition to the normal search locations)" configProgramPathExtra (\v flags -> flags {configProgramPathExtra = v})@@ -597,13 +664,6 @@ (readP_to_E (const "dependency expected") ((\x -> [x]) `fmap` parseDependency)) (map (\x -> display (fst x) ++ "=" ++ display (snd x)))) - ,option "" ["instantiate-with"]- "A mapping of signature names to concrete module instantiations. E.g., --instantiate-with=\"Map=Data.Map.Strict@containers-0.5.5.1-inplace\""- configInstantiateWith (\v flags -> flags { configInstantiateWith = v })- (reqArg "NAME=PKG:MOD"- (readP_to_E (const "signature mapping expected") ((\x -> [x]) `fmap` parseHoleMapEntry))- (map (\(n,(p,m)) -> display n ++ "=" ++ display m ++ "@" ++ display p)))- ,option "" ["tests"] "dependency checking and compilation for test suites listed in the package description file." configTests (\v flags -> flags { configTests = v })@@ -619,6 +679,13 @@ configLibCoverage (\v flags -> flags { configLibCoverage = v }) (boolOpt [] []) + ,option [] ["allow-newer"]+ ("Ignore upper bounds in all dependencies or DEPS")+ configAllowNewer (\v flags -> flags { configAllowNewer = v})+ (optArg "DEPS"+ (readP_to_E ("Cannot parse the list of packages: " ++) allowNewerParser)+ (Just AllowNewerAll) allowNewerPrinter)+ ,option "" ["exact-configuration"] "All direct dependencies and flags are provided on the command line." configExactConfiguration@@ -667,33 +734,16 @@ showPackageDb (Just (SpecificPackageDB db)) = db showProfDetailLevelFlag :: Flag ProfDetailLevel -> [String]-showProfDetailLevelFlag dl =- case dl of- NoFlag -> []- Flag ProfDetailNone -> ["none"]- Flag ProfDetailDefault -> ["default"]- Flag ProfDetailExportedFunctions -> ["exported-functions"]- Flag ProfDetailToplevelFunctions -> ["toplevel-functions"]- Flag ProfDetailAllFunctions -> ["all-functions"]- Flag (ProfDetailOther other) -> [other]-+showProfDetailLevelFlag NoFlag = []+showProfDetailLevelFlag (Flag dl) = [showProfDetailLevel dl] -parseDependency :: Parse.ReadP r (PackageName, InstalledPackageId)+parseDependency :: Parse.ReadP r (PackageName, UnitId) parseDependency = do x <- parse _ <- Parse.char '=' y <- parse return (x, y) -parseHoleMapEntry :: Parse.ReadP r (ModuleName, (InstalledPackageId, ModuleName))-parseHoleMapEntry = do- x <- parse- _ <- Parse.char '='- y <- parse- _ <- Parse.char '@'- z <- parse- return (x, (z, y))- installDirsOptions :: [OptionField (InstallDirs (Flag PathTemplate))] installDirsOptions = [ option "" ["prefix"]@@ -760,98 +810,12 @@ emptyConfigFlags = mempty instance Monoid ConfigFlags where- mempty = ConfigFlags {- configPrograms = error "FIXME: remove configPrograms",- configProgramPaths = mempty,- configProgramArgs = mempty,- configProgramPathExtra = mempty,- configHcFlavor = mempty,- configHcPath = mempty,- configHcPkg = mempty,- configVanillaLib = mempty,- configProfLib = mempty,- configSharedLib = mempty,- configDynExe = mempty,- configProfExe = mempty,- configProf = mempty,- configProfDetail = mempty,- configProfLibDetail = mempty,- configConfigureArgs = mempty,- configOptimization = mempty,- configProgPrefix = mempty,- configProgSuffix = mempty,- configInstallDirs = mempty,- configScratchDir = mempty,- configDistPref = mempty,- configVerbosity = mempty,- configUserInstall = mempty,- configPackageDBs = mempty,- configGHCiLib = mempty,- configSplitObjs = mempty,- configStripExes = mempty,- configStripLibs = mempty,- configExtraLibDirs = mempty,- configConstraints = mempty,- configDependencies = mempty,- configInstantiateWith = mempty,- configExtraIncludeDirs = mempty,- configConfigurationsFlags = mempty,- configTests = mempty,- configCoverage = mempty,- configLibCoverage = mempty,- configExactConfiguration = mempty,- configBenchmarks = mempty,- configFlagError = mempty,- configRelocatable = mempty,- configDebugInfo = mempty- }- mappend a b = ConfigFlags {- configPrograms = configPrograms b,- configProgramPaths = combine configProgramPaths,- configProgramArgs = combine configProgramArgs,- configProgramPathExtra = combine configProgramPathExtra,- configHcFlavor = combine configHcFlavor,- configHcPath = combine configHcPath,- configHcPkg = combine configHcPkg,- configVanillaLib = combine configVanillaLib,- configProfLib = combine configProfLib,- configSharedLib = combine configSharedLib,- configDynExe = combine configDynExe,- configProfExe = combine configProfExe,- configProf = combine configProf,- configProfDetail = combine configProfDetail,- configProfLibDetail = combine configProfLibDetail,- configConfigureArgs = combine configConfigureArgs,- configOptimization = combine configOptimization,- configProgPrefix = combine configProgPrefix,- configProgSuffix = combine configProgSuffix,- configInstallDirs = combine configInstallDirs,- configScratchDir = combine configScratchDir,- configDistPref = combine configDistPref,- configVerbosity = combine configVerbosity,- configUserInstall = combine configUserInstall,- configPackageDBs = combine configPackageDBs,- configGHCiLib = combine configGHCiLib,- configSplitObjs = combine configSplitObjs,- configStripExes = combine configStripExes,- configStripLibs = combine configStripLibs,- configExtraLibDirs = combine configExtraLibDirs,- configConstraints = combine configConstraints,- configDependencies = combine configDependencies,- configInstantiateWith = combine configInstantiateWith,- configExtraIncludeDirs = combine configExtraIncludeDirs,- configConfigurationsFlags = combine configConfigurationsFlags,- configTests = combine configTests,- configCoverage = combine configCoverage,- configLibCoverage = combine configLibCoverage,- configExactConfiguration = combine configExactConfiguration,- configBenchmarks = combine configBenchmarks,- configFlagError = combine configFlagError,- configRelocatable = combine configRelocatable,- configDebugInfo = combine configDebugInfo- }- where combine field = field a `mappend` field b+ mempty = gmempty+ mappend = (Semi.<>) +instance Semigroup ConfigFlags where+ (<>) = gmappend+ -- ------------------------------------------------------------ -- * Copy flags -- ------------------------------------------------------------@@ -860,27 +824,39 @@ data CopyFlags = CopyFlags { copyDest :: Flag CopyDest, copyDistPref :: Flag FilePath,- copyVerbosity :: Flag Verbosity+ copyVerbosity :: Flag Verbosity,+ -- This is the same hack as in 'buildArgs'. But I (ezyang) don't+ -- think it's a hack, it's the right way to make hooks more robust+ copyArgs :: [String] }- deriving Show+ deriving (Show, Generic) defaultCopyFlags :: CopyFlags defaultCopyFlags = CopyFlags { copyDest = Flag NoCopyDest, copyDistPref = NoFlag,- copyVerbosity = Flag normal+ copyVerbosity = Flag normal,+ copyArgs = [] } copyCommand :: CommandUI CopyFlags copyCommand = CommandUI { commandName = "copy"- , commandSynopsis = "Copy the files into the install locations."+ , commandSynopsis = "Copy the files of all/specific components to install locations." , commandDescription = Just $ \_ -> wrapText $- "Does not call register, and allows a prefix at install time. "+ "Components encompass executables and libraries."+ ++ "Does not call register, and allows a prefix at install time. " ++ "Without the --destdir flag, configure determines location.\n"- , commandNotes = Nothing- , commandUsage = \pname ->- "Usage: " ++ pname ++ " copy [FLAGS]\n"+ , commandNotes = Just $ \pname ->+ "Examples:\n"+ ++ " " ++ pname ++ " build "+ ++ " All the components in the package\n"+ ++ " " ++ pname ++ " build foo "+ ++ " A component (i.e. lib, exe, test suite)"+ , commandUsage = usageAlternatives "copy" $+ [ "[FLAGS]"+ , "COMPONENTS [FLAGS]"+ ] , commandDefaultFlags = defaultCopyFlags , commandOptions = \showOrParseArgs -> [optionVerbosity copyVerbosity (\v flags -> flags { copyVerbosity = v })@@ -901,18 +877,12 @@ emptyCopyFlags = mempty instance Monoid CopyFlags where- mempty = CopyFlags {- copyDest = mempty,- copyDistPref = mempty,- copyVerbosity = mempty- }- mappend a b = CopyFlags {- copyDest = combine copyDest,- copyDistPref = combine copyDistPref,- copyVerbosity = combine copyVerbosity- }- where combine field = field a `mappend` field b+ mempty = gmempty+ mappend = (Semi.<>) +instance Semigroup CopyFlags where+ (<>) = gmappend+ -- ------------------------------------------------------------ -- * Install flags -- ------------------------------------------------------------@@ -925,7 +895,7 @@ installInPlace :: Flag Bool, installVerbosity :: Flag Verbosity }- deriving Show+ deriving (Show, Generic) defaultInstallFlags :: InstallFlags defaultInstallFlags = InstallFlags {@@ -978,22 +948,12 @@ emptyInstallFlags = mempty instance Monoid InstallFlags where- mempty = InstallFlags{- installPackageDB = mempty,- installDistPref = mempty,- installUseWrapper = mempty,- installInPlace = mempty,- installVerbosity = mempty- }- mappend a b = InstallFlags{- installPackageDB = combine installPackageDB,- installDistPref = combine installDistPref,- installUseWrapper = combine installUseWrapper,- installInPlace = combine installInPlace,- installVerbosity = combine installVerbosity- }- where combine field = field a `mappend` field b+ mempty = gmempty+ mappend = (Semi.<>) +instance Semigroup InstallFlags where+ (<>) = gmappend+ -- ------------------------------------------------------------ -- * SDist flags -- ------------------------------------------------------------@@ -1006,7 +966,7 @@ sDistListSources :: Flag FilePath, sDistVerbosity :: Flag Verbosity }- deriving Show+ deriving (Show, Generic) defaultSDistFlags :: SDistFlags defaultSDistFlags = SDistFlags {@@ -1055,22 +1015,12 @@ emptySDistFlags = mempty instance Monoid SDistFlags where- mempty = SDistFlags {- sDistSnapshot = mempty,- sDistDirectory = mempty,- sDistDistPref = mempty,- sDistListSources = mempty,- sDistVerbosity = mempty- }- mappend a b = SDistFlags {- sDistSnapshot = combine sDistSnapshot,- sDistDirectory = combine sDistDirectory,- sDistDistPref = combine sDistDistPref,- sDistListSources = combine sDistListSources,- sDistVerbosity = combine sDistVerbosity- }- where combine field = field a `mappend` field b+ mempty = gmempty+ mappend = (Semi.<>) +instance Semigroup SDistFlags where+ (<>) = gmappend+ -- ------------------------------------------------------------ -- * Register flags -- ------------------------------------------------------------@@ -1086,7 +1036,7 @@ regPrintId :: Flag Bool, regVerbosity :: Flag Verbosity }- deriving Show+ deriving (Show, Generic) defaultRegisterFlags :: RegisterFlags defaultRegisterFlags = RegisterFlags {@@ -1178,26 +1128,12 @@ emptyRegisterFlags = mempty instance Monoid RegisterFlags where- mempty = RegisterFlags {- regPackageDB = mempty,- regGenScript = mempty,- regGenPkgConf = mempty,- regInPlace = mempty,- regPrintId = mempty,- regDistPref = mempty,- regVerbosity = mempty- }- mappend a b = RegisterFlags {- regPackageDB = combine regPackageDB,- regGenScript = combine regGenScript,- regGenPkgConf = combine regGenPkgConf,- regInPlace = combine regInPlace,- regPrintId = combine regPrintId,- regDistPref = combine regDistPref,- regVerbosity = combine regVerbosity- }- where combine field = field a `mappend` field b+ mempty = gmempty+ mappend = (Semi.<>) +instance Semigroup RegisterFlags where+ (<>) = gmappend+ -- ------------------------------------------------------------ -- * HsColour flags -- ------------------------------------------------------------@@ -1210,7 +1146,7 @@ hscolourDistPref :: Flag FilePath, hscolourVerbosity :: Flag Verbosity }- deriving Show+ deriving (Show, Generic) emptyHscolourFlags :: HscolourFlags emptyHscolourFlags = mempty@@ -1226,24 +1162,12 @@ } instance Monoid HscolourFlags where- mempty = HscolourFlags {- hscolourCSS = mempty,- hscolourExecutables = mempty,- hscolourTestSuites = mempty,- hscolourBenchmarks = mempty,- hscolourDistPref = mempty,- hscolourVerbosity = mempty- }- mappend a b = HscolourFlags {- hscolourCSS = combine hscolourCSS,- hscolourExecutables = combine hscolourExecutables,- hscolourTestSuites = combine hscolourTestSuites,- hscolourBenchmarks = combine hscolourBenchmarks,- hscolourDistPref = combine hscolourDistPref,- hscolourVerbosity = combine hscolourVerbosity- }- where combine field = field a `mappend` field b+ mempty = gmempty+ mappend = (Semi.<>) +instance Semigroup HscolourFlags where+ (<>) = gmappend+ hscolourCommand :: CommandUI HscolourFlags hscolourCommand = CommandUI { commandName = "hscolour"@@ -1303,6 +1227,7 @@ haddockHoogle :: Flag Bool, haddockHtml :: Flag Bool, haddockHtmlLocation :: Flag String,+ haddockForHackage :: Flag Bool, haddockExecutables :: Flag Bool, haddockTestSuites :: Flag Bool, haddockBenchmarks :: Flag Bool,@@ -1315,7 +1240,7 @@ haddockKeepTempFiles:: Flag Bool, haddockVerbosity :: Flag Verbosity }- deriving Show+ deriving (Show, Generic) defaultHaddockFlags :: HaddockFlags defaultHaddockFlags = HaddockFlags {@@ -1324,6 +1249,7 @@ haddockHoogle = Flag False, haddockHtml = Flag False, haddockHtmlLocation = NoFlag,+ haddockForHackage = Flag False, haddockExecutables = Flag False, haddockTestSuites = Flag False, haddockBenchmarks = Flag False,@@ -1389,6 +1315,11 @@ haddockHtmlLocation (\v flags -> flags { haddockHtmlLocation = v }) (reqArgFlag "URL") + ,option "" ["for-hackage"]+ "Collection of flags to generate documentation suitable for upload to hackage"+ haddockForHackage (\v flags -> flags { haddockForHackage = v })+ trueArg+ ,option "" ["executables"] "Run haddock for Executables targets" haddockExecutables (\v flags -> flags { haddockExecutables = v })@@ -1446,44 +1377,12 @@ emptyHaddockFlags = mempty instance Monoid HaddockFlags where- mempty = HaddockFlags {- haddockProgramPaths = mempty,- haddockProgramArgs = mempty,- haddockHoogle = mempty,- haddockHtml = mempty,- haddockHtmlLocation = mempty,- haddockExecutables = mempty,- haddockTestSuites = mempty,- haddockBenchmarks = mempty,- haddockInternal = mempty,- haddockCss = mempty,- haddockHscolour = mempty,- haddockHscolourCss = mempty,- haddockContents = mempty,- haddockDistPref = mempty,- haddockKeepTempFiles= mempty,- haddockVerbosity = mempty- }- mappend a b = HaddockFlags {- haddockProgramPaths = combine haddockProgramPaths,- haddockProgramArgs = combine haddockProgramArgs,- haddockHoogle = combine haddockHoogle,- haddockHtml = combine haddockHtml,- haddockHtmlLocation = combine haddockHtmlLocation,- haddockExecutables = combine haddockExecutables,- haddockTestSuites = combine haddockTestSuites,- haddockBenchmarks = combine haddockBenchmarks,- haddockInternal = combine haddockInternal,- haddockCss = combine haddockCss,- haddockHscolour = combine haddockHscolour,- haddockHscolourCss = combine haddockHscolourCss,- haddockContents = combine haddockContents,- haddockDistPref = combine haddockDistPref,- haddockKeepTempFiles= combine haddockKeepTempFiles,- haddockVerbosity = combine haddockVerbosity- }- where combine field = field a `mappend` field b+ mempty = gmempty+ mappend = (Semi.<>) +instance Semigroup HaddockFlags where+ (<>) = gmappend+ -- ------------------------------------------------------------ -- * Clean flags -- ------------------------------------------------------------@@ -1493,7 +1392,7 @@ cleanDistPref :: Flag FilePath, cleanVerbosity :: Flag Verbosity }- deriving Show+ deriving (Show, Generic) defaultCleanFlags :: CleanFlags defaultCleanFlags = CleanFlags {@@ -1529,18 +1428,12 @@ emptyCleanFlags = mempty instance Monoid CleanFlags where- mempty = CleanFlags {- cleanSaveConf = mempty,- cleanDistPref = mempty,- cleanVerbosity = mempty- }- mappend a b = CleanFlags {- cleanSaveConf = combine cleanSaveConf,- cleanDistPref = combine cleanDistPref,- cleanVerbosity = combine cleanVerbosity- }- where combine field = field a `mappend` field b+ mempty = gmempty+ mappend = (Semi.<>) +instance Semigroup CleanFlags where+ (<>) = gmappend+ -- ------------------------------------------------------------ -- * Build flags -- ------------------------------------------------------------@@ -1555,7 +1448,7 @@ -- UserHooks stop us from passing extra info in other ways buildArgs :: [String] }- deriving Show+ deriving (Show, Generic) {-# DEPRECATED buildVerbose "Use buildVerbosity instead" #-} buildVerbose :: BuildFlags -> Verbosity@@ -1630,24 +1523,12 @@ emptyBuildFlags = mempty instance Monoid BuildFlags where- mempty = BuildFlags {- buildProgramPaths = mempty,- buildProgramArgs = mempty,- buildVerbosity = mempty,- buildDistPref = mempty,- buildNumJobs = mempty,- buildArgs = mempty- }- mappend a b = BuildFlags {- buildProgramPaths = combine buildProgramPaths,- buildProgramArgs = combine buildProgramArgs,- buildVerbosity = combine buildVerbosity,- buildDistPref = combine buildDistPref,- buildNumJobs = combine buildNumJobs,- buildArgs = combine buildArgs- }- where combine field = field a `mappend` field b+ mempty = gmempty+ mappend = (Semi.<>) +instance Semigroup BuildFlags where+ (<>) = gmappend+ -- ------------------------------------------------------------ -- * REPL Flags -- ------------------------------------------------------------@@ -1659,7 +1540,7 @@ replVerbosity :: Flag Verbosity, replReload :: Flag Bool }- deriving Show+ deriving (Show, Generic) defaultReplFlags :: ReplFlags defaultReplFlags = ReplFlags {@@ -1671,22 +1552,12 @@ } instance Monoid ReplFlags where- mempty = ReplFlags {- replProgramPaths = mempty,- replProgramArgs = mempty,- replVerbosity = mempty,- replDistPref = mempty,- replReload = mempty- }- mappend a b = ReplFlags {- replProgramPaths = combine replProgramPaths,- replProgramArgs = combine replProgramArgs,- replVerbosity = combine replVerbosity,- replDistPref = combine replDistPref,- replReload = combine replReload- }- where combine field = field a `mappend` field b+ mempty = gmempty+ mappend = (Semi.<>) +instance Semigroup ReplFlags where+ (<>) = gmappend+ replCommand :: ProgramConfiguration -> CommandUI ReplFlags replCommand progConf = CommandUI { commandName = "repl"@@ -1759,7 +1630,7 @@ -- * Test flags -- ------------------------------------------------------------ -data TestShowDetails = Never | Failures | Always | Streaming+data TestShowDetails = Never | Failures | Always | Streaming | Direct deriving (Eq, Ord, Enum, Bounded, Show) knownTestShowDetails :: [TestShowDetails]@@ -1779,8 +1650,11 @@ --TODO: do we need this instance? instance Monoid TestShowDetails where mempty = Never- mappend a b = if a < b then b else a+ mappend = (Semi.<>) +instance Semigroup TestShowDetails where+ a <> b = if a < b then b else a+ data TestFlags = TestFlags { testDistPref :: Flag FilePath, testVerbosity :: Flag Verbosity,@@ -1790,7 +1664,7 @@ testKeepTix :: Flag Bool, -- TODO: think about if/how options are passed to test exes testOptions :: [PathTemplate]- }+ } deriving (Generic) defaultTestFlags :: TestFlags defaultTestFlags = TestFlags {@@ -1847,7 +1721,8 @@ ("'always': always show results of individual test cases. " ++ "'never': never show results of individual test cases. " ++ "'failures': show results of failing test cases. "- ++ "'streaming': show results of test cases in real time.")+ ++ "'streaming': show results of test cases in real time."+ ++ "'direct': send results of test cases in real time; no log file.") testShowDetails (\v flags -> flags { testShowDetails = v }) (reqArg "FILTER" (readP_to_E (\_ -> "--show-details flag expects one of "@@ -1881,26 +1756,12 @@ emptyTestFlags = mempty instance Monoid TestFlags where- mempty = TestFlags {- testDistPref = mempty,- testVerbosity = mempty,- testHumanLog = mempty,- testMachineLog = mempty,- testShowDetails = mempty,- testKeepTix = mempty,- testOptions = mempty- }- mappend a b = TestFlags {- testDistPref = combine testDistPref,- testVerbosity = combine testVerbosity,- testHumanLog = combine testHumanLog,- testMachineLog = combine testMachineLog,- testShowDetails = combine testShowDetails,- testKeepTix = combine testKeepTix,- testOptions = combine testOptions- }- where combine field = field a `mappend` field b+ mempty = gmempty+ mappend = (Semi.<>) +instance Semigroup TestFlags where+ (<>) = gmappend+ -- ------------------------------------------------------------ -- * Benchmark flags -- ------------------------------------------------------------@@ -1909,7 +1770,7 @@ benchmarkDistPref :: Flag FilePath, benchmarkVerbosity :: Flag Verbosity, benchmarkOptions :: [PathTemplate]- }+ } deriving (Generic) defaultBenchmarkFlags :: BenchmarkFlags defaultBenchmarkFlags = BenchmarkFlags {@@ -1968,18 +1829,12 @@ emptyBenchmarkFlags = mempty instance Monoid BenchmarkFlags where- mempty = BenchmarkFlags {- benchmarkDistPref = mempty,- benchmarkVerbosity = mempty,- benchmarkOptions = mempty- }- mappend a b = BenchmarkFlags {- benchmarkDistPref = combine benchmarkDistPref,- benchmarkVerbosity = combine benchmarkVerbosity,- benchmarkOptions = combine benchmarkOptions- }- where combine field = field a `mappend` field b+ mempty = gmempty+ mappend = (Semi.<>) +instance Semigroup BenchmarkFlags where+ (<>) = gmappend+ -- ------------------------------------------------------------ -- * Shared options utils -- ------------------------------------------------------------@@ -2133,13 +1988,16 @@ _ -> case reads s of [(n, "")] | n < 1 -> Left "The number of jobs should be 1 or more."- | n > 64 -> Left "You probably don't want that many jobs." | otherwise -> Right (Just n) _ -> Left "The jobs value should be a number or '$ncpus'" -- ------------------------------------------------------------ -- * Other Utils -- ------------------------------------------------------------++readPToMaybe :: Parse.ReadP a a -> String -> Maybe a+readPToMaybe p str = listToMaybe [ r | (r,s) <- Parse.readP_to_S p str+ , all isSpace s ] -- | Arguments to pass to a @configure@ script, e.g. generated by -- @autoconf@.
cabal/Cabal/Distribution/Simple/SrcDist.hs view
@@ -40,35 +40,20 @@ ) where -import Distribution.PackageDescription- ( PackageDescription(..), BuildInfo(..), Executable(..), Library(..)- , TestSuite(..), TestSuiteInterface(..), Benchmark(..)- , BenchmarkInterface(..) )-import Distribution.PackageDescription.Check- ( PackageCheck(..), checkConfiguredPackage, checkPackageFiles )+import Distribution.PackageDescription hiding (Flag)+import Distribution.PackageDescription.Check hiding (doesFileExist) import Distribution.Package- ( PackageIdentifier(pkgVersion), Package(..), packageVersion )-import Distribution.ModuleName (ModuleName)+import Distribution.ModuleName import qualified Distribution.ModuleName as ModuleName import Distribution.Version- ( Version(versionBranch) ) import Distribution.Simple.Utils- ( createDirectoryIfMissingVerbose, withUTF8FileContents, writeUTF8File- , installOrdinaryFiles, installMaybeExecutableFiles- , findFile, findFileWithExtension, findAllFilesWithExtension, matchFileGlob- , withTempDirectory, defaultPackageDesc- , die, warn, notice, info, setupMessage )-import Distribution.Simple.Setup ( Flag(..), SDistFlags(..)- , fromFlag, flagToMaybe)-import Distribution.Simple.PreProcess ( PPSuffixHandler, ppSuffixes- , preprocessComponent )+import Distribution.Simple.Setup+import Distribution.Simple.PreProcess import Distribution.Simple.LocalBuildInfo- ( LocalBuildInfo(..), withAllComponentsInBuildOrder )-import Distribution.Simple.BuildPaths ( autogenModuleName )-import Distribution.Simple.Program ( defaultProgramConfiguration, requireProgram,- runProgram, programProperties, tarProgram )+import Distribution.Simple.BuildPaths+import Distribution.Simple.Program import Distribution.Text- ( display )+import Distribution.Verbosity import Control.Monad(when, unless, forM) import Data.Char (toLower)@@ -78,7 +63,6 @@ import Data.Time (UTCTime, getCurrentTime, toGregorian, utctDay) import System.Directory ( doesFileExist ) import System.IO (IOMode(WriteMode), hPutStrLn, withFile)-import Distribution.Verbosity (Verbosity) import System.FilePath ( (</>), (<.>), dropExtension, isAbsolute ) @@ -169,19 +153,20 @@ fmap concat . sequence $ [ -- Library sources.- withLib $ \Library { exposedModules = modules, libBuildInfo = libBi } ->+ fmap concat+ . withAllLib $ \Library { exposedModules = modules, libBuildInfo = libBi } -> allSourcesBuildInfo libBi pps modules -- Executables sources. , fmap concat- . withExe $ \Executable { modulePath = mainPath, buildInfo = exeBi } -> do+ . withAllExe $ \Executable { modulePath = mainPath, buildInfo = exeBi } -> do biSrcs <- allSourcesBuildInfo exeBi pps [] mainSrc <- findMainExeFile exeBi pps mainPath return (mainSrc:biSrcs) -- Test suites sources. , fmap concat- . withTest $ \t -> do+ . withAllTest $ \t -> do let bi = testBuildInfo t case testInterface t of TestSuiteExeV10 _ mainPath -> do@@ -200,7 +185,7 @@ -- Benchmarks sources. , fmap concat- . withBenchmark $ \bm -> do+ . withAllBenchmark $ \bm -> do let bi = benchmarkBuildInfo bm case benchmarkInterface bm of BenchmarkExeV10 _ mainPath -> do@@ -229,7 +214,8 @@ , return (licenseFiles pkg_descr) -- Install-include files.- , withLib $ \ l -> do+ , fmap concat+ . withAllLib $ \ l -> do let lbi = libBuildInfo l relincdirs = "." : filter (not.isAbsolute) (includeDirs lbi) mapM (fmap snd . findIncludeFile relincdirs) (installIncludes lbi)@@ -244,10 +230,10 @@ where -- We have to deal with all libs and executables, so we have local -- versions of these functions that ignore the 'buildable' attribute:- withLib action = maybe (return []) action (library pkg_descr)- withExe action = mapM action (executables pkg_descr)- withTest action = mapM action (testSuites pkg_descr)- withBenchmark action = mapM action (benchmarks pkg_descr)+ withAllLib action = mapM action (libraries pkg_descr)+ withAllExe action = mapM action (executables pkg_descr)+ withAllTest action = mapM action (testSuites pkg_descr)+ withAllBenchmark action = mapM action (benchmarks pkg_descr) -- |Prepare a directory tree of source files.@@ -263,8 +249,8 @@ case mb_lbi of Just lbi | not (null pps) -> do let lbi' = lbi{ buildDir = targetDir </> buildDir lbi }- withAllComponentsInBuildOrder pkg_descr lbi' $ \c _ ->- preprocessComponent pkg_descr c lbi' True verbosity pps+ withAllComponentsInBuildOrder pkg_descr lbi' $ \c clbi ->+ preprocessComponent pkg_descr c lbi' clbi True verbosity pps _ -> return () (ordinary, mExecutable) <- listPackageSources verbosity pkg_descr0 pps@@ -325,7 +311,7 @@ filterAutogenModule pkg_descr0 = mapLib filterAutogenModuleLib $ mapAllBuildInfo filterAutogenModuleBI pkg_descr0 where- mapLib f pkg = pkg { library = fmap f (library pkg) }+ mapLib f pkg = pkg { libraries = map f (libraries pkg) } filterAutogenModuleLib lib = lib { exposedModules = filter (/=autogenModule) (exposedModules lib) }@@ -481,7 +467,7 @@ mapAllBuildInfo :: (BuildInfo -> BuildInfo) -> (PackageDescription -> PackageDescription) mapAllBuildInfo f pkg = pkg {- library = fmap mapLibBi (library pkg),+ libraries = fmap mapLibBi (libraries pkg), executables = fmap mapExeBi (executables pkg), testSuites = fmap mapTestBi (testSuites pkg), benchmarks = fmap mapBenchBi (benchmarks pkg)
cabal/Cabal/Distribution/Simple/Test.hs view
@@ -16,23 +16,17 @@ ) where import qualified Distribution.PackageDescription as PD- ( PackageDescription(..), BuildInfo(buildable)- , TestSuite(..)- , TestSuiteInterface(..), testType, hasTests )-import Distribution.Simple.Compiler ( compilerInfo )-import Distribution.Simple.Hpc ( markupPackage )+import Distribution.Simple.Compiler+import Distribution.Simple.Hpc import Distribution.Simple.InstallDirs- ( fromPathTemplate, initialPathTemplateEnv, substPathTemplate- , PathTemplate ) import qualified Distribution.Simple.LocalBuildInfo as LBI- ( LocalBuildInfo(..), localLibraryName )-import Distribution.Simple.Setup ( TestFlags(..), fromFlag, configCoverage )-import Distribution.Simple.UserHooks ( Args )+import Distribution.Simple.Setup+import Distribution.Simple.UserHooks import qualified Distribution.Simple.Test.ExeV10 as ExeV10 import qualified Distribution.Simple.Test.LibV09 as LibV09 import Distribution.Simple.Test.Log-import Distribution.Simple.Utils ( die, notice )-import Distribution.TestSuite ( Result(..) )+import Distribution.Simple.Utils+import Distribution.TestSuite import Distribution.Text import Control.Monad ( when, unless, filterM )@@ -132,5 +126,5 @@ fromPathTemplate $ substPathTemplate env template where env = initialPathTemplateEnv- (PD.package pkg_descr) (LBI.localLibraryName lbi)+ (PD.package pkg_descr) (LBI.localUnitId lbi) (compilerInfo $ LBI.compiler lbi) (LBI.hostPlatform lbi)
cabal/Cabal/Distribution/Simple/Test/ExeV10.hs view
@@ -2,26 +2,22 @@ ( runTest ) where -import Distribution.Compat.CreatePipe ( createPipe )-import Distribution.Compat.Environment ( getEnvironment )+import Distribution.Compat.CreatePipe+import Distribution.Compat.Environment import qualified Distribution.PackageDescription as PD-import Distribution.Simple.Build.PathsModule ( pkgPathEnvVar )-import Distribution.Simple.BuildPaths ( exeExtension )-import Distribution.Simple.Compiler ( compilerInfo )-import Distribution.Simple.Hpc ( guessWay, markupTest, tixDir, tixFilePath )+import Distribution.Simple.Build.PathsModule+import Distribution.Simple.BuildPaths+import Distribution.Simple.Compiler+import Distribution.Simple.Hpc import Distribution.Simple.InstallDirs- ( fromPathTemplate, initialPathTemplateEnv, PathTemplateVariable(..)- , substPathTemplate , toPathTemplate, PathTemplate ) import qualified Distribution.Simple.LocalBuildInfo as LBI import Distribution.Simple.Setup- ( TestFlags(..), TestShowDetails(..), fromFlag, configCoverage ) import Distribution.Simple.Test.Log import Distribution.Simple.Utils- ( die, notice, rawSystemIOWithEnv, addLibraryPath )-import Distribution.System ( Platform (..) )+import Distribution.System import Distribution.TestSuite import Distribution.Text-import Distribution.Verbosity ( normal )+import Distribution.Verbosity import Control.Concurrent (forkIO) import Control.Monad ( unless, void, when )@@ -30,7 +26,7 @@ , getCurrentDirectory, removeDirectoryRecursive ) import System.Exit ( ExitCode(..) ) import System.FilePath ( (</>), (<.>) )-import System.IO ( hGetContents, hPutStr, stdout )+import System.IO ( hGetContents, hPutStr, stdout, stderr ) runTest :: PD.PackageDescription -> LBI.LocalBuildInfo@@ -63,16 +59,21 @@ -- Write summary notices indicating start of test suite notice verbosity $ summarizeSuiteStart $ PD.testName suite - (rOut, wOut) <- createPipe+ (wOut, wErr, logText) <- case details of+ Direct -> return (stdout, stderr, "")+ _ -> do+ (rOut, wOut) <- createPipe - -- Read test executable's output lazily (returns immediately)- logText <- hGetContents rOut- -- Force the IO manager to drain the test output pipe- void $ forkIO $ length logText `seq` return ()+ -- Read test executable's output lazily (returns immediately)+ logText <- hGetContents rOut+ -- Force the IO manager to drain the test output pipe+ void $ forkIO $ length logText `seq` return () - -- '--show-details=streaming': print the log output in another thread- when (details == Streaming) $ void $ forkIO $ hPutStr stdout logText+ -- '--show-details=streaming': print the log output in another thread+ when (details == Streaming) $ void $ forkIO $ hPutStr stdout logText + return (wOut, wOut, logText)+ -- Run the test executable let opts = map (testOption pkg_descr lbi suite) (testOptions flags)@@ -93,7 +94,7 @@ exit <- rawSystemIOWithEnv verbosity cmd opts Nothing (Just shellEnv') -- these handles are automatically closed- Nothing (Just wOut) (Just wOut)+ Nothing (Just wOut) (Just wErr) -- Generate TestSuiteLog from executable exit code and a machine- -- readable test log.@@ -112,12 +113,10 @@ -- Show the contents of the human-readable log file on the terminal -- if there is a failure and/or detailed output is requested let whenPrinting = when $- (details > Never)- && (not (suitePassed $ testLogs suiteLog) || details == Always)+ ( details == Always ||+ details == Failures && not (suitePassed $ testLogs suiteLog)) -- verbosity overrides show-details && verbosity >= normal- -- if streaming, we already printed the log- && details /= Streaming whenPrinting $ putStr $ unlines $ lines logText -- Write summary notice to terminal indicating end of test suite@@ -163,6 +162,6 @@ fromPathTemplate $ substPathTemplate env template where env = initialPathTemplateEnv- (PD.package pkg_descr) (LBI.localLibraryName lbi)+ (PD.package pkg_descr) (LBI.localUnitId lbi) (compilerInfo $ LBI.compiler lbi) (LBI.hostPlatform lbi) ++ [(TestSuiteNameVar, toPathTemplate $ PD.testName suite)]
cabal/Cabal/Distribution/Simple/Test/LibV09.hs view
@@ -6,32 +6,27 @@ , writeSimpleTestStub ) where -import Distribution.Compat.CreatePipe ( createPipe )-import Distribution.Compat.Environment ( getEnvironment )-import Distribution.Compat.TempFile ( openTempFile )-import Distribution.ModuleName ( ModuleName )+import Distribution.Compat.CreatePipe+import Distribution.Compat.Environment+import Distribution.Compat.Internal.TempFile+import Distribution.ModuleName import qualified Distribution.PackageDescription as PD-import Distribution.Simple.Build.PathsModule ( pkgPathEnvVar )-import Distribution.Simple.BuildPaths ( exeExtension )-import Distribution.Simple.Compiler ( compilerInfo )-import Distribution.Simple.Hpc ( guessWay, markupTest, tixDir, tixFilePath )+import Distribution.Simple.Build.PathsModule+import Distribution.Simple.BuildPaths+import Distribution.Simple.Compiler+import Distribution.Simple.Hpc import Distribution.Simple.InstallDirs- ( fromPathTemplate, initialPathTemplateEnv, PathTemplateVariable(..)- , substPathTemplate , toPathTemplate, PathTemplate ) import qualified Distribution.Simple.LocalBuildInfo as LBI import Distribution.Simple.Setup- ( TestFlags(..), TestShowDetails(..), fromFlag, configCoverage ) import Distribution.Simple.Test.Log import Distribution.Simple.Utils- ( die, notice, rawSystemIOWithEnv, addLibraryPath )-import Distribution.System ( Platform (..) )+import Distribution.System import Distribution.TestSuite import Distribution.Text-import Distribution.Verbosity ( normal )+import Distribution.Verbosity -import Control.Concurrent (forkIO) import Control.Exception ( bracket )-import Control.Monad ( when, unless, void )+import Control.Monad ( when, unless ) import Data.Maybe ( mapMaybe ) import System.Directory ( createDirectoryIfMissing, doesDirectoryExist, doesFileExist@@ -40,6 +35,7 @@ import System.Exit ( ExitCode(..), exitWith ) import System.FilePath ( (</>), (<.>) ) import System.IO ( hClose, hGetContents, hPutStr )+import System.Process (StdStream(..), waitForProcess) runTest :: PD.PackageDescription -> LBI.LocalBuildInfo@@ -74,22 +70,11 @@ suiteLog <- bracket openCabalTemp deleteIfExists $ \tempLog -> do - (rIn, wIn) <- createPipe (rOut, wOut) <- createPipe - -- Prepare standard input for test executable- --appendFile tempInput $ show (tempInput, PD.testName suite)- hPutStr wIn $ show (tempLog, PD.testName suite)- hClose wIn-- -- Append contents of temporary log file to the final human-- -- readable log file- logText <- hGetContents rOut- -- Force the IO manager to drain the test output pipe- void $ forkIO $ length logText `seq` return ()- -- Run test executable- _ <- do let opts = map (testOption pkg_descr lbi suite) $ testOptions flags+ (Just wIn, _, _, process) <- do+ let opts = map (testOption pkg_descr lbi suite) $ testOptions flags dataDirPath = pwd </> PD.dataDir pkg_descr tixFile = pwd </> tixFilePath distPref way (PD.testName suite) pkgPathEnv = (pkgPathEnvVar pkg_descr "datadir", dataDirPath)@@ -108,10 +93,23 @@ True False lbi clbi return (addLibraryPath os paths shellEnv) else return shellEnv- rawSystemIOWithEnv verbosity cmd opts Nothing (Just shellEnv')- -- these handles are closed automatically- (Just rIn) (Just wOut) (Just wOut)+ createProcessWithEnv verbosity cmd opts Nothing (Just shellEnv')+ -- these handles are closed automatically+ CreatePipe (UseHandle wOut) (UseHandle wOut) + hPutStr wIn $ show (tempLog, PD.testName suite)+ hClose wIn++ -- Append contents of temporary log file to the final human-+ -- readable log file+ logText <- hGetContents rOut+ -- Force the IO manager to drain the test output pipe+ length logText `seq` return ()++ exitcode <- waitForProcess process+ unless (exitcode == ExitSuccess) $ do+ debug verbosity $ cmd ++ " returned " ++ show exitcode+ -- Generate final log file name let finalLogName l = testLogDir </> testSuiteLogPath@@ -171,7 +169,7 @@ fromPathTemplate $ substPathTemplate env template where env = initialPathTemplateEnv- (PD.package pkg_descr) (LBI.localLibraryName lbi)+ (PD.package pkg_descr) (LBI.localUnitId lbi) (compilerInfo $ LBI.compiler lbi) (LBI.hostPlatform lbi) ++ [(TestSuiteNameVar, toPathTemplate $ PD.testName suite)]
cabal/Cabal/Distribution/Simple/Test/Log.hs view
@@ -11,18 +11,16 @@ , testSuiteLogPath ) where -import Distribution.Package ( PackageId )+import Distribution.Package import qualified Distribution.PackageDescription as PD-import Distribution.Simple.Compiler ( Compiler(..), compilerInfo, CompilerId )+import Distribution.Simple.Compiler import Distribution.Simple.InstallDirs- ( fromPathTemplate, initialPathTemplateEnv, PathTemplateVariable(..)- , substPathTemplate , toPathTemplate, PathTemplate ) import qualified Distribution.Simple.LocalBuildInfo as LBI-import Distribution.Simple.Setup ( TestShowDetails(..) )-import Distribution.Simple.Utils ( notice )-import Distribution.System ( Platform )-import Distribution.TestSuite ( Options, Result(..) )-import Distribution.Verbosity ( Verbosity )+import Distribution.Simple.Setup+import Distribution.Simple.Utils+import Distribution.System+import Distribution.TestSuite+import Distribution.Verbosity import Control.Monad ( when ) import Data.Char ( toUpper )@@ -109,13 +107,13 @@ -> String -- ^ test suite name -> TestLogs -- ^ test suite results -> FilePath-testSuiteLogPath template pkg_descr lbi name result =+testSuiteLogPath template pkg_descr lbi test_name result = fromPathTemplate $ substPathTemplate env template where env = initialPathTemplateEnv- (PD.package pkg_descr) (LBI.localLibraryName lbi)+ (PD.package pkg_descr) (LBI.localUnitId lbi) (compilerInfo $ LBI.compiler lbi) (LBI.hostPlatform lbi)- ++ [ (TestSuiteNameVar, toPathTemplate name)+ ++ [ (TestSuiteNameVar, toPathTemplate test_name) , (TestSuiteResultVar, toPathTemplate $ resultString result) ]
cabal/Cabal/Distribution/Simple/UHC.hs view
@@ -16,15 +16,12 @@ module Distribution.Simple.UHC ( configure, getInstalledPackages,- buildLib, buildExe, installLib, registerPackage+ buildLib, buildExe, installLib, registerPackage, inplacePackageDbPath ) where -import Control.Monad-import Data.List-import qualified Data.Map as M ( empty ) import Distribution.Compat.ReadP import Distribution.InstalledPackageInfo-import Distribution.Package hiding (installedPackageId)+import Distribution.Package hiding (installedUnitId) import Distribution.PackageDescription import Distribution.Simple.BuildPaths import Distribution.Simple.Compiler as C@@ -35,10 +32,14 @@ import Distribution.Text import Distribution.Verbosity import Distribution.Version+import Distribution.System import Language.Haskell.Extension++import Control.Monad+import Data.List+import qualified Data.Map as M ( empty ) import System.Directory import System.FilePath-import Distribution.System ( Platform ) -- ----------------------------------------------------------------------------- -- Configuring@@ -91,14 +92,13 @@ -> IO InstalledPackageIndex getInstalledPackages verbosity comp packagedbs conf = do let compilerid = compilerId comp- systemPkgDir <- rawSystemProgramStdoutConf verbosity uhcProgram conf ["--meta-pkgdir-system"]+ systemPkgDir <- getGlobalPackageDir verbosity conf userPkgDir <- getUserPackageDir let pkgDirs = nub (concatMap (packageDbPaths userPkgDir systemPkgDir) packagedbs) -- putStrLn $ "pkgdirs: " ++ show pkgDirs- -- call to "lines" necessary, because pkgdir contains an extra newline at the end- pkgs <- liftM (map addBuiltinVersions . concat) .- mapM (\ d -> getDirectoryContents d >>= filterM (isPkgDir (display compilerid) d)) .- concatMap lines $ pkgDirs+ pkgs <- liftM (map addBuiltinVersions . concat) $+ mapM (\ d -> getDirectoryContents d >>= filterM (isPkgDir (display compilerid) d))+ pkgDirs -- putStrLn $ "pkgs: " ++ show pkgs let iPkgs = map mkInstalledPackageInfo $@@ -107,9 +107,16 @@ -- putStrLn $ "installed pkgs: " ++ show iPkgs return (fromList iPkgs) +getGlobalPackageDir :: Verbosity -> ProgramConfiguration -> IO FilePath+getGlobalPackageDir verbosity conf = do+ output <- rawSystemProgramStdoutConf verbosity+ uhcProgram conf ["--meta-pkgdir-system"]+ -- call to "lines" necessary, because pkgdir contains an extra newline at the end+ let [pkgdir] = lines output+ return pkgdir+ getUserPackageDir :: IO FilePath-getUserPackageDir =- do+getUserPackageDir = do homeDir <- getHomeDirectory return $ homeDir </> ".cabal" </> "lib" -- TODO: determine in some other way @@ -150,8 +157,8 @@ -- | Create a trivial package info from a directory name. mkInstalledPackageInfo :: PackageId -> InstalledPackageInfo mkInstalledPackageInfo p = emptyInstalledPackageInfo- { installedPackageId = InstalledPackageId (display p),- sourcePackageId = p }+ { installedUnitId = mkLegacyUnitId p,+ sourcePackageId = p } -- -----------------------------------------------------------------------------@@ -161,7 +168,7 @@ -> Library -> ComponentLocalBuildInfo -> IO () buildLib verbosity pkg_descr lbi lib clbi = do - systemPkgDir <- rawSystemProgramStdoutConf verbosity uhcProgram (withPrograms lbi) ["--meta-pkgdir-system"]+ systemPkgDir <- getGlobalPackageDir verbosity (withPrograms lbi) userPkgDir <- getUserPackageDir let runUhcProg = rawSystemProgramConf verbosity uhcProgram (withPrograms lbi) let uhcArgs = -- set package name@@ -183,7 +190,7 @@ buildExe :: Verbosity -> PackageDescription -> LocalBuildInfo -> Executable -> ComponentLocalBuildInfo -> IO () buildExe verbosity _pkg_descr lbi exe clbi = do- systemPkgDir <- rawSystemProgramStdoutConf verbosity uhcProgram (withPrograms lbi) ["--meta-pkgdir-system"]+ systemPkgDir <- getGlobalPackageDir verbosity (withPrograms lbi) userPkgDir <- getUserPackageDir let runUhcProg = rawSystemProgramConf verbosity uhcProgram (withPrograms lbi) let uhcArgs = -- common flags lib/exe@@ -216,7 +223,7 @@ -- search paths ++ ["-i" ++ odir] ++ ["-i" ++ l | l <- nub (hsSourceDirs bi)]- ++ ["-i" ++ autogenModulesDir lbi]+ ++ ["-i" ++ autogenModulesDir lbi clbi] -- cpp options ++ ["--optP=" ++ opt | opt <- cppOptions bi] -- output path@@ -236,8 +243,8 @@ installLib :: Verbosity -> LocalBuildInfo -> FilePath -> FilePath -> FilePath- -> PackageDescription -> Library -> IO ()-installLib verbosity _lbi targetDir _dynlibTargetDir builtDir pkg _library = do+ -> PackageDescription -> Library -> ComponentLocalBuildInfo -> IO ()+installLib verbosity _lbi targetDir _dynlibTargetDir builtDir pkg _library _clbi = do -- putStrLn $ "dest: " ++ targetDir -- putStrLn $ "built: " ++ builtDir installDirectoryContents verbosity (builtDir </> display (packageId pkg)) targetDir@@ -258,19 +265,23 @@ registerPackage :: Verbosity- -> InstalledPackageInfo- -> PackageDescription- -> LocalBuildInfo- -> Bool+ -> Compiler+ -> ProgramConfiguration -> PackageDBStack+ -> InstalledPackageInfo -> IO ()-registerPackage verbosity installedPkgInfo pkg lbi inplace _packageDbs = do- let installDirs = absoluteInstallDirs pkg lbi NoCopyDest- pkgdir | inplace = buildDir lbi </> uhcPackageDir (display pkgid) (display compilerid)- | otherwise = libdir installDirs </> uhcPackageSubDir (display compilerid)+registerPackage verbosity comp progdb packageDbs installedPkgInfo = do+ dbdir <- case last packageDbs of+ GlobalPackageDB -> getGlobalPackageDir verbosity progdb+ UserPackageDB -> getUserPackageDir+ SpecificPackageDB dir -> return dir+ let pkgdir = dbdir </> uhcPackageDir (display pkgid) (display compilerid) createDirectoryIfMissingVerbose verbosity True pkgdir writeUTF8File (pkgdir </> installedPkgConfig) (showInstalledPackageInfo installedPkgInfo) where- pkgid = packageId pkg- compilerid = compilerId (compiler lbi)+ pkgid = sourcePackageId installedPkgInfo+ compilerid = compilerId comp++inplacePackageDbPath :: LocalBuildInfo -> FilePath+inplacePackageDbPath lbi = buildDir lbi
cabal/Cabal/Distribution/Simple/UserHooks.hs view
@@ -29,16 +29,11 @@ ) where import Distribution.PackageDescription- (PackageDescription, GenericPackageDescription,- HookedBuildInfo, emptyHookedBuildInfo)-import Distribution.Simple.Program (Program)-import Distribution.Simple.Command (noExtraFlags)-import Distribution.Simple.PreProcess (PPSuffixHandler)+import Distribution.Simple.Program+import Distribution.Simple.Command+import Distribution.Simple.PreProcess import Distribution.Simple.Setup- (ConfigFlags, BuildFlags, ReplFlags, CleanFlags, CopyFlags,- InstallFlags, SDistFlags, RegisterFlags, HscolourFlags,- HaddockFlags, TestFlags, BenchmarkFlags)-import Distribution.Simple.LocalBuildInfo (LocalBuildInfo)+import Distribution.Simple.LocalBuildInfo type Args = [String] @@ -178,7 +173,7 @@ preClean = rn, cleanHook = ru, postClean = ru,- preCopy = rn,+ preCopy = rn', copyHook = ru, postCopy = ru, preInst = rn,
cabal/Cabal/Distribution/Simple/Utils.hs view
@@ -26,6 +26,9 @@ debugNoWrap, chattyTry, printRawCommandAndArgs, printRawCommandAndArgsAndEnv, + -- * exceptions+ handleDoesNotExist,+ -- * running programs rawSystemExit, rawSystemExitCode,@@ -33,6 +36,7 @@ rawSystemStdout, rawSystemStdInOut, rawSystemIOWithEnv,+ createProcessWithEnv, maybeExit, xargs, findProgramLocation,@@ -64,6 +68,8 @@ -- * file names currentDir, shortRelativePath,+ dropExeExtension,+ exeExtensions, -- * finding files findFile,@@ -131,12 +137,37 @@ listUnionRight, ordNub, ordNubRight,+ safeTail, wrapText, wrapLine, ) where +import Distribution.Text+import Distribution.Package+import Distribution.ModuleName as ModuleName+import Distribution.System+import Distribution.Version+import Distribution.Compat.CopyFile+import Distribution.Compat.Internal.TempFile+import Distribution.Compat.Exception+import Distribution.Verbosity++#if __GLASGOW_HASKELL__ < 711+#ifdef VERSION_base+#define BOOTSTRAPPED_CABAL 1+#endif+#else+#ifdef CURRENT_PACKAGE_KEY+#define BOOTSTRAPPED_CABAL 1+#endif+#endif++#ifdef BOOTSTRAPPED_CABAL+import qualified Paths_Cabal (version)+#endif+ import Control.Monad- ( join, when, unless, filterM )+ ( when, unless, filterM ) import Control.Concurrent.MVar ( newEmptyMVar, putMVar, takeMVar ) import Data.Bits@@ -146,9 +177,11 @@ import Data.Foldable ( traverse_ ) import Data.List- ( nub, unfoldr, isPrefixOf, tails, intercalate )+ ( nub, unfoldr, intercalate, isInfixOf ) import Data.Typeable ( cast )+import Data.Ord+ ( comparing ) import qualified Data.ByteString.Lazy as BS import qualified Data.ByteString.Lazy.Char8 as BS.Char8 import qualified Data.Set as Set@@ -181,40 +214,17 @@ ( unsafeInterleaveIO ) import qualified Control.Exception as Exception -import Distribution.Text- ( display, simpleParse )-import Distribution.Package- ( PackageIdentifier )-import Distribution.ModuleName (ModuleName)-import qualified Distribution.ModuleName as ModuleName-import Distribution.System- ( OS (..) )-import Distribution.Version- (Version(..))- import Control.Exception (IOException, evaluate, throwIO) import Control.Concurrent (forkIO) import qualified System.Process as Process ( CreateProcess(..), StdStream(..), proc) import System.Process- ( createProcess, rawSystem, runInteractiveProcess+ ( ProcessHandle, createProcess, rawSystem, runInteractiveProcess , showCommandForUser, waitForProcess)-import Distribution.Compat.CopyFile- ( copyFile, copyOrdinaryFile, copyExecutableFile- , setFileOrdinary, setFileExecutable, setDirOrdinary )-import Distribution.Compat.TempFile- ( openTempFile, createTempDirectory )-import Distribution.Compat.Exception- ( tryIO, catchIO, catchExit )-import Distribution.Verbosity -#ifdef VERSION_base-import qualified Paths_Cabal (version)-#endif- -- We only get our own version number when we're building with ourselves cabalVersion :: Version-#if defined(VERSION_base)+#if defined(BOOTSTRAPPED_CABAL) cabalVersion = Paths_Cabal.version #elif defined(CABAL_VERSION) cabalVersion = Version [CABAL_VERSION] []@@ -258,7 +268,7 @@ handle se = do hFlush stdout pname <- getProgName- hPutStr stderr (message pname se)+ hPutStr stderr (wrapText (message pname se)) cont se message :: String -> Exception.SomeException -> String@@ -272,7 +282,7 @@ l@(n:_) | Char.isDigit n -> ':' : l _ -> "" detail = ioeGetErrorString ioe- in wrapText (pname ++ ": " ++ file ++ detail)+ in pname ++ ": " ++ file ++ detail Nothing -> #if __GLASGOW_HASKELL__ < 710 show se@@ -345,6 +355,14 @@ catchIO action $ \exception -> putStrLn $ "Error while " ++ desc ++ ": " ++ show exception +-- | Run an IO computation, returning @e@ if it raises a "file+-- does not exist" error.+handleDoesNotExist :: a -> IO a -> IO a+handleDoesNotExist e =+ Exception.handleJust+ (\ioe -> if isDoesNotExistError ioe then Just ioe else Nothing)+ (\_ -> return e)+ -- ----------------------------------------------------------------------------- -- Helper functions @@ -450,29 +468,47 @@ -> Maybe Handle -- ^ stderr -> IO ExitCode rawSystemIOWithEnv verbosity path args mcwd menv inp out err = do+ (_,_,_,ph) <- createProcessWithEnv verbosity path args mcwd menv+ (mbToStd inp) (mbToStd out) (mbToStd err)+ exitcode <- waitForProcess ph+ unless (exitcode == ExitSuccess) $ do+ debug verbosity $ path ++ " returned " ++ show exitcode+ return exitcode+ where+ mbToStd :: Maybe Handle -> Process.StdStream+ mbToStd = maybe Process.Inherit Process.UseHandle++createProcessWithEnv ::+ Verbosity+ -> FilePath+ -> [String]+ -> Maybe FilePath -- ^ New working dir or inherit+ -> Maybe [(String, String)] -- ^ New environment or inherit+ -> Process.StdStream -- ^ stdin+ -> Process.StdStream -- ^ stdout+ -> Process.StdStream -- ^ stderr+ -> IO (Maybe Handle, Maybe Handle, Maybe Handle,ProcessHandle)+ -- ^ Any handles created for stdin, stdout, or stderr+ -- with 'CreateProcess', and a handle to the process.+createProcessWithEnv verbosity path args mcwd menv inp out err = do printRawCommandAndArgsAndEnv verbosity path args menv hFlush stdout- (_,_,_,ph) <- createProcess $- (Process.proc path args) { Process.cwd = mcwd- , Process.env = menv- , Process.std_in = mbToStd inp- , Process.std_out = mbToStd out- , Process.std_err = mbToStd err+ (inp', out', err', ph) <- createProcess $+ (Process.proc path args) {+ Process.cwd = mcwd+ , Process.env = menv+ , Process.std_in = inp+ , Process.std_out = out+ , Process.std_err = err #ifdef MIN_VERSION_process #if MIN_VERSION_process(1,2,0) -- delegate_ctlc has been added in process 1.2, and we still want to be able to -- bootstrap GHC on systems not having that version- , Process.delegate_ctlc = True+ , Process.delegate_ctlc = True #endif #endif- }- exitcode <- waitForProcess ph- unless (exitcode == ExitSuccess) $ do- debug verbosity $ path ++ " returned " ++ show exitcode- return exitcode- where- mbToStd :: Maybe Handle -> Process.StdStream- mbToStd = maybe Process.Inherit Process.UseHandle+ }+ return (inp', out', err', ph) -- | Run a command and return its output. --@@ -554,6 +590,8 @@ return (out, err, exitcode) +{-# DEPRECATED findProgramLocation+ "No longer used within Cabal, try findProgramOnSearchPath" #-} -- | Look for a program on the path. findProgramLocation :: Verbosity -> FilePath -> IO (Maybe FilePath) findProgramLocation verbosity prog = do@@ -699,12 +737,12 @@ -> [String] -- ^ search suffixes -> ModuleName -- ^ module -> IO (FilePath, FilePath)-findModuleFile searchPath extensions moduleName =+findModuleFile searchPath extensions mod_name = maybe notFound return =<< findFileWithExtension' extensions searchPath- (ModuleName.toFilePath moduleName)+ (ModuleName.toFilePath mod_name) where- notFound = die $ "Error: Could not find module: " ++ display moduleName+ notFound = die $ "Error: Could not find module: " ++ display mod_name ++ " with any suffix: " ++ show extensions ++ " in the search path: " ++ show searchPath @@ -998,7 +1036,8 @@ copyDirectoryRecursive verbosity srcDir destDir = do info verbosity ("copy directory '" ++ srcDir ++ "' to '" ++ destDir ++ "'.") srcFiles <- getDirectoryContentsRecursive srcDir- copyFilesWith (const copyFile) verbosity destDir [ (srcDir, f) | f <- srcFiles ]+ copyFilesWith (const copyFile) verbosity destDir [ (srcDir, f)+ | f <- srcFiles ] ------------------- -- File permissions@@ -1060,7 +1099,8 @@ Exception.bracket (openTempFile tmpDir template) (\(name, handle) -> do hClose handle- unless (optKeepTempFiles opts) $ removeFile name)+ unless (optKeepTempFiles opts) $+ handleDoesNotExist () . removeFile $ name) (uncurry action) -- | Create and use a temporary directory.@@ -1086,7 +1126,8 @@ withTempDirectoryEx _verbosity opts targetDir template = Exception.bracket (createTempDirectory targetDir template)- (unless (optKeepTempFiles opts) . removeDirectoryRecursive)+ (unless (optKeepTempFiles opts)+ . handleDoesNotExist () . removeDirectoryRecursive) ----------------------------------- -- Safely reading and writing files@@ -1153,6 +1194,24 @@ | x == y = dropCommonPrefix xs ys dropCommonPrefix xs ys = (xs,ys) +-- | Drop the extension if it's one of 'exeExtensions', or return the path+-- unchanged.+dropExeExtension :: FilePath -> FilePath+dropExeExtension filepath =+ case splitExtension filepath of+ (filepath', extension) | extension `elem` exeExtensions -> filepath'+ | otherwise -> filepath++-- | List of possible executable file extensions on the current platform.+exeExtensions :: [String]+exeExtensions = case buildOS of+ -- Possible improvement: on Windows, read the list of extensions from the+ -- PATHEXT environment variable. By default PATHEXT is ".com; .exe; .bat;+ -- .cmd".+ Windows -> ["", "exe"]+ Ghcjs -> ["", "exe"]+ _ -> [""]+ -- ------------------------------------------------------------ -- * Finding the description file -- ------------------------------------------------------------@@ -1191,7 +1250,7 @@ -- |Like 'findPackageDesc', but calls 'die' in case of error. tryFindPackageDesc :: FilePath -> IO FilePath-tryFindPackageDesc dir = join . fmap (either die return) $ findPackageDesc dir+tryFindPackageDesc dir = either die return =<< findPackageDesc dir -- |Optional auxiliary package information file (/pkgname/@.buildinfo@) defaultHookedPackageDesc :: IO (Maybe FilePath)@@ -1415,14 +1474,13 @@ where bSet = Set.fromList b +-- | A total variant of 'tail'.+safeTail :: [a] -> [a]+safeTail [] = []+safeTail (_:xs) = xs+ equating :: Eq a => (b -> a) -> b -> b -> Bool equating p x y = p x == p y--comparing :: Ord a => (b -> a) -> b -> b -> Ordering-comparing p x y = p x `compare` p y--isInfixOf :: String -> String -> Bool-isInfixOf needle haystack = any (isPrefixOf needle) (tails haystack) lowercase :: String -> String lowercase = map Char.toLower
cabal/Cabal/Distribution/System.hs view
@@ -13,7 +13,7 @@ -- probably know about the 'System.Info.os' however using that is very -- inconvenient because it is a string and different Haskell implementations -- do not agree on using the same strings for the same platforms! (In--- particular see the controversy over \"windows\" vs \"ming32\"). So to make it+-- particular see the controversy over \"windows\" vs \"mingw32\"). So to make it -- more consistent and easy to use we have an 'OS' enumeration. -- module Distribution.System (@@ -28,18 +28,24 @@ -- * Platform is a pair of arch and OS Platform(..), buildPlatform,- platformFromTriple+ platformFromTriple,++ -- * Internal+ knownOSs,+ knownArches ) where import qualified System.Info (os, arch)-import qualified Data.Char as Char (toLower, isAlphaNum)+import qualified Data.Char as Char (toLower, isAlphaNum, isAlpha) -import Distribution.Compat.Binary (Binary)+import Distribution.Compat.Binary+import Distribution.Text+import qualified Distribution.Compat.ReadP as Parse++import Control.Monad (liftM2) import Data.Data (Data) import Data.Typeable (Typeable) import Data.Maybe (fromMaybe, listToMaybe)-import Distribution.Text (Text(..), display)-import qualified Distribution.Compat.ReadP as Parse import GHC.Generics (Generic) import qualified Text.PrettyPrint as Disp import Text.PrettyPrint ((<>))@@ -178,11 +184,21 @@ instance Text Platform where disp (Platform arch os) = disp arch <> Disp.char '-' <> disp os+ -- TODO: there are ambigious platforms like: `arch-word-os`+ -- which could be parsed as+ -- * Platform "arch-word" "os"+ -- * Platform "arch" "word-os"+ -- We could support that preferring variants 'OtherOS' or 'OtherArch'+ --+ -- For now we split into arch and os parts on the first dash. parse = do- arch <- parse+ arch <- parseDashlessArch _ <- Parse.char '-' os <- parse return (Platform arch os)+ where+ parseDashlessArch :: Parse.ReadP r Arch+ parseDashlessArch = fmap (classifyArch Strict) dashlessIdent -- | The platform Cabal was compiled on. In most cases, -- @LocalBuildInfo.hostPlatform@ should be used instead (the platform we're@@ -193,9 +209,15 @@ -- Utils: ident :: Parse.ReadP r String-ident = Parse.munch1 (\c -> Char.isAlphaNum c || c == '_' || c == '-')- --TODO: probably should disallow starting with a number+ident = liftM2 (:) first rest+ where first = Parse.satisfy Char.isAlpha+ rest = Parse.munch (\c -> Char.isAlphaNum c || c == '_' || c == '-') +dashlessIdent :: Parse.ReadP r String+dashlessIdent = liftM2 (:) first rest+ where first = Parse.satisfy Char.isAlpha+ rest = Parse.munch (\c -> Char.isAlphaNum c || c == '_')+ lowercase :: String -> String lowercase = map Char.toLower @@ -204,10 +226,10 @@ fmap fst (listToMaybe $ Parse.readP_to_S parseTriple triple) where parseWord = Parse.munch1 (\c -> Char.isAlphaNum c || c == '_') parseTriple = do- arch <- fmap (classifyArch Strict) parseWord+ arch <- fmap (classifyArch Permissive) parseWord _ <- Parse.char '-' _ <- parseWord -- Skip vendor _ <- Parse.char '-'- os <- fmap (classifyOS Compat) ident -- OS may have hyphens, like+ os <- fmap (classifyOS Permissive) ident -- OS may have hyphens, like -- 'nto-qnx' return $ Platform arch os
cabal/Cabal/Distribution/Text.hs view
@@ -13,6 +13,7 @@ -- module Distribution.Text ( Text(..),+ defaultStyle, display, simpleParse, ) where@@ -27,13 +28,15 @@ disp :: a -> Disp.Doc parse :: Parse.ReadP r a +-- | The default rendering style used in Cabal for console output.+defaultStyle :: Disp.Style+defaultStyle = Disp.Style { Disp.mode = Disp.PageMode+ , Disp.lineLength = 79+ , Disp.ribbonsPerLine = 1.0+ }+ display :: Text a => a -> String-display = Disp.renderStyle style . disp- where style = Disp.Style {- Disp.mode = Disp.PageMode,- Disp.lineLength = 79,- Disp.ribbonsPerLine = 1.0- }+display = Disp.renderStyle defaultStyle . disp simpleParse :: Text a => String -> Maybe a simpleParse str = case [ p | (p, s) <- Parse.readP_to_S parse str@@ -51,19 +54,20 @@ , (Parse.string "False" Parse.+++ Parse.string "false") >> return False ] +instance Text Int where+ disp = Disp.text . show+ parse = (fmap negate $ Parse.char '-' >> parseNat) Parse.+++ parseNat++-- | Parser for non-negative integers.+parseNat :: Parse.ReadP r Int+parseNat = read `fmap` Parse.munch1 Char.isDigit+ instance Text Version where disp (Version branch _tags) -- Death to version tags!! = Disp.hcat (Disp.punctuate (Disp.char '.') (map Disp.int branch)) parse = do- branch <- Parse.sepBy1 digits (Parse.char '.')+ branch <- Parse.sepBy1 parseNat (Parse.char '.') -- allow but ignore tags: _tags <- Parse.many (Parse.char '-' >> Parse.munch1 Char.isAlphaNum) return (Version branch [])- where- digits = do- first <- Parse.satisfy Char.isDigit- if first == '0'- then return 0- else do rest <- Parse.munch Char.isDigit- return (read (first : rest))
cabal/Cabal/Distribution/Utils/NubList.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE CPP #-} module Distribution.Utils.NubList ( NubList -- opaque , toNubList -- smart construtor@@ -11,12 +10,9 @@ , overNubListR ) where +import Distribution.Compat.Semigroup as Semi import Distribution.Compat.Binary-#if __GLASGOW_HASKELL__ < 710-import Data.Monoid-#endif--import Distribution.Simple.Utils (ordNub, listUnion, ordNubRight, listUnionRight)+import Distribution.Simple.Utils import qualified Text.Read as R @@ -54,8 +50,11 @@ instance Ord a => Monoid (NubList a) where mempty = NubList []- mappend (NubList xs) (NubList ys) = NubList $ xs `listUnion` ys+ mappend = (Semi.<>) +instance Ord a => Semigroup (NubList a) where+ (NubList xs) <> (NubList ys) = NubList $ xs `listUnion` ys+ instance Show a => Show (NubList a) where show (NubList list) = show list @@ -91,7 +90,10 @@ instance Ord a => Monoid (NubListR a) where mempty = NubListR []- mappend (NubListR xs) (NubListR ys) = NubListR $ xs `listUnionRight` ys+ mappend = (Semi.<>)++instance Ord a => Semigroup (NubListR a) where+ (NubListR xs) <> (NubListR ys) = NubListR $ xs `listUnionRight` ys instance Show a => Show (NubListR a) where show (NubListR list) = show list
cabal/Cabal/Distribution/Verbosity.hs view
@@ -24,9 +24,10 @@ showForCabal, showForGHC ) where -import Distribution.Compat.Binary (Binary)-import Data.List (elemIndex)+import Distribution.Compat.Binary import Distribution.ReadE++import Data.List (elemIndex) import GHC.Generics data Verbosity = Silent | Normal | Verbose | Deafening
cabal/Cabal/Distribution/Version.hs view
@@ -3,7 +3,28 @@ #if __GLASGOW_HASKELL__ < 707 {-# LANGUAGE StandaloneDeriving #-} #endif++-- Hack approach to support bootstrapping.+-- When MIN_VERSION_binary macro is available, use it. But it's not available+-- during bootstrapping (or anyone else building Setup.hs directly). If the+-- builder specifies -DMIN_VERSION_binary_0_8_0=1 or =0 then we respect that.+-- Otherwise we pick a default based on GHC version: assume binary <0.8 when+-- GHC < 8.0, and binary >=0.8 when GHC >= 8.0.+#ifdef MIN_VERSION_binary+#define MIN_VERSION_binary_0_8_0 MIN_VERSION_binary(0,8,0)+#else+#ifndef MIN_VERSION_binary_0_8_0+#if __GLASGOW_HASKELL__ >= 800+#define MIN_VERSION_binary_0_8_0 1+#else+#define MIN_VERSION_binary_0_8_0 0+#endif+#endif+#endif++#if !MIN_VERSION_binary_0_8_0 {-# OPTIONS_GHC -fno-warn-orphans #-}+#endif ----------------------------------------------------------------------------- -- |@@ -81,9 +102,10 @@ import Data.Version ( Version(..) ) import GHC.Generics ( Generic ) -import Distribution.Text ( Text(..) )+import Distribution.Text import qualified Distribution.Compat.ReadP as Parse-import Distribution.Compat.ReadP ((+++))+import Distribution.Compat.ReadP hiding (get)+ import qualified Text.PrettyPrint as Disp import Text.PrettyPrint ((<>), (<+>)) import qualified Data.Char as Char (isDigit)@@ -113,6 +135,7 @@ deriving instance Data Version #endif +#if !(MIN_VERSION_binary_0_8_0) -- Deriving this instance from Generic gives trouble on GHC 7.2 because the -- Generic instance has to be standalone-derived. So, we hand-roll our own. -- We can't use a generic Binary instance on later versions because we must@@ -123,14 +146,22 @@ tags <- get return $ Version br tags put (Version br tags) = put br >> put tags+#endif -{-# DEPRECATED AnyVersion "Use 'anyVersion', 'foldVersionRange' or 'asVersionIntervals'" #-}-{-# DEPRECATED ThisVersion "use 'thisVersion', 'foldVersionRange' or 'asVersionIntervals'" #-}-{-# DEPRECATED LaterVersion "use 'laterVersion', 'foldVersionRange' or 'asVersionIntervals'" #-}-{-# DEPRECATED EarlierVersion "use 'earlierVersion', 'foldVersionRange' or 'asVersionIntervals'" #-}-{-# DEPRECATED WildcardVersion "use 'anyVersion', 'foldVersionRange' or 'asVersionIntervals'" #-}-{-# DEPRECATED UnionVersionRanges "use 'unionVersionRanges', 'foldVersionRange' or 'asVersionIntervals'" #-}-{-# DEPRECATED IntersectVersionRanges "use 'intersectVersionRanges', 'foldVersionRange' or 'asVersionIntervals'" #-}+{-# DeprecateD AnyVersion+ "Use 'anyVersion', 'foldVersionRange' or 'asVersionIntervals'" #-}+{-# DEPRECATED ThisVersion+ "Use 'thisVersion', 'foldVersionRange' or 'asVersionIntervals'" #-}+{-# DEPRECATED LaterVersion+ "Use 'laterVersion', 'foldVersionRange' or 'asVersionIntervals'" #-}+{-# DEPRECATED EarlierVersion+ "Use 'earlierVersion', 'foldVersionRange' or 'asVersionIntervals'" #-}+{-# DEPRECATED WildcardVersion+ "Use 'anyVersion', 'foldVersionRange' or 'asVersionIntervals'" #-}+{-# DEPRECATED UnionVersionRanges+ "Use 'unionVersionRanges', 'foldVersionRange' or 'asVersionIntervals'" #-}+{-# DEPRECATED IntersectVersionRanges+ "Use 'intersectVersionRanges', 'foldVersionRange' or 'asVersionIntervals'"#-} -- | The version range @-any@. That is, a version range containing all -- versions.@@ -216,7 +247,8 @@ -- invertVersionRange :: VersionRange -> VersionRange invertVersionRange =- fromVersionIntervals . invertVersionIntervals . VersionIntervals . asVersionIntervals+ fromVersionIntervals . invertVersionIntervals+ . VersionIntervals . asVersionIntervals -- | The version range @== v.*@. --@@ -286,9 +318,10 @@ (orLaterVersion v) (earlierVersion (wildcardUpperBound v)) --- | An extended variant of 'foldVersionRange' that also provides a view of--- in which the syntactic sugar @\">= v\"@, @\"<= v\"@ and @\"== v.*\"@ is presented--- explicitly rather than in terms of the other basic syntax.+-- | An extended variant of 'foldVersionRange' that also provides a view of the+-- expression in which the syntactic sugar @\">= v\"@, @\"<= v\"@ and @\"==+-- v.*\"@ is presented explicitly rather than in terms of the other basic+-- syntax. -- foldVersionRange' :: a -- ^ @\"-any\"@ version -> (Version -> a) -- ^ @\"== v\"@@@ -697,10 +730,12 @@ -- Empty interval set [] -> VersionIntervals [(noLowerBound, NoUpperBound)] -- Interval with no lower bound- ((lb, ub) : more) | lb == noLowerBound -> VersionIntervals $ invertVersionIntervals' ub more+ ((lb, ub) : more) | lb == noLowerBound ->+ VersionIntervals $ invertVersionIntervals' ub more -- Interval with a lower bound ((lb, ub) : more) ->- VersionIntervals $ (noLowerBound, invertLowerBound lb) : invertVersionIntervals' ub more+ VersionIntervals $ (noLowerBound, invertLowerBound lb)+ : invertVersionIntervals' ub more where -- Invert subsequent version intervals given the upper bound of -- the intervals already inverted.@@ -743,8 +778,10 @@ (\v -> (Disp.text ">=" <> disp v , 0)) (\v -> (Disp.text "<=" <> disp v , 0)) (\v _ -> (Disp.text "==" <> dispWild v , 0))- (\(r1, p1) (r2, p2) -> (punct 2 p1 r1 <+> Disp.text "||" <+> punct 2 p2 r2 , 2))- (\(r1, p1) (r2, p2) -> (punct 1 p1 r1 <+> Disp.text "&&" <+> punct 1 p2 r2 , 1))+ (\(r1, p1) (r2, p2) ->+ (punct 2 p1 r1 <+> Disp.text "||" <+> punct 2 p2 r2 , 2))+ (\(r1, p1) (r2, p2) ->+ (punct 1 p1 r1 <+> Disp.text "&&" <+> punct 1 p2 r2 , 1)) (\(r, _) -> (Disp.parens r, 0)) where dispWild (Version b _) =@@ -812,14 +849,12 @@ -- -- @since 1.24.0.0 hasUpperBound :: VersionRange -> Bool-hasUpperBound AnyVersion = False-hasUpperBound (ThisVersion _) = True-hasUpperBound (LaterVersion _) = False-hasUpperBound (EarlierVersion _) = True-hasUpperBound (WildcardVersion _) = True-hasUpperBound (UnionVersionRanges x y) = hasUpperBound x && hasUpperBound y-hasUpperBound (IntersectVersionRanges x y) = hasUpperBound x || hasUpperBound y-hasUpperBound (VersionRangeParens x) = hasUpperBound x+hasUpperBound = foldVersionRange+ False+ (const True)+ (const False)+ (const True)+ (&&) (||) -- | Does the version range have an explicit lower bound? --@@ -828,11 +863,9 @@ -- -- @since 1.24.0.0 hasLowerBound :: VersionRange -> Bool-hasLowerBound AnyVersion = False-hasLowerBound (ThisVersion _) = True-hasLowerBound (LaterVersion _) = True-hasLowerBound (EarlierVersion _) = False-hasLowerBound (WildcardVersion _) = True-hasLowerBound (UnionVersionRanges x y) = hasLowerBound x && hasLowerBound y-hasLowerBound (IntersectVersionRanges x y) = hasLowerBound x || hasLowerBound y-hasLowerBound (VersionRangeParens x) = hasLowerBound x+hasLowerBound = foldVersionRange+ False+ (const True)+ (const True)+ (const False)+ (&&) (||)
cabal/Cabal/Language/Haskell/Extension.hs view
@@ -22,12 +22,13 @@ deprecatedExtensions ) where -import Distribution.Text (Text(..))+import Distribution.Text import qualified Distribution.Compat.ReadP as Parse+import Distribution.Compat.Binary+ import qualified Text.PrettyPrint as Disp import qualified Data.Char as Char (isAlphaNum) import Data.Array (Array, accumArray, bounds, Ix(inRange), (!))-import Distribution.Compat.Binary (Binary) import Data.Data (Data) import Data.Typeable (Typeable) import GHC.Generics (Generic)@@ -713,6 +714,57 @@ -- -- * <http://www.haskell.org/ghc/docs/latest/html/users_guide/other-type-extensions.html#derive-any-class> | DeriveAnyClass++ -- | Enable @deriving@ for the 'Language.Haskell.TH.Syntax.Lift' class.+ --+ -- * <http://www.haskell.org/ghc/docs/latest/html/users_guide/deriving.html#deriving-lift>+ | DeriveLift++ -- | Enable support for 'static pointers' (and the @static@+ -- keyword) to refer to globally stable names, even across+ -- different programs.+ --+ -- * <http://www.haskell.org/ghc/docs/latest/html/users_guide/static-pointers.html>+ | StaticPointers++ -- | Switches data type declarations to be strict by default (as if+ -- they had a bang using @BangPatterns@), and allow opt-in field+ -- laziness using @~@.+ | StrictData++ -- | Switches all pattern bindings to be strict by default (as if+ -- they had a bang using @BangPatterns@), ordinary patterns are+ -- recovered using @~@. Implies @StrictData@.+ | Strict++ -- | Allows @do@-notation for types that are @'Applicative'@ as well+ -- as @'Monad'@. When enabled, desugaring @do@ notation tries to use+ -- @(<*>)@ and @'fmap'@ and @'join'@ as far as possible.+ | ApplicativeDo++ -- | Allow records to use duplicated field labels for accessors.+ | DuplicateRecordFields++ -- | Enable explicit type applications with the syntax @id \@Int@.+ | TypeApplications++ -- | Dissolve the distinction between types and kinds, allowing the compiler+ -- to reason about kind equality and therefore enabling GADTs to be promoted+ -- to the type-level.+ | TypeInType++ -- | Allow recursive (and therefore undecideable) super-class relationships.+ | UndecidableSuperClasses++ -- | A temporary extension to help library authors check if their+ -- code will compile with the new planned desugaring of fail.+ | MonadFailDesugaring++ -- | A subset of @TemplateHaskell@ including only quasi-quoting.+ | TemplateHaskellQuotes++ -- | Allows use of the @#label@ syntax.+ | OverloadedLabels deriving (Generic, Show, Read, Eq, Ord, Enum, Bounded, Typeable, Data)
cabal/Cabal/Makefile view
@@ -1,5 +1,5 @@ -VERSION=1.23.0.0+VERSION=1.25.0.0 #KIND=devel KIND=rc@@ -14,7 +14,10 @@ # build the library itself -SOURCES=Distribution/*.hs Distribution/Simple/*.hs Distribution/PackageDescription/*.hs Distribution/Simple/GHC/*.hs Distribution/Simple/Build/*.hs Distribution/Compat/*.hs Distribution/Simple/Program/*.hs+SOURCES=Distribution/*.hs Distribution/Simple/*.hs \+ Distribution/PackageDescription/*.hs Distribution/Simple/GHC/*.hs \+ Distribution/Simple/Build/*.hs Distribution/Compat/*.hs \+ Distribution/Simple/Program/*.hs CONFIG_STAMP=dist/setup-config BUILD_STAMP=dist/build/libHSCabal-$(VERSION).a HADDOCK_STAMP=dist/doc/html/Cabal/index.html@@ -27,7 +30,8 @@ setup: $(SOURCES) Setup.hs -mkdir -p dist/setup- $(HC) $(GHCFLAGS) --make -i. -odir dist/setup -hidir dist/setup Setup.hs -o setup+ $(HC) $(GHCFLAGS) --make -i. -odir dist/setup -hidir dist/setup Setup.hs \+ -o setup $(CONFIG_STAMP): setup Cabal.cabal ./setup configure --with-compiler=$(HC) --prefix=$(PREFIX)@@ -58,7 +62,9 @@ mkdir -p $(PANDOC_HTML_OUTDIR) for file in $^; do \ [ $${file} != doc/index.markdown ] && TOC=--table-of-contents || TOC=; \- $(PANDOC) $(PANDOC_OPTIONS) $${TOC} --from=markdown --to=html --output $(PANDOC_HTML_OUTDIR)/$$(basename $${file} .markdown).html $${file}; \+ $(PANDOC) $(PANDOC_OPTIONS) $${TOC} --from=markdown --to=html \+ --output $(PANDOC_HTML_OUTDIR)/$$(basename $${file} .markdown).html \+ $${file}; \ done cp doc/$(PANDOC_HTML_CSS) $(PANDOC_HTML_OUTDIR)
cabal/Cabal/Setup.hs view
@@ -8,3 +8,6 @@ -- on any previous installation. This also means we can use any new features -- immediately because we never have to worry about building Cabal with an -- older version of itself.+--+-- NOTE 25/01/2015: Bootstrapping is disabled for now, see+-- https://github.com/haskell/cabal/issues/3003.
cabal/Cabal/changelog view
@@ -1,16 +1,49 @@ -*-change-log-*- -1.23.x.x (current development version)+1.25.x.x (current development version)+ * Dropped support for versions of GHC earlier than 6.12 (#3111).+ * Convenience/internal libraries are now supported (#269).+ An internal library is declared using the stanza "library+ 'libname'".+ * Backwards incompatible change to preprocessor interface:+ the function in 'PPSuffixHandler' now takes an additional+ 'ComponentLocalBuildInfo' specifying the build information+ of the component being preprocessed.+ * Backwards incompatible change to 'cabal_macros.h' (#1893): we now+ generate a macro file for each component which contains only+ information about the direct dependencies of that component.+ Consequently, 'dist/build/autogen/cabal_macros.h' contains+ only the macros for the library, and is not generated if a+ package has no library; to find the macros for an executable+ named 'foobar', look in 'dist/build/foobar/autogen/cabal_macros.h'.++1.24.0.0 Ryan Thomas <ryan@ryant.org> March 2016+ * Support GHC 8. * Deal with extra C sources from preprocessors (#238). * Include cabal_macros.h when running c2hs (#2600). * Don't recompile C sources unless needed (#2601). * Read 'builddir' option from 'CABAL_BUILDDIR' environment variable- * Add '--profiling-defail=$level' flag with a default for libraries+ * Add '--profiling-detail=$level' flag with a default for libraries and executables of 'exported-functions' and 'toplevel-functions' respetively (GHC's '-fprof-auto-{exported,top}' flags) (#193). * New 'custom-setup' stanza to specify setup deps. Setup is also built with the cabal_macros.h style macros, for conditional compilation. * Support Haddock response files (#2746).+ * Fixed a bug in the Text instance for Platform (#2862).+ * New 'setup haddock' option: '--for-hackage' (#2852).+ * New --show-detail=direct; like streaming, but allows the test+ program to detect that is connected to a terminal, and works+ reliable with a non-threaded runtime (#2911, and serves as a+ work-around for #2398)+ * Library support for multi-instance package DBs (#2948).+ * Improved the './Setup configure' solver (#3082, #3076).+ * The '--allow-newer' option can be now used with './Setup+ configure' (#3163).+ * Added a way to specify extra locations to find OS X frameworks+ in ('extra-framework-dirs'). Can be used both in .cabal files and+ as an argument to './Setup configure' (#3158).+ * Macros 'VERSION_$pkgname' and 'MIN_VERSION_$pkgname' are now+ also generated for the current package. (#3235). 1.22.0.0 Johan Tibell <johan.tibell@gmail.com> January 2015 * Support GHC 7.10.@@ -43,7 +76,7 @@ * Make 'cabal test'/'cabal bench' build only what's needed for running tests/benchmarks (#1821). * Build shared libraries by default when linking executables dynamically.- * Build profiled libraries by default when profiling executable.s+ * Build profiled libraries by default when profiling executables. 1.20.0.1 Johan Tibell <johan.tibell@gmail.com> May 2014 * Fix streaming test output.
cabal/Cabal/doc/developing-packages.markdown view
@@ -676,10 +676,11 @@ The syntax of the value depends on the field. Field types include: _token_, _filename_, _directory_-: Either a sequence of one or more non-space non-comma characters, or- a quoted string in Haskell 98 lexical syntax. Unless otherwise- stated, relative filenames and directories are interpreted from the- package root directory.+: Either a sequence of one or more non-space non-comma characters, or a quoted+ string in Haskell 98 lexical syntax. The latter can be used for escaping+ whitespace, for example: `ghc-options: -Wall "-with-rtsopts=-T -I1"`.+ Unless otherwise stated, relative filenames and directories are interpreted+ from the package root directory. _freeform_, _URL_, _address_ : An arbitrary, uninterpreted string.@@ -965,6 +966,43 @@ The library section may also contain build information fields (see the section on [build information](#build-information)). +Cabal 1.23 and later support "internal libraries", which are extra named+libraries (as opposed to the usual unnamed library section). For+example, suppose that your test suite needs access to some internal+modules in your library, which you do not otherwise want to export. You+could put these modules in an internal library, which the main library+and the test suite `build-depends` upon. Then your Cabal file might+look something like this:++~~~~~~~~~~~~~~~~+name: foo+version: 1.0+license: BSD3+cabal-version: >= 1.23+build-type: Simple++library foo-internal+ exposed-modules: Foo.Internal+ build-depends: base++library+ exposed-modules: Foo.Public+ build-depends: foo-internal, base++test-suite test-foo+ type: exitcode-stdio-1.0+ main-is: test-foo.hs+ build-depends: foo-internal, base+~~~~~~~~~~~~~~~~++Internal libraries are also useful for packages that define multiple+executables, but do not define a publically accessible library.+Internal libraries are only visible internally in the package (so they+can only be added to the `build-depends` of same-package libraries,+executables, test suites, etc.) Internal libraries locally shadow any+packages which have the same name (so don't name an internal library+with the same name as an external dependency.)+ #### Opening an interpreter session #### While developing a package, it is often useful to make its code available inside@@ -1002,6 +1040,32 @@ `cabal.config` file. All environments which share this file will use the dependency versions specified in it. +#### Generating dependency version bounds ####++Cabal also has the ability to suggest dependency version bounds that conform to+[Package Versioning Policy][PVP], which is a recommended versioning system for+publicly released Cabal packages. This is done by running the `gen-bounds`+command:++~~~~~~~~~~~~~~~~+cabal gen-bounds+~~~~~~~~~~~~~~~~++For example, given the following dependencies specified in `build-depends`:++~~~~~~~~~~~~~~~~+foo == 0.5.2+bar == 1.1+~~~~~~~~~~~~~~~~++`gen-bounds` will suggest changing them to the following:++~~~~~~~~~~~~~~~~+foo >= 0.5.2 && < 0.6+bar >= 1.1 && < 1.2+~~~~~~~~~~~~~~~~++ ### Executables ### Executable sections (if present) describe executable programs contained@@ -1255,12 +1319,12 @@ ### Build information ### -The following fields may be optionally present in a library or-executable section, and give information for the building of the+The following fields may be optionally present in a library, executable, test+suite or benchmark section, and give information for the building of the corresponding library or executable. See also the sections on [system-dependent parameters](#system-dependent-parameters) and-[configurations](#configurations) for a way to supply system-dependent-values for these fields.+[configurations](#configurations) for a way to supply system-dependent values+for these fields. `build-depends:` _package list_ : A list of packages needed to build this one. Each package can be@@ -1290,29 +1354,16 @@ It is only syntactic sugar. It is exactly equivalent to `foo >= 1.2 && < 1.3`. - With Cabal 1.20 and GHC 7.10, `build-depends` also supports module- thinning and renaming, which allows you to selectively decide what- modules become visible from a package dependency. For example:-- ~~~~~~~~~~~~~~~~- build-depends: containers (Data.Set, Data.IntMap as Map)- ~~~~~~~~~~~~~~~~-- This results in only the modules `Data.Set` and `Map` being visible to- the user from containers, hiding all other modules. To add additional- names for modules without hiding the others, you can use the `with`- keyword:-- ~~~~~~~~~~~~~~~~- build-depends: containers with (Data.IntMap as Map)- ~~~~~~~~~~~~~~~~- Note: Prior to Cabal 1.8, build-depends specified in each section were global to all sections. This was unintentional, but some packages were written to depend on it, so if you need your build-depends to be local to each section, you must specify at least `Cabal-Version: >= 1.8` in your `.cabal` file. + Note: Cabal 1.20 experimentally supported module thinning and+ renaming in `build-depends`; however, this support has since been+ removed and should not be used.+ `other-modules:` _identifier list_ : A list of modules used by the component but not exposed to users. For a library component, these would be hidden modules of the@@ -1362,8 +1413,10 @@ `build-tools:` _program list_ : A list of programs, possibly annotated with versions, needed to- build this package, e.g. `c2hs >= 0.15, cpphs`.If no version+ build this package, e.g. `c2hs >= 0.15, cpphs`. If no version constraint is specified, any version is assumed to be acceptable.+ `build-tools` can refer to locally defined executables, in which+ case Cabal will make sure that executable is built first. `buildable:` _boolean_ (default: `True`) : Is the component buildable? Like some of the other fields below,@@ -1378,12 +1431,15 @@ Options required only by one module may be specified by placing an `OPTIONS_GHC` pragma in the source file affected. + As with many other fields, whitespace can be escaped by using Haskell string+ syntax. Example: `ghc-options: -Wcompat "-with-rtsopts=-T -I1" -Wall`.+ `ghc-prof-options:` _token list_ : Additional options for GHC when the package is built with profiling enabled. Note that as of Cabal-1.24, the default profiling detail level defaults to- `exported-functions` for libraries and `toplevel-funcitons` for+ `exported-functions` for libraries and `toplevel-functions` for executables. For GHC these correspond to the flags `-fprof-auto-exported` and `-fprof-auto-top`. Prior to Cabal-1.24 the level defaulted to `none`. These levels can be adjusted by the person building the package with the@@ -1399,6 +1455,9 @@ `ghc-shared-options:` _token list_ : Additional options for GHC when the package is built as shared library.+ The options specified via this field are combined with the ones specified+ via `ghc-options`, and are passed to GHC during both the compile and+ link phases. `includes:` _filename list_ : A list of header files to be included in any compilations via C.@@ -1460,7 +1519,7 @@ parameters](#system-dependent-parameters)>. `pkgconfig-depends:` _package list_-: A list of [pkg-config][] packages, needed to build this package.+: A list of [pkg-config] packages, needed to build this package. They can be annotated with versions, e.g. `gtk+-2.0 >= 2.10, cairo >= 1.0`. If no version constraint is specified, any version is assumed to be acceptable. Cabal uses `pkg-config` to find if the@@ -1477,6 +1536,10 @@ developer documentation for more details on frameworks. This entry is ignored on all other platforms. +`extra-frameworks-dirs:` _directory list_+: On Darwin/MacOS X, a list of directories to search for frameworks.+ This entry is ignored on all other platforms.+ ### Configurations ### Library and executable sections may include conditional@@ -2189,3 +2252,4 @@ [Hackage]: http://hackage.haskell.org/ [pkg-config]: http://www.freedesktop.org/wiki/Software/pkg-config/ [REPL]: http://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop+[PVP]: https://wiki.haskell.org/Package_versioning_policy
cabal/Cabal/doc/index.markdown view
@@ -1,4 +1,5 @@ % Cabal User Guide+**Version: 1.25.0.0** Cabal is the standard package system for [Haskell] software. It helps people to configure, build and install Haskell software and to
cabal/Cabal/doc/installing-packages.markdown view
@@ -1,5 +1,140 @@ % Cabal User Guide +# Configuration #++## Overview ##++The global configuration file for `cabal-install` is `~/.cabal/config`. If you+do not have this file, `cabal` will create it for you on the first call to+`cabal update`. Alternatively, you can explicitly ask `cabal` to create it for+you using++> `cabal user-config update`++Most of the options in this configuration file are also available as command+line arguments, and the corresponding documentation can be used to lookup their+meaning. The created configuration file only specifies values for a handful of+options. Most options are left at their default value, which it documents;+for instance,++~~~~~~~~~~~~~~~~+-- executable-stripping: True+~~~~~~~~~~~~~~~~++means that the configuration file currently does not specify a value for the+`executable-stripping` option (the line is commented out), and that the default+is `True`; if you wanted to disable stripping of executables by default, you+would change this line to++~~~~~~~~~~~~~~~~+executable-stripping: False+~~~~~~~~~~~~~~~~++You can also use `cabal user-config update` to migrate configuration files+created by older versions of `cabal`.++## Repository specification ##++An important part of the configuration if the specification of the repository.+When `cabal` creates a default config file, it configures the repository to+be the central Hackage server:++~~~~~~~~~~~~~~~~+repository hackage.haskell.org+ url: http://hackage.haskell.org/+~~~~~~~~~~~~~~~~++The name of the repository is given on the first line, and can be anything;+packages downloaded from this repository will be cached under+`~/.cabal/packages/hackage.haskell.org` (or whatever name you specify; you can+change the prefix by changing the value of `remote-repo-cache`). If you want,+you can configure multiple repositories, and `cabal` will combine them and be+able to download packages from any of them.++### Using secure repositories ###++For repositories that support the TUF security infrastructure (this includes+Hackage), you can enable secure access to the repository by specifying:++~~~~~~~~~~~~~~~~+repository hackage.haskell.org+ url: http://hackage.haskell.org/+ secure: True+ root-keys: <root-key-IDs>+ key-threshold: <key-threshold>+~~~~~~~~~~~~~~~~++The `<root-key-IDs>` and `<key-threshold>` values are used for bootstrapping. As+part of the TUF infrastructure the repository will contain a file `root.json`+(for instance,+[http://hackage.haskell.org/root.json](http://hackage.haskell.org/root.json))+which the client needs to do verification. However, how can `cabal` verify the+`root.json` file _itself_? This is known as bootstrapping: if you specify a list+of root key IDs and a corresponding threshold, `cabal` will verify that the+downloaded `root.json` file has been signed with at least `<key-threshold>`+keys from your set of `<root-key-IDs>`.++You can, but are not recommended to, omit these two fields. In that case `cabal`+will download the `root.json` field and use it without verification. Although+this bootstrapping step is then unsafe, all subsequent access is secure+(provided that the downloaded `root.json` was not tempered with). Of course,+adding `root-keys` and `key-threshold` to your repository specification only+shifts the problem, because now you somehow need to make sure that the key IDs+you received were the right ones. How that is done is however outside the scope+of `cabal` proper.++More information about the security infrastructure can be found at+[https://github.com/well-typed/hackage-security](https://github.com/well-typed/hackage-security).++### Legacy repositories ###++Currently `cabal` supports two kinds of “legacy” repositories. The+first is specified using++~~~~~~~~~~~~~~~~+remote-repo: hackage.haskell.org:http://hackage.haskell.org/packages/archive+~~~~~~~~~~~~~~~~++This is just syntactic sugar for++~~~~~~~~~~~~~~~~+repository hackage.haskell.org+ url: hackage.haskell.org:http://hackage.haskell.org/packages/archive+~~~~~~~~~~~~~~~~++although, in (and only in) the specific case of Hackage, the URL+`http://hackage.haskell.org/packages/archive` will be silently translated to+`http://hackage.haskell.org/`.++The second kind of legacy repositories are so-called “local”+repositories:++~~~~~~~~~~~~~~~~+local-repo: my-local-repo:/path/to/local/repo+~~~~~~~~~~~~~~~~++This can be used to access repositories on the local file system. However, the+layout of these local repositories is different from the layout of remote+repositories, and usage of these local repositories is deprecated.++### Secure local repositories ###++If you want to use repositories on your local file system, it is recommended+instead to use a _secure_ local repository:++~~~~~~~~~~~~~~~~+repository my-local-repo+ url: file:/path/to/local/repo+ secure: True+ root-keys: <root-key-IDs>+ key-threshold: <key-threshold>+~~~~~~~~~~~~~~~~++The layout of these secure local repos matches the layout of remote repositories+exactly; the+[hackage-repo-tool](http://hackage.haskell.org/package/hackage-repo-tool) can be+used to create and manage such repositories.+ # Building and installing packages # After you've unpacked a Cabal package, you can build it by moving into@@ -610,6 +745,14 @@ be a file or directory. Not all implementations support arbitrary package databases. +`--default-user-config=` _file_+: Allows a "default" `cabal.config` freeze file to be passed in+ manually. This file will only be used if one does not exist in the+ project directory already. Typically, this can be set from the global+ cabal `config` file so as to provide a default set of partial+ constraints to be used by projects, providing a way for users to peg+ themselves to stable package collections.+ `--enable-optimization`[=_n_] or `-O`[_n_] : (default) Build with optimization flags (if available). This is appropriate for production use, taking more time to build faster@@ -789,6 +932,10 @@ : An extra directory to search for system libraries files. You can use this flag multiple times to get a list of directories. +`--extra-framework-dirs`[=_dir_]+: An extra directory to search for frameworks (OS X only). You can use this+ flag multiple times to get a list of directories.+ You might need to use this flag if you have standard system libraries in a non-standard location that is not mentioned in the package's `.cabal` file. Using this option has the same affect as@@ -835,8 +982,26 @@ $ cabal install --allow-newer=bar --constraint="bar==2.1" foo ~~~~~~~~~~~~~~~~ - It's also possible to enable `--allow-newer` permanently by setting- `allow-newer: True` in the `~/.cabal/config` file.+ It's also possible to limit the scope of `--allow-newer` to single+ packages with the `--allow-newer=scope:dep` syntax. This means that the+ dependency on `dep` will be relaxed only for the package `scope`.++ Example:++ ~~~~~~~~~~~~~~~~+ # Relax upper bound in foo's dependency on base; also relax upper bound in+ # every package's dependency on lens.+ $ cabal install --allow-newer=foo:base,lens++ # Relax upper bounds in foo's dependency on base and bar's dependency+ # on time; also relax the upper bound in the dependency on lens specified by+ # any package.+ $ cabal install --allow-newer=foo:base,lens --allow-newer=bar:time+ ~~~~~~~~~~~~~~~~++ Finally, one can enable `--allow-newer` permanently by setting `allow-newer:+ True` in the `~/.cabal/config` file. Enabling 'allow-newer' selectively is+ also supported in the config file (`allow-newer: foo, bar, baz:base`). `--constraint=`_constraint_ : Restrict solutions involving a package to a given version range.
cabal/Cabal/misc/gen-extra-source-files.sh view
@@ -1,5 +1,22 @@-#! /bin/sh+#!/bin/sh +if [ "$#" -ne 1 ]; then+ echo "Error: too few arguments!"+ echo "Usage: $0 FILE"+ exit 1+fi++set -ex+ find tests -type f \( -name '*.hs' -or -name '*.lhs' -or -name '*.c' -or -name '*.sh' \- -or -name '*.cabal' -or -name '*.hsc' \) -and -not -regex ".*/dist/.*" \- | awk '/Check.hs$|UnitTests|PackageTester|autogen|PackageTests.hs|CreatePipe/ { next } { print }'+ -or -name '*.cabal' -or -name '*.hsc' -or -name '*.err' -or -name '*.out' -or -name "ghc*" \) -and -not -regex ".*/dist.*/.*" \+ | awk '/Check.hs$|UnitTests|PackageTester|autogen|PackageTests.hs|IntegrationTests.hs|CreatePipe|^tests\/Test/ { next } { print }' \+ | LC_ALL=C sort \+ | sed -e 's/^/ /' \+ > source-file-list++lead='^ -- BEGIN gen-extra-source-files'+tail='^ -- END gen-extra-source-files'+# cribbed off of http://superuser.com/questions/440013/how-to-replace-part-of-a-text-file-between-markers-with-another-text-file+sed -i.bak -e "/$lead/,/$tail/{ /$lead/{p; r source-file-list+ }; /$tail/p; d }" $1
+ cabal/Cabal/misc/travis-diff-files.sh view
@@ -0,0 +1,3 @@+#!/bin/sh+git status > /dev/null # See https://github.com/haskell/cabal/pull/3088#commitcomment-15818452+git diff-files -p --exit-code
cabal/HACKING.md view
@@ -34,7 +34,7 @@ _The steps below will make use of sandboxes for building. The process might be somewhat different when you do not want to use sandboxes._ -Building Cabal from from source requires the following:+Building Cabal from source requires the following: * Glorious/Glasgow Haskell Compiler (ghc). * An existing (relatively recent) `cabal` binary (e.g. obtained as part of the@@ -70,7 +70,7 @@ we cannot use `cabal` for the next steps; we need to use Setup instead. So, compile Setup.hs:- + ~~~~ ghc --make -threaded Setup.hs ~~~~@@ -89,7 +89,7 @@ ~~~~ ~/MyHaskellCode/cabal/Cabal/.cabal-sandbox/$SOMESTUFF-packages.conf.d ~~~~- + (or, as a relative path with my setup:) ~~~~@@ -128,6 +128,18 @@ (In addition, the absolute sandbox path will be slightly different because we have to use the `cabal-install` sandbox, not the Cabal one. If you use the relative path, you are set.)++Coding Conventions+------------------++Use spaces, not tabs. Use lines no longer than 80 characters. If you modify a+file, please follow the style conventions used in that file. When you add a new+top-level definition, please also add a Haddock comment. Use explicit import+lists for third-party and standard library imports. Use of GHC extensions is+allowed (except Template Haskell), provided that the dependencies policy is+respected. In general, try to adhere to [this style guide][guide].++[guide]: https://github.com/tibbe/haskell-style-guide/blob/master/haskell-style.md Dependencies policy -------------------
cabal/Paths_Cabal.hs view
@@ -5,4 +5,4 @@ import Data.Version (Version(..)) version :: Version-version = Version {versionBranch = [1,23,0,0], versionTags = []}+version = Version [1,25,0,0] []
cabal/Paths_cabal_install.hs view
@@ -5,4 +5,4 @@ import Data.Version (Version(..)) version :: Version-version = Version {versionBranch = [1,23,0,0], versionTags = []}+version = Version [1,25,0,0] []
cabal/README.md view
@@ -1,4 +1,4 @@-# Cabal [](http://travis-ci.org/haskell/cabal)+# Cabal [](https://hackage.haskell.org/package/Cabal) [](http://travis-ci.org/haskell/cabal) [](https://ci.appveyor.com/project/23Skidoo/cabal) This Cabal Git repository contains the following packages:
+ cabal/appveyor.yml view
@@ -0,0 +1,28 @@+install:+ - choco install ghc -version 7.10.3 > NUL+ - SET PATH=%PATH%;C:\tools\ghc\ghc-7.10.3\bin+ - curl -o cabal.zip --silent https://www.haskell.org/cabal/release/cabal-install-1.24.0.0-rc1/cabal-install-1.24.0.0-rc1-x86_64-unknown-mingw32.zip+ - 7z x -bd cabal.zip+ - cabal --version+ - cabal update++build_script:+ - cd Cabal+ - ghc --make -threaded -i -i. Setup.hs -Wall -Werror++ # 'echo "" |' works around an AppVeyor issue:+ # https://github.com/commercialhaskell/stack/issues/1097#issuecomment-145747849+ - echo "" | ..\cabal install --only-dependencies --enable-tests++ - Setup configure --user --ghc-option=-Werror --enable-tests+ - Setup build+ - Setup test --show-details=streaming --test-option=--hide-successes+ - Setup install+ - cd ..\cabal-install+ - ghc --make -threaded -i -i. Setup.hs -Wall -Werror+ - echo "" | ..\cabal install happy+ - echo "" | ..\cabal install --only-dependencies --enable-tests+ - ..\cabal configure --user --ghc-option=-Werror --enable-tests+ - ..\cabal build+ - ..\cabal test unit-tests --show-details=streaming --test-option=--pattern=!FileMonitor --test-option=--hide-successes+ - ..\cabal test integration-tests --show-details=streaming --test-option=--pattern=!exec --test-option=--hide-successes
cabal/cabal-install/Distribution/Client/BuildReports/Anonymous.hs view
@@ -191,7 +191,6 @@ ParseFailed perror -> Left msg where (_, msg) = locatedErrorMsg perror ParseOk _ report -> Right report ---FIXME: this does not allow for optional or repeated fields parseFields :: String -> ParseResult BuildReport parseFields input = do fields <- mapM extractField =<< readFields input
cabal/cabal-install/Distribution/Client/BuildReports/Storage.hs view
@@ -79,7 +79,9 @@ -> [(BuildReport, Repo, RemoteRepo)] onlyRemote rs = [ (report, repo, remoteRepo)- | (report, Just repo@Repo { repoKind = Left remoteRepo }) <- rs ]+ | (report, Just repo) <- rs+ , Just remoteRepo <- [maybeRepoRemote repo]+ ] storeLocal :: CompilerInfo -> [PathTemplate] -> [(BuildReport, Maybe Repo)] -> Platform -> IO ()@@ -101,7 +103,7 @@ fromPathTemplate (substPathTemplate env template) where env = initialPathTemplateEnv (BuildReport.package report)- -- ToDo: In principle, we can support $pkgkey, but only+ -- TODO: In principle, we can support $pkgkey, but only -- if the configure step succeeds. So add a Maybe field -- to the build report, and either use that or make up -- a fake identifier if it's not available.@@ -130,7 +132,7 @@ -> InstallPlan.PlanPackage -> Maybe (BuildReport, Maybe Repo) fromPlanPackage (Platform arch os) comp planPackage = case planPackage of- InstallPlan.Installed (ReadyPackage (ConfiguredPackage srcPkg flags _ _) deps)+ InstallPlan.Installed (ReadyPackage (ConfiguredPackage srcPkg flags _ deps)) _ result -> Just $ ( BuildReport.new os arch comp (packageId srcPkg) flags
cabal/cabal-install/Distribution/Client/BuildReports/Types.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE DeriveGeneric #-} ----------------------------------------------------------------------------- -- | -- Module : Distribution.Client.BuildReports.Types@@ -24,9 +25,14 @@ import Data.Char as Char ( isAlpha, toLower )+import GHC.Generics (Generic)+import Distribution.Compat.Binary (Binary) + data ReportLevel = NoReports | AnonymousReports | DetailedReports- deriving (Eq, Ord, Show)+ deriving (Eq, Ord, Enum, Show, Generic)++instance Binary ReportLevel instance Text.Text ReportLevel where disp NoReports = Disp.text "none"
cabal/cabal-install/Distribution/Client/BuildReports/Upload.hs view
@@ -27,22 +27,24 @@ import Distribution.Verbosity (Verbosity) import Distribution.Simple.Utils (die) import Distribution.Client.HttpUtils+import Distribution.Client.Setup+ ( RepoContext(..) ) type BuildReportId = URI type BuildLog = String -uploadReports :: Verbosity -> (String, String) -> URI -> [(BuildReport, Maybe BuildLog)] -> IO ()-uploadReports verbosity auth uri reports = do+uploadReports :: Verbosity -> RepoContext -> (String, String) -> URI -> [(BuildReport, Maybe BuildLog)] -> IO ()+uploadReports verbosity repoCtxt auth uri reports = do forM_ reports $ \(report, mbBuildLog) -> do- buildId <- postBuildReport verbosity auth uri report+ buildId <- postBuildReport verbosity repoCtxt auth uri report case mbBuildLog of- Just buildLog -> putBuildLog verbosity auth buildId buildLog+ Just buildLog -> putBuildLog verbosity repoCtxt auth buildId buildLog Nothing -> return () -postBuildReport :: Verbosity -> (String, String) -> URI -> BuildReport -> IO BuildReportId-postBuildReport verbosity auth uri buildReport = do+postBuildReport :: Verbosity -> RepoContext -> (String, String) -> URI -> BuildReport -> IO BuildReportId+postBuildReport verbosity repoCtxt auth uri buildReport = do let fullURI = uri { uriPath = "/package" </> display (BuildReport.package buildReport) </> "reports" }- transport <- configureTransport verbosity Nothing+ transport <- repoContextGetTransport repoCtxt res <- postHttp transport verbosity fullURI (BuildReport.show buildReport) (Just auth) case res of (303, redir) -> return $ undefined redir --TODO parse redir@@ -78,12 +80,12 @@ -- TODO force this to be a PUT? -putBuildLog :: Verbosity -> (String, String)+putBuildLog :: Verbosity -> RepoContext -> (String, String) -> BuildReportId -> BuildLog -> IO ()-putBuildLog verbosity auth reportId buildLog = do+putBuildLog verbosity repoCtxt auth reportId buildLog = do let fullURI = reportId {uriPath = uriPath reportId </> "log"}- transport <- configureTransport verbosity Nothing+ transport <- repoContextGetTransport repoCtxt res <- postHttp transport verbosity fullURI buildLog (Just auth) case res of (200, _) -> return ()
cabal/cabal-install/Distribution/Client/Compat/Time.hs view
@@ -1,74 +1,69 @@-{-# LANGUAGE CPP, ForeignFunctionInterface #-}+{-# LANGUAGE CPP, ForeignFunctionInterface, GeneralizedNewtypeDeriving #-} module Distribution.Client.Compat.Time- (EpochTime, getModTime, getFileAge, getCurTime)+ ( ModTime(..) -- Needed for testing+ , getModTime, getFileAge, getCurTime+ , posixSecondsToModTime ) where -import Data.Int (Int64)-import System.Directory (getModificationTime)+import Control.Arrow ( first )+import Data.Int ( Int64 )+import Data.Word ( Word64 )+import System.Directory ( getModificationTime ) +import Distribution.Compat.Binary ( Binary )++import Data.Time.Clock.POSIX ( POSIXTime, getPOSIXTime ) #if MIN_VERSION_directory(1,2,0)-import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds, posixDayLength)-import Data.Time (getCurrentTime, diffUTCTime)+import Data.Time.Clock.POSIX ( posixDayLength )+import Data.Time ( diffUTCTime, getCurrentTime ) #else-import System.Time (ClockTime(..), getClockTime- ,diffClockTimes, normalizeTimeDiff, tdDay, tdHour)+import System.Time ( getClockTime, diffClockTimes+ , normalizeTimeDiff, tdDay, tdHour ) #endif #if defined mingw32_HOST_OS +import Data.Bits ((.|.), unsafeShiftL) #if MIN_VERSION_base(4,7,0)-import Data.Bits ((.|.), finiteBitSize, unsafeShiftL)-#else-import Data.Bits ((.|.), bitSize, unsafeShiftL)-#endif-import Data.Int (Int32)-import Data.Word (Word64)-import Foreign (allocaBytes, peekByteOff)-import System.IO.Error (mkIOError, doesNotExistErrorType)-import System.Win32.Types (BOOL, DWORD, LPCTSTR, LPVOID, withTString)--#ifdef x86_64_HOST_ARCH-#define CALLCONV ccall+import Data.Bits (finiteBitSize) #else-#define CALLCONV stdcall+import Data.Bits (bitSize) #endif -foreign import CALLCONV "windows.h GetFileAttributesExW"- c_getFileAttributesEx :: LPCTSTR -> Int32 -> LPVOID -> IO BOOL--getFileAttributesEx :: String -> LPVOID -> IO BOOL-getFileAttributesEx path lpFileInformation =- withTString path $ \c_path ->- c_getFileAttributesEx c_path getFileExInfoStandard lpFileInformation--getFileExInfoStandard :: Int32-getFileExInfoStandard = 0--size_WIN32_FILE_ATTRIBUTE_DATA :: Int-size_WIN32_FILE_ATTRIBUTE_DATA = 36+import Data.Int ( Int32 )+import Foreign ( allocaBytes, peekByteOff )+import System.IO.Error ( mkIOError, doesNotExistErrorType )+import System.Win32.Types ( BOOL, DWORD, LPCTSTR, LPVOID, withTString ) -index_WIN32_FILE_ATTRIBUTE_DATA_ftLastWriteTime_dwLowDateTime :: Int-index_WIN32_FILE_ATTRIBUTE_DATA_ftLastWriteTime_dwLowDateTime = 20+#else -index_WIN32_FILE_ATTRIBUTE_DATA_ftLastWriteTime_dwHighDateTime :: Int-index_WIN32_FILE_ATTRIBUTE_DATA_ftLastWriteTime_dwHighDateTime = 24+import System.Posix.Files ( FileStatus, getFileStatus ) +#if MIN_VERSION_unix(2,6,0)+import System.Posix.Files ( modificationTimeHiRes ) #else--import Foreign.C.Types (CTime(..))-import System.Posix.Files (getFileStatus, modificationTime)+import System.Posix.Files ( modificationTime )+#endif #endif --- | The number of seconds since the UNIX epoch.-type EpochTime = Int64+-- | An opaque type representing a file's modification time, represented+-- internally as a 64-bit unsigned integer in the Windows UTC format.+newtype ModTime = ModTime Word64+ deriving (Binary, Bounded, Eq, Ord) --- | Return modification time of given file. Works around the low clock+instance Show ModTime where+ show (ModTime x) = show x++instance Read ModTime where+ readsPrec p str = map (first ModTime) (readsPrec p str)++-- | Return modification time of the given file. Works around the low clock -- resolution problem that 'getModificationTime' has on GHC < 7.8. ----- This is a modified version of the code originally written for OpenShake by--- Neil Mitchell. See module Development.Shake.FileTime.-getModTime :: FilePath -> IO EpochTime+-- This is a modified version of the code originally written for Shake by Neil+-- Mitchell. See module Development.Shake.FileInfo.+getModTime :: FilePath -> IO ModTime #if defined mingw32_HOST_OS @@ -86,39 +81,74 @@ index_WIN32_FILE_ATTRIBUTE_DATA_ftLastWriteTime_dwLowDateTime dwHigh <- peekByteOff info index_WIN32_FILE_ATTRIBUTE_DATA_ftLastWriteTime_dwHighDateTime- return $! windowsTimeToPOSIXSeconds dwLow dwHigh- where- windowsTimeToPOSIXSeconds :: DWORD -> DWORD -> EpochTime- windowsTimeToPOSIXSeconds dwLow dwHigh =- let wINDOWS_TICK = 10000000- sEC_TO_UNIX_EPOCH = 11644473600 #if MIN_VERSION_base(4,7,0)- qwTime = (fromIntegral dwHigh `unsafeShiftL` finiteBitSize dwHigh)- .|. (fromIntegral dwLow)+ let qwTime =+ (fromIntegral (dwHigh :: DWORD) `unsafeShiftL` finiteBitSize dwHigh)+ .|. (fromIntegral (dwLow :: DWORD)) #else- qwTime = (fromIntegral dwHigh `unsafeShiftL` bitSize dwHigh)- .|. (fromIntegral dwLow)+ let qwTime =+ (fromIntegral (dwHigh :: DWORD) `unsafeShiftL` bitSize dwHigh)+ .|. (fromIntegral (dwLow :: DWORD)) #endif- res = ((qwTime :: Word64) `div` wINDOWS_TICK)- - sEC_TO_UNIX_EPOCH- -- TODO: What if the result is not representable as POSIX seconds?- -- Probably fine to return garbage.- in fromIntegral res+ return $! ModTime (qwTime :: Word64)++#ifdef x86_64_HOST_ARCH+#define CALLCONV ccall #else+#define CALLCONV stdcall+#endif +foreign import CALLCONV "windows.h GetFileAttributesExW"+ c_getFileAttributesEx :: LPCTSTR -> Int32 -> LPVOID -> IO BOOL++getFileAttributesEx :: String -> LPVOID -> IO BOOL+getFileAttributesEx path lpFileInformation =+ withTString path $ \c_path ->+ c_getFileAttributesEx c_path getFileExInfoStandard lpFileInformation++getFileExInfoStandard :: Int32+getFileExInfoStandard = 0++size_WIN32_FILE_ATTRIBUTE_DATA :: Int+size_WIN32_FILE_ATTRIBUTE_DATA = 36++index_WIN32_FILE_ATTRIBUTE_DATA_ftLastWriteTime_dwLowDateTime :: Int+index_WIN32_FILE_ATTRIBUTE_DATA_ftLastWriteTime_dwLowDateTime = 20++index_WIN32_FILE_ATTRIBUTE_DATA_ftLastWriteTime_dwHighDateTime :: Int+index_WIN32_FILE_ATTRIBUTE_DATA_ftLastWriteTime_dwHighDateTime = 24++#else+ -- Directly against the unix library. getModTime path = do- -- CTime is Int32 in base 4.5, Int64 in base >= 4.6, and an abstract type in- -- base < 4.5.- t <- fmap modificationTime $ getFileStatus path-#if MIN_VERSION_base(4,5,0)- let CTime i = t- return (fromIntegral i)+ st <- getFileStatus path+ return $! (extractFileTime st)++extractFileTime :: FileStatus -> ModTime+#if MIN_VERSION_unix(2,6,0)+extractFileTime x = posixTimeToModTime (modificationTimeHiRes x) #else- return (read . show $ t)+extractFileTime x = posixSecondsToModTime $ fromIntegral $ fromEnum $+ modificationTime x #endif+ #endif +windowsTick, secToUnixEpoch :: Word64+windowsTick = 10000000+secToUnixEpoch = 11644473600++-- | Convert POSIX seconds to ModTime.+posixSecondsToModTime :: Int64 -> ModTime+posixSecondsToModTime s =+ ModTime $ ((fromIntegral s :: Word64) + secToUnixEpoch) * windowsTick++-- | Convert 'POSIXTime' to 'ModTime'.+posixTimeToModTime :: POSIXTime -> ModTime+posixTimeToModTime p = ModTime $ (ceiling $ p * 1e7) -- 100 ns precision+ + (secToUnixEpoch * windowsTick)+ -- | Return age of given file in days. getFileAge :: FilePath -> IO Double getFileAge file = do@@ -132,11 +162,6 @@ return $ fromIntegral ((24 * tdDay dt) + tdHour dt) / 24.0 #endif -getCurTime :: IO EpochTime-getCurTime = do-#if MIN_VERSION_directory(1,2,0)- (truncate . utcTimeToPOSIXSeconds) `fmap` getCurrentTime-#else- (TOD s _) <- getClockTime- return $! fromIntegral s-#endif+-- | Return the current time as 'ModTime'.+getCurTime :: IO ModTime+getCurTime = posixTimeToModTime `fmap` getPOSIXTime -- Uses 'gettimeofday'.
cabal/cabal-install/Distribution/Client/ComponentDeps.hs view
@@ -10,6 +10,7 @@ -- > import qualified Distribution.Client.ComponentDeps as CD {-# LANGUAGE CPP #-} {-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveGeneric #-} module Distribution.Client.ComponentDeps ( -- * Fine-grained package dependencies Component(..)@@ -20,6 +21,7 @@ , fromList , singleton , insert+ , filterDeps , fromLibraryDeps , fromSetupDeps , fromInstalled@@ -34,6 +36,9 @@ import Data.Map (Map) import qualified Data.Map as Map+import Distribution.Compat.Binary (Binary)+import Distribution.Compat.Semigroup (Semigroup((<>)))+import GHC.Generics import Data.Foldable (fold) #if !MIN_VERSION_base(4,8,0)@@ -46,34 +51,44 @@ Types -------------------------------------------------------------------------------} --- | Component of a package+-- | Component of a package. data Component =- ComponentLib+ ComponentLib String | ComponentExe String | ComponentTest String | ComponentBench String | ComponentSetup- deriving (Show, Eq, Ord)+ deriving (Show, Eq, Ord, Generic) --- | Dependency for a single component+instance Binary Component++-- | Dependency for a single component. type ComponentDep a = (Component, a) --- | Fine-grained dependencies for a package+-- | Fine-grained dependencies for a package.+--+-- Typically used as @ComponentDeps [Dependency]@, to represent the list of+-- dependencies for each named component within a package.+-- newtype ComponentDeps a = ComponentDeps { unComponentDeps :: Map Component a }- deriving (Show, Functor, Eq, Ord)+ deriving (Show, Functor, Eq, Ord, Generic) -instance Monoid a => Monoid (ComponentDeps a) where- mempty =- ComponentDeps Map.empty- (ComponentDeps d) `mappend` (ComponentDeps d') =- ComponentDeps (Map.unionWith mappend d d')+instance Semigroup a => Monoid (ComponentDeps a) where+ mempty = ComponentDeps Map.empty+ mappend = (<>) +instance Semigroup a => Semigroup (ComponentDeps a) where+ ComponentDeps d <> ComponentDeps d' =+ ComponentDeps (Map.unionWith (<>) d d')+ instance Foldable ComponentDeps where foldMap f = foldMap f . unComponentDeps instance Traversable ComponentDeps where traverse f = fmap ComponentDeps . traverse f . unComponentDeps +instance Binary a => Binary (ComponentDeps a)+ {------------------------------------------------------------------------------- Construction -------------------------------------------------------------------------------}@@ -93,18 +108,22 @@ aux Nothing = Just a aux (Just a') = Just $ a `mappend` a' +-- | Keep only selected components (and their associated deps info).+filterDeps :: (Component -> a -> Bool) -> ComponentDeps a -> ComponentDeps a+filterDeps p = ComponentDeps . Map.filterWithKey p . unComponentDeps+ -- | ComponentDeps containing library dependencies only-fromLibraryDeps :: a -> ComponentDeps a-fromLibraryDeps = singleton ComponentLib+fromLibraryDeps :: String -> a -> ComponentDeps a+fromLibraryDeps n = singleton (ComponentLib n) --- | ComponentDeps containing setup dependencies only+-- | ComponentDeps containing setup dependencies only. fromSetupDeps :: a -> ComponentDeps a fromSetupDeps = singleton ComponentSetup --- | ComponentDeps for installed packages+-- | ComponentDeps for installed packages. ----- We assume that installed packages only record their library dependencies-fromInstalled :: a -> ComponentDeps a+-- We assume that installed packages only record their library dependencies.+fromInstalled :: String -> a -> ComponentDeps a fromInstalled = fromLibraryDeps {-------------------------------------------------------------------------------@@ -114,7 +133,7 @@ toList :: ComponentDeps a -> [ComponentDep a] toList = Map.toList . unComponentDeps --- | All dependencies of a package+-- | All dependencies of a package. -- -- This is just a synonym for 'fold', but perhaps a use of 'flatDeps' is more -- obvious than a use of 'fold', and moreover this avoids introducing lots of@@ -122,21 +141,22 @@ flatDeps :: Monoid a => ComponentDeps a -> a flatDeps = fold --- | All dependencies except the setup dependencies+-- | All dependencies except the setup dependencies. ----- Prior to the introduction of setup dependencies (TODO: Version? 1.23) this--- would have been _all_ dependencies+-- Prior to the introduction of setup dependencies in version 1.24 this+-- would have been _all_ dependencies. nonSetupDeps :: Monoid a => ComponentDeps a -> a nonSetupDeps = select (/= ComponentSetup) --- | Library dependencies proper only+-- | Library dependencies proper only. libraryDeps :: Monoid a => ComponentDeps a -> a-libraryDeps = select (== ComponentLib)+libraryDeps = select (\c -> case c of ComponentLib _ -> True+ _ -> False) --- | Setup dependencies+-- | Setup dependencies. setupDeps :: Monoid a => ComponentDeps a -> a setupDeps = select (== ComponentSetup) --- | Select dependencies satisfying a given predicate+-- | Select dependencies satisfying a given predicate. select :: Monoid a => (Component -> Bool) -> ComponentDeps a -> a select p = foldMap snd . filter (p . fst) . toList
cabal/cabal-install/Distribution/Client/Config.hs view
@@ -1,4 +1,5 @@-{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE NoMonoLocalBinds #-} ----------------------------------------------------------------------------- -- |@@ -16,6 +17,7 @@ module Distribution.Client.Config ( SavedConfig(..), loadConfig,+ getConfigFilePath, showConfig, showConfigWithComments,@@ -37,7 +39,10 @@ withProgramsFields, withProgramOptionsFields, userConfigDiff,- userConfigUpdate+ userConfigUpdate,+ createDefaultConfigFile,++ remoteRepoFields ) where import Distribution.Client.Types@@ -60,6 +65,7 @@ ( DebugInfoLevel(..), OptimisationLevel(..) ) import Distribution.Simple.Setup ( ConfigFlags(..), configureOptions, defaultConfigFlags+ , AllowNewer(..) , HaddockFlags(..), haddockOptions, defaultHaddockFlags , installDirsOptions, optionDistPref , programConfigurationPaths', programConfigurationOptions@@ -73,9 +79,11 @@ , locatedErrorMsg, showPWarning , readFields, warning, lineNo , simpleField, listField, spaceListField- , parseFilePathQ, parseTokenQ )+ , parseFilePathQ, parseOptCommaList, parseTokenQ ) import Distribution.Client.ParseUtils ( parseFields, ppFields, ppSection )+import Distribution.Client.HttpUtils+ ( isOldHackageURI ) import qualified Distribution.ParseUtils as ParseUtils ( Field(..) ) import qualified Distribution.Text as Text@@ -96,14 +104,11 @@ ( partition, find, foldl' ) import Data.Maybe ( fromMaybe )-#if !MIN_VERSION_base(4,8,0)-import Data.Monoid- ( Monoid(..) )-#endif import Control.Monad- ( unless, foldM, liftM, liftM2 )+ ( when, unless, foldM, liftM, liftM2 ) import qualified Distribution.Compat.ReadP as Parse- ( option )+ ( (<++), option )+import Distribution.Compat.Semigroup import qualified Text.PrettyPrint as Disp ( render, text, empty ) import Text.PrettyPrint@@ -133,6 +138,7 @@ ( on ) import Data.List ( nubBy )+import GHC.Generics ( Generic ) -- -- * Configuration saved in the config file@@ -148,21 +154,14 @@ savedUploadFlags :: UploadFlags, savedReportFlags :: ReportFlags, savedHaddockFlags :: HaddockFlags- }+ } deriving Generic instance Monoid SavedConfig where- mempty = SavedConfig {- savedGlobalFlags = mempty,- savedInstallFlags = mempty,- savedConfigureFlags = mempty,- savedConfigureExFlags = mempty,- savedUserInstallDirs = mempty,- savedGlobalInstallDirs = mempty,- savedUploadFlags = mempty,- savedReportFlags = mempty,- savedHaddockFlags = mempty- }- mappend a b = SavedConfig {+ mempty = gmempty+ mappend = (<>)++instance Semigroup SavedConfig where+ a <> b = SavedConfig { savedGlobalFlags = combinedSavedGlobalFlags, savedInstallFlags = combinedSavedInstallFlags, savedConfigureFlags = combinedSavedConfigureFlags,@@ -195,6 +194,11 @@ combine' field subfield = (subfield . field $ a) `mappend` (subfield . field $ b) + combineMonoid :: Monoid mon => (SavedConfig -> flags) -> (flags -> mon)+ -> mon+ combineMonoid field subfield =+ (subfield . field $ a) `mappend` (subfield . field $ b)+ lastNonEmpty' :: (SavedConfig -> flags) -> (flags -> [a]) -> [a] lastNonEmpty' field subfield = let a' = subfield . field $ a@@ -215,6 +219,7 @@ globalNumericVersion = combine globalNumericVersion, globalConfigFile = combine globalConfigFile, globalSandboxConfigFile = combine globalSandboxConfigFile,+ globalConstraintsFile = combine globalConstraintsFile, globalRemoteRepos = lastNonEmptyNL globalRemoteRepos, globalCacheDir = combine globalCacheDir, globalLocalRepos = lastNonEmptyNL globalLocalRepos,@@ -222,6 +227,7 @@ globalWorldFile = combine globalWorldFile, globalRequireSandbox = combine globalRequireSandbox, globalIgnoreSandbox = combine globalIgnoreSandbox,+ globalIgnoreExpiry = combine globalIgnoreExpiry, globalHttpTransport = combine globalHttpTransport } where@@ -259,7 +265,7 @@ lastNonEmptyNL = lastNonEmptyNL' savedInstallFlags combinedSavedConfigureFlags = ConfigFlags {- configPrograms = configPrograms . savedConfigureFlags $ b,+ configPrograms_ = configPrograms_ . savedConfigureFlags $ b, -- TODO: NubListify configProgramPaths = lastNonEmpty configProgramPaths, -- TODO: NubListify@@ -290,7 +296,10 @@ -- TODO: NubListify configExtraLibDirs = lastNonEmpty configExtraLibDirs, -- TODO: NubListify+ configExtraFrameworkDirs = lastNonEmpty configExtraFrameworkDirs,+ -- TODO: NubListify configExtraIncludeDirs = lastNonEmpty configExtraIncludeDirs,+ configIPID = combine configIPID, configDistPref = combine configDistPref, configVerbosity = combine configVerbosity, configUserInstall = combine configUserInstall,@@ -304,7 +313,6 @@ configConstraints = lastNonEmpty configConstraints, -- TODO: NubListify configDependencies = lastNonEmpty configDependencies,- configInstantiateWith = lastNonEmpty configInstantiateWith, -- TODO: NubListify configConfigurationsFlags = lastNonEmpty configConfigurationsFlags, configTests = combine configTests,@@ -313,7 +321,9 @@ configLibCoverage = combine configLibCoverage, configExactConfiguration = combine configExactConfiguration, configFlagError = combine configFlagError,- configRelocatable = combine configRelocatable+ configRelocatable = combine configRelocatable,+ configAllowNewer = combineMonoid savedConfigureFlags+ configAllowNewer } where combine = combine' savedConfigureFlags@@ -326,8 +336,7 @@ configExConstraints = lastNonEmpty configExConstraints, -- TODO: NubListify configPreferences = lastNonEmpty configPreferences,- configSolver = combine configSolver,- configAllowNewer = combine configAllowNewer+ configSolver = combine configSolver } where combine = combine' savedConfigureExFlags@@ -343,6 +352,7 @@ combinedSavedUploadFlags = UploadFlags { uploadCheck = combine uploadCheck,+ uploadDoc = combine uploadDoc, uploadUsername = combine uploadUsername, uploadPassword = combine uploadPassword, uploadPasswordCmd = combine uploadPasswordCmd,@@ -367,6 +377,7 @@ haddockHoogle = combine haddockHoogle, haddockHtml = combine haddockHtml, haddockHtmlLocation = combine haddockHtmlLocation,+ haddockForHackage = combine haddockForHackage, haddockExecutables = combine haddockExecutables, haddockTestSuites = combine haddockTestSuites, haddockBenchmarks = combine haddockBenchmarks,@@ -481,7 +492,7 @@ -- global installs on Windows but that no longer works on Windows Vista or 7. defaultRemoteRepo :: RemoteRepo-defaultRemoteRepo = RemoteRepo name uri () False+defaultRemoteRepo = RemoteRepo name uri Nothing [] 0 False where name = "hackage.haskell.org" uri = URI "http:" (Just (URIAuth "" name "")) "/" "" ""@@ -490,10 +501,6 @@ -- but new config files can use the new url (without the /packages/archive) -- and avoid having to do a http redirect - -- Use this as a source for crypto credentials when finding old remote-repo- -- entries that match repo name and url (not only be used for generating- -- fresh config files).- -- For the default repo we know extra information, fill this in. -- -- We need this because the 'defaultRemoteRepo' above is only used for the@@ -501,12 +508,15 @@ -- we might have only have older info. This lets us fill that in even for old -- config files. --+-- TODO: Once we migrate from opt-in to opt-out security for the central+-- Hackage repository, we should enable security and specify keys and threshold+-- for repositories that have their security setting as 'Nothing' (default). addInfoForKnownRepos :: RemoteRepo -> RemoteRepo addInfoForKnownRepos repo@RemoteRepo{ remoteRepoName = "hackage.haskell.org" } =- repo {- --remoteRepoRootKeys --TODO: when this list is empty, fill in known crypto credentials- remoteRepoShouldTryHttps = True- }+ tryHttps+ $ if isOldHackageURI (remoteRepoURI repo) then defaultRemoteRepo else repo+ where+ tryHttps r = r { remoteRepoShouldTryHttps = True } addInfoForKnownRepos other = other --@@ -515,26 +525,14 @@ loadConfig :: Verbosity -> Flag FilePath -> IO SavedConfig loadConfig verbosity configFileFlag = addBaseConf $ do- let sources = [- ("commandline option", return . flagToMaybe $ configFileFlag),- ("env var CABAL_CONFIG", lookup "CABAL_CONFIG" `liftM` getEnvironment),- ("default config file", Just `liftM` defaultConfigFile) ]-- getSource [] = error "no config file path candidate found."- getSource ((msg,action): xs) =- action >>= maybe (getSource xs) (return . (,) msg)-- (source, configFile) <- getSource sources+ (source, configFile) <- getConfigFilePathAndSource configFileFlag minp <- readConfigFile mempty configFile case minp of Nothing -> do- notice verbosity $ "Config file path source is " ++ source ++ "."+ notice verbosity $ "Config file path source is " ++ sourceMsg source ++ "." notice verbosity $ "Config file " ++ configFile ++ " not found."- notice verbosity $ "Writing default configuration to " ++ configFile- commentConf <- commentSavedConfig- initialConf <- initialSavedConfig- writeConfigFile configFile commentConf initialConf- return initialConf+ createDefaultConfigFile verbosity configFile+ loadConfig verbosity configFileFlag Just (ParseOk ws conf) -> do unless (null ws) $ warn verbosity $ unlines (map (showPWarning configFile) ws)@@ -551,6 +549,32 @@ extra <- body return (base `mappend` extra) + sourceMsg CommandlineOption = "commandline option"+ sourceMsg EnvironmentVariable = "env var CABAL_CONFIG"+ sourceMsg Default = "default config file"++data ConfigFileSource = CommandlineOption+ | EnvironmentVariable+ | Default++-- | Returns the config file path, without checking that the file exists.+-- The order of precedence is: input flag, CABAL_CONFIG, default location.+getConfigFilePath :: Flag FilePath -> IO FilePath+getConfigFilePath = fmap snd . getConfigFilePathAndSource++getConfigFilePathAndSource :: Flag FilePath -> IO (ConfigFileSource, FilePath)+getConfigFilePathAndSource configFileFlag =+ getSource sources+ where+ sources =+ [ (CommandlineOption, return . flagToMaybe $ configFileFlag)+ , (EnvironmentVariable, lookup "CABAL_CONFIG" `liftM` getEnvironment)+ , (Default, Just `liftM` defaultConfigFile) ]++ getSource [] = error "no config file path candidate found."+ getSource ((source,action): xs) =+ action >>= maybe (getSource xs) (return . (,) source)+ readConfigFile :: SavedConfig -> FilePath -> IO (Maybe (ParseResult SavedConfig)) readConfigFile initial file = handleNotExists $ fmap (Just . parseConfig (ConstraintSourceMainConfig file) initial)@@ -562,6 +586,13 @@ then return Nothing else ioError ioe +createDefaultConfigFile :: Verbosity -> FilePath -> IO ()+createDefaultConfigFile verbosity filePath = do+ commentConf <- commentSavedConfig+ initialConf <- initialSavedConfig+ notice verbosity $ "Writing default configuration to " ++ filePath+ writeConfigFile filePath commentConf initialConf+ writeConfigFile :: FilePath -> SavedConfig -> SavedConfig -> IO () writeConfigFile file comments vals = do let tmpFile = file <.> "tmp"@@ -598,7 +629,8 @@ savedInstallFlags = defaultInstallFlags, savedConfigureExFlags = defaultConfigExFlags, savedConfigureFlags = (defaultConfigFlags defaultProgramConfiguration) {- configUserInstall = toFlag defaultUserInstall+ configUserInstall = toFlag defaultUserInstall,+ configAllowNewer = Just AllowNewerNone }, savedUserInstallDirs = fmap toFlag userInstallDirs, savedGlobalInstallDirs = fmap toFlag globalInstallDirs,@@ -618,21 +650,34 @@ ++ toSavedConfig liftConfigFlag (configureOptions ParseArgs)- (["builddir", "constraint", "dependency"]+ (["builddir", "constraint", "dependency", "ipid"] ++ map fieldName installDirsFields) - --FIXME: this is only here because viewAsFieldDescr gives us a parser+ -- This is only here because viewAsFieldDescr gives us a parser -- that only recognises 'ghc' etc, the case-sensitive flag names, not -- what the normal case-insensitive parser gives us. [simpleField "compiler" (fromFlagOrDefault Disp.empty . fmap Text.disp) (optional Text.parse) configHcFlavor (\v flags -> flags { configHcFlavor = v })+ ,let showAllowNewer Nothing = mempty+ showAllowNewer (Just AllowNewerNone) = Disp.text "False"+ showAllowNewer (Just _) = Disp.text "True"++ toAllowNewer True = Just AllowNewerAll+ toAllowNewer False = Just AllowNewerNone++ pkgs = (Just . AllowNewerSome) `fmap` parseOptCommaList Text.parse+ parseAllowNewer = (toAllowNewer `fmap` Text.parse) Parse.<++ pkgs in+ simpleField "allow-newer"+ showAllowNewer parseAllowNewer+ configAllowNewer (\v flags -> flags { configAllowNewer = v }) -- TODO: The following is a temporary fix. The "optimization" -- and "debug-info" fields are OptArg, and viewAsFieldDescr -- fails on that. Instead of a hand-written hackaged parser -- and printer, we should handle this case properly in the -- library.- ,liftField configOptimization (\v flags -> flags { configOptimization = v }) $+ ,liftField configOptimization (\v flags ->+ flags { configOptimization = v }) $ let name = "optimization" in FieldDescr name (\f -> case f of@@ -652,7 +697,8 @@ where lstr = lowercase str caseWarning = PWarning $- "The '" ++ name ++ "' field is case sensitive, use 'True' or 'False'.")+ "The '" ++ name+ ++ "' field is case sensitive, use 'True' or 'False'.") ,liftField configDebugInfo (\v flags -> flags { configDebugInfo = v }) $ let name = "debug-info" in FieldDescr name@@ -675,7 +721,8 @@ where lstr = lowercase str caseWarning = PWarning $- "The '" ++ name ++ "' field is case sensitive, use 'True' or 'False'.")+ "The '" ++ name+ ++ "' field is case sensitive, use 'True' or 'False'.") ] ++ toSavedConfig liftConfigExFlag@@ -688,7 +735,7 @@ ++ toSavedConfig liftUploadFlag (commandOptions uploadCommand ParseArgs)- ["verbose", "check"] []+ ["verbose", "check", "documentation"] [] ++ toSavedConfig liftReportFlag (commandOptions reportCommand ParseArgs)@@ -703,8 +750,10 @@ (configDistPref . savedConfigureFlags) (\distPref config -> config- { savedConfigureFlags = (savedConfigureFlags config) { configDistPref = distPref }- , savedHaddockFlags = (savedHaddockFlags config) { haddockDistPref = distPref }+ { savedConfigureFlags = (savedConfigureFlags config) {+ configDistPref = distPref }+ , savedHaddockFlags = (savedHaddockFlags config) {+ haddockDistPref = distPref } } ) ParseArgs@@ -825,7 +874,7 @@ } where- isKnownSection (ParseUtils.Section _ "remote-repo" _ _) = True+ isKnownSection (ParseUtils.Section _ "repository" _ _) = True isKnownSection (ParseUtils.F _ "remote-repo" _) = True isKnownSection (ParseUtils.Section _ "haddock" _ _) = True isKnownSection (ParseUtils.Section _ "install-dirs" _ _) = True@@ -837,8 +886,15 @@ ++ deprecatedFieldDescriptions) initial parseSections (rs, h, u, g, p, a)- (ParseUtils.Section _ "remote-repo" name fs) = do+ (ParseUtils.Section _ "repository" name fs) = do r' <- parseFields remoteRepoFields (emptyRemoteRepo name) fs+ when (remoteRepoKeyThreshold r' > length (remoteRepoRootKeys r')) $+ warning $ "'key-threshold' for repository " ++ show (remoteRepoName r')+ ++ " higher than number of keys"+ when (not (null (remoteRepoRootKeys r'))+ && remoteRepoSecure r' /= Just True) $+ warning $ "'root-keys' for repository " ++ show (remoteRepoName r')+ ++ " non-empty, but 'secure' not set to True." return (r':rs, h, u, g, p, a) parseSections (rs, h, u, g, p, a)@@ -887,7 +943,8 @@ showConfigWithComments :: SavedConfig -> SavedConfig -> String showConfigWithComments comment vals = Disp.render $- case fmap ppRemoteRepoSection . fromNubList . globalRemoteRepos . savedGlobalFlags $ vals of+ case fmap ppRemoteRepoSection . fromNubList . globalRemoteRepos+ . savedGlobalFlags $ vals of [] -> Disp.text "" (x:xs) -> foldl' (\ r r' -> r $+$ Disp.text "" $+$ r') x xs $+$ Disp.text ""@@ -925,20 +982,43 @@ installDirsFields = map viewAsFieldDescr installDirsOptions ppRemoteRepoSection :: RemoteRepo -> Doc-ppRemoteRepoSection vals = ppSection "remote-repo" (remoteRepoName vals)- remoteRepoFields Nothing vals+ppRemoteRepoSection vals = ppSection "repository" (remoteRepoName vals)+ remoteRepoFields def vals+ where+ def = Just (emptyRemoteRepo "ignored") { remoteRepoSecure = Just False } remoteRepoFields :: [FieldDescr RemoteRepo] remoteRepoFields =- [ FieldDescr { fieldName = "url",- fieldGet = text . show . remoteRepoURI,- fieldSet = \ _ uriString remoteRepo -> maybe- (fail $ "remote-repo: no parse on " ++ show uriString)- (\ uri -> return $ remoteRepo { remoteRepoURI = uri })- (parseURI uriString)- }+ [ simpleField "url"+ (text . show) (parseTokenQ >>= parseURI')+ remoteRepoURI (\x repo -> repo { remoteRepoURI = x })+ , simpleField "secure"+ showSecure (Just `fmap` Text.parse)+ remoteRepoSecure (\x repo -> repo { remoteRepoSecure = x })+ , listField "root-keys"+ text parseTokenQ+ remoteRepoRootKeys (\x repo -> repo { remoteRepoRootKeys = x })+ , simpleField "key-threshold"+ showThreshold Text.parse+ remoteRepoKeyThreshold (\x repo -> repo { remoteRepoKeyThreshold = x }) ]+ where+ parseURI' uriString =+ case parseURI uriString of+ Nothing -> fail $ "remote-repo: no parse on " ++ show uriString+ Just uri -> return uri + showSecure Nothing = mempty -- default 'secure' setting+ showSecure (Just True) = text "True" -- user explicitly enabled it+ showSecure (Just False) = text "False" -- user explicitly disabled it++ -- If the key-threshold is set to 0, we omit it as this is the default+ -- and it looks odd to have a value for key-threshold but not for 'secure'+ -- (note that an empty list of keys is already omitted by default, since+ -- that is what we do for all list fields)+ showThreshold 0 = mempty+ showThreshold t = text (show t)+ -- | Fields for the 'haddock' section. haddockFlagsFields :: [FieldDescr HaddockFlags] haddockFlagsFields = [ field@@ -978,12 +1058,14 @@ combine (Nothing, Just b) (Just a, Nothing) = (Just a, Just b) combine (Just a, Nothing) (Nothing, Just b) = (Just a, Just b)- combine x y = error $ "Can't happen : userConfigDiff " ++ show x ++ " " ++ show y+ combine x y = error $ "Can't happen : userConfigDiff "+ ++ show x ++ " " ++ show y createDiff :: [String] -> (String, (Maybe String, Maybe String)) -> [String] createDiff acc (key, (Just a, Just b)) | a == b = acc- | otherwise = ("+ " ++ key ++ ": " ++ b) : ("- " ++ key ++ ": " ++ a) : acc+ | otherwise = ("+ " ++ key ++ ": " ++ b)+ : ("- " ++ key ++ ": " ++ a) : acc createDiff acc (key, (Nothing, Just b)) = ("+ " ++ key ++ ": " ++ b) : acc createDiff acc (key, (Just a, Nothing)) = ("- " ++ key ++ ": " ++ a) : acc createDiff acc (_, (Nothing, Nothing)) = acc@@ -1012,7 +1094,7 @@ userConfig <- loadConfig normal (globalConfigFile globalFlags) newConfig <- liftM2 mappend baseSavedConfig initialSavedConfig commentConf <- commentSavedConfig- cabalFile <- defaultConfigFile+ cabalFile <- getConfigFilePath $ globalConfigFile globalFlags let backup = cabalFile ++ ".backup" notice verbosity $ "Renaming " ++ cabalFile ++ " to " ++ backup ++ "." renameFile cabalFile backup
cabal/cabal-install/Distribution/Client/Configure.hs view
@@ -20,15 +20,17 @@ import Distribution.Client.Dependency import Distribution.Client.Dependency.Types- ( AllowNewer(..), isAllowNewer, ConstraintSource(..)+ ( ConstraintSource(..) , LabeledPackageConstraint(..), showConstraintSource ) import qualified Distribution.Client.InstallPlan as InstallPlan import Distribution.Client.InstallPlan (InstallPlan) import Distribution.Client.IndexUtils as IndexUtils ( getSourcePackages, getInstalledPackages ) import Distribution.Client.PackageIndex ( PackageIndex, elemByPackageName )+import Distribution.Client.PkgConfigDb (PkgConfigDb, readPkgConfigDb) import Distribution.Client.Setup- ( ConfigExFlags(..), configureCommand, filterConfigureFlags )+ ( ConfigExFlags(..), configureCommand, filterConfigureFlags+ , RepoContext(..) ) import Distribution.Client.Types as Source import Distribution.Client.SetupWrapper ( setupWrapper, SetupScriptOptions(..), defaultSetupScriptOptions )@@ -42,14 +44,14 @@ ( Compiler, CompilerInfo, compilerInfo, PackageDB(..), PackageDBStack ) import Distribution.Simple.Program (ProgramConfiguration ) import Distribution.Simple.Setup- ( ConfigFlags(..), fromFlag, toFlag, flagToMaybe, fromFlagOrDefault )+ ( ConfigFlags(..), AllowNewer(..)+ , fromFlag, toFlag, flagToMaybe, fromFlagOrDefault ) import Distribution.Simple.PackageIndex ( InstalledPackageIndex, lookupPackageName ) import Distribution.Simple.Utils ( defaultPackageDesc )-import qualified Distribution.InstalledPackageInfo as Installed import Distribution.Package- ( Package(..), InstalledPackageId, packageName+ ( Package(..), UnitId, packageName , Dependency(..), thisPackageVersion ) import qualified Distribution.PackageDescription as PkgDesc@@ -60,7 +62,9 @@ import Distribution.Version ( anyVersion, thisVersion ) import Distribution.Simple.Utils as Utils- ( warn, notice, info, debug, die )+ ( warn, notice, debug, die )+import Distribution.Simple.Setup+ ( isAllowNewer ) import Distribution.System ( Platform ) import Distribution.Text ( display )@@ -77,14 +81,14 @@ -- | Choose the Cabal version such that the setup scripts compiled against this -- version will support the given command-line flags.-chooseCabalVersion :: ConfigExFlags -> Maybe Version -> VersionRange-chooseCabalVersion configExFlags maybeVersion =+chooseCabalVersion :: ConfigFlags -> Maybe Version -> VersionRange+chooseCabalVersion configFlags maybeVersion = maybe defaultVersionRange thisVersion maybeVersion where -- Cabal < 1.19.2 doesn't support '--exact-configuration' which is needed -- for '--allow-newer' to work.- allowNewer = fromFlagOrDefault False $- fmap isAllowNewer (configAllowNewer configExFlags)+ allowNewer = isAllowNewer+ (fromMaybe AllowNewerNone $ configAllowNewer configFlags) defaultVersionRange = if allowNewer then orLaterVersion (Version [1,19,2] [])@@ -93,7 +97,7 @@ -- | Configure the package found in the local directory configure :: Verbosity -> PackageDBStack- -> [Repo]+ -> RepoContext -> Compiler -> Platform -> ProgramConfiguration@@ -101,24 +105,26 @@ -> ConfigExFlags -> [String] -> IO ()-configure verbosity packageDBs repos comp platform conf+configure verbosity packageDBs repoCtxt comp platform conf configFlags configExFlags extraArgs = do installedPkgIndex <- getInstalledPackages verbosity comp packageDBs conf- sourcePkgDb <- getSourcePackages verbosity repos+ sourcePkgDb <- getSourcePackages verbosity repoCtxt+ pkgConfigDb <- readPkgConfigDb verbosity conf+ checkConfigExFlags verbosity installedPkgIndex (packageIndex sourcePkgDb) configExFlags progress <- planLocalPackage verbosity comp platform configFlags configExFlags- installedPkgIndex sourcePkgDb+ installedPkgIndex sourcePkgDb pkgConfigDb notice verbosity "Resolving dependencies..." maybePlan <- foldProgress logMsg (return . Left) (return . Right) progress case maybePlan of Left message -> do- info verbosity $- "Warning: solver failed to find a solution:\n"+ warn verbosity $+ "solver failed to find a solution:\n" ++ message ++ "Trying configure anyway." setupWrapper verbosity (setupScriptOptions installedPkgIndex Nothing)@@ -127,8 +133,7 @@ Right installPlan -> case InstallPlan.ready installPlan of [pkg@(ReadyPackage (ConfiguredPackage (SourcePackage _ _ (LocalUnpackedPackage _) _)- _ _ _)- _)] -> do+ _ _ _))] -> do configurePackage verbosity platform (compilerInfo comp) (setupScriptOptions installedPkgIndex (Just pkg))@@ -151,7 +156,7 @@ (useDistPref defaultSetupScriptOptions) (configDistPref configFlags)) (chooseCabalVersion- configExFlags+ configFlags (flagToMaybe (configCabalVersion configExFlags))) Nothing False@@ -181,6 +186,7 @@ mpkg = SetupScriptOptions { useCabalVersion = cabalVersion+ , useCabalSpecVersion = Nothing , useCompiler = Just comp , usePlatform = Just platform , usePackageDB = packageDBs'@@ -200,6 +206,7 @@ -- Therefore, for now, we just leave this blank. , useDependencies = fromMaybe [] explicitSetupDeps , useDependenciesExclusive = isJust explicitSetupDeps+ , useVersionMacros = isJust explicitSetupDeps } where -- When we are compiling a legacy setup script without an explicit@@ -217,17 +224,15 @@ -- but if the user is using an odd db stack, don't touch it _otherwise -> (packageDBs, Just index) - explicitSetupDeps :: Maybe [(InstalledPackageId, PackageId)]+ explicitSetupDeps :: Maybe [(UnitId, PackageId)] explicitSetupDeps = do- ReadyPackage (ConfiguredPackage (SourcePackage _ gpkg _ _) _ _ _) deps- <- mpkg+ ReadyPackage cpkg <- mpkg+ let gpkg = packageDescription (confPkgSource cpkg) -- Check if there is an explicit setup stanza _buildInfo <- PkgDesc.setupBuildInfo (PkgDesc.packageDescription gpkg) -- Return the setup dependencies computed by the solver- return [ ( Installed.installedPackageId deppkg- , Installed.sourcePackageId deppkg- )- | deppkg <- CD.setupDeps deps+ return [ ( uid, srcid )+ | ConfiguredId srcid uid <- CD.setupDeps (confPkgDeps cpkg) ] -- | Warn if any constraints or preferences name packages that are not in the@@ -263,10 +268,10 @@ -> ConfigFlags -> ConfigExFlags -> InstalledPackageIndex -> SourcePackageDb+ -> PkgConfigDb -> IO (Progress String String InstallPlan) planLocalPackage verbosity comp platform configFlags configExFlags- installedPkgIndex- (SourcePackageDb _ packagePrefs) = do+ installedPkgIndex (SourcePackageDb _ packagePrefs) pkgConfigDb = do pkg <- readPackageDescription verbosity =<< defaultPackageDesc verbosity solver <- chooseSolver verbosity (fromFlag $ configSolver configExFlags) (compilerInfo comp)@@ -284,8 +289,8 @@ fromFlagOrDefault False $ configBenchmarks configFlags resolverParams =- removeUpperBounds (fromFlagOrDefault AllowNewerNone $- configAllowNewer configExFlags)+ removeUpperBounds+ (fromMaybe AllowNewerNone $ configAllowNewer configFlags) . addPreferences -- preferences from the config file or command line@@ -320,7 +325,7 @@ (SourcePackageDb mempty packagePrefs) [SpecificSourcePackage localPkg] - return (resolveDependencies platform (compilerInfo comp) solver resolverParams)+ return (resolveDependencies platform (compilerInfo comp) pkgConfigDb solver resolverParams) -- | Call an installer for an 'SourcePackage' but override the configure@@ -339,25 +344,23 @@ -> [String] -> IO () configurePackage verbosity platform comp scriptOptions configFlags- (ReadyPackage (ConfiguredPackage (SourcePackage _ gpkg _ _)- flags stanzas _)- deps)+ (ReadyPackage (ConfiguredPackage spkg flags stanzas deps)) extraArgs = setupWrapper verbosity scriptOptions (Just pkg) configureCommand configureFlags extraArgs where+ gpkg = packageDescription spkg configureFlags = filterConfigureFlags configFlags { configConfigurationsFlags = flags, -- We generate the legacy constraints as well as the new style precise -- deps. In the end only one set gets passed to Setup.hs configure, -- depending on the Cabal version we are talking to.- configConstraints = [ thisPackageVersion (packageId deppkg)- | deppkg <- CD.nonSetupDeps deps ],- configDependencies = [ (packageName (Installed.sourcePackageId deppkg),- Installed.installedPackageId deppkg)- | deppkg <- CD.nonSetupDeps deps ],+ configConstraints = [ thisPackageVersion srcid+ | ConfiguredId srcid _uid <- CD.nonSetupDeps deps ],+ configDependencies = [ (packageName srcid, uid)+ | ConfiguredId srcid uid <- CD.nonSetupDeps deps ], -- Use '--exact-configuration' if supported. configExactConfiguration = toFlag True, configVerbosity = toFlag verbosity,
cabal/cabal-install/Distribution/Client/Dependency.hs view
@@ -53,10 +53,11 @@ setStrongFlags, setMaxBackjumps, addSourcePackages,- hideInstalledPackagesSpecificByInstalledPackageId,+ hideInstalledPackagesSpecificByUnitId, hideInstalledPackagesSpecificBySourcePackageId, hideInstalledPackagesAllVersions,- removeUpperBounds+ removeUpperBounds,+ addDefaultSetupDependencies, ) where import Distribution.Client.Dependency.TopDown@@ -68,15 +69,18 @@ import qualified Distribution.Simple.PackageIndex as InstalledPackageIndex import qualified Distribution.Client.InstallPlan as InstallPlan import Distribution.Client.InstallPlan (InstallPlan)+import Distribution.Client.PkgConfigDb (PkgConfigDb) import Distribution.Client.Types ( SourcePackageDb(SourcePackageDb), SourcePackage(..)- , ConfiguredPackage(..), ConfiguredId(..), enableStanzas )+ , ConfiguredPackage(..), ConfiguredId(..)+ , UnresolvedPkgLoc, UnresolvedSourcePackage+ , OptionalStanza(..), enableStanzas ) import Distribution.Client.Dependency.Types ( PreSolver(..), Solver(..), DependencyResolver, ResolverPackage(..) , PackageConstraint(..), showPackageConstraint , LabeledPackageConstraint(..), unlabelPackageConstraint , ConstraintSource(..), showConstraintSource- , AllowNewer(..), PackagePreferences(..), InstalledPreference(..)+ , PackagePreferences(..), InstalledPreference(..) , PackagesPreferenceDefault(..) , Progress(..), foldProgress ) import Distribution.Client.Sandbox.Types@@ -88,20 +92,18 @@ import Distribution.Package ( PackageName(..), PackageIdentifier(PackageIdentifier), PackageId , Package(..), packageName, packageVersion- , InstalledPackageId, Dependency(Dependency))+ , UnitId, Dependency(Dependency)) import qualified Distribution.PackageDescription as PD- ( PackageDescription(..), Library(..), Executable(..)- , TestSuite(..), Benchmark(..), SetupBuildInfo(..)- , GenericPackageDescription(..), CondTree+ ( PackageDescription(..), SetupBuildInfo(..)+ , GenericPackageDescription(..) , Flag(flagName), FlagName(..) )-import Distribution.PackageDescription (BuildInfo(targetBuildDepends)) import Distribution.PackageDescription.Configuration- ( mapCondTree, finalizePackageDescription )+ ( finalizePackageDescription ) import Distribution.Client.PackageUtils ( externalBuildDepends ) import Distribution.Version ( VersionRange, anyVersion, thisVersion, withinRange- , removeUpperBound, simplifyVersionRange )+ , simplifyVersionRange ) import Distribution.Compiler ( CompilerInfo(..) ) import Distribution.System@@ -110,13 +112,17 @@ ( duplicates, duplicatesBy, mergeBy, MergeResult(..) ) import Distribution.Simple.Utils ( comparing, warn, info )+import Distribution.Simple.Configure+ ( relaxPackageDeps )+import Distribution.Simple.Setup+ ( AllowNewer(..) ) import Distribution.Text ( display ) import Distribution.Verbosity ( Verbosity ) import Data.List- ( foldl', sort, sortBy, nubBy, maximumBy, intercalate )+ ( foldl', sort, sortBy, nubBy, maximumBy, intercalate, nub ) import Data.Function (on) import Data.Maybe (fromMaybe) import qualified Data.Map as Map@@ -140,7 +146,7 @@ depResolverPreferences :: [PackagePreference], depResolverPreferenceDefault :: PackagesPreferenceDefault, depResolverInstalledPkgIndex :: InstalledPackageIndex,- depResolverSourcePkgIndex :: PackageIndex.PackageIndex SourcePackage,+ depResolverSourcePkgIndex :: PackageIndex.PackageIndex UnresolvedSourcePackage, depResolverReorderGoals :: Bool, depResolverIndependentGoals :: Bool, depResolverAvoidReinstalls :: Bool,@@ -158,7 +164,14 @@ ++ "\npreferences: " ++ concatMap (("\n " ++) . showPackagePreference) (depResolverPreferences p)- ++ "\nstrategy: " ++ show (depResolverPreferenceDefault p)+ ++ "\nstrategy: " ++ show (depResolverPreferenceDefault p)+ ++ "\nreorder goals: " ++ show (depResolverReorderGoals p)+ ++ "\nindependent goals: " ++ show (depResolverIndependentGoals p)+ ++ "\navoid reinstalls: " ++ show (depResolverAvoidReinstalls p)+ ++ "\nshadow packages: " ++ show (depResolverShadowPkgs p)+ ++ "\nstrong flags: " ++ show (depResolverStrongFlags p)+ ++ "\nmax backjumps: " ++ maybe "infinite" show+ (depResolverMaxBackjumps p) where showLabeledConstraint :: LabeledPackageConstraint -> String showLabeledConstraint (LabeledPackageConstraint pc src) =@@ -178,6 +191,11 @@ -- | If we prefer versions of packages that are already installed. | PackageInstalledPreference PackageName InstalledPreference + -- | If we would prefer to enable these optional stanzas+ -- (i.e. test suites and/or benchmarks)+ | PackageStanzasPreference PackageName [OptionalStanza]++ -- | Provide a textual representation of a package preference -- for debugging purposes. --@@ -186,9 +204,11 @@ display pn ++ " " ++ display (simplifyVersionRange vr) showPackagePreference (PackageInstalledPreference pn ip) = display pn ++ " " ++ show ip+showPackagePreference (PackageStanzasPreference pn st) =+ display pn ++ " " ++ show st basicDepResolverParams :: InstalledPackageIndex- -> PackageIndex.PackageIndex SourcePackage+ -> PackageIndex.PackageIndex UnresolvedSourcePackage -> DepResolverParams basicDepResolverParams installedPkgIndex sourcePkgIndex = DepResolverParams {@@ -282,7 +302,7 @@ [ LabeledPackageConstraint (PackageConstraintInstalled pkgname) ConstraintSourceNonUpgradeablePackage- | all (/=PackageName "base") (depResolverTargets params)+ | notElem (PackageName "base") (depResolverTargets params) , pkgname <- map PackageName [ "base", "ghc-prim", "integer-gmp" , "integer-simple" ] , isInstalled pkgname ]@@ -293,7 +313,7 @@ . InstalledPackageIndex.lookupPackageName (depResolverInstalledPkgIndex params) -addSourcePackages :: [SourcePackage]+addSourcePackages :: [UnresolvedSourcePackage] -> DepResolverParams -> DepResolverParams addSourcePackages pkgs params = params {@@ -302,14 +322,14 @@ (depResolverSourcePkgIndex params) pkgs } -hideInstalledPackagesSpecificByInstalledPackageId :: [InstalledPackageId]+hideInstalledPackagesSpecificByUnitId :: [UnitId] -> DepResolverParams -> DepResolverParams-hideInstalledPackagesSpecificByInstalledPackageId pkgids params =+hideInstalledPackagesSpecificByUnitId pkgids params = --TODO: this should work using exclude constraints instead params { depResolverInstalledPkgIndex =- foldl' (flip InstalledPackageIndex.deleteInstalledPackageId)+ foldl' (flip InstalledPackageIndex.deleteUnitId) (depResolverInstalledPkgIndex params) pkgids } @@ -337,96 +357,68 @@ hideBrokenInstalledPackages :: DepResolverParams -> DepResolverParams hideBrokenInstalledPackages params =- hideInstalledPackagesSpecificByInstalledPackageId pkgids params+ hideInstalledPackagesSpecificByUnitId pkgids params where- pkgids = map Installed.installedPackageId+ pkgids = map Installed.installedUnitId . InstalledPackageIndex.reverseDependencyClosure (depResolverInstalledPkgIndex params)- . map (Installed.installedPackageId . fst)+ . map (Installed.installedUnitId . fst) . InstalledPackageIndex.brokenPackages $ depResolverInstalledPkgIndex params -- | Remove upper bounds in dependencies using the policy specified by the -- 'AllowNewer' argument (all/some/none).+--+-- Note: It's important to apply 'removeUpperBounds' after+-- 'addSourcePackages'. Otherwise, the packages inserted by+-- 'addSourcePackages' won't have upper bounds in dependencies relaxed.+-- removeUpperBounds :: AllowNewer -> DepResolverParams -> DepResolverParams-removeUpperBounds allowNewer params =+removeUpperBounds AllowNewerNone params = params+removeUpperBounds allowNewer params = params {- -- NB: It's important to apply 'removeUpperBounds' after- -- 'addSourcePackages'. Otherwise, the packages inserted by- -- 'addSourcePackages' won't have upper bounds in dependencies relaxed.- depResolverSourcePkgIndex = sourcePkgIndex' } where- sourcePkgIndex = depResolverSourcePkgIndex params- sourcePkgIndex' = case allowNewer of- AllowNewerNone -> sourcePkgIndex- AllowNewerAll -> fmap relaxAllPackageDeps sourcePkgIndex- AllowNewerSome pkgs -> fmap (relaxSomePackageDeps pkgs) sourcePkgIndex-- relaxAllPackageDeps :: SourcePackage -> SourcePackage- relaxAllPackageDeps = onAllBuildDepends doRelax- where- doRelax (Dependency pkgName verRange) =- Dependency pkgName (removeUpperBound verRange)-- relaxSomePackageDeps :: [PackageName] -> SourcePackage -> SourcePackage- relaxSomePackageDeps pkgNames = onAllBuildDepends doRelax- where- doRelax d@(Dependency pkgName verRange)- | pkgName `elem` pkgNames = Dependency pkgName- (removeUpperBound verRange)- | otherwise = d-- -- Walk a 'GenericPackageDescription' and apply 'f' to all 'build-depends'- -- fields.- onAllBuildDepends :: (Dependency -> Dependency)- -> SourcePackage -> SourcePackage- onAllBuildDepends f srcPkg = srcPkg'- where- gpd = packageDescription srcPkg- pd = PD.packageDescription gpd- condLib = PD.condLibrary gpd- condExes = PD.condExecutables gpd- condTests = PD.condTestSuites gpd- condBenchs = PD.condBenchmarks gpd-- f' = onBuildInfo f- onBuildInfo g bi = bi- { targetBuildDepends = map g (targetBuildDepends bi) }+ sourcePkgIndex' = fmap relaxDeps $ depResolverSourcePkgIndex params - onLibrary lib = lib { PD.libBuildInfo = f' $ PD.libBuildInfo lib }- onExecutable exe = exe { PD.buildInfo = f' $ PD.buildInfo exe }- onTestSuite tst = tst { PD.testBuildInfo = f' $ PD.testBuildInfo tst }- onBenchmark bmk = bmk { PD.benchmarkBuildInfo =- f' $ PD.benchmarkBuildInfo bmk }+ relaxDeps :: UnresolvedSourcePackage -> UnresolvedSourcePackage+ relaxDeps srcPkg = srcPkg {+ packageDescription = relaxPackageDeps allowNewer+ (packageDescription srcPkg)+ } - srcPkg' = srcPkg { packageDescription = gpd' }- gpd' = gpd {- PD.packageDescription = pd',- PD.condLibrary = condLib',- PD.condExecutables = condExes',- PD.condTestSuites = condTests',- PD.condBenchmarks = condBenchs'- }- pd' = pd {- PD.buildDepends = map f (PD.buildDepends pd),- PD.library = fmap onLibrary (PD.library pd),- PD.executables = map onExecutable (PD.executables pd),- PD.testSuites = map onTestSuite (PD.testSuites pd),- PD.benchmarks = map onBenchmark (PD.benchmarks pd)+-- | Supply defaults for packages without explicit Setup dependencies+--+-- Note: It's important to apply 'addDefaultSetupDepends' after+-- 'addSourcePackages'. Otherwise, the packages inserted by+-- 'addSourcePackages' won't have upper bounds in dependencies relaxed.+--+addDefaultSetupDependencies :: (UnresolvedSourcePackage -> [Dependency])+ -> DepResolverParams -> DepResolverParams+addDefaultSetupDependencies defaultSetupDeps params =+ params {+ depResolverSourcePkgIndex =+ fmap applyDefaultSetupDeps (depResolverSourcePkgIndex params)+ }+ where+ applyDefaultSetupDeps :: UnresolvedSourcePackage -> UnresolvedSourcePackage+ applyDefaultSetupDeps srcpkg =+ srcpkg {+ packageDescription = gpkgdesc {+ PD.packageDescription = pkgdesc {+ PD.setupBuildInfo =+ case PD.setupBuildInfo pkgdesc of+ Just sbi -> Just sbi+ Nothing -> Just PD.SetupBuildInfo {+ PD.setupDepends = defaultSetupDeps srcpkg+ }+ } }- condLib' = fmap (onCondTree onLibrary) condLib- condExes' = map (mapSnd $ onCondTree onExecutable) condExes- condTests' = map (mapSnd $ onCondTree onTestSuite) condTests- condBenchs' = map (mapSnd $ onCondTree onBenchmark) condBenchs-- mapSnd :: (a -> b) -> (c,a) -> (c,b)- mapSnd = fmap-- onCondTree :: (a -> b) -> PD.CondTree v [Dependency] a- -> PD.CondTree v [Dependency] b- onCondTree g = mapCondTree g (map f) id+ }+ where+ gpkgdesc = packageDescription srcpkg+ pkgdesc = PD.packageDescription gpkgdesc upgradeDependencies :: DepResolverParams -> DepResolverParams@@ -440,7 +432,7 @@ standardInstallPolicy :: InstalledPackageIndex -> SourcePackageDb- -> [PackageSpecifier SourcePackage]+ -> [PackageSpecifier UnresolvedSourcePackage] -> DepResolverParams standardInstallPolicy installedPkgIndex (SourcePackageDb sourcePkgIndex sourcePkgPrefs)@@ -521,7 +513,7 @@ info verbosity "Choosing modular solver." return Modular -runSolver :: Solver -> SolverConfig -> DependencyResolver+runSolver :: Solver -> SolverConfig -> DependencyResolver UnresolvedPkgLoc runSolver TopDown = const topDownResolver -- TODO: warn about unsupported options runSolver Modular = modularResolver @@ -533,25 +525,26 @@ -- resolveDependencies :: Platform -> CompilerInfo+ -> PkgConfigDb -> Solver -> DepResolverParams -> Progress String String InstallPlan --TODO: is this needed here? see dontUpgradeNonUpgradeablePackages-resolveDependencies platform comp _solver params+resolveDependencies platform comp _pkgConfigDB _solver params | null (depResolverTargets params) = return (validateSolverResult platform comp indGoals []) where indGoals = depResolverIndependentGoals params -resolveDependencies platform comp solver params =+resolveDependencies platform comp pkgConfigDB solver params = Step (showDepResolverParams finalparams) $ fmap (validateSolverResult platform comp indGoals) $ runSolver solver (SolverConfig reorderGoals indGoals noReinstalls shadowing strFlags maxBkjumps) platform comp installedPkgIndex sourcePkgIndex- preferences constraints targets+ pkgConfigDB preferences constraints targets where finalparams @ (DepResolverParams@@ -586,14 +579,15 @@ -> [PackagePreference] -> (PackageName -> PackagePreferences) interpretPackagesPreference selected defaultPref prefs =- \pkgname -> PackagePreferences (versionPref pkgname) (installPref pkgname)-+ \pkgname -> PackagePreferences (versionPref pkgname)+ (installPref pkgname)+ (stanzasPref pkgname) where versionPref pkgname =- fromMaybe anyVersion (Map.lookup pkgname versionPrefs)- versionPrefs = Map.fromList- [ (pkgname, pref)- | PackageVersionPreference pkgname pref <- prefs ]+ fromMaybe [anyVersion] (Map.lookup pkgname versionPrefs)+ versionPrefs = Map.fromListWith (++)+ [(pkgname, [pref])+ | PackageVersionPreference pkgname pref <- prefs] installPref pkgname = fromMaybe (installPrefDefault pkgname) (Map.lookup pkgname installPrefs)@@ -601,14 +595,21 @@ [ (pkgname, pref) | PackageInstalledPreference pkgname pref <- prefs ] installPrefDefault = case defaultPref of- PreferAllLatest -> \_ -> PreferLatest- PreferAllInstalled -> \_ -> PreferInstalled+ PreferAllLatest -> const PreferLatest+ PreferAllInstalled -> const PreferInstalled PreferLatestForSelected -> \pkgname -> -- When you say cabal install foo, what you really mean is, prefer the -- latest version of foo, but the installed version of everything else if pkgname `Set.member` selected then PreferLatest else PreferInstalled + stanzasPref pkgname =+ fromMaybe [] (Map.lookup pkgname stanzasPrefs)+ stanzasPrefs = Map.fromListWith (\a b -> nub (a ++ b))+ [ (pkgname, pref)+ | PackageStanzasPreference pkgname pref <- prefs ]++ -- ------------------------------------------------------------ -- * Checking the result of the solver -- ------------------------------------------------------------@@ -619,7 +620,7 @@ validateSolverResult :: Platform -> CompilerInfo -> Bool- -> [ResolverPackage]+ -> [ResolverPackage UnresolvedPkgLoc] -> InstallPlan validateSolverResult platform comp indepGoals pkgs = case planPackagesProblems platform comp pkgs of@@ -637,7 +638,7 @@ formatPkgProblems = formatProblemMessage . map showPlanPackageProblem formatPlanProblems = formatProblemMessage . map InstallPlan.showPlanProblem - formatProblemMessage problems = + formatProblemMessage problems = unlines $ "internal error: could not construct a valid install plan." : "The proposed (invalid) plan contained the following problems:"@@ -647,7 +648,7 @@ data PlanPackageProblem =- InvalidConfiguredPackage ConfiguredPackage [PackageProblem]+ InvalidConfiguredPackage (ConfiguredPackage UnresolvedPkgLoc) [PackageProblem] showPlanPackageProblem :: PlanPackageProblem -> String showPlanPackageProblem (InvalidConfiguredPackage pkg packageProblems) =@@ -657,7 +658,7 @@ | problem <- packageProblems ] planPackagesProblems :: Platform -> CompilerInfo- -> [ResolverPackage]+ -> [ResolverPackage UnresolvedPkgLoc] -> [PlanPackageProblem] planPackagesProblems platform cinfo pkgs = [ InvalidConfiguredPackage pkg packageProblems@@ -706,7 +707,7 @@ -- dependencies are satisfied by the specified packages. -- configuredPackageProblems :: Platform -> CompilerInfo- -> ConfiguredPackage -> [PackageProblem]+ -> ConfiguredPackage UnresolvedPkgLoc -> [PackageProblem] configuredPackageProblems platform cinfo (ConfiguredPackage pkg specifiedFlags stanzas specifiedDeps') = [ DuplicateFlag flag | ((flag,_):_) <- duplicates specifiedFlags ]@@ -787,14 +788,14 @@ -- It simply means preferences for installed packages will be ignored. -- resolveWithoutDependencies :: DepResolverParams- -> Either [ResolveNoDepsError] [SourcePackage]+ -> Either [ResolveNoDepsError] [UnresolvedSourcePackage] resolveWithoutDependencies (DepResolverParams targets constraints prefs defpref installedPkgIndex sourcePkgIndex _reorderGoals _indGoals _avoidReinstalls _shadowing _strFlags _maxBjumps) = collectEithers (map selectPackage targets) where- selectPackage :: PackageName -> Either ResolveNoDepsError SourcePackage+ selectPackage :: PackageName -> Either ResolveNoDepsError UnresolvedSourcePackage selectPackage pkgname | null choices = Left $! ResolveUnsatisfiable pkgname requiredVersions | otherwise = Right $! maximumBy bestByPrefs choices@@ -807,7 +808,7 @@ pkgDependency -- Preferences- PackagePreferences preferredVersions preferInstalled+ PackagePreferences preferredVersions preferInstalled _ = packagePreferences pkgname bestByPrefs = comparing $ \pkg ->@@ -818,7 +819,8 @@ . InstalledPackageIndex.lookupSourcePackageId installedPkgIndex . packageId- versionPref pkg = packageVersion pkg `withinRange` preferredVersions+ versionPref pkg = length . filter (packageVersion pkg `withinRange`) $+ preferredVersions packageConstraints :: PackageName -> VersionRange packageConstraints pkgname =
cabal/cabal-install/Distribution/Client/Dependency/Modular.hs view
@@ -12,9 +12,7 @@ import Data.Map as M ( fromListWith ) import Distribution.Client.Dependency.Modular.Assignment- ( Assignment, toCPs )-import Distribution.Client.Dependency.Modular.Dependency- ( RevDepMap )+ ( toCPs ) import Distribution.Client.Dependency.Modular.ConfiguredConversion ( convCP ) import Distribution.Client.Dependency.Modular.IndexConversion@@ -26,18 +24,18 @@ import Distribution.Client.Dependency.Modular.Solver ( SolverConfig(..), solve ) import Distribution.Client.Dependency.Types- ( DependencyResolver, ResolverPackage+ ( DependencyResolver , PackageConstraint(..), unlabelPackageConstraint ) import Distribution.System ( Platform(..) ) -- | Ties the two worlds together: classic cabal-install vs. the modular -- solver. Performs the necessary translations before and after.-modularResolver :: SolverConfig -> DependencyResolver-modularResolver sc (Platform arch os) cinfo iidx sidx pprefs pcs pns =+modularResolver :: SolverConfig -> DependencyResolver loc+modularResolver sc (Platform arch os) cinfo iidx sidx pkgConfigDB pprefs pcs pns = fmap (uncurry postprocess) $ -- convert install plan logToProgress (maxBackjumps sc) $ -- convert log format into progress format- solve sc idx pprefs gcs pns+ solve sc cinfo idx pkgConfigDB pprefs gcs pns where -- Indices have to be converted into solver-specific uniform index. idx = convPIs os arch cinfo (shadowPkgs sc) (strongFlags sc) iidx sidx@@ -47,7 +45,6 @@ pair lpc = (pcName $ unlabelPackageConstraint lpc, [lpc]) -- Results have to be converted into an install plan.- postprocess :: Assignment -> RevDepMap -> [ResolverPackage] postprocess a rdm = map (convCP iidx sidx) (toCPs a rdm) -- Helper function to extract the PN from a constraint.
cabal/cabal-install/Distribution/Client/Dependency/Modular/Assignment.hs view
@@ -1,4 +1,11 @@-module Distribution.Client.Dependency.Modular.Assignment where+module Distribution.Client.Dependency.Modular.Assignment+ ( Assignment(..)+ , FAssignment+ , SAssignment+ , PreAssignment(..)+ , extend+ , toCPs+ ) where import Control.Applicative import Control.Monad@@ -8,6 +15,8 @@ import Data.Maybe import Prelude hiding (pi) +import Language.Haskell.Extension (Extension, Language)+ import Distribution.PackageDescription (FlagAssignment) -- from Cabal import Distribution.Client.Types (OptionalStanza) import Distribution.Client.Utils.LabeledGraph@@ -17,7 +26,6 @@ import Distribution.Client.Dependency.Modular.Configured import Distribution.Client.Dependency.Modular.Dependency import Distribution.Client.Dependency.Modular.Flag-import Distribution.Client.Dependency.Modular.Index import Distribution.Client.Dependency.Modular.Package import Distribution.Client.Dependency.Modular.Version @@ -53,14 +61,31 @@ -- -- Either returns a witness of the conflict that would arise during the merge, -- or the successfully extended assignment.-extend :: Var QPN -> PPreAssignment -> [Dep QPN] -> Either (ConflictSet QPN, [Dep QPN]) PPreAssignment-extend var pa qa = foldM (\ a (Dep qpn ci) ->- let ci' = M.findWithDefault (Constrained []) qpn a- in case (\ x -> M.insert qpn x a) <$> merge ci' ci of- Left (c, (d, d')) -> Left (c, L.map (Dep qpn) (simplify (P qpn) d d'))- Right x -> Right x)- pa qa+extend :: (Extension -> Bool) -- ^ is a given extension supported+ -> (Language -> Bool) -- ^ is a given language supported+ -> (PN -> VR -> Bool) -- ^ is a given pkg-config requirement satisfiable+ -> Goal QPN+ -> PPreAssignment -> [Dep QPN] -> Either (ConflictSet QPN, [Dep QPN]) PPreAssignment+extend extSupported langSupported pkgPresent goal@(Goal var _) = foldM extendSingle where++ extendSingle :: PPreAssignment -> Dep QPN+ -> Either (ConflictSet QPN, [Dep QPN]) PPreAssignment+ extendSingle a (Ext ext ) =+ if extSupported ext then Right a+ else Left (toConflictSet goal, [Ext ext])+ extendSingle a (Lang lang) =+ if langSupported lang then Right a+ else Left (toConflictSet goal, [Lang lang])+ extendSingle a (Pkg pn vr) =+ if pkgPresent pn vr then Right a+ else Left (toConflictSet goal, [Pkg pn vr])+ extendSingle a (Dep qpn ci) =+ let ci' = M.findWithDefault (Constrained []) qpn a+ in case (\ x -> M.insert qpn x a) <$> merge ci' ci of+ Left (c, (d, d')) -> Left (c, L.map (Dep qpn) (simplify (P qpn) d d'))+ Right x -> Right x+ -- We're trying to remove trivial elements of the conflict. If we're just -- making a choice pkg == instance, and pkg => pkg == instance is a part -- of the conflict, then this info is clear from the context and does not@@ -123,37 +148,3 @@ (M.findWithDefault [] qpn sapp) (depp' qpn)) ps---- | Finalize an assignment and a reverse dependency map.------ This is preliminary, and geared towards output right now.-finalize :: Index -> Assignment -> RevDepMap -> IO ()-finalize idx (A pa fa _) rdm =- let- -- get hold of the graph- g :: Graph Component- vm :: Vertex -> ((), QPN, [(Component, QPN)])- (g, vm) = graphFromEdges' (L.map (\ (x, xs) -> ((), x, xs)) (M.toList rdm))- -- topsort the dependency graph, yielding a list of pkgs in the right order- f :: [PI QPN]- f = L.filter (not . instPI) (L.map ((\ (_, x, _) -> PI x (pa M.! x)) . vm) (topSort g))- fapp :: Map QPN [(QFN, Bool)] -- flags per package- fapp = M.fromListWith (++) $- L.map (\ (qfn@(FN (PI qpn _) _), b) -> (qpn, [(qfn, b)])) $ M.toList $ fa- -- print one instance- ppi pi@(PI qpn _) = showPI pi ++ status pi ++ " " ++ pflags (M.findWithDefault [] qpn fapp)- -- print install status- status :: PI QPN -> String- status (PI (Q _ pn) _) =- case insts of- [] -> " (new)"- vs -> " (" ++ intercalate ", " (L.map showVer vs) ++ ")"- where insts = L.map (\ (I v _) -> v) $ L.filter isInstalled $- M.keys (M.findWithDefault M.empty pn idx)- isInstalled (I _ (Inst _ )) = True- isInstalled _ = False- -- print flag assignment- pflags = unwords . L.map (uncurry showFBool)- in- -- show packages with associated flag assignments- putStr (unlines (L.map ppi f))
cabal/cabal-install/Distribution/Client/Dependency/Modular/Builder.hs view
@@ -24,7 +24,8 @@ import Distribution.Client.Dependency.Modular.Flag import Distribution.Client.Dependency.Modular.Index import Distribution.Client.Dependency.Modular.Package-import Distribution.Client.Dependency.Modular.PSQ as P+import Distribution.Client.Dependency.Modular.PSQ (PSQ)+import qualified Distribution.Client.Dependency.Modular.PSQ as P import Distribution.Client.Dependency.Modular.Tree import Distribution.Client.ComponentDeps (Component)@@ -58,8 +59,11 @@ | qpn `M.member` g = go (M.adjust ((c, qpn'):) qpn g) o ngs | otherwise = go (M.insert qpn [(c, qpn')] g) (cons' ng () o) ngs -- code above is correct; insert/adjust have different arg order+ go g o ( (OpenGoal (Simple (Ext _ext ) _) _gr) : ngs) = go g o ngs+ go g o ( (OpenGoal (Simple (Lang _lang)_) _gr) : ngs) = go g o ngs+ go g o ( (OpenGoal (Simple (Pkg _pn _vr)_) _gr) : ngs)= go g o ngs - cons' = cons . forgetCompOpenGoal+ cons' = P.cons . forgetCompOpenGoal -- | Given the current scope, qualify all the package names in the given set of -- dependencies and then extend the set of open goals accordingly.@@ -114,6 +118,12 @@ -- -- For a package, we look up the instances available in the global info, -- and then handle each instance in turn.+ go (BS { index = _ , next = OneGoal (OpenGoal (Simple (Ext _ ) _) _ ) }) =+ error "Distribution.Client.Dependency.Modular.Builder: build.go called with Ext goal"+ go (BS { index = _ , next = OneGoal (OpenGoal (Simple (Lang _ ) _) _ ) }) =+ error "Distribution.Client.Dependency.Modular.Builder: build.go called with Lang goal"+ go (BS { index = _ , next = OneGoal (OpenGoal (Simple (Pkg _ _ ) _) _ ) }) =+ error "Distribution.Client.Dependency.Modular.Builder: build.go called with Pkg goal" go bs@(BS { index = idx, next = OneGoal (OpenGoal (Simple (Dep qpn@(Q _ pn) _) _) gr) }) = case M.lookup pn idx of Nothing -> FailF (toConflictSet (Goal (P qpn) gr)) (BuildFailureNotInIndex pn)@@ -135,6 +145,11 @@ reorder False = reverse trivial = L.null t && L.null f + -- For a stanza, we also create only two subtrees. The order is initially+ -- False, True. This can be changed later by constraints (force enabling+ -- the stanza by replacing the False branch with failure) or preferences+ -- (try enabling the stanza if possible by moving the True branch first).+ go bs@(BS { next = OneGoal (OpenGoal (Stanza qsn@(SN (PI qpn _) _) t) gr) }) = SChoiceF qsn gr trivial (P.fromList [(False, bs { next = Goals }),@@ -165,4 +180,4 @@ topLevelGoal qpn = OpenGoal (Simple (Dep qpn (Constrained [])) ()) [UserGoal] qpns | ind = makeIndependent igs- | otherwise = L.map (Q None) igs+ | otherwise = L.map (Q (PP DefaultNamespace Unqualified)) igs
cabal/cabal-install/Distribution/Client/Dependency/Modular/Configured.hs view
@@ -1,4 +1,6 @@-module Distribution.Client.Dependency.Modular.Configured where+module Distribution.Client.Dependency.Modular.Configured+ ( CP(..)+ ) where import Distribution.PackageDescription (FlagAssignment) -- from Cabal import Distribution.Client.Types (OptionalStanza)
cabal/cabal-install/Distribution/Client/Dependency/Modular/ConfiguredConversion.hs view
@@ -1,8 +1,12 @@-module Distribution.Client.Dependency.Modular.ConfiguredConversion where+module Distribution.Client.Dependency.Modular.ConfiguredConversion+ ( convCP+ ) where import Data.Maybe import Prelude hiding (pi) +import Distribution.Package (UnitId)+ import Distribution.Client.Types import Distribution.Client.Dependency.Types (ResolverPackage(..)) import qualified Distribution.Client.PackageIndex as CI@@ -13,23 +17,28 @@ import Distribution.Client.ComponentDeps (ComponentDeps) --convCP :: SI.InstalledPackageIndex -> CI.PackageIndex SourcePackage ->- CP QPN -> ResolverPackage+-- | Converts from the solver specific result @CP QPN@ into+-- a 'ResolverPackage', which can then be converted into+-- the install plan.+convCP :: SI.InstalledPackageIndex ->+ CI.PackageIndex (SourcePackage loc) ->+ CP QPN -> ResolverPackage loc convCP iidx sidx (CP qpi fa es ds) = case convPI qpi of Left pi -> PreExisting- (fromJust $ SI.lookupInstalledPackageId iidx pi)+ (fromJust $ SI.lookupUnitId iidx pi) Right pi -> Configured $ ConfiguredPackage- (fromJust $ CI.lookupPackageId sidx pi)+ srcpkg fa es ds'+ where+ Just srcpkg = CI.lookupPackageId sidx pi where ds' :: ComponentDeps [ConfiguredId] ds' = fmap (map convConfId) ds -convPI :: PI QPN -> Either InstalledPackageId PackageId+convPI :: PI QPN -> Either UnitId PackageId convPI (PI _ (I _ (Inst pi))) = Left pi convPI qpi = Right $ confSrcId $ convConfId qpi @@ -42,4 +51,4 @@ sourceId = PackageIdentifier pn v installedId = case loc of Inst pi -> pi- _otherwise -> fakeInstalledPackageId sourceId+ _otherwise -> fakeUnitId sourceId
+ cabal/cabal-install/Distribution/Client/Dependency/Modular/Cycles.hs view
@@ -0,0 +1,73 @@+{-# LANGUAGE CPP #-}+module Distribution.Client.Dependency.Modular.Cycles (+ detectCyclesPhase+ ) where++import Prelude hiding (cycle)+import Control.Monad+import Control.Monad.Reader+import Data.Graph (SCC)+import Data.Set (Set)+import qualified Data.Graph as Gr+import qualified Data.Map as Map+import qualified Data.Set as Set+import qualified Data.Traversable as T++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative ((<$>))+#endif++import Distribution.Client.Dependency.Modular.Dependency+import Distribution.Client.Dependency.Modular.Flag+import Distribution.Client.Dependency.Modular.Package+import Distribution.Client.Dependency.Modular.Tree++type DetectCycles = Reader (ConflictSet QPN)++-- | Find and reject any solutions that are cyclic+detectCyclesPhase :: Tree QGoalReasonChain -> Tree QGoalReasonChain+detectCyclesPhase = (`runReader` Set.empty) . cata go+ where+ -- Most cases are simple; we just need to remember which choices we made+ go :: TreeF QGoalReasonChain (DetectCycles (Tree QGoalReasonChain)) -> DetectCycles (Tree QGoalReasonChain)+ go (PChoiceF qpn gr cs) = PChoice qpn gr <$> local (extendConflictSet $ P qpn) (T.sequence cs)+ go (FChoiceF qfn gr w m cs) = FChoice qfn gr w m <$> local (extendConflictSet $ F qfn) (T.sequence cs)+ go (SChoiceF qsn gr w cs) = SChoice qsn gr w <$> local (extendConflictSet $ S qsn) (T.sequence cs)+ go (GoalChoiceF cs) = GoalChoice <$> (T.sequence cs)+ go (FailF cs reason) = return $ Fail cs reason++ -- We check for cycles only if we have actually found a solution+ -- This minimizes the number of cycle checks we do as cycles are rare+ go (DoneF revDeps) = do+ fullSet <- ask+ return $ case findCycles fullSet revDeps of+ Nothing -> Done revDeps+ Just relSet -> Fail relSet CyclicDependencies++-- | Given the reverse dependency map from a 'Done' node in the tree, as well+-- as the full conflict set containing all decisions that led to that 'Done'+-- node, check if the solution is cyclic. If it is, return the conflict set+-- containing all decisions that could potentially break the cycle.+findCycles :: ConflictSet QPN -> RevDepMap -> Maybe (ConflictSet QPN)+findCycles fullSet revDeps = do+ guard $ not (null cycles)+ return $ relevantConflictSet (Set.fromList (concat cycles)) fullSet+ where+ cycles :: [[QPN]]+ cycles = [vs | Gr.CyclicSCC vs <- scc]++ scc :: [SCC QPN]+ scc = Gr.stronglyConnComp . map aux . Map.toList $ revDeps++ aux :: (QPN, [(comp, QPN)]) -> (QPN, QPN, [QPN])+ aux (fr, to) = (fr, fr, map snd to)++-- | Construct the relevant conflict set given the full conflict set that+-- lead to this decision and the set of packages involved in the cycle+relevantConflictSet :: Set QPN -> ConflictSet QPN -> ConflictSet QPN+relevantConflictSet cycle = Set.filter isRelevant+ where+ isRelevant :: Var QPN -> Bool+ isRelevant (P qpn) = qpn `Set.member` cycle+ isRelevant (F (FN (PI qpn _i) _fn)) = qpn `Set.member` cycle+ isRelevant (S (SN (PI qpn _i) _sn)) = qpn `Set.member` cycle
cabal/cabal-install/Distribution/Client/Dependency/Modular/Dependency.hs view
@@ -4,20 +4,16 @@ -- * Variables Var(..) , simplifyVar- , showVar , varPI -- * Conflict sets , ConflictSet , showCS -- * Constrained instances , CI(..)- , showCI , merge -- * Flagged dependencies , FlaggedDeps , FlaggedDep(..)- , TrueFlaggedDeps- , FalseFlaggedDeps , Dep(..) , showDep , flattenFlaggedDeps@@ -26,39 +22,32 @@ -- ** Setting/forgetting components , forgetCompOpenGoal , setCompFlaggedDeps- -- ** Selecting subsets- , nonSetupDeps- , setupDeps- , select -- * Reverse dependency map , RevDepMap -- * Goals , Goal(..) , GoalReason(..)- , GoalReasonChain , QGoalReasonChain , ResetGoal(..) , toConflictSet- , goalReasonToVars- , goalReasonChainToVars- , goalReasonChainsToVars+ , extendConflictSet -- * Open goals , OpenGoal(..) , close- -- * Version ranges pairsed with origins (goals)- , VROrigin- , collapse ) where import Prelude hiding (pi) import Data.List (intercalate) import Data.Map (Map)-import Data.Maybe (mapMaybe) import Data.Set (Set) import qualified Data.List as L import qualified Data.Set as S +import Language.Haskell.Extension (Extension(..), Language(..))++import Distribution.Text+ import Distribution.Client.Dependency.Modular.Flag import Distribution.Client.Dependency.Modular.Package import Distribution.Client.Dependency.Modular.Version@@ -180,10 +169,15 @@ -- | Flagged dependencies can either be plain dependency constraints, -- or flag-dependent dependency trees. data FlaggedDep comp qpn =+ -- | Dependencies which are conditional on a flag choice. Flagged (FN qpn) FInfo (TrueFlaggedDeps qpn) (FalseFlaggedDeps qpn)+ -- | Dependencies which are conditional on whether or not a stanza+ -- (e.g., a test suite or benchmark) is enabled. | Stanza (SN qpn) (TrueFlaggedDeps qpn)+ -- | Dependencies for which are always enabled, for the component+ -- 'comp' (or requested for the user, if comp is @()@). | Simple (Dep qpn) comp- deriving (Eq, Show, Functor)+ deriving (Eq, Show) -- | Conversatively flatten out flagged dependencies --@@ -201,8 +195,16 @@ -- | A dependency (constraint) associates a package name with a -- constrained instance.-data Dep qpn = Dep qpn (CI qpn)- deriving (Eq, Show, Functor)+--+-- 'Dep' intentionally has no 'Functor' instance because the type variable+-- is used both to record the dependencies as well as who's doing the+-- depending; having a 'Functor' instance makes bugs where we don't distinguish+-- these two far too likely. (By rights 'Dep' ought to have two type variables.)+data Dep qpn = Dep qpn (CI qpn) -- dependency on a package+ | Ext Extension -- dependency on a language extension+ | Lang Language -- dependency on a language version+ | Pkg PN VR -- dependency on a pkg-config package+ deriving (Eq, Show) showDep :: Dep QPN -> String showDep (Dep qpn (Fixed i (Goal v _)) ) =@@ -212,6 +214,11 @@ showVar v ++ " => " ++ showQPN qpn ++ showVR vr showDep (Dep qpn ci ) = showQPN qpn ++ showCI ci+showDep (Ext ext) = "requires " ++ display ext+showDep (Lang lang) = "requires " ++ display lang+showDep (Pkg pn vr) = "requires pkg-config package "+ ++ display pn ++ display vr+ ++ ", not found in the pkg-config database" -- | Options for goal qualification (used in 'qualifyDeps') --@@ -230,12 +237,8 @@ -- NOTE: It's the _dependencies_ of a package that may or may not be independent -- from the package itself. Package flag choices must of course be consistent. qualifyDeps :: QualifyOptions -> QPN -> FlaggedDeps Component PN -> FlaggedDeps Component QPN-qualifyDeps QO{..} (Q pp' pn) = go+qualifyDeps QO{..} (Q pp@(PP ns q) pn) = go where- -- The Base qualifier does not get inherited- pp :: PP- pp = (if qoBaseShim then stripBase else id) pp'- go :: FlaggedDeps Component PN -> FlaggedDeps Component QPN go = map go1 @@ -244,17 +247,41 @@ go1 (Stanza sn t) = Stanza (fmap (Q pp) sn) (go t) go1 (Simple dep comp) = Simple (goD dep comp) comp + -- Suppose package B has a setup dependency on package A.+ -- This will be recorded as something like+ --+ -- > Dep "A" (Constrained [(AnyVersion, Goal (P "B") reason])+ --+ -- Observe that when we qualify this dependency, we need to turn that+ -- @"A"@ into @"B-setup.A"@, but we should not apply that same qualifier+ -- to the goal or the goal reason chain. goD :: Dep PN -> Component -> Dep QPN- goD dep comp- | qBase dep = fmap (Q (Base pn pp)) dep- | qSetup comp = fmap (Q (Setup pn pp)) dep- | otherwise = fmap (Q pp ) dep+ goD (Ext ext) _ = Ext ext+ goD (Lang lang) _ = Lang lang+ goD (Pkg pkn vr) _ = Pkg pkn vr+ goD (Dep dep ci) comp+ | qBase dep = Dep (Q (PP ns (Base pn)) dep) (fmap (Q pp) ci)+ | qSetup comp = Dep (Q (PP ns (Setup pn)) dep) (fmap (Q pp) ci)+ | otherwise = Dep (Q (PP ns inheritedQ) dep) (fmap (Q pp) ci) + -- If P has a setup dependency on Q, and Q has a regular dependency on R, then+ -- we say that the 'Setup' qualifier is inherited: P has an (indirect) setup+ -- dependency on R. We do not do this for the base qualifier however.+ --+ -- The inherited qualifier is only used for regular dependencies; for setup+ -- and base deppendencies we override the existing qualifier. See #3160 for+ -- a detailed discussion.+ inheritedQ :: Qualifier+ inheritedQ = case q of+ Setup _ -> q+ Unqualified -> q+ Base _ -> Unqualified+ -- Should we qualify this goal with the 'Base' package path?- qBase :: Dep PN -> Bool- qBase (Dep dep _ci) = qoBaseShim && unPackageName dep == "base"+ qBase :: PN -> Bool+ qBase dep = qoBaseShim && unPackageName dep == "base" - -- Should we qualify this goal with the 'Setup' packaeg path?+ -- Should we qualify this goal with the 'Setup' package path? qSetup :: Component -> Bool qSetup comp = qoSetupIndependent && comp == ComponentSetup @@ -288,60 +315,6 @@ mapCompFlaggedDep g (Simple pn a ) = Simple pn (g a) {-------------------------------------------------------------------------------- Selecting FlaggedDeps subsets-- (Correspond to the functions with the same names in ComponentDeps).--------------------------------------------------------------------------------}--nonSetupDeps :: FlaggedDeps Component a -> FlaggedDeps Component a-nonSetupDeps = select (/= ComponentSetup)--setupDeps :: FlaggedDeps Component a -> FlaggedDeps Component a-setupDeps = select (== ComponentSetup)---- | Select the dependencies of a given component------ The modular solver kind of flattens the dependency trees from the .cabal--- file, putting the component of each dependency at the leaves, rather than--- indexing per component. For instance, package C might have flagged deps that--- look something like------ > Flagged <flagName> ..--- > [Simple <package A> ComponentLib]--- > [Simple <package B> ComponentLib]------ indicating that the library component of C relies on either A or B, depending--- on the flag. This makes it somewhat awkward however to extract certain kinds--- of dependencies. In particular, extracting, say, the setup dependencies from--- the above set of dependencies could either return the empty list, or else------ > Flagged <flagName> ..--- > []--- > []------ Both answers are reasonable; we opt to return the empty list in this--- case, as it results in simpler search trees in the builder.------ (Note that the builder already introduces separate goals for all flags of a--- package, independently of whether or not they are used in any component, so--- we don't have to worry about preserving flags here.)-select :: (Component -> Bool) -> FlaggedDeps Component a -> FlaggedDeps Component a-select p = mapMaybe go- where- go :: FlaggedDep Component a -> Maybe (FlaggedDep Component a)- go (Flagged fn nfo t f) = let t' = mapMaybe go t- f' = mapMaybe go f- in if null t' && null f'- then Nothing- else Just $ Flagged fn nfo t' f'- go (Stanza sn t ) = let t' = mapMaybe go t- in if null t'- then Nothing- else Just $ Stanza sn t'- go (Simple pn comp ) = if p comp then Just $ Simple pn comp- else Nothing--{------------------------------------------------------------------------------- Reverse dependency map -------------------------------------------------------------------------------} @@ -381,15 +354,22 @@ instance ResetGoal Dep where resetGoal g (Dep qpn ci) = Dep qpn (resetGoal g ci)+ resetGoal _ (Ext ext) = Ext ext+ resetGoal _ (Lang lang) = Lang lang+ resetGoal _ (Pkg pn vr) = Pkg pn vr instance ResetGoal Goal where resetGoal = const --- | Compute a conflic set from a goal. The conflict set contains the--- closure of goal reasons as well as the variable of the goal itself.+-- | Compute a conflict set from a goal. The conflict set contains the closure+-- of goal reasons as well as the variable of the goal itself. toConflictSet :: Ord qpn => Goal qpn -> ConflictSet qpn toConflictSet (Goal g grs) = S.insert (simplifyVar g) (goalReasonChainToVars grs) +-- | Add another variable into a conflict set+extendConflictSet :: Ord qpn => Var qpn -> ConflictSet qpn -> ConflictSet qpn+extendConflictSet = S.insert . simplifyVar+ goalReasonToVars :: GoalReason qpn -> ConflictSet qpn goalReasonToVars UserGoal = S.empty goalReasonToVars (PDependency (PI qpn _)) = S.singleton (P qpn)@@ -399,9 +379,6 @@ goalReasonChainToVars :: Ord qpn => GoalReasonChain qpn -> ConflictSet qpn goalReasonChainToVars = S.unions . L.map goalReasonToVars -goalReasonChainsToVars :: Ord qpn => [GoalReasonChain qpn] -> ConflictSet qpn-goalReasonChainsToVars = S.unions . L.map goalReasonChainToVars- {------------------------------------------------------------------------------- Open goals -------------------------------------------------------------------------------}@@ -415,6 +392,12 @@ -- need only during the build phase. close :: OpenGoal comp -> Goal QPN close (OpenGoal (Simple (Dep qpn _) _) gr) = Goal (P qpn) gr+close (OpenGoal (Simple (Ext _) _) _ ) =+ error "Distribution.Client.Dependency.Modular.Dependency.close: called on Ext goal"+close (OpenGoal (Simple (Lang _) _) _ ) =+ error "Distribution.Client.Dependency.Modular.Dependency.close: called on Lang goal"+close (OpenGoal (Simple (Pkg _ _) _) _ ) =+ error "Distribution.Client.Dependency.Modular.Dependency.close: called on Pkg goal" close (OpenGoal (Flagged qfn _ _ _ ) gr) = Goal (F qfn) gr close (OpenGoal (Stanza qsn _) gr) = Goal (S qsn) gr @@ -427,4 +410,4 @@ -- | Helper function to collapse a list of version ranges with origins into -- a single, simplified, version range. collapse :: [VROrigin qpn] -> VR-collapse = simplifyVR . L.foldr (.&&.) anyVR . L.map fst+collapse = simplifyVR . L.foldr ((.&&.) . fst) anyVR
cabal/cabal-install/Distribution/Client/Dependency/Modular/Explore.hs view
@@ -1,8 +1,9 @@-module Distribution.Client.Dependency.Modular.Explore where+module Distribution.Client.Dependency.Modular.Explore+ ( backjump+ , backjumpAndExplore+ ) where -import Control.Applicative as A-import Data.Foldable-import Data.List as L+import Data.Foldable as F import Data.Map as M import Data.Set as S @@ -11,139 +12,77 @@ import Distribution.Client.Dependency.Modular.Log import Distribution.Client.Dependency.Modular.Message import Distribution.Client.Dependency.Modular.Package-import Distribution.Client.Dependency.Modular.PSQ as P+import qualified Distribution.Client.Dependency.Modular.PSQ as P import Distribution.Client.Dependency.Modular.Tree---- | Backjumping.------ A tree traversal that tries to propagate conflict sets--- up the tree from the leaves, and thereby cut branches.--- All the tricky things are done in the function 'combine'.-backjump :: Tree a -> Tree (Maybe (ConflictSet QPN))-backjump = snd . cata go- where- go (FailF c fr) = (Just c, Fail c fr)- go (DoneF rdm ) = (Nothing, Done rdm)- go (PChoiceF qpn _ ts) = (c, PChoice qpn c (P.fromList ts'))- where- ~(c, ts') = combine (P qpn) (P.toList ts) S.empty- go (FChoiceF qfn _ b m ts) = (c, FChoice qfn c b m (P.fromList ts'))- where- ~(c, ts') = combine (F qfn) (P.toList ts) S.empty- go (SChoiceF qsn _ b ts) = (c, SChoice qsn c b (P.fromList ts'))- where- ~(c, ts') = combine (S qsn) (P.toList ts) S.empty- go (GoalChoiceF ts) = (c, GoalChoice (P.fromList ts'))- where- ~(cs, ts') = unzip $ L.map (\ (k, (x, v)) -> (x, (k, v))) $ P.toList ts- c = case cs of [] -> Nothing- d : _ -> d+import qualified Distribution.Client.Dependency.Types as T --- | The 'combine' function is at the heart of backjumping. It takes--- the variable we're currently considering, and a list of children--- annotated with their respective conflict sets, and an accumulator--- for the result conflict set. It returns a combined conflict set--- for the parent node, and a (potentially shortened) list of children--- with the annotations removed.------ It is *essential* that we produce the results as early as possible.--- In particular, we have to produce the list of children prior to--- traversing the entire list -- otherwise we lose the desired behaviour--- of being able to traverse the tree from left to right incrementally.+-- | This function takes the variable we're currently considering and a+-- list of children's logs. Each log yields either a solution or a+-- conflict set. The result is a combined log for the parent node that+-- has explored a prefix of the children. ----- We can shorten the list of children if we find an individual conflict--- set that does not contain the current variable. In this case, we can--- just lift the conflict set to the current level, because the current--- level cannot possibly have contributed to this conflict, so no other--- choice at the current level would avoid the conflict.+-- We can stop traversing the children's logs if we find an individual+-- conflict set that does not contain the current variable. In this+-- case, we can just lift the conflict set to the current level,+-- because the current level cannot possibly have contributed to this+-- conflict, so no other choice at the current level would avoid the+-- conflict. ----- If any of the children might contain a successful solution--- (indicated by Nothing), then Nothing will be the combined--- conflict set. If all children contain conflict sets, we can+-- If any of the children might contain a successful solution, we can+-- return it immediately. If all children contain conflict sets, we can -- take the union as the combined conflict set.-combine :: Var QPN -> [(a, (Maybe (ConflictSet QPN), b))] ->- ConflictSet QPN -> (Maybe (ConflictSet QPN), [(a, b)])-combine _ [] c = (Just c, [])-combine var ((k, ( d, v)) : xs) c = (\ ~(e, ys) -> (e, (k, v) : ys)) $- case d of- Just e | not (simplifyVar var `S.member` e) -> (Just e, [])- | otherwise -> combine var xs (e `S.union` c)- Nothing -> (Nothing, snd $ combine var xs S.empty)---- | Naive backtracking exploration of the search tree. This will yield correct--- assignments only once the tree itself is validated.-explore :: Alternative m => Tree a -> (Assignment -> m (Assignment, RevDepMap))-explore = cata go+backjump :: F.Foldable t => Var QPN -> t (ConflictSetLog a) -> ConflictSetLog a+backjump var xs = F.foldr combine logBackjump xs S.empty where- go (FailF _ _) _ = A.empty- go (DoneF rdm) a = pure (a, rdm)- go (PChoiceF qpn _ ts) (A pa fa sa) =- asum $ -- try children in order,- P.mapWithKey -- when descending ...- (\ (POption k _) r -> r (A (M.insert qpn k pa) fa sa)) -- record the pkg choice- ts- go (FChoiceF qfn _ _ _ ts) (A pa fa sa) =- asum $ -- try children in order,- P.mapWithKey -- when descending ...- (\ k r -> r (A pa (M.insert qfn k fa) sa)) -- record the flag choice- ts- go (SChoiceF qsn _ _ ts) (A pa fa sa) =- asum $ -- try children in order,- P.mapWithKey -- when descending ...- (\ k r -> r (A pa fa (M.insert qsn k sa))) -- record the flag choice- ts- go (GoalChoiceF ts) a =- casePSQ ts A.empty -- empty goal choice is an internal error- (\ _k v _xs -> v a) -- commit to the first goal choice+ combine :: ConflictSetLog a+ -> (ConflictSet QPN -> ConflictSetLog a)+ -> ConflictSet QPN -> ConflictSetLog a+ combine (T.Done x) _ _ = T.Done x+ combine (T.Fail cs) f csAcc+ | not (simplifyVar var `S.member` cs) = logBackjump cs+ | otherwise = f (csAcc `S.union` cs)+ combine (T.Step m ms) f cs = T.Step m (combine ms f cs) --- | Version of 'explore' that returns a 'Log'.-exploreLog :: Tree (Maybe (ConflictSet QPN)) ->- (Assignment -> Log Message (Assignment, RevDepMap))+ logBackjump :: ConflictSet QPN -> ConflictSetLog a+ logBackjump cs = failWith (Failure cs Backjump) cs++type ConflictSetLog = T.Progress Message (ConflictSet QPN)++-- | A tree traversal that simultaneously propagates conflict sets up+-- the tree from the leaves and creates a log.+exploreLog :: Tree a -> (Assignment -> ConflictSetLog (Assignment, RevDepMap)) exploreLog = cata go where- go (FailF c fr) _ = failWith (Failure c fr)+ go :: TreeF a (Assignment -> ConflictSetLog (Assignment, RevDepMap))+ -> (Assignment -> ConflictSetLog (Assignment, RevDepMap))+ go (FailF c fr) _ = failWith (Failure c fr) c go (DoneF rdm) a = succeedWith Success (a, rdm)- go (PChoiceF qpn c ts) (A pa fa sa) =- backjumpInfo c $- asum $ -- try children in order,+ go (PChoiceF qpn _ ts) (A pa fa sa) =+ backjump (P qpn) $ -- try children in order, P.mapWithKey -- when descending ...- (\ i@(POption k _) r -> tryWith (TryP qpn i) $ -- log and ...+ (\ i@(POption k _) r -> tryWith (TryP qpn i) $ -- log and ... r (A (M.insert qpn k pa) fa sa)) -- record the pkg choice ts- go (FChoiceF qfn c _ _ ts) (A pa fa sa) =- backjumpInfo c $- asum $ -- try children in order,+ go (FChoiceF qfn _ _ _ ts) (A pa fa sa) =+ backjump (F qfn) $ -- try children in order, P.mapWithKey -- when descending ... (\ k r -> tryWith (TryF qfn k) $ -- log and ... r (A pa (M.insert qfn k fa) sa)) -- record the pkg choice ts- go (SChoiceF qsn c _ ts) (A pa fa sa) =- backjumpInfo c $- asum $ -- try children in order,+ go (SChoiceF qsn _ _ ts) (A pa fa sa) =+ backjump (S qsn) $ -- try children in order, P.mapWithKey -- when descending ... (\ k r -> tryWith (TryS qsn k) $ -- log and ... r (A pa fa (M.insert qsn k sa))) -- record the pkg choice ts go (GoalChoiceF ts) a =- casePSQ ts- (failWith (Failure S.empty EmptyGoalChoice)) -- empty goal choice is an internal error- (\ k v _xs -> continueWith (Next (close k)) (v a)) -- commit to the first goal choice---- | Add in information about pruned trees.------ TODO: This isn't quite optimal, because we do not merely report the shape of the--- tree, but rather make assumptions about where that shape originated from. It'd be--- better if the pruning itself would leave information that we could pick up at this--- point.-backjumpInfo :: Maybe (ConflictSet QPN) -> Log Message a -> Log Message a-backjumpInfo c m = m <|> case c of -- important to produce 'm' before matching on 'c'!- Nothing -> A.empty- Just cs -> failWith (Failure cs Backjump)---- | Interface.-exploreTree :: Alternative m => Tree a -> m (Assignment, RevDepMap)-exploreTree t = explore t (A M.empty M.empty M.empty)+ P.casePSQ ts+ (failWith (Failure S.empty EmptyGoalChoice) S.empty) -- empty goal choice is an internal error+ (\ k v _xs -> continueWith (Next (close k)) (v a)) -- commit to the first goal choice -- | Interface.-exploreTreeLog :: Tree (Maybe (ConflictSet QPN)) -> Log Message (Assignment, RevDepMap)-exploreTreeLog t = exploreLog t (A M.empty M.empty M.empty)+backjumpAndExplore :: Tree a -> Log Message (Assignment, RevDepMap)+backjumpAndExplore t = toLog $ exploreLog t (A M.empty M.empty M.empty)+ where+ toLog :: T.Progress step fail done -> Log step done+ toLog = T.foldProgress T.Step (const (T.Fail ())) T.Done
cabal/cabal-install/Distribution/Client/Dependency/Modular/Flag.hs view
@@ -1,5 +1,19 @@ {-# LANGUAGE DeriveFunctor #-}-module Distribution.Client.Dependency.Modular.Flag where+module Distribution.Client.Dependency.Modular.Flag+ ( FInfo(..)+ , Flag+ , FlagInfo+ , FN(..)+ , QFN+ , QSN+ , SN(..)+ , mkFlag+ , showFBool+ , showQFN+ , showQFNBool+ , showQSN+ , showQSNBool+ ) where import Data.Map as M import Prelude hiding (pi)@@ -12,10 +26,6 @@ -- | Flag name. Consists of a package instance and the flag identifier itself. data FN qpn = FN (PI qpn) Flag deriving (Eq, Ord, Show, Functor)---- | Extract the package name from a flag name.-getPN :: FN qpn -> qpn-getPN (FN (PI qpn _) _) = qpn -- | Flag identifier. Just a string. type Flag = FlagName
cabal/cabal-install/Distribution/Client/Dependency/Modular/Index.hs view
@@ -1,4 +1,9 @@-module Distribution.Client.Dependency.Modular.Index where+module Distribution.Client.Dependency.Modular.Index+ ( Index+ , PInfo(..)+ , defaultQualifyOptions+ , mkIndex+ ) where import Data.List as L import Data.Map as M
cabal/cabal-install/Distribution/Client/Dependency/Modular/IndexConversion.hs view
@@ -1,14 +1,12 @@-module Distribution.Client.Dependency.Modular.IndexConversion (- convPIs- -- * TODO: The following don't actually seem to be used anywhere?- , convIPI- , convSPI- , convPI- ) where+module Distribution.Client.Dependency.Modular.IndexConversion+ ( convPIs+ ) where import Data.List as L import Data.Map as M import Data.Maybe+import Data.Monoid as Mon+import Data.Set as S import Prelude hiding (pi) import qualified Distribution.Client.PackageIndex as CI@@ -18,6 +16,7 @@ import Distribution.InstalledPackageInfo as IPI import Distribution.Package -- from Cabal import Distribution.PackageDescription as PD -- from Cabal+import Distribution.PackageDescription.Configuration as PDC import qualified Distribution.Simple.PackageIndex as SI import Distribution.System @@ -40,7 +39,7 @@ -- fix the problem there, so for now, shadowing is only activated if -- explicitly requested. convPIs :: OS -> Arch -> CompilerInfo -> Bool -> Bool ->- SI.InstalledPackageIndex -> CI.PackageIndex SourcePackage -> Index+ SI.InstalledPackageIndex -> CI.PackageIndex (SourcePackage loc) -> Index convPIs os arch comp sip strfl iidx sidx = mkIndex (convIPI' sip iidx ++ convSPI' os arch comp strfl sidx) @@ -59,21 +58,18 @@ shadow (pn, i, PInfo fdeps fds _) | sip = (pn, i, PInfo fdeps fds (Just Shadowed)) shadow x = x -convIPI :: Bool -> SI.InstalledPackageIndex -> Index-convIPI sip = mkIndex . convIPI' sip- -- | Convert a single installed package into the solver-specific format. convIP :: SI.InstalledPackageIndex -> InstalledPackageInfo -> (PN, I, PInfo) convIP idx ipi =- let ipid = IPI.installedPackageId ipi- i = I (pkgVersion (sourcePackageId ipi)) (Inst ipid)- pn = pkgName (sourcePackageId ipi)- in case mapM (convIPId pn idx) (IPI.depends ipi) of+ case mapM (convIPId pn idx) (IPI.depends ipi) of Nothing -> (pn, i, PInfo [] M.empty (Just Broken)) Just fds -> (pn, i, PInfo (setComp fds) M.empty Nothing) where -- We assume that all dependencies of installed packages are _library_ deps- setComp = setCompFlaggedDeps ComponentLib+ ipid = IPI.installedUnitId ipi+ i = I (pkgVersion (sourcePackageId ipi)) (Inst ipid)+ pn = pkgName (sourcePackageId ipi)+ setComp = setCompFlaggedDeps (ComponentLib (unPackageName pn)) -- TODO: Installed packages should also store their encapsulations! -- | Convert dependencies specified by an installed package id into@@ -82,9 +78,9 @@ -- May return Nothing if the package can't be found in the index. That -- indicates that the original package having this dependency is broken -- and should be ignored.-convIPId :: PN -> SI.InstalledPackageIndex -> InstalledPackageId -> Maybe (FlaggedDep () PN)+convIPId :: PN -> SI.InstalledPackageIndex -> UnitId -> Maybe (FlaggedDep () PN) convIPId pn' idx ipid =- case SI.lookupInstalledPackageId idx ipid of+ case SI.lookupUnitId idx ipid of Nothing -> Nothing Just ipi -> let i = I (pkgVersion (sourcePackageId ipi)) (Inst ipid) pn = pkgName (sourcePackageId ipi)@@ -93,15 +89,11 @@ -- | Convert a cabal-install source package index to the simpler, -- more uniform index format of the solver. convSPI' :: OS -> Arch -> CompilerInfo -> Bool ->- CI.PackageIndex SourcePackage -> [(PN, I, PInfo)]+ CI.PackageIndex (SourcePackage loc) -> [(PN, I, PInfo)] convSPI' os arch cinfo strfl = L.map (convSP os arch cinfo strfl) . CI.allPackages -convSPI :: OS -> Arch -> CompilerInfo -> Bool ->- CI.PackageIndex SourcePackage -> Index-convSPI os arch cinfo strfl = mkIndex . convSPI' os arch cinfo strfl- -- | Convert a single source package into the solver-specific format.-convSP :: OS -> Arch -> CompilerInfo -> Bool -> SourcePackage -> (PN, I, PInfo)+convSP :: OS -> Arch -> CompilerInfo -> Bool -> SourcePackage loc -> (PN, I, PInfo) convSP os arch cinfo strfl (SourcePackage (PackageIdentifier pn pv) gpd _ _pl) = let i = I pv InRepo in (pn, i, convGPD os arch cinfo strfl (PI pn i) gpd)@@ -113,24 +105,84 @@ -- | Convert a generic package description to a solver-specific 'PInfo'. convGPD :: OS -> Arch -> CompilerInfo -> Bool -> PI PN -> GenericPackageDescription -> PInfo-convGPD os arch comp strfl pi+convGPD os arch cinfo strfl pi@(PI pn _) (GenericPackageDescription pkg flags libs exes tests benchs) = let fds = flagInfo strfl flags- conv = convCondTree os arch comp pi fds (const True)++ -- | We have to be careful to filter out dependencies on+ -- internal libraries, since they don't refer to real packages+ -- and thus cannot actually be solved over. We'll do this+ -- by creating a set of package names which are "internal"+ -- and dropping them as we convert.+ ipns = S.fromList [ PackageName nm+ | (nm, _) <- libs+ -- Don't include the true package name;+ -- qualification could make this relevant.+ -- TODO: Can we qualify over internal+ -- dependencies? Not for now!+ , PackageName nm /= pn ]++ conv :: Mon.Monoid a => Component -> (a -> BuildInfo) ->+ CondTree ConfVar [Dependency] a -> FlaggedDeps Component PN+ conv comp getInfo = convCondTree os arch cinfo pi fds comp getInfo ipns .+ PDC.addBuildableCondition getInfo++ flagged_deps+ = concatMap (\(nm, ds) -> conv (ComponentLib nm) libBuildInfo ds) libs+ ++ concatMap (\(nm, ds) -> conv (ComponentExe nm) buildInfo ds) exes+ ++ prefix (Stanza (SN pi TestStanzas))+ (L.map (\(nm, ds) -> conv (ComponentTest nm) testBuildInfo ds) tests)+ ++ prefix (Stanza (SN pi BenchStanzas))+ (L.map (\(nm, ds) -> conv (ComponentBench nm) benchmarkBuildInfo ds) benchs)+ ++ maybe [] (convSetupBuildInfo pi) (setupBuildInfo pkg)+ in- PInfo- (maybe [] (conv ComponentLib ) libs ++- maybe [] (convSetupBuildInfo pi) (setupBuildInfo pkg) ++- concatMap (\(nm, ds) -> conv (ComponentExe nm) ds) exes ++- prefix (Stanza (SN pi TestStanzas))- (L.map (\(nm, ds) -> conv (ComponentTest nm) ds) tests) ++- prefix (Stanza (SN pi BenchStanzas))- (L.map (\(nm, ds) -> conv (ComponentBench nm) ds) benchs))- fds- Nothing+ PInfo flagged_deps fds Nothing -prefix :: (FlaggedDeps comp qpn -> FlaggedDep comp' qpn) -> [FlaggedDeps comp qpn] -> FlaggedDeps comp' qpn+-- With convenience libraries, we have to do some work. Imagine you+-- have the following Cabal file:+--+-- name: foo+-- library foo-internal+-- build-depends: external-a+-- library+-- build-depends: foo-internal, external-b+-- library foo-helper+-- build-depends: foo, external-c+-- test-suite foo-tests+-- build-depends: foo-helper, external-d+--+-- What should the final flagged dependency tree be? Ideally, it+-- should look like this:+--+-- [ Simple (Dep external-a) (Library foo-internal)+-- , Simple (Dep external-b) (Library foo)+-- , Stanza (SN foo TestStanzas) $+-- [ Simple (Dep external-c) (Library foo-helper)+-- , Simple (Dep external-d) (TestSuite foo-tests) ]+-- ]+--+-- There are two things to note:+--+-- 1. First, we eliminated the "local" dependencies foo-internal+-- and foo-helper. This are implicitly assumed to refer to "foo"+-- so we don't need to have them around. If you forget this,+-- Cabal will then try to pick a version for "foo-helper" but+-- no such package exists (this is the cost of overloading+-- build-depends to refer to both packages and components.)+--+-- 2. Second, it is more precise to have external-c be qualified+-- by a test stanza, since foo-helper only needs to be built if+-- your are building the test suite (and not the main library).+-- If you omit it, Cabal will always attempt to depsolve for+-- foo-helper even if you aren't building the test suite.++-- | Create a flagged dependency tree from a list @fds@ of flagged+-- dependencies, using @f@ to form the tree node (@f@ will be+-- something like @Stanza sn@).+prefix :: (FlaggedDeps comp qpn -> FlaggedDep comp' qpn)+ -> [FlaggedDeps comp qpn] -> FlaggedDeps comp' qpn prefix _ [] = [] prefix f fds = [f (concat fds)] @@ -139,17 +191,37 @@ flagInfo :: Bool -> [PD.Flag] -> FlagInfo flagInfo strfl = M.fromList . L.map (\ (MkFlag fn _ b m) -> (fn, FInfo b m (not (strfl || m)))) --- | Convert condition trees to flagged dependencies.+-- | Internal package names, which should not be interpreted as true+-- dependencies.+type IPNs = Set PN++-- | Convenience function to delete a 'FlaggedDep' if it's+-- for a 'PN' that isn't actually real.+filterIPNs :: IPNs -> Dependency -> FlaggedDep Component PN -> FlaggedDeps Component PN+filterIPNs ipns (Dependency pn _) fd+ | S.notMember pn ipns = [fd]+ | otherwise = []++-- | Convert condition trees to flagged dependencies. Mutually+-- recursive with 'convBranch'. See 'convBranch' for an explanation+-- of all arguments preceeding the input 'CondTree'. convCondTree :: OS -> Arch -> CompilerInfo -> PI PN -> FlagInfo ->- (a -> Bool) -> -- how to detect if a branch is active Component ->+ (a -> BuildInfo) ->+ IPNs -> CondTree ConfVar [Dependency] a -> FlaggedDeps Component PN-convCondTree os arch cinfo pi@(PI pn _) fds p comp (CondNode info ds branches)- | p info = L.map (\d -> D.Simple (convDep pn d) comp) ds -- unconditional dependencies- ++ concatMap (convBranch os arch cinfo pi fds p comp) branches- | otherwise = []+convCondTree os arch cinfo pi@(PI pn _) fds comp getInfo ipns (CondNode info ds branches) =+ concatMap+ (\d -> filterIPNs ipns d (D.Simple (convDep pn d) comp))+ ds -- unconditional package dependencies+ ++ L.map (\e -> D.Simple (Ext e) comp) (PD.allExtensions bi) -- unconditional extension dependencies+ ++ L.map (\l -> D.Simple (Lang l) comp) (PD.allLanguages bi) -- unconditional language dependencies+ ++ L.map (\(Dependency pkn vr) -> D.Simple (Pkg pkn vr) comp) (PD.pkgconfigDepends bi) -- unconditional pkg-config dependencies+ ++ concatMap (convBranch os arch cinfo pi fds comp getInfo ipns) branches+ where+ bi = getInfo info --- | Branch interpreter.+-- | Branch interpreter. Mutually recursive with 'convCondTree'. -- -- Here, we try to simplify one of Cabal's condition tree branches into the -- solver's flagged dependency format, which is weaker. Condition trees can@@ -157,16 +229,40 @@ -- flags (such as architecture, or compiler flavour). We try to evaluate the -- special flags and subsequently simplify to a tree that only depends on -- simple flag choices.+--+-- This function takes a number of arguments:+--+-- 1. Some pre dependency-solving known information ('OS', 'Arch',+-- 'CompilerInfo') for @os()@, @arch()@ and @impl()@ variables,+--+-- 2. The package instance @'PI' 'PN'@ which this condition tree+-- came from, so that we can correctly associate @flag()@+-- variables with the correct package name qualifier,+--+-- 3. The flag defaults 'FlagInfo' so that we can populate+-- 'Flagged' dependencies with 'FInfo',+--+-- 4. The name of the component 'Component' so we can record where+-- the fine-grained information about where the component came+-- from (see 'convCondTree'), and+--+-- 5. A selector to extract the 'BuildInfo' from the leaves of+-- the 'CondTree' (which actually contains the needed+-- dependency information.)+--+-- 6. The set of package names which should be considered internal+-- dependencies, and thus not handled as dependencies. convBranch :: OS -> Arch -> CompilerInfo -> PI PN -> FlagInfo ->- (a -> Bool) -> -- how to detect if a branch is active Component ->+ (a -> BuildInfo) ->+ IPNs -> (Condition ConfVar, CondTree ConfVar [Dependency] a, Maybe (CondTree ConfVar [Dependency] a)) -> FlaggedDeps Component PN-convBranch os arch cinfo pi fds p comp (c', t', mf') =- go c' ( convCondTree os arch cinfo pi fds p comp t')- (maybe [] (convCondTree os arch cinfo pi fds p comp) mf')+convBranch os arch cinfo pi@(PI pn _) fds comp getInfo ipns (c', t', mf') =+ go c' ( convCondTree os arch cinfo pi fds comp getInfo ipns t')+ (maybe [] (convCondTree os arch cinfo pi fds comp getInfo ipns) mf') where go :: Condition ConfVar -> FlaggedDeps Component PN -> FlaggedDeps Component PN -> FlaggedDeps Component PN@@ -198,20 +294,21 @@ -- with deferring flag choices will then usually first resolve this package, -- and try an already installed version before imposing a default flag choice -- that might not be what we want.+ --+ -- Note that we make assumptions here on the form of the dependencies that+ -- can occur at this point. In particular, no occurrences of Fixed, and no+ -- occurrences of multiple version ranges, as all dependencies below this+ -- point have been generated using 'convDep'. extractCommon :: FlaggedDeps Component PN -> FlaggedDeps Component PN -> FlaggedDeps Component PN- extractCommon ps ps' = [ D.Simple (Dep pn (Constrained [])) comp- | D.Simple (Dep pn _) _ <- ps- , D.Simple (Dep pn' _) _ <- ps'- , pn == pn'+ extractCommon ps ps' = [ D.Simple (Dep pn1 (Constrained [(vr1 .||. vr2, Goal (P pn) [])])) comp+ | D.Simple (Dep pn1 (Constrained [(vr1, _)])) _ <- ps+ , D.Simple (Dep pn2 (Constrained [(vr2, _)])) _ <- ps'+ , pn1 == pn2 ] -- | Convert a Cabal dependency to a solver-specific dependency. convDep :: PN -> Dependency -> Dep PN convDep pn' (Dependency pn vr) = Dep pn (Constrained [(vr, Goal (P pn') [])])---- | Convert a Cabal package identifier to a solver-specific dependency.-convPI :: PN -> PackageIdentifier -> Dep PN-convPI pn' (PackageIdentifier pn v) = Dep pn (Constrained [(eqVR v, Goal (P pn') [])]) -- | Convert setup dependencies convSetupBuildInfo :: PI PN -> SetupBuildInfo -> FlaggedDeps Component PN
cabal/cabal-install/Distribution/Client/Dependency/Modular/Linking.hs view
@@ -274,6 +274,12 @@ lg' = M.findWithDefault (lgSingleton qpn' Nothing) qpn' $ vsLinks vs lg'' <- lift' $ lgMerge parents lg lg' updateLinkGroup lg''+ -- For extensions and language dependencies, there is nothing to do.+ -- No choice is involved, just checking, so there is nothing to link.+ go (Simple (Ext _) _) = return ()+ go (Simple (Lang _) _) = return ()+ -- Similarly for pkg-config constraints+ go (Simple (Pkg _ _) _) = return () go (Flagged fn _ t f) = do vs <- get case M.lookup fn (vsFlags vs) of
cabal/cabal-install/Distribution/Client/Dependency/Modular/Log.hs view
@@ -1,7 +1,15 @@-module Distribution.Client.Dependency.Modular.Log where+module Distribution.Client.Dependency.Modular.Log+ ( Log+ , continueWith+ , failWith+ , logToProgress+ , succeedWith+ , tryWith+ ) where import Control.Applicative import Data.List as L+import Data.Maybe (isNothing) import Data.Set as S import Distribution.Client.Dependency.Types -- from Cabal@@ -18,107 +26,81 @@ -- Parameterized over the type of actual messages and the final result. type Log m a = Progress m () a --- | Turns a log into a list of messages paired with a final result. A final result--- of 'Nothing' indicates failure. A final result of 'Just' indicates success.--- Keep in mind that forcing the second component of the returned pair will force the--- entire log.-runLog :: Log m a -> ([m], Maybe a)-runLog (Done x) = ([], Just x)-runLog (Fail _) = ([], Nothing)-runLog (Step m p) = let- (ms, r) = runLog p- in- (m : ms, r)+messages :: Progress step fail done -> [step]+messages = foldProgress (:) (const []) (const []) -- | Postprocesses a log file. Takes as an argument a limit on allowed backjumps. -- If the limit is 'Nothing', then infinitely many backjumps are allowed. If the -- limit is 'Just 0', backtracking is completely disabled. logToProgress :: Maybe Int -> Log Message a -> Progress String String a logToProgress mbj l = let- (ms, s) = runLog l- -- 'Nothing' for 's' means search tree exhaustively searched and failed- (es, e) = proc 0 ms -- catch first error (always)- -- 'Nothing' in 'e' means no backjump found- (ns, t) = case mbj of- Nothing -> (ms, Nothing)- Just n -> proc n ms- -- 'Nothing' in 't' means backjump limit not reached- -- prefer first error over later error- (exh, r) = case t of- -- backjump limit not reached- Nothing -> case s of- Nothing -> (True, e) -- failed after exhaustive search- Just _ -> (True, Nothing) -- success- -- backjump limit reached; prefer first error- Just _ -> (False, e) -- failed after backjump limit was reached+ es = proc (Just 0) l -- catch first error (always)+ ms = useFirstError (proc mbj l) in go es es -- trace for first error- (showMessages (const True) True ns) -- shortened run- r s exh- where- -- Proc takes the allowed number of backjumps and a list of messages and explores the- -- message list until the maximum number of backjumps has been reached. The log until- -- that point as well as whether we have encountered an error or not are returned.- proc :: Int -> [Message] -> ([Message], Maybe (ConflictSet QPN))- proc _ [] = ([], Nothing)- proc n ( Failure cs Backjump : xs@(Leave : Failure cs' Backjump : _))- | cs == cs' = proc n xs -- repeated backjumps count as one- proc 0 ( Failure cs Backjump : _ ) = ([], Just cs)- proc n (x@(Failure _ Backjump) : xs) = (\ ~(ys, r) -> (x : ys, r)) (proc (n - 1) xs)- proc n (x : xs) = (\ ~(ys, r) -> (x : ys, r)) (proc n xs)-- -- This function takes a lot of arguments. The first two are both supposed to be- -- the log up to the first error. That's the error that will always be printed in- -- case we do not find a solution. We pass this log twice, because we evaluate it- -- in parallel with the full log, but we also want to retain the reference to its- -- beginning for when we print it. This trick prevents a space leak!- --- -- The third argument is the full log, the fifth and six error conditions.- -- The seventh argument indicates whether the search was exhaustive.- --- -- The order of arguments is important! In particular 's' must not be evaluated- -- unless absolutely necessary. It contains the final result, and if we shortcut- -- with an error due to backjumping, evaluating 's' would still require traversing- -- the entire tree.- go ms (_ : ns) (x : xs) r s exh = Step x (go ms ns xs r s exh)- go ms [] (x : xs) r s exh = Step x (go ms [] xs r s exh)- go ms _ [] (Just cs) _ exh = Fail $- "Could not resolve dependencies:\n" ++- unlines (showMessages (L.foldr (\ v _ -> v `S.member` cs) True) False ms) ++- (if exh then "Dependency tree exhaustively searched.\n"- else "Backjump limit reached (" ++ currlimit mbj ++- "change with --max-backjumps or try to run with --reorder-goals).\n")- where currlimit (Just n) = "currently " ++ show n ++ ", "- currlimit Nothing = ""- go _ _ [] _ (Just s) _ = Done s- go _ _ [] _ _ _ = Fail ("Could not resolve dependencies; something strange happened.") -- should not happen--logToProgress' :: Log Message a -> Progress String String a-logToProgress' l = let- (ms, r) = runLog l- xs = showMessages (const True) True ms- in go xs r+ (showMessages (const True) True ms) -- run with backjump limit applied where- go [x] Nothing = Fail x- go [] Nothing = Fail ""- go [] (Just r) = Done r- go (x:xs) r = Step x (go xs r)+ -- Proc takes the allowed number of backjumps and a 'Progress' and explores the+ -- messages until the maximum number of backjumps has been reached. It filters out+ -- and ignores repeated backjumps. If proc reaches the backjump limit, it truncates+ -- the 'Progress' and ends it with the last conflict set. Otherwise, it leaves the+ -- original success result or replaces the original failure with 'Nothing'.+ proc :: Maybe Int -> Progress Message a b -> Progress Message (Maybe (ConflictSet QPN)) b+ proc _ (Done x) = Done x+ proc _ (Fail _) = Fail Nothing+ proc mbj' (Step (Failure cs Backjump) xs@(Step Leave (Step (Failure cs' Backjump) _)))+ | cs == cs' = proc mbj' xs -- repeated backjumps count as one+ proc (Just 0) (Step (Failure cs Backjump) _) = Fail (Just cs)+ proc (Just n) (Step x@(Failure _ Backjump) xs) = Step x (proc (Just (n - 1)) xs)+ proc mbj' (Step x xs) = Step x (proc mbj' xs) + -- Sets the conflict set from the first backjump as the final error, and records+ -- whether the search was exhaustive.+ useFirstError :: Progress Message (Maybe (ConflictSet QPN)) b+ -> Progress Message (Bool, Maybe (ConflictSet QPN)) b+ useFirstError = replace Nothing+ where+ replace _ (Done x) = Done x+ replace cs' (Fail cs) = -- 'Nothing' means backjump limit not reached.+ -- Prefer first error over later error.+ Fail (isNothing cs, cs' <|> cs)+ replace Nothing (Step x@(Failure cs Backjump) xs) = Step x $ replace (Just cs) xs+ replace cs' (Step x xs) = Step x $ replace cs' xs -runLogIO :: Log Message a -> IO (Maybe a)-runLogIO x =- do- let (ms, r) = runLog x- putStr (unlines $ showMessages (const True) True ms)- return r+ -- The first two arguments are both supposed to be the log up to the first error.+ -- That's the error that will always be printed in case we do not find a solution.+ -- We pass this log twice, because we evaluate it in parallel with the full log,+ -- but we also want to retain the reference to its beginning for when we print it.+ -- This trick prevents a space leak!+ --+ -- The third argument is the full log, ending with either the solution or the+ -- exhaustiveness and first conflict set.+ go :: Progress Message a b+ -> Progress Message a b+ -> Progress String (Bool, Maybe (ConflictSet QPN)) b+ -> Progress String String b+ go ms (Step _ ns) (Step x xs) = Step x (go ms ns xs)+ go ms r (Step x xs) = Step x (go ms r xs)+ go ms _ (Fail (exh, Just cs)) = Fail $+ "Could not resolve dependencies:\n" +++ unlines (messages $ showMessages (L.foldr (\ v _ -> v `S.member` cs) True) False ms) +++ (if exh then "Dependency tree exhaustively searched.\n"+ else "Backjump limit reached (" ++ currlimit mbj +++ "change with --max-backjumps or try to run with --reorder-goals).\n")+ where currlimit (Just n) = "currently " ++ show n ++ ", "+ currlimit Nothing = ""+ go _ _ (Done s) = Done s+ go _ _ (Fail (_, Nothing)) = Fail ("Could not resolve dependencies; something strange happened.") -- should not happen -failWith :: m -> Log m a-failWith m = Step m (Fail ())+failWith :: step -> fail -> Progress step fail done+failWith s f = Step s (Fail f) -succeedWith :: m -> a -> Log m a-succeedWith m x = Step m (Done x)+succeedWith :: step -> done -> Progress step fail done+succeedWith s d = Step s (Done d) -continueWith :: m -> Log m a -> Log m a+continueWith :: step -> Progress step fail done -> Progress step fail done continueWith = Step -tryWith :: Message -> Log Message a -> Log Message a-tryWith m x = Step m (Step Enter x) <|> failWith Leave+tryWith :: Message+ -> Progress Message fail done+ -> Progress Message fail done+tryWith m = Step m . Step Enter . foldProgress Step (failWith Leave) Done
cabal/cabal-install/Distribution/Client/Dependency/Modular/Message.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE BangPatterns #-}+ module Distribution.Client.Dependency.Modular.Message ( Message(..), showMessages@@ -12,8 +14,9 @@ import Distribution.Client.Dependency.Modular.Flag import Distribution.Client.Dependency.Modular.Package import Distribution.Client.Dependency.Modular.Tree+ ( FailReason(..), POption(..) ) import Distribution.Client.Dependency.Types- ( ConstraintSource(..), showConstraintSource )+ ( ConstraintSource(..), showConstraintSource, Progress(..) ) data Message = Enter -- ^ increase indentation level@@ -37,41 +40,73 @@ -- The second argument indicates if the level numbers should be shown. This is -- recommended for any trace that involves backtracking, because only the level -- numbers will allow to keep track of backjumps.-showMessages :: ([Var QPN] -> Bool) -> Bool -> [Message] -> [String]+showMessages :: ([Var QPN] -> Bool) -> Bool -> Progress Message a b -> Progress String a b showMessages p sl = go [] 0 where- go :: [Var QPN] -> Int -> [Message] -> [String]- go _ _ [] = []+ -- The stack 'v' represents variables that are currently assigned by the+ -- solver. 'go' pushes a variable for a recursive call when it encounters+ -- 'TryP', 'TryF', or 'TryS' and pops a variable when it encounters 'Leave'.+ -- When 'go' processes a package goal, or a package goal followed by a+ -- 'Failure', it calls 'atLevel' with the goal variable at the head of the+ -- stack so that the predicate can also select messages relating to package+ -- goal choices.+ go :: [Var QPN] -> Int -> Progress Message a b -> Progress String a b+ go !_ !_ (Done x) = Done x+ go !_ !_ (Fail x) = Fail x -- complex patterns- go v l (TryP qpn i : Enter : Failure c fr : Leave : ms) = goPReject v l qpn [i] c fr ms- go v l (TryF qfn b : Enter : Failure c fr : Leave : ms) = (atLevel (add (F qfn) v) l $ "rejecting: " ++ showQFNBool qfn b ++ showFR c fr) (go v l ms)- go v l (TryS qsn b : Enter : Failure c fr : Leave : ms) = (atLevel (add (S qsn) v) l $ "rejecting: " ++ showQSNBool qsn b ++ showFR c fr) (go v l ms)- go v l (Next (Goal (P qpn) gr) : TryP qpn' i : ms@(Enter : Next _ : _)) = (atLevel (add (P qpn) v) l $ "trying: " ++ showQPNPOpt qpn' i ++ showGRs gr) (go (add (P qpn) v) l ms)- go v l (Failure c Backjump : ms@(Leave : Failure c' Backjump : _)) | c == c' = go v l ms+ go !v !l (Step (TryP qpn i) (Step Enter (Step (Failure c fr) (Step Leave ms)))) =+ goPReject v l qpn [i] c fr ms+ go !v !l (Step (TryF qfn b) (Step Enter (Step (Failure c fr) (Step Leave ms)))) =+ (atLevel (add (F qfn) v) l $ "rejecting: " ++ showQFNBool qfn b ++ showFR c fr) (go v l ms)+ go !v !l (Step (TryS qsn b) (Step Enter (Step (Failure c fr) (Step Leave ms)))) =+ (atLevel (add (S qsn) v) l $ "rejecting: " ++ showQSNBool qsn b ++ showFR c fr) (go v l ms)+ go !v !l (Step (Next (Goal (P qpn) gr)) (Step (TryP qpn' i) ms@(Step Enter (Step (Next _) _)))) =+ (atLevel (add (P qpn) v) l $ "trying: " ++ showQPNPOpt qpn' i ++ showGRs gr) (go (add (P qpn) v) l ms)+ go !v !l (Step (Next (Goal (P qpn) gr)) (Step (Failure c fr) ms)) =+ let v' = add (P qpn) v+ in (atLevel v' l $ showPackageGoal qpn gr) $ (atLevel v' l $ showFailure c fr) (go v l ms)+ go !v !l (Step (Failure c Backjump) ms@(Step Leave (Step (Failure c' Backjump) _)))+ | c == c' = go v l ms -- standard display- go v l (Enter : ms) = go v (l+1) ms- go v l (Leave : ms) = go (drop 1 v) (l-1) ms- go v l (TryP qpn i : ms) = (atLevel (add (P qpn) v) l $ "trying: " ++ showQPNPOpt qpn i) (go (add (P qpn) v) l ms)- go v l (TryF qfn b : ms) = (atLevel (add (F qfn) v) l $ "trying: " ++ showQFNBool qfn b) (go (add (F qfn) v) l ms)- go v l (TryS qsn b : ms) = (atLevel (add (S qsn) v) l $ "trying: " ++ showQSNBool qsn b) (go (add (S qsn) v) l ms)- go v l (Next (Goal (P qpn) gr) : ms) = (atLevel (add (P qpn) v) l $ "next goal: " ++ showQPN qpn ++ showGRs gr) (go v l ms)- go v l (Next _ : ms) = go v l ms -- ignore flag goals in the log- go v l (Success : ms) = (atLevel v l $ "done") (go v l ms)- go v l (Failure c fr : ms) = (atLevel v l $ "fail" ++ showFR c fr) (go v l ms)+ go !v !l (Step Enter ms) = go v (l+1) ms+ go !v !l (Step Leave ms) = go (drop 1 v) (l-1) ms+ go !v !l (Step (TryP qpn i) ms) = (atLevel (add (P qpn) v) l $ "trying: " ++ showQPNPOpt qpn i) (go (add (P qpn) v) l ms)+ go !v !l (Step (TryF qfn b) ms) = (atLevel (add (F qfn) v) l $ "trying: " ++ showQFNBool qfn b) (go (add (F qfn) v) l ms)+ go !v !l (Step (TryS qsn b) ms) = (atLevel (add (S qsn) v) l $ "trying: " ++ showQSNBool qsn b) (go (add (S qsn) v) l ms)+ go !v !l (Step (Next (Goal (P qpn) gr)) ms) = (atLevel (add (P qpn) v) l $ showPackageGoal qpn gr) (go v l ms)+ go !v !l (Step (Next _) ms) = go v l ms -- ignore flag goals in the log+ go !v !l (Step (Success) ms) = (atLevel v l $ "done") (go v l ms)+ go !v !l (Step (Failure c fr) ms) = (atLevel v l $ showFailure c fr) (go v l ms) + showPackageGoal :: QPN -> QGoalReasonChain -> String+ showPackageGoal qpn gr = "next goal: " ++ showQPN qpn ++ showGRs gr++ showFailure :: ConflictSet QPN -> FailReason -> String+ showFailure c fr = "fail" ++ showFR c fr+ add :: Var QPN -> [Var QPN] -> [Var QPN] add v vs = simplifyVar v : vs -- special handler for many subsequent package rejections- goPReject :: [Var QPN] -> Int -> QPN -> [POption] -> ConflictSet QPN -> FailReason -> [Message] -> [String]- goPReject v l qpn is c fr (TryP qpn' i : Enter : Failure _ fr' : Leave : ms) | qpn == qpn' && fr == fr' = goPReject v l qpn (i : is) c fr ms- goPReject v l qpn is c fr ms = (atLevel (P qpn : v) l $ "rejecting: " ++ L.intercalate ", " (map (showQPNPOpt qpn) (reverse is)) ++ showFR c fr) (go v l ms)+ goPReject :: [Var QPN]+ -> Int+ -> QPN+ -> [POption]+ -> ConflictSet QPN+ -> FailReason+ -> Progress Message a b+ -> Progress String a b+ goPReject v l qpn is c fr (Step (TryP qpn' i) (Step Enter (Step (Failure _ fr') (Step Leave ms))))+ | qpn == qpn' && fr == fr' = goPReject v l qpn (i : is) c fr ms+ goPReject v l qpn is c fr ms =+ (atLevel (P qpn : v) l $ "rejecting: " ++ L.intercalate ", " (map (showQPNPOpt qpn) (reverse is)) ++ showFR c fr) (go v l ms) -- write a message, but only if it's relevant; we can also enable or disable the display of the current level+ atLevel :: [Var QPN] -> Int -> String -> Progress String a b -> Progress String a b atLevel v l x xs | sl && p v = let s = show l- in ("[" ++ replicate (3 - length s) '_' ++ s ++ "] " ++ x) : xs- | p v = x : xs+ in Step ("[" ++ replicate (3 - length s) '_' ++ s ++ "] " ++ x) xs+ | p v = Step x xs | otherwise = xs showQPNPOpt :: QPN -> POption -> String@@ -106,6 +141,7 @@ showFR c Backjump = " (backjumping, conflict set: " ++ showCS c ++ ")" showFR _ MultipleInstances = " (multiple instances)" showFR c (DependenciesNotLinked msg) = " (dependencies not linked: " ++ msg ++ "; conflict set: " ++ showCS c ++ ")"+showFR c CyclicDependencies = " (cyclic dependencies; conflict set: " ++ showCS c ++ ")" -- The following are internal failures. They should not occur. In the -- interest of not crashing unnecessarily, we still just print an error -- message though.
cabal/cabal-install/Distribution/Client/Dependency/Modular/PSQ.hs view
@@ -1,5 +1,36 @@-{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveFoldable, DeriveTraversable #-}-module Distribution.Client.Dependency.Modular.PSQ where+{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-}+module Distribution.Client.Dependency.Modular.PSQ+ ( PSQ(..) -- Unit test needs constructor access+ , Degree(..)+ , casePSQ+ , cons+ , degree+ , delete+ , dminimumBy+ , length+ , lookup+ , filter+ , filterKeys+ , firstOnly+ , fromList+ , isZeroOrOne+ , keys+ , map+ , mapKeys+ , mapWithKey+ , mapWithKeyState+ , minimumBy+ , null+ , prefer+ , preferByKeys+ , preferOrElse+ , snoc+ , sortBy+ , sortByKeys+ , splits+ , toList+ , union+ ) where -- Priority search queues. --@@ -8,14 +39,17 @@ -- (inefficiently implemented) lookup, because I think that queue-based -- operations and sorting turn out to be more efficiency-critical in practice. -import Data.Foldable+import Control.Arrow (first, second)++import qualified Data.Foldable as F import Data.Function-import Data.List as S hiding (foldr)+import qualified Data.List as S+import Data.Ord (comparing) import Data.Traversable-import Prelude hiding (foldr)+import Prelude hiding (foldr, length, lookup, filter, null, map) newtype PSQ k v = PSQ [(k, v)]- deriving (Eq, Show, Functor, Foldable, Traversable)+ deriving (Eq, Show, Functor, F.Foldable, Traversable) -- Qualified Foldable to avoid issues with FTP keys :: PSQ k v -> [k] keys (PSQ xs) = fmap fst xs@@ -24,22 +58,22 @@ lookup k (PSQ xs) = S.lookup k xs map :: (v1 -> v2) -> PSQ k v1 -> PSQ k v2-map f (PSQ xs) = PSQ (fmap (\ (k, v) -> (k, f v)) xs)+map f (PSQ xs) = PSQ (fmap (second f) xs) mapKeys :: (k1 -> k2) -> PSQ k1 v -> PSQ k2 v-mapKeys f (PSQ xs) = PSQ (fmap (\ (k, v) -> (f k, v)) xs)+mapKeys f (PSQ xs) = PSQ (fmap (first f) xs) mapWithKey :: (k -> a -> b) -> PSQ k a -> PSQ k b mapWithKey f (PSQ xs) = PSQ (fmap (\ (k, v) -> (k, f k v)) xs) mapWithKeyState :: (s -> k -> a -> (b, s)) -> PSQ k a -> s -> PSQ k b mapWithKeyState p (PSQ xs) s0 =- PSQ (foldr (\ (k, v) r s -> case p s k v of- (w, n) -> (k, w) : (r n))- (const []) xs s0)+ PSQ (F.foldr (\ (k, v) r s -> case p s k v of+ (w, n) -> (k, w) : (r n))+ (const []) xs s0) delete :: Eq k => k -> PSQ k a -> PSQ k a-delete k (PSQ xs) = PSQ (snd (partition ((== k) . fst) xs))+delete k (PSQ xs) = PSQ (snd (S.partition ((== k) . fst) xs)) fromList :: [(k, a)] -> PSQ k a fromList = PSQ@@ -69,6 +103,62 @@ sortByKeys :: (k -> k -> Ordering) -> PSQ k a -> PSQ k a sortByKeys cmp (PSQ xs) = PSQ (S.sortBy (cmp `on` fst) xs) +-- | Given a measure in form of a pseudo-peano-natural number,+-- determine the approximate minimum. This is designed to stop+-- even traversing the list as soon as we find any element with+-- measure 'ZeroOrOne'.+--+-- Always returns a one-element queue (except if the queue is+-- empty, then we return an empty queue again).+--+dminimumBy :: (a -> Degree) -> PSQ k a -> PSQ k a+dminimumBy _ (PSQ []) = PSQ []+dminimumBy sel (PSQ (x : xs)) = go (sel (snd x)) x xs+ where+ go ZeroOrOne v _ = PSQ [v]+ go _ v [] = PSQ [v]+ go c v (y : ys) = case compare c d of+ LT -> go c v ys+ EQ -> go c v ys+ GT -> go d y ys+ where+ d = sel (snd y)++minimumBy :: (a -> Int) -> PSQ k a -> PSQ k a+minimumBy sel (PSQ xs) =+ PSQ [snd (S.minimumBy (comparing fst) (S.map (\ x -> (sel (snd x), x)) xs))]++-- | Will partition the list according to the predicate. If+-- there is any element that satisfies the precidate, then only+-- the elements satisfying the predicate are returned.+-- Otherwise, the rest is returned.+--+prefer :: (a -> Bool) -> PSQ k a -> PSQ k a+prefer p (PSQ xs) =+ let+ (pro, con) = S.partition (p . snd) xs+ in+ if S.null pro then PSQ con else PSQ pro++-- | Variant of 'prefer' that takes a continuation for the case+-- that there are none of the desired elements.+preferOrElse :: (a -> Bool) -> (PSQ k a -> PSQ k a) -> PSQ k a -> PSQ k a+preferOrElse p k (PSQ xs) =+ let+ (pro, con) = S.partition (p . snd) xs+ in+ if S.null pro then k (PSQ con) else PSQ pro++-- | Variant of 'prefer' that takes a predicate on the keys+-- rather than on the values.+--+preferByKeys :: (k -> Bool) -> PSQ k a -> PSQ k a+preferByKeys p (PSQ xs) =+ let+ (pro, con) = S.partition (p . fst) xs+ in+ if S.null pro then PSQ con else PSQ pro+ filterKeys :: (k -> Bool) -> PSQ k a -> PSQ k a filterKeys p (PSQ xs) = PSQ (S.filter (p . fst) xs) @@ -78,17 +168,43 @@ length :: PSQ k a -> Int length (PSQ xs) = S.length xs --- | "Lazy length".+-- | Approximation of the branching degree. ----- Only approximates the length, but doesn't force the list.-llength :: PSQ k a -> Int-llength (PSQ []) = 0-llength (PSQ (_:[])) = 1-llength (PSQ (_:_:[])) = 2-llength (PSQ _) = 3+-- This is designed for computing the branching degree of a goal choice+-- node. If the degree is 0 or 1, it is always good to take that goal,+-- because we can either abort immediately, or have no other choice anyway.+--+-- So we do not actually want to compute the full degree (which is+-- somewhat costly) in cases where we have such an easy choice.+--+data Degree = ZeroOrOne | Two | Other+ deriving (Show, Eq) +instance Ord Degree where+ compare ZeroOrOne _ = LT -- lazy approximation+ compare _ ZeroOrOne = GT -- approximation+ compare Two Two = EQ+ compare Two Other = LT+ compare Other Two = GT+ compare Other Other = EQ++degree :: PSQ k a -> Degree+degree (PSQ []) = ZeroOrOne+degree (PSQ [_]) = ZeroOrOne+degree (PSQ [_, _]) = Two+degree (PSQ _) = Other+ null :: PSQ k a -> Bool null (PSQ xs) = S.null xs++isZeroOrOne :: PSQ k a -> Bool+isZeroOrOne (PSQ []) = True+isZeroOrOne (PSQ [_]) = True+isZeroOrOne _ = False++firstOnly :: PSQ k a -> PSQ k a+firstOnly (PSQ []) = PSQ []+firstOnly (PSQ (x : _)) = PSQ [x] toList :: PSQ k a -> [(k, a)] toList (PSQ xs) = xs
cabal/cabal-install/Distribution/Client/Dependency/Modular/Package.hs view
@@ -1,7 +1,26 @@ {-# LANGUAGE DeriveFunctor #-} module Distribution.Client.Dependency.Modular.Package- (module Distribution.Client.Dependency.Modular.Package,- module Distribution.Package) where+ ( I(..)+ , Loc(..)+ , PackageId+ , PackageIdentifier(..)+ , PackageName(..)+ , PI(..)+ , PN+ , PP(..)+ , Namespace(..)+ , Qualifier(..)+ , QPN+ , QPV+ , Q(..)+ , instI+ , makeIndependent+ , primaryPP+ , showI+ , showPI+ , showQPN+ , unPN+ ) where import Data.List as L @@ -24,7 +43,7 @@ type QPV = Q PV -- | Package id. Currently just a black-box string.-type PId = InstalledPackageId+type PId = UnitId -- | Location. Info about whether a package is installed or not, and where -- exactly it is located. For installed packages, uniquely identifies the@@ -41,11 +60,13 @@ -- | String representation of an instance. showI :: I -> String showI (I v InRepo) = showVer v-showI (I v (Inst (InstalledPackageId i))) = showVer v ++ "/installed" ++ shortId i+showI (I v (Inst uid)) = showVer v ++ "/installed" ++ shortId uid where -- A hack to extract the beginning of the package ABI hash- shortId = snip (splitAt 4) (++ "...") .- snip ((\ (x, y) -> (reverse x, y)) . break (=='-') . reverse) ('-':)+ shortId (SimpleUnitId (ComponentId i))+ = snip (splitAt 4) (++ "...")+ . snip ((\ (x, y) -> (reverse x, y)) . break (=='-') . reverse) ('-':)+ $ i snip p f xs = case p xs of (ys, zs) -> (if L.null zs then id else f) ys @@ -57,46 +78,79 @@ showPI :: PI QPN -> String showPI (PI qpn i) = showQPN qpn ++ "-" ++ showI i --- | Checks if a package instance corresponds to an installed package.-instPI :: PI qpn -> Bool-instPI (PI _ (I _ (Inst _))) = True-instPI _ = False- instI :: I -> Bool instI (I _ (Inst _)) = True instI _ = False --- | Package path.+-- | A package path consists of a namespace and a package path inside that+-- namespace.+data PP = PP Namespace Qualifier+ deriving (Eq, Ord, Show)++-- | Top-level namespace ----- Stored in reverse order-data PP =- -- User-specified independent goal- Independent Int PP- -- Setup dependencies are always considered independent from their package- | Setup PN PP- -- Any dependency on base is considered independent (allows for base shims)- | Base PN PP- -- Unqualified- | None+-- Package choices in different namespaces are considered completely independent+-- by the solver.+data Namespace =+ -- | The default namespace+ DefaultNamespace++ -- | Independent namespace+ --+ -- For now we just number these (rather than giving them more structure).+ | Independent Int deriving (Eq, Ord, Show) --- | Strip any 'Base' qualifiers from a PP+-- | Qualifier of a package within a namespace (see 'PP')+data Qualifier =+ -- | Top-level dependency in this namespace+ Unqualified++ -- | Any dependency on base is considered independent+ --+ -- This makes it possible to have base shims.+ | Base PN++ -- | Setup dependency+ --+ -- By rights setup dependencies ought to be nestable; after all, the setup+ -- dependencies of a package might themselves have setup dependencies, which+ -- are independent from everything else. However, this very quickly leads to+ -- infinite search trees in the solver. Therefore we limit ourselves to+ -- a single qualifier (within a given namespace).+ | Setup PN+ deriving (Eq, Ord, Show)++-- | Is the package in the primary group of packages. In particular this+-- does not include packages pulled in as setup deps. ----- (the Base qualifier does not get inherited)-stripBase :: PP -> PP-stripBase (Independent i pp) = Independent i (stripBase pp)-stripBase (Setup pn pp) = Setup pn (stripBase pp)-stripBase (Base _pn pp) = stripBase pp-stripBase None = None+primaryPP :: PP -> Bool+primaryPP (PP _ns q) = go q+ where+ go Unqualified = True+ go (Base _) = True+ go (Setup _) = False -- | String representation of a package path. ----- NOTE: This always ends in a period+-- NOTE: The result of 'showPP' is either empty or results in a period, so that+-- it can be prepended to a package name. showPP :: PP -> String-showPP (Independent i pp) = show i ++ "." ++ showPP pp-showPP (Setup pn pp) = display pn ++ "-setup" ++ "." ++ showPP pp-showPP (Base pn pp) = display pn ++ "." ++ showPP pp-showPP None = ""+showPP (PP ns q) =+ case ns of+ DefaultNamespace -> go q+ Independent i -> show i ++ "." ++ go q+ where+ -- Print the qualifier+ --+ -- NOTE: the base qualifier is for a dependency _on_ base; the qualifier is+ -- there to make sure different dependencies on base are all independent.+ -- So we want to print something like @"A.base"@, where the @"A."@ part+ -- is the qualifier and @"base"@ is the actual dependency (which, for the+ -- 'Base' qualifier, will always be @base@).+ go Unqualified = ""+ go (Setup pn) = display pn ++ "-setup."+ go (Base pn) = display pn ++ "." -- | A qualified entity. Pairs a package path with the entity. data Q a = Q PP a@@ -104,8 +158,7 @@ -- | Standard string representation of a qualified entity. showQ :: (a -> String) -> (Q a -> String)-showQ showa (Q None x) = showa x-showQ showa (Q pp x) = showPP pp ++ showa x+showQ showa (Q pp x) = showPP pp ++ showa x -- | Qualified package name. type QPN = Q PN@@ -118,8 +171,5 @@ -- them all independent. makeIndependent :: [PN] -> [QPN] makeIndependent ps = [ Q pp pn | (pn, i) <- zip ps [0::Int ..]- , let pp = Independent i None+ , let pp = PP (Independent i) Unqualified ]--unQualify :: Q a -> a-unQualify (Q _ x) = x
cabal/cabal-install/Distribution/Client/Dependency/Modular/Preference.hs view
@@ -1,5 +1,19 @@ {-# LANGUAGE CPP #-}-module Distribution.Client.Dependency.Modular.Preference where+module Distribution.Client.Dependency.Modular.Preference+ ( avoidReinstalls+ , deferSetupChoices+ , deferWeakFlagChoices+ , enforceManualFlags+ , enforcePackageConstraints+ , enforceSingleInstanceRestriction+ , firstGoal+ , preferBaseGoalChoice+ , preferEasyGoalChoices+ , preferLinked+ , preferPackagePreferences+ , preferReallyEasyGoalChoices+ , requireInstalled+ ) where -- Reordering or pruning the tree in order to prefer or make certain choices. @@ -12,12 +26,11 @@ import qualified Data.Set as S import Prelude hiding (sequence) import Control.Monad.Reader hiding (sequence)-import Data.Ord import Data.Map (Map) import Data.Traversable (sequence) import Distribution.Client.Dependency.Types- ( PackageConstraint(..), LabeledPackageConstraint(..)+ ( PackageConstraint(..), LabeledPackageConstraint(..), ConstraintSource(..) , PackagePreferences(..), InstalledPreference(..) ) import Distribution.Client.Types ( OptionalStanza(..) )@@ -25,7 +38,7 @@ import Distribution.Client.Dependency.Modular.Dependency import Distribution.Client.Dependency.Modular.Flag import Distribution.Client.Dependency.Modular.Package-import Distribution.Client.Dependency.Modular.PSQ as P+import qualified Distribution.Client.Dependency.Modular.PSQ as P import Distribution.Client.Dependency.Modular.Tree import Distribution.Client.Dependency.Modular.Version @@ -56,21 +69,23 @@ cmpL (Just _) Nothing = LT cmpL (Just _) (Just _) = EQ ---- | Ordering that treats preferred versions as greater than non-preferred--- versions.-preferredVersionsOrdering :: VR -> Ver -> Ver -> Ordering-preferredVersionsOrdering vr v1 v2 =- compare (checkVR vr v1) (checkVR vr v2)+-- | Ordering that treats versions satisfying more preferred ranges as greater+-- than versions satisfying less preferred ranges.+preferredVersionsOrdering :: [VR] -> Ver -> Ver -> Ordering+preferredVersionsOrdering vrs v1 v2 = compare (check v1) (check v2)+ where+ check v = Prelude.length . Prelude.filter (==True) .+ Prelude.map (flip checkVR v) $ vrs -- | Traversal that tries to establish package preferences (not constraints).--- Works by reordering choice nodes.+-- Works by reordering choice nodes. Also applies stanza preferences. preferPackagePreferences :: (PN -> PackagePreferences) -> Tree a -> Tree a-preferPackagePreferences pcs = packageOrderFor (const True) preference+preferPackagePreferences pcs = preferPackageStanzaPreferences pcs+ . packageOrderFor (const True) preference where preference pn i1@(I v1 _) i2@(I v2 _) =- let PackagePreferences vr ipref = pcs pn- in preferredVersionsOrdering vr v1 v2 `mappend` -- combines lexically+ let PackagePreferences vrs ipref _ = pcs pn+ in preferredVersionsOrdering vrs v1 v2 `mappend` -- combines lexically locationsOrdering ipref i1 i2 -- Note that we always rank installed before uninstalled, and later@@ -92,16 +107,36 @@ preferLatestOrdering :: I -> I -> Ordering preferLatestOrdering (I v1 _) (I v2 _) = compare v1 v2 +-- | Traversal that tries to establish package stanza enable\/disable+-- preferences. Works by reordering the branches of stanza choices.+preferPackageStanzaPreferences :: (PN -> PackagePreferences) -> Tree a -> Tree a+preferPackageStanzaPreferences pcs = trav go+ where+ go (SChoiceF qsn@(SN (PI (Q pp pn) _) s) gr _tr ts) | primaryPP pp =+ let PackagePreferences _ _ spref = pcs pn+ enableStanzaPref = s `elem` spref+ -- move True case first to try enabling the stanza+ ts' | enableStanzaPref = P.sortByKeys (flip compare) ts+ | otherwise = ts+ in SChoiceF qsn gr True ts' -- True: now weak choice+ go x = x+ -- | Helper function that tries to enforce a single package constraint on a -- given instance for a P-node. Translates the constraint into a -- tree-transformer that either leaves the subtree untouched, or replaces it -- with an appropriate failure node.-processPackageConstraintP :: ConflictSet QPN+processPackageConstraintP :: PP+ -> ConflictSet QPN -> I -> LabeledPackageConstraint -> Tree a -> Tree a-processPackageConstraintP c i (LabeledPackageConstraint pc src) r = go i pc+processPackageConstraintP pp _ _ (LabeledPackageConstraint _ src) r+ | src == ConstraintSourceUserTarget && not (primaryPP pp) = r+ -- the constraints arising from targets, like "foo-1.0" only apply to+ -- the main packages in the solution, they don't constrain setup deps++processPackageConstraintP _ c i (LabeledPackageConstraint pc src) r = go i pc where go (I v _) (PackageConstraintVersion _ vr) | checkVR vr v = r@@ -158,10 +193,10 @@ -> Tree QGoalReasonChain enforcePackageConstraints pcs = trav go where- go (PChoiceF qpn@(Q _ pn) gr ts) =+ go (PChoiceF qpn@(Q pp pn) gr ts) = let c = toConflictSet (Goal (P qpn) gr) -- compose the transformation functions for each of the relevant constraint- g = \ (POption i _) -> foldl (\ h pc -> h . processPackageConstraintP c i pc) id+ g = \ (POption i _) -> foldl (\ h pc -> h . processPackageConstraintP pp c i pc) id (M.findWithDefault [] pn pcs) in PChoiceF qpn gr (P.mapWithKey g ts) go (FChoiceF qfn@(FN (PI (Q _ pn) _) f) gr tr m ts) =@@ -196,22 +231,6 @@ isDisabled _ = False go x = x --- | Prefer installed packages over non-installed packages, generally.--- All installed packages or non-installed packages are treated as--- equivalent.-preferInstalled :: Tree a -> Tree a-preferInstalled = packageOrderFor (const True) (const preferInstalledOrdering)---- | Prefer packages with higher version numbers over packages with--- lower version numbers, for certain packages.-preferLatestFor :: (PN -> Bool) -> Tree a -> Tree a-preferLatestFor p = packageOrderFor p (const preferLatestOrdering)---- | Prefer packages with higher version numbers over packages with--- lower version numbers, for all packages.-preferLatest :: Tree a -> Tree a-preferLatest = preferLatestFor (const True)- -- | Require installed packages. requireInstalled :: (PN -> Bool) -> Tree QGoalReasonChain -> Tree QGoalReasonChain requireInstalled p = trav go@@ -245,7 +264,7 @@ | otherwise = PChoiceF qpn gr cs where disableReinstalls =- let installed = [ v | (POption (I v (Inst _)) _, _) <- toList cs ]+ let installed = [ v | (POption (I v (Inst _)) _, _) <- P.toList cs ] in P.mapWithKey (notReinstall installed) cs notReinstall vs (POption (I v InRepo) _) _ | v `elem` vs =@@ -263,8 +282,7 @@ firstGoal :: Tree a -> Tree a firstGoal = trav go where- go (GoalChoiceF xs) = -- casePSQ xs (GoalChoiceF xs) (\ _ t _ -> out t) -- more space efficient, but removes valuable debug info- casePSQ xs (GoalChoiceF (fromList [])) (\ g t _ -> GoalChoiceF (fromList [(g, t)]))+ go (GoalChoiceF xs) = GoalChoiceF (P.firstOnly xs) go x = x -- Note that we keep empty choice nodes, because they mean success. @@ -274,37 +292,24 @@ preferBaseGoalChoice :: Tree a -> Tree a preferBaseGoalChoice = trav go where- go (GoalChoiceF xs) = GoalChoiceF (P.sortByKeys preferBase xs)+ go (GoalChoiceF xs) = GoalChoiceF (P.preferByKeys isBase xs) go x = x - preferBase :: OpenGoal comp -> OpenGoal comp -> Ordering- preferBase (OpenGoal (Simple (Dep (Q _pp pn) _) _) _) _ | unPN pn == "base" = LT- preferBase _ (OpenGoal (Simple (Dep (Q _pp pn) _) _) _) | unPN pn == "base" = GT- preferBase _ _ = EQ+ isBase :: OpenGoal comp -> Bool+ isBase (OpenGoal (Simple (Dep (Q _pp pn) _) _) _) | unPN pn == "base" = True+ isBase _ = False -- | Deal with setup dependencies after regular dependencies, so that we can -- will link setup depencencies against package dependencies when possible deferSetupChoices :: Tree a -> Tree a deferSetupChoices = trav go where- go (GoalChoiceF xs) = GoalChoiceF (P.sortByKeys deferSetup xs)+ go (GoalChoiceF xs) = GoalChoiceF (P.preferByKeys noSetup xs) go x = x - deferSetup :: OpenGoal comp -> OpenGoal comp -> Ordering- deferSetup (OpenGoal (Simple (Dep (Q (Setup _ _) _) _) _) _) _ = GT- deferSetup _ (OpenGoal (Simple (Dep (Q (Setup _ _) _) _) _) _) = LT- deferSetup _ _ = EQ---- | Transformation that sorts choice nodes so that--- child nodes with a small branching degree are preferred. As a--- special case, choices with 0 branches will be preferred (as they--- are immediately considered inconsistent), and choices with 1--- branch will also be preferred (as they don't involve choice).-preferEasyGoalChoices :: Tree a -> Tree a-preferEasyGoalChoices = trav go- where- go (GoalChoiceF xs) = GoalChoiceF (P.sortBy (comparing choices) xs)- go x = x+ noSetup :: OpenGoal comp -> Bool+ noSetup (OpenGoal (Simple (Dep (Q (PP _ns (Setup _)) _) _) _) _) = False+ noSetup _ = True -- | Transformation that tries to avoid making weak flag choices early. -- Weak flags are trivial flags (not influencing dependencies) or such@@ -312,33 +317,45 @@ deferWeakFlagChoices :: Tree a -> Tree a deferWeakFlagChoices = trav go where- go (GoalChoiceF xs) = GoalChoiceF (P.sortBy defer xs)+ go (GoalChoiceF xs) = GoalChoiceF (P.prefer noWeakStanza (P.prefer noWeakFlag xs)) go x = x - defer :: Tree a -> Tree a -> Ordering- defer (FChoice _ _ True _ _) _ = GT- defer _ (FChoice _ _ True _ _) = LT- defer _ _ = EQ+ noWeakStanza :: Tree a -> Bool+ noWeakStanza (SChoice _ _ True _) = False+ noWeakStanza _ = True --- | Variant of 'preferEasyGoalChoices'.+ noWeakFlag :: Tree a -> Bool+ noWeakFlag (FChoice _ _ True _ _) = False+ noWeakFlag _ = True++-- | Transformation that sorts choice nodes so that+-- child nodes with a small branching degree are preferred. ----- Only approximates the number of choices in the branches. Less accurate,--- more efficient.-lpreferEasyGoalChoices :: Tree a -> Tree a-lpreferEasyGoalChoices = trav go+-- Only approximates the number of choices in the branches.+-- In particular, we try to take any goal immediately if it has+-- a branching degree of 0 (guaranteed failure) or 1 (no other+-- choice possible).+--+-- Returns at most one choice.+--+preferEasyGoalChoices :: Tree a -> Tree a+preferEasyGoalChoices = trav go where- go (GoalChoiceF xs) = GoalChoiceF (P.sortBy (comparing lchoices) xs)+ go (GoalChoiceF xs) = GoalChoiceF (P.dminimumBy dchoices xs)+ -- (a different implementation that seems slower):+ -- GoalChoiceF (P.firstOnly (P.preferOrElse zeroOrOneChoices (P.minimumBy choices) xs)) go x = x --- | Variant of 'preferEasyGoalChoices'.+-- | A variant of 'preferEasyGoalChoices' that just keeps the+-- ones with a branching degree of 0 or 1. Note that unlike+-- 'preferEasyGoalChoices', this may return more than one+-- choice. ----- I first thought that using a paramorphism might be faster here,--- but it doesn't seem to make any difference.-preferEasyGoalChoices' :: Tree a -> Tree a-preferEasyGoalChoices' = para (inn . go)+preferReallyEasyGoalChoices :: Tree a -> Tree a+preferReallyEasyGoalChoices = trav go where- go (GoalChoiceF xs) = GoalChoiceF (P.map fst (P.sortBy (comparing (choices . snd)) xs))- go x = fmap fst x+ go (GoalChoiceF xs) = GoalChoiceF (P.prefer zeroOrOneChoices xs)+ go x = x -- | Monad used internally in enforceSingleInstanceRestriction type EnforceSIR = Reader (Map (PI PN) QPN)
cabal/cabal-install/Distribution/Client/Dependency/Modular/Solver.hs view
@@ -1,11 +1,19 @@-module Distribution.Client.Dependency.Modular.Solver where+module Distribution.Client.Dependency.Modular.Solver+ ( SolverConfig(..)+ , solve+ ) where import Data.Map as M +import Distribution.Compiler (CompilerInfo)++import Distribution.Client.PkgConfigDb (PkgConfigDb)+ import Distribution.Client.Dependency.Types import Distribution.Client.Dependency.Modular.Assignment import Distribution.Client.Dependency.Modular.Builder+import Distribution.Client.Dependency.Modular.Cycles import Distribution.Client.Dependency.Modular.Dependency import Distribution.Client.Dependency.Modular.Explore import Distribution.Client.Dependency.Modular.Index@@ -26,35 +34,62 @@ maxBackjumps :: Maybe Int } -solve :: SolverConfig -> -- solver parameters- Index -> -- all available packages as an index- (PN -> PackagePreferences) -> -- preferences- Map PN [LabeledPackageConstraint] -> -- global constraints- [PN] -> -- global goals+-- | Run all solver phases.+--+-- In principle, we have a valid tree after 'validationPhase', which+-- means that every 'Done' node should correspond to valid solution.+--+-- There is one exception, though, and that is cycle detection, which+-- has been added relatively recently. Cycles are only removed directly+-- before exploration.+--+-- Semantically, there is no difference. Cycle detection, as implemented+-- now, only occurs for 'Done' nodes we encounter during exploration,+-- and cycle detection itself does not change the shape of the tree,+-- it only marks some 'Done' nodes as 'Fail', if they contain cyclic+-- solutions.+--+-- There is a tiny performance impact, however, in doing cycle detection+-- directly after validation. Probably because cycle detection maintains+-- some information, and the various reorderings implemented by+-- 'preferencesPhase' and 'heuristicsPhase' are ever so slightly more+-- costly if that information is already around during the reorderings.+--+-- With the current positioning directly before the 'explorePhase', there+-- seems to be no statistically significant performance impact of cycle+-- detection in the common case where there are no cycles.+--+solve :: SolverConfig -> -- ^ solver parameters+ CompilerInfo ->+ Index -> -- ^ all available packages as an index+ PkgConfigDb -> -- ^ available pkg-config pkgs+ (PN -> PackagePreferences) -> -- ^ preferences+ Map PN [LabeledPackageConstraint] -> -- ^ global constraints+ [PN] -> -- ^ global goals Log Message (Assignment, RevDepMap)-solve sc idx userPrefs userConstraints userGoals =+solve sc cinfo idx pkgConfigDB userPrefs userConstraints userGoals = explorePhase $+ detectCyclesPhase$ heuristicsPhase $ preferencesPhase $ validationPhase $ prunePhase $ buildPhase where- explorePhase = exploreTreeLog . backjump- heuristicsPhase = P.firstGoal . -- after doing goal-choice heuristics, commit to the first choice (saves space)- P.deferSetupChoices .+ explorePhase = backjumpAndExplore+ heuristicsPhase = (if preferEasyGoalChoices sc+ then P.preferEasyGoalChoices -- also leaves just one choice+ else P.firstGoal) . -- after doing goal-choice heuristics, commit to the first choice (saves space) P.deferWeakFlagChoices .+ P.deferSetupChoices . P.preferBaseGoalChoice .- if preferEasyGoalChoices sc- then P.lpreferEasyGoalChoices- else id . P.preferLinked preferencesPhase = P.preferPackagePreferences userPrefs validationPhase = P.enforceManualFlags . -- can only be done after user constraints P.enforcePackageConstraints userConstraints . P.enforceSingleInstanceRestriction . validateLinking idx .- validateTree idx+ validateTree cinfo idx pkgConfigDB prunePhase = (if avoidReinstalls sc then P.avoidReinstalls (const True) else id) . -- packages that can never be "upgraded": P.requireInstalled (`elem` [ PackageName "base"
cabal/cabal-install/Distribution/Client/Dependency/Modular/Tree.hs view
@@ -1,5 +1,19 @@ {-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-}-module Distribution.Client.Dependency.Modular.Tree where+module Distribution.Client.Dependency.Modular.Tree+ ( FailReason(..)+ , POption(..)+ , Tree(..)+ , TreeF(..)+ , ana+ , cata+ , choices+ , dchoices+ , inn+ , innM+ , para+ , trav+ , zeroOrOneChoices+ ) where import Control.Monad hiding (mapM, sequence) import Data.Foldable@@ -9,7 +23,8 @@ import Distribution.Client.Dependency.Modular.Dependency import Distribution.Client.Dependency.Modular.Flag import Distribution.Client.Dependency.Modular.Package-import Distribution.Client.Dependency.Modular.PSQ as P+import Distribution.Client.Dependency.Modular.PSQ (PSQ)+import qualified Distribution.Client.Dependency.Modular.PSQ as P import Distribution.Client.Dependency.Modular.Version import Distribution.Client.Dependency.Types ( ConstraintSource(..) ) @@ -42,7 +57,7 @@ -- -- Linking is an essential part of this story. In addition to picking a specific -- version for @1.P@, the solver can also decide to link @1.P@ to @0.P@ (or--- vice versa). Teans that @1.P@ and @0.P@ really must be the very same package+-- vice versa). It means that @1.P@ and @0.P@ really must be the very same package -- (and hence must have the same build time configuration, and their -- dependencies must also be the exact same). --@@ -68,6 +83,7 @@ | Backjump | MultipleInstances | DependenciesNotLinked String+ | CyclicDependencies deriving (Eq, Show) -- | Functor for the tree type.@@ -119,16 +135,24 @@ choices (Done _ ) = 1 choices (Fail _ _ ) = 0 --- | Variant of 'choices' that only approximates the number of choices,--- using 'llength'.-lchoices :: Tree a -> Int-lchoices (PChoice _ _ ts) = P.llength (P.filter active ts)-lchoices (FChoice _ _ _ _ ts) = P.llength (P.filter active ts)-lchoices (SChoice _ _ _ ts) = P.llength (P.filter active ts)-lchoices (GoalChoice _ ) = 1-lchoices (Done _ ) = 1-lchoices (Fail _ _ ) = 0+-- | Variant of 'choices' that only approximates the number of choices.+dchoices :: Tree a -> P.Degree+dchoices (PChoice _ _ ts) = P.degree (P.filter active ts)+dchoices (FChoice _ _ _ _ ts) = P.degree (P.filter active ts)+dchoices (SChoice _ _ _ ts) = P.degree (P.filter active ts)+dchoices (GoalChoice _ ) = P.ZeroOrOne+dchoices (Done _ ) = P.ZeroOrOne+dchoices (Fail _ _ ) = P.ZeroOrOne +-- | Variant of 'choices' that only approximates the number of choices.+zeroOrOneChoices :: Tree a -> Bool+zeroOrOneChoices (PChoice _ _ ts) = P.isZeroOrOne (P.filter active ts)+zeroOrOneChoices (FChoice _ _ _ _ ts) = P.isZeroOrOne (P.filter active ts)+zeroOrOneChoices (SChoice _ _ _ ts) = P.isZeroOrOne (P.filter active ts)+zeroOrOneChoices (GoalChoice _ ) = True+zeroOrOneChoices (Done _ ) = True+zeroOrOneChoices (Fail _ _ ) = True+ -- | Catamorphism on trees. cata :: (TreeF a b -> b) -> Tree a -> b cata phi x = (phi . fmap (cata phi) . out) x@@ -140,12 +164,6 @@ para :: (TreeF a (b, Tree a) -> b) -> Tree a -> b para phi = phi . fmap (\ x -> (para phi x, x)) . out -cataM :: Monad m => (TreeF a b -> m b) -> Tree a -> m b-cataM phi = phi <=< mapM (cataM phi) <=< return . out- -- | Anamorphism on trees. ana :: (b -> TreeF a b) -> b -> Tree a ana psi = inn . fmap (ana psi) . psi--anaM :: Monad m => (b -> m (TreeF a b)) -> b -> m (Tree a)-anaM psi = return . inn <=< mapM (anaM psi) <=< psi
cabal/cabal-install/Distribution/Client/Dependency/Modular/Validate.hs view
@@ -10,18 +10,25 @@ import Control.Monad.Reader hiding (sequence) import Data.List as L import Data.Map as M+import Data.Set as S import Data.Traversable import Prelude hiding (sequence) +import Language.Haskell.Extension (Extension, Language)++import Distribution.Compiler (CompilerInfo(..))+ import Distribution.Client.Dependency.Modular.Assignment import Distribution.Client.Dependency.Modular.Dependency import Distribution.Client.Dependency.Modular.Flag import Distribution.Client.Dependency.Modular.Index import Distribution.Client.Dependency.Modular.Package-import Distribution.Client.Dependency.Modular.PSQ as P+import qualified Distribution.Client.Dependency.Modular.PSQ as P import Distribution.Client.Dependency.Modular.Tree+import Distribution.Client.Dependency.Modular.Version (VR) import Distribution.Client.ComponentDeps (Component)+import Distribution.Client.PkgConfigDb (PkgConfigDb, pkgConfigPkgIsPresent) -- In practice, most constraints are implication constraints (IF we have made -- a number of choices, THEN we also have to ensure that). We call constraints@@ -75,6 +82,9 @@ -- | The state needed during validation. data ValidateState = VS {+ supportedExt :: Extension -> Bool,+ supportedLang :: Language -> Bool,+ presentPkgs :: PN -> VR -> Bool, index :: Index, saved :: Map QPN (FlaggedDeps Component QPN), -- saved, scoped, dependencies pa :: PreAssignment,@@ -123,6 +133,9 @@ goP :: QPN -> QGoalReasonChain -> POption -> Validate (Tree QGoalReasonChain) -> Validate (Tree QGoalReasonChain) goP qpn@(Q _pp pn) gr (POption i _) r = do PA ppa pfa psa <- asks pa -- obtain current preassignment+ extSupported <- asks supportedExt -- obtain the supported extensions+ langSupported <- asks supportedLang -- obtain the supported languages+ pkgPresent <- asks presentPkgs -- obtain the present pkg-config pkgs idx <- asks index -- obtain the index svd <- asks saved -- obtain saved dependencies qo <- asks qualifyOptions@@ -135,7 +148,7 @@ let goal = Goal (P qpn) gr let newactives = Dep qpn (Fixed i goal) : L.map (resetGoal goal) (extractDeps pfa psa qdeps) -- We now try to extend the partial assignment with the new active constraints.- let mnppa = extend (P qpn) ppa newactives+ let mnppa = extend extSupported langSupported pkgPresent goal ppa newactives -- In case we continue, we save the scoped dependencies let nsvd = M.insert qpn qdeps svd case mfr of@@ -151,6 +164,9 @@ goF :: QFN -> QGoalReasonChain -> Bool -> Validate (Tree QGoalReasonChain) -> Validate (Tree QGoalReasonChain) goF qfn@(FN (PI qpn _i) _f) gr b r = do PA ppa pfa psa <- asks pa -- obtain current preassignment+ extSupported <- asks supportedExt -- obtain the supported extensions+ langSupported <- asks supportedLang -- obtain the supported languages+ pkgPresent <- asks presentPkgs -- obtain the present pkg-config pkgs svd <- asks saved -- obtain saved dependencies -- Note that there should be saved dependencies for the package in question, -- because while building, we do not choose flags before we see the packages@@ -165,7 +181,7 @@ -- we have chosen a new flag. let newactives = extractNewDeps (F qfn) gr b npfa psa qdeps -- As in the package case, we try to extend the partial assignment.- case extend (F qfn) ppa newactives of+ case extend extSupported langSupported pkgPresent (Goal (F qfn) gr) ppa newactives of Left (c, d) -> return (Fail c (Conflicting d)) -- inconsistency found Right nppa -> local (\ s -> s { pa = PA nppa npfa psa }) r @@ -173,6 +189,9 @@ goS :: QSN -> QGoalReasonChain -> Bool -> Validate (Tree QGoalReasonChain) -> Validate (Tree QGoalReasonChain) goS qsn@(SN (PI qpn _i) _f) gr b r = do PA ppa pfa psa <- asks pa -- obtain current preassignment+ extSupported <- asks supportedExt -- obtain the supported extensions+ langSupported <- asks supportedLang -- obtain the supported languages+ pkgPresent <- asks presentPkgs -- obtain the present pkg-config pkgs svd <- asks saved -- obtain saved dependencies -- Note that there should be saved dependencies for the package in question, -- because while building, we do not choose flags before we see the packages@@ -187,7 +206,7 @@ -- we have chosen a new flag. let newactives = extractNewDeps (S qsn) gr b pfa npsa qdeps -- As in the package case, we try to extend the partial assignment.- case extend (S qsn) ppa newactives of+ case extend extSupported langSupported pkgPresent (Goal (S qsn) gr) ppa newactives of Left (c, d) -> return (Fail c (Conflicting d)) -- inconsistency found Right nppa -> local (\ s -> s { pa = PA nppa pfa npsa }) r @@ -235,10 +254,17 @@ Just False -> [] -- | Interface.-validateTree :: Index -> Tree QGoalReasonChain -> Tree QGoalReasonChain-validateTree idx t = runReader (validate t) VS {- index = idx- , saved = M.empty- , pa = PA M.empty M.empty M.empty+validateTree :: CompilerInfo -> Index -> PkgConfigDb -> Tree QGoalReasonChain -> Tree QGoalReasonChain+validateTree cinfo idx pkgConfigDb t = runReader (validate t) VS {+ supportedExt = maybe (const True) -- if compiler has no list of extensions, we assume everything is supported+ (\ es -> let s = S.fromList es in \ x -> S.member x s)+ (compilerInfoExtensions cinfo)+ , supportedLang = maybe (const True)+ (flip L.elem) -- use list lookup because language list is small and no Ord instance+ (compilerInfoLanguages cinfo)+ , presentPkgs = pkgConfigPkgIsPresent pkgConfigDb+ , index = idx+ , saved = M.empty+ , pa = PA M.empty M.empty M.empty , qualifyOptions = defaultQualifyOptions idx }
cabal/cabal-install/Distribution/Client/Dependency/Modular/Version.hs view
@@ -1,4 +1,15 @@-module Distribution.Client.Dependency.Modular.Version where+module Distribution.Client.Dependency.Modular.Version+ ( Ver+ , VR+ , anyVR+ , checkVR+ , eqVR+ , showVer+ , showVR+ , simplifyVR+ , (.&&.)+ , (.||.)+ ) where import qualified Distribution.Version as CV -- from Cabal import Distribution.Text -- from Cabal@@ -29,6 +40,10 @@ (.&&.) :: VR -> VR -> VR (.&&.) = CV.intersectVersionRanges +-- | Union of two version ranges.+(.||.) :: VR -> VR -> VR+(.||.) = CV.unionVersionRanges+ -- | Simplify a version range. simplifyVR :: VR -> VR simplifyVR = CV.simplifyVersionRange@@ -36,7 +51,3 @@ -- | Checking a version against a version range. checkVR :: VR -> Ver -> Bool checkVR = flip CV.withinRange---- | Make a version number.-mkV :: [Int] -> Ver-mkV xs = CV.Version xs []
cabal/cabal-install/Distribution/Client/Dependency/TopDown.hs view
@@ -21,7 +21,8 @@ ( Satisfiable(..) ) import Distribution.Client.Types ( SourcePackage(..), ConfiguredPackage(..)- , enableStanzas, ConfiguredId(..), fakeInstalledPackageId )+ , UnresolvedPkgLoc, UnresolvedSourcePackage+ , enableStanzas, ConfiguredId(..), fakeUnitId ) import Distribution.Client.Dependency.Types ( DependencyResolver, ResolverPackage(..) , PackageConstraint(..), unlabelPackageConstraint@@ -39,7 +40,7 @@ ( PackageIndex ) import Distribution.Package ( PackageName(..), PackageId, PackageIdentifier(..)- , InstalledPackageId(..)+ , UnitId(..), ComponentId(..) , Package(..), packageVersion, packageName , Dependency(Dependency), thisPackageVersion, simplifyDependency ) import Distribution.PackageDescription@@ -127,8 +128,10 @@ where isInstalled (SourceOnly _) = False isInstalled _ = True- isPreferred p = packageVersion p `withinRange` preferredVersions- (PackagePreferences preferredVersions packageInstalledPreference)+ isPreferred p = length . filter (packageVersion p `withinRange`) $+ preferredVersions++ (PackagePreferences preferredVersions packageInstalledPreference _) = pref pkgname logInfo node = Select selected discarded@@ -248,8 +251,8 @@ -- | The main exported resolver, with string logging and failure types to fit -- the standard 'DependencyResolver' interface. ---topDownResolver :: DependencyResolver-topDownResolver platform cinfo installedPkgIndex sourcePkgIndex+topDownResolver :: DependencyResolver UnresolvedPkgLoc+topDownResolver platform cinfo installedPkgIndex sourcePkgIndex _pkgConfigDB preferences constraints targets = mapMessages $ topDownResolver' platform cinfo@@ -266,11 +269,11 @@ -- topDownResolver' :: Platform -> CompilerInfo -> PackageIndex InstalledPackage- -> PackageIndex SourcePackage+ -> PackageIndex UnresolvedSourcePackage -> (PackageName -> PackagePreferences) -> [PackageConstraint] -> [PackageName]- -> Progress Log Failure [ResolverPackage]+ -> Progress Log Failure [ResolverPackage UnresolvedPkgLoc] topDownResolver' platform cinfo installedPkgIndex sourcePkgIndex preferences constraints targets = fmap (uncurry finalise)@@ -298,7 +301,7 @@ . PackageIndex.fromList $ finaliseSelectedPackages preferences selected' constraints' - toResolverPackage :: FinalSelectedPackage -> ResolverPackage+ toResolverPackage :: FinalSelectedPackage -> ResolverPackage UnresolvedPkgLoc toResolverPackage (SelectedInstalled (InstalledPackage pkg _)) = PreExisting pkg toResolverPackage (SelectedSource pkg) = Configured pkg@@ -444,7 +447,7 @@ -- annotateSourcePackages :: [PackageConstraint] -> (PackageName -> TopologicalSortNumber)- -> PackageIndex SourcePackage+ -> PackageIndex UnresolvedSourcePackage -> PackageIndex UnconfiguredPackage annotateSourcePackages constraints dfsNumber sourcePkgIndex = PackageIndex.fromList@@ -481,7 +484,7 @@ -- heuristic. -- topologicalSortNumbering :: PackageIndex InstalledPackage- -> PackageIndex SourcePackage+ -> PackageIndex UnresolvedSourcePackage -> (PackageName -> TopologicalSortNumber) topologicalSortNumbering installedPkgIndex sourcePkgIndex = \pkgname -> let Just vertex = toVertex pkgname@@ -508,17 +511,17 @@ -- and looking at the names of all possible dependencies. -- selectNeededSubset :: PackageIndex InstalledPackage- -> PackageIndex SourcePackage+ -> PackageIndex UnresolvedSourcePackage -> Set PackageName -> (PackageIndex InstalledPackage- ,PackageIndex SourcePackage)+ ,PackageIndex UnresolvedSourcePackage) selectNeededSubset installedPkgIndex sourcePkgIndex = select mempty mempty where select :: PackageIndex InstalledPackage- -> PackageIndex SourcePackage+ -> PackageIndex UnresolvedSourcePackage -> Set PackageName -> (PackageIndex InstalledPackage- ,PackageIndex SourcePackage)+ ,PackageIndex UnresolvedSourcePackage) select installedPkgIndex' sourcePkgIndex' remaining | Set.null remaining = (installedPkgIndex', sourcePkgIndex') | otherwise = select installedPkgIndex'' sourcePkgIndex'' remaining''@@ -567,9 +570,9 @@ | (_,ipkg:_) <- InstalledPackageIndex.allPackagesBySourcePackageId index' ] where -- The InstalledPackageInfo only lists dependencies by the- -- InstalledPackageId, which means we do not directly know the corresponding+ -- UnitId, which means we do not directly know the corresponding -- source dependency. The only way to find out is to lookup the- -- InstalledPackageId to get the InstalledPackageInfo and look at its+ -- UnitId to get the InstalledPackageInfo and look at its -- source PackageId. But if the package is broken because it depends on -- other packages that do not exist then we have a problem we cannot find -- the original source package id. Instead we make up a bogus package id.@@ -578,10 +581,10 @@ sourceDepsOf index ipkg = [ maybe (brokenPackageId depid) packageId mdep | let depids = InstalledPackageInfo.depends ipkg- getpkg = InstalledPackageIndex.lookupInstalledPackageId index+ getpkg = InstalledPackageIndex.lookupUnitId index , (depid, mdep) <- zip depids (map getpkg depids) ] - brokenPackageId (InstalledPackageId str) =+ brokenPackageId (SimpleUnitId (ComponentId str)) = PackageIdentifier (PackageName (str ++ "-broken")) (Version [] []) -- ------------------------------------------------------------@@ -614,7 +617,8 @@ -- We cheat in the cabal solver, and classify all dependencies as -- library dependencies. deps' :: ComponentDeps [ConfiguredId]- deps' = CD.fromLibraryDeps $ map (confId . pickRemaining mipkg) deps+ deps' = CD.fromLibraryDeps (unPackageName (packageName pkg))+ (map (confId . pickRemaining mipkg) deps) -- InstalledOrSource indicates that we either have a source package -- available, or an installed one, or both. In the case that we have both@@ -644,7 +648,7 @@ confId :: InstalledOrSource InstalledPackageEx UnconfiguredPackage -> ConfiguredId confId pkg = ConfiguredId { confSrcId = packageId pkg- , confInstId = fakeInstalledPackageId (packageId pkg)+ , confInstId = fakeUnitId (packageId pkg) } pickRemaining mipkg dep@(Dependency _name versionRange) =@@ -669,9 +673,11 @@ -- version constraints. TODO: distinguish hacks from prefs bounded = boundedAbove versionRange isPreferred p- | bounded = True -- any constant will do- | otherwise = packageVersion p `withinRange` preferredVersions- where (PackagePreferences preferredVersions _) = pref (packageName p)+ | bounded = boundedRank -- this is a dummy constant+ | otherwise = length . filter (packageVersion p `withinRange`) $+ preferredVersions+ where (PackagePreferences preferredVersions _ _) = pref (packageName p)+ boundedRank = 0 -- any value will do boundedAbove :: VersionRange -> Bool boundedAbove vr = case asVersionIntervals vr of
cabal/cabal-install/Distribution/Client/Dependency/TopDown/Types.hs view
@@ -14,7 +14,8 @@ module Distribution.Client.Dependency.TopDown.Types where import Distribution.Client.Types- ( SourcePackage(..), ConfiguredPackage(..)+ ( ConfiguredPackage(..)+ , UnresolvedPkgLoc, UnresolvedSourcePackage , OptionalStanza, ConfiguredId(..) ) import Distribution.InstalledPackageInfo ( InstalledPackageInfo )@@ -44,7 +45,7 @@ data FinalSelectedPackage = SelectedInstalled InstalledPackage- | SelectedSource ConfiguredPackage+ | SelectedSource (ConfiguredPackage UnresolvedPkgLoc) type TopologicalSortNumber = Int @@ -62,18 +63,18 @@ data UnconfiguredPackage = UnconfiguredPackage- SourcePackage+ UnresolvedSourcePackage !TopologicalSortNumber FlagAssignment [OptionalStanza] data SemiConfiguredPackage = SemiConfiguredPackage- SourcePackage -- package info- FlagAssignment -- total flag assignment for the package- [OptionalStanza] -- enabled optional stanzas- [Dependency] -- dependencies we end up with when we apply- -- the flag assignment+ UnresolvedSourcePackage -- package info+ FlagAssignment -- total flag assignment for the package+ [OptionalStanza] -- enabled optional stanzas+ [Dependency] -- dependencies we end up with when we apply+ -- the flag assignment instance Package InstalledPackage where packageId (InstalledPackage pkg _) = packageId pkg@@ -113,9 +114,9 @@ -- -- The top-down solver uses its down type class for package dependencies, -- because it wants to know these dependencies as PackageIds, rather than as--- InstalledPackageIds (so it cannot use PackageFixedDeps).+-- ComponentIds (so it cannot use PackageFixedDeps). ----- Ideally we would switch the top-down solver over to use InstalledPackageIds+-- Ideally we would switch the top-down solver over to use ComponentIds -- throughout; that means getting rid of this type class, and changing over the -- package index type to use Cabal's rather than cabal-install's. That will -- avoid the need for the local definitions of dependencyGraph and@@ -131,8 +132,8 @@ instance PackageSourceDeps InstalledPackageEx where sourceDeps (InstalledPackageEx _ _ deps) = deps -instance PackageSourceDeps ConfiguredPackage where- sourceDeps (ConfiguredPackage _ _ _ deps) = map confSrcId $ CD.nonSetupDeps deps+instance PackageSourceDeps (ConfiguredPackage loc) where+ sourceDeps cpkg = map confSrcId $ CD.nonSetupDeps (confPkgDeps cpkg) instance PackageSourceDeps InstalledPackage where sourceDeps (InstalledPackage _ deps) = deps
cabal/cabal-install/Distribution/Client/Dependency/Types.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveGeneric #-} ----------------------------------------------------------------------------- -- | -- Module : Distribution.Client.Dependency.Types@@ -18,7 +19,6 @@ DependencyResolver, ResolverPackage(..), - AllowNewer(..), isAllowNewer, PackageConstraint(..), showPackageConstraint, PackagePreferences(..),@@ -32,6 +32,7 @@ ConstraintSource(..), unlabelPackageConstraint, showConstraintSource+ ) where #if !MIN_VERSION_base(4,8,0)@@ -48,6 +49,8 @@ ( Monoid(..) ) #endif +import Distribution.Client.PkgConfigDb+ ( PkgConfigDb ) import Distribution.Client.Types ( OptionalStanza(..), SourcePackage(..), ConfiguredPackage ) @@ -73,18 +76,23 @@ import Text.PrettyPrint ( text )+import GHC.Generics (Generic)+import Distribution.Compat.Binary (Binary(..)) import Prelude hiding (fail) -- | All the solvers that can be selected. data PreSolver = AlwaysTopDown | AlwaysModular | Choose- deriving (Eq, Ord, Show, Bounded, Enum)+ deriving (Eq, Ord, Show, Bounded, Enum, Generic) -- | All the solvers that can be used. data Solver = TopDown | Modular- deriving (Eq, Ord, Show, Bounded, Enum)+ deriving (Eq, Ord, Show, Bounded, Enum, Generic) +instance Binary PreSolver+instance Binary Solver+ instance Text PreSolver where disp AlwaysTopDown = text "topdown" disp AlwaysModular = text "modular"@@ -105,22 +113,23 @@ -- solving the package dependency problem and we want to make it easy to swap -- in alternatives. ---type DependencyResolver = Platform- -> CompilerInfo- -> InstalledPackageIndex- -> PackageIndex.PackageIndex SourcePackage- -> (PackageName -> PackagePreferences)- -> [LabeledPackageConstraint]- -> [PackageName]- -> Progress String String [ResolverPackage]+type DependencyResolver loc = Platform+ -> CompilerInfo+ -> InstalledPackageIndex+ -> PackageIndex.PackageIndex (SourcePackage loc)+ -> PkgConfigDb+ -> (PackageName -> PackagePreferences)+ -> [LabeledPackageConstraint]+ -> [PackageName]+ -> Progress String String [ResolverPackage loc] -- | The dependency resolver picks either pre-existing installed packages -- or it picks source packages along with package configuration. -- -- This is like the 'InstallPlan.PlanPackage' but with fewer cases. ---data ResolverPackage = PreExisting InstalledPackageInfo- | Configured ConfiguredPackage+data ResolverPackage loc = PreExisting InstalledPackageInfo+ | Configured (ConfiguredPackage loc) -- | Per-package constraints. Package constraints must be respected by the -- solver. Multiple constraints for each package can be given, though obviously@@ -133,8 +142,10 @@ | PackageConstraintSource PackageName | PackageConstraintFlags PackageName FlagAssignment | PackageConstraintStanzas PackageName [OptionalStanza]- deriving (Show,Eq)+ deriving (Eq,Show,Generic) +instance Binary PackageConstraint+ -- | Provide a textual representation of a package constraint -- for debugging purposes. --@@ -156,17 +167,20 @@ showStanza TestStanzas = "test" showStanza BenchStanzas = "bench" --- | A per-package preference on the version. It is a soft constraint that the+-- | Per-package preferences on the version. It is a soft constraint that the -- 'DependencyResolver' should try to respect where possible. It consists of--- a 'InstalledPreference' which says if we prefer versions of packages--- that are already installed. It also has a 'PackageVersionPreference' which--- is a suggested constraint on the version number. The resolver should try to--- use package versions that satisfy the suggested version constraint.+-- an 'InstalledPreference' which says if we prefer versions of packages+-- that are already installed. It also has (possibly multiple)+-- 'PackageVersionPreference's which are suggested constraints on the version+-- number. The resolver should try to use package versions that satisfy+-- the maximum number of the suggested version constraints. -- -- It is not specified if preferences on some packages are more important than -- others. ---data PackagePreferences = PackagePreferences VersionRange InstalledPreference+data PackagePreferences = PackagePreferences [VersionRange]+ InstalledPreference+ [OptionalStanza] -- | Whether we prefer an installed version of a package or simply the latest -- version.@@ -199,28 +213,6 @@ | PreferLatestForSelected deriving Show --- | Policy for relaxing upper bounds in dependencies. For example, given--- 'build-depends: array >= 0.3 && < 0.5', are we allowed to relax the upper--- bound and choose a version of 'array' that is greater or equal to 0.5? By--- default the upper bounds are always strictly honored.-data AllowNewer =-- -- | Default: honor the upper bounds in all dependencies, never choose- -- versions newer than allowed.- AllowNewerNone-- -- | Ignore upper bounds in dependencies on the given packages.- | AllowNewerSome [PackageName]-- -- | Ignore upper bounds in dependencies on all packages.- | AllowNewerAll---- | Convert 'AllowNewer' to a boolean.-isAllowNewer :: AllowNewer -> Bool-isAllowNewer AllowNewerNone = False-isAllowNewer (AllowNewerSome _) = True-isAllowNewer AllowNewerAll = True- -- | A type to represent the unfolding of an expensive long running -- calculation that may fail. We may get intermediate steps before the final -- result which may be used to indicate progress and\/or logging messages.@@ -245,7 +237,7 @@ fold (Done r) = done r instance Monad (Progress step fail) where- return a = Done a+ return = pure p >>= f = foldProgress Step Fail f p instance Applicative (Progress step fail) where@@ -269,11 +261,14 @@ -- | Main config file, which is ~/.cabal/config by default. ConstraintSourceMainConfig FilePath + -- | Local cabal.project file+ | ConstraintSourceProjectConfig FilePath+ -- | Sandbox config file, which is ./cabal.sandbox.config by default. | ConstraintSourceSandboxConfig FilePath - -- | ./cabal.config.- | ConstraintSourceUserConfig+ -- | User config file, which is ./cabal.config by default.+ | ConstraintSourceUserConfig FilePath -- | Flag specified on the command line. | ConstraintSourceCommandlineFlag@@ -298,15 +293,19 @@ -- | The source of the constraint is not specified. | ConstraintSourceUnknown- deriving (Eq, Show)+ deriving (Eq, Show, Generic) +instance Binary ConstraintSource+ -- | Description of a 'ConstraintSource'. showConstraintSource :: ConstraintSource -> String showConstraintSource (ConstraintSourceMainConfig path) = "main config " ++ path+showConstraintSource (ConstraintSourceProjectConfig path) =+ "project config " ++ path showConstraintSource (ConstraintSourceSandboxConfig path) = "sandbox config " ++ path-showConstraintSource ConstraintSourceUserConfig = "cabal.config"+showConstraintSource (ConstraintSourceUserConfig path)= "user config " ++ path showConstraintSource ConstraintSourceCommandlineFlag = "command line flag" showConstraintSource ConstraintSourceUserTarget = "user target" showConstraintSource ConstraintSourceNonUpgradeablePackage =
+ cabal/cabal-install/Distribution/Client/DistDirLayout.hs view
@@ -0,0 +1,134 @@+{-# LANGUAGE RecordWildCards #-}++-- | +--+-- The layout of the .\/dist\/ directory where cabal keeps all of it's state+-- and build artifacts.+--+module Distribution.Client.DistDirLayout where++import System.FilePath+import Distribution.Package+ ( PackageId )+import Distribution.Compiler+import Distribution.Simple.Compiler (PackageDB(..))+import Distribution.Text+import Distribution.Client.Types+ ( InstalledPackageId )++++-- | The layout of the project state directory. Traditionally this has been+-- called the @dist@ directory.+--+data DistDirLayout = DistDirLayout {++ -- | The dist directory, which is the root of where cabal keeps all its+ -- state including the build artifacts from each package we build.+ --+ distDirectory :: FilePath,++ -- | The directory under dist where we keep the build artifacts for a+ -- package we're building from a local directory.+ --+ -- This uses a 'PackageId' not just a 'PackageName' because technically+ -- we can have multiple instances of the same package in a solution+ -- (e.g. setup deps).+ --+ distBuildDirectory :: PackageId -> FilePath,+ distBuildRootDirectory :: FilePath,++ -- | The directory under dist where we put the unpacked sources of+ -- packages, in those cases where it makes sense to keep the build+ -- artifacts to reduce rebuild times. These can be tarballs or could be+ -- scm repos.+ --+ distUnpackedSrcDirectory :: PackageId -> FilePath,+ distUnpackedSrcRootDirectory :: FilePath,++ -- | The location for project-wide cache files (e.g. state used in+ -- incremental rebuilds).+ --+ distProjectCacheFile :: String -> FilePath,+ distProjectCacheDirectory :: FilePath,++ -- | The location for package-specific cache files (e.g. state used in+ -- incremental rebuilds).+ --+ distPackageCacheFile :: PackageId -> String -> FilePath,+ distPackageCacheDirectory :: PackageId -> FilePath,++ distTempDirectory :: FilePath,+ distBinDirectory :: FilePath,++ distPackageDB :: CompilerId -> PackageDB+ }++++--TODO: move to another module, e.g. CabalDirLayout?+data CabalDirLayout = CabalDirLayout {+ cabalStoreDirectory :: CompilerId -> FilePath,+ cabalStorePackageDirectory :: CompilerId -> InstalledPackageId+ -> FilePath,+ cabalStorePackageDBPath :: CompilerId -> FilePath,+ cabalStorePackageDB :: CompilerId -> PackageDB,++ cabalPackageCacheDirectory :: FilePath,+ cabalLogsDirectory :: FilePath,+ cabalWorldFile :: FilePath+ }+++defaultDistDirLayout :: FilePath -> DistDirLayout+defaultDistDirLayout projectRootDirectory =+ DistDirLayout {..}+ where+ distDirectory = projectRootDirectory </> "dist-newstyle"+ --TODO: switch to just dist at some point, or some other new name++ distBuildRootDirectory = distDirectory </> "build"+ distBuildDirectory pkgid = distBuildRootDirectory </> display pkgid++ distUnpackedSrcRootDirectory = distDirectory </> "src"+ distUnpackedSrcDirectory pkgid = distUnpackedSrcRootDirectory+ </> display pkgid++ distProjectCacheDirectory = distDirectory </> "cache"+ distProjectCacheFile name = distProjectCacheDirectory </> name++ distPackageCacheDirectory pkgid = distBuildDirectory pkgid </> "cache"+ distPackageCacheFile pkgid name = distPackageCacheDirectory pkgid </> name++ distTempDirectory = distDirectory </> "tmp"++ distBinDirectory = distDirectory </> "bin"++ distPackageDBPath compid = distDirectory </> "packagedb" </> display compid+ distPackageDB = SpecificPackageDB . distPackageDBPath++++defaultCabalDirLayout :: FilePath -> CabalDirLayout+defaultCabalDirLayout cabalDir =+ CabalDirLayout {..}+ where++ cabalStoreDirectory compid =+ cabalDir </> "store" </> display compid++ cabalStorePackageDirectory compid ipkgid = + cabalStoreDirectory compid </> display ipkgid++ cabalStorePackageDBPath compid =+ cabalStoreDirectory compid </> "package.db"++ cabalStorePackageDB =+ SpecificPackageDB . cabalStorePackageDBPath++ cabalPackageCacheDirectory = cabalDir </> "packages"++ cabalLogsDirectory = cabalDir </> "logs"++ cabalWorldFile = cabalDir </> "world"+
cabal/cabal-install/Distribution/Client/Exec.hs view
@@ -14,8 +14,6 @@ import Control.Monad (unless) -import Data.Foldable (forM_)- import qualified Distribution.Simple.GHC as GHC import qualified Distribution.Simple.GHCJS as GHCJS @@ -65,7 +63,7 @@ [] -> die "Please specify an executable to run" where- environmentOverrides = + environmentOverrides = case useSandbox of NoSandbox -> return [] (UseSandbox sandboxDir) ->@@ -91,21 +89,18 @@ let Just program = lookupProgram hcProgram programDb gDb <- getGlobalPackageDB verbosity program sandboxConfigFilePath <- getSandboxConfigFilePath mempty- let compilerPackagePath = hcPackagePath gDb+ let sandboxPackagePath = sandboxPackageDBPath sandboxDir comp platform+ compilerPackagePaths = prependToSearchPath gDb sandboxPackagePath -- Packages database must exist, otherwise things will start -- failing in mysterious ways.- forM_ compilerPackagePath $ \fp -> do- exists <- doesDirectoryExist fp- unless exists $ warn verbosity $ "Package database is not a directory: " ++ fp+ exists <- doesDirectoryExist sandboxPackagePath+ unless exists $ warn verbosity $ "Package database is not a directory: "+ ++ sandboxPackagePath -- Build the environment- return [ (packagePathEnvVar, compilerPackagePath)- , ("CABAL_SANDBOX_PACKAGE_PATH", compilerPackagePath)+ return [ (packagePathEnvVar, Just compilerPackagePaths)+ , ("CABAL_SANDBOX_PACKAGE_PATH", Just compilerPackagePaths) , ("CABAL_SANDBOX_CONFIG", Just sandboxConfigFilePath) ]-- hcPackagePath gDb =- let s = sandboxPackageDBPath sandboxDir comp platform- in Just $ prependToSearchPath gDb s prependToSearchPath path newValue = newValue ++ [searchPathSeparator] ++ path
cabal/cabal-install/Distribution/Client/Fetch.hs view
@@ -21,11 +21,11 @@ import Distribution.Client.Dependency import Distribution.Client.IndexUtils as IndexUtils ( getSourcePackages, getInstalledPackages )-import Distribution.Client.HttpUtils- ( configureTransport, HttpTransport(..) ) import qualified Distribution.Client.InstallPlan as InstallPlan+import Distribution.Client.PkgConfigDb+ ( PkgConfigDb, readPkgConfigDb ) import Distribution.Client.Setup- ( GlobalFlags(..), FetchFlags(..) )+ ( GlobalFlags(..), FetchFlags(..), RepoContext(..) ) import Distribution.Package ( packageId )@@ -35,7 +35,7 @@ import Distribution.Simple.Program ( ProgramConfiguration ) import Distribution.Simple.Setup- ( fromFlag, flagToMaybe )+ ( fromFlag ) import Distribution.Simple.Utils ( die, notice, debug ) import Distribution.System@@ -66,7 +66,7 @@ -- fetch :: Verbosity -> PackageDBStack- -> [Repo]+ -> RepoContext -> Compiler -> Platform -> ProgramConfiguration@@ -77,24 +77,23 @@ fetch verbosity _ _ _ _ _ _ _ [] = notice verbosity "No packages requested. Nothing to do." -fetch verbosity packageDBs repos comp platform conf+fetch verbosity packageDBs repoCtxt comp platform conf globalFlags fetchFlags userTargets = do mapM_ checkTarget userTargets installedPkgIndex <- getInstalledPackages verbosity comp packageDBs conf- sourcePkgDb <- getSourcePackages verbosity repos-- transport <- configureTransport verbosity (flagToMaybe (globalHttpTransport globalFlags))+ sourcePkgDb <- getSourcePackages verbosity repoCtxt+ pkgConfigDb <- readPkgConfigDb verbosity conf - pkgSpecifiers <- resolveUserTargets verbosity transport+ pkgSpecifiers <- resolveUserTargets verbosity repoCtxt (fromFlag $ globalWorldFile globalFlags) (packageIndex sourcePkgDb) userTargets pkgs <- planPackages verbosity comp platform fetchFlags- installedPkgIndex sourcePkgDb pkgSpecifiers+ installedPkgIndex sourcePkgDb pkgConfigDb pkgSpecifiers pkgs' <- filterM (fmap not . isFetched . packageSource) pkgs if null pkgs'@@ -109,7 +108,7 @@ "The following packages would be fetched:" : map (display . packageId) pkgs' - else mapM_ (fetchPackage transport verbosity . packageSource) pkgs'+ else mapM_ (fetchPackage verbosity repoCtxt . packageSource) pkgs' where dryRun = fromFlag (fetchDryRun fetchFlags)@@ -120,10 +119,11 @@ -> FetchFlags -> InstalledPackageIndex -> SourcePackageDb- -> [PackageSpecifier SourcePackage]- -> IO [SourcePackage]+ -> PkgConfigDb+ -> [PackageSpecifier UnresolvedSourcePackage]+ -> IO [UnresolvedSourcePackage] planPackages verbosity comp platform fetchFlags- installedPkgIndex sourcePkgDb pkgSpecifiers+ installedPkgIndex sourcePkgDb pkgConfigDb pkgSpecifiers | includeDependencies = do solver <- chooseSolver verbosity@@ -131,15 +131,15 @@ notice verbosity "Resolving dependencies..." installPlan <- foldProgress logMsg die return $ resolveDependencies- platform (compilerInfo comp)+ platform (compilerInfo comp) pkgConfigDb solver resolverParams -- The packages we want to fetch are those packages the 'InstallPlan' -- that are in the 'InstallPlan.Configured' state. return- [ pkg- | (InstallPlan.Configured (ConfiguredPackage pkg _ _ _))+ [ confPkgSource cpkg+ | (InstallPlan.Configured cpkg) <- InstallPlan.toList installPlan ] | otherwise =@@ -185,8 +185,8 @@ ++ "In the meantime you can use the 'unpack' commands." _ -> return () -fetchPackage :: HttpTransport -> Verbosity -> PackageLocation a -> IO ()-fetchPackage transport verbosity pkgsrc = case pkgsrc of+fetchPackage :: Verbosity -> RepoContext -> PackageLocation a -> IO ()+fetchPackage verbosity repoCtxt pkgsrc = case pkgsrc of LocalUnpackedPackage _dir -> return () LocalTarballPackage _file -> return () @@ -195,5 +195,5 @@ ++ "In the meantime you can use the 'unpack' commands." RepoTarballPackage repo pkgid _ -> do- _ <- fetchRepoTarball transport verbosity repo pkgid+ _ <- fetchRepoTarball verbosity repoCtxt repo pkgid return ()
cabal/cabal-install/Distribution/Client/FetchUtils.hs view
@@ -11,6 +11,7 @@ -- -- Functions for fetching packages -----------------------------------------------------------------------------+{-# LANGUAGE RecordWildCards #-} module Distribution.Client.FetchUtils ( -- * fetching packages@@ -38,6 +39,8 @@ ( display ) import Distribution.Verbosity ( Verbosity )+import Distribution.Client.GlobalFlags+ ( RepoContext(..) ) import Data.Maybe import System.Directory@@ -51,6 +54,8 @@ import Network.URI ( URI(uriPath) ) +import qualified Hackage.Security.Client as Sec+ -- ------------------------------------------------------------ -- * Actually fetch things -- ------------------------------------------------------------@@ -58,7 +63,7 @@ -- | Returns @True@ if the package has already been fetched -- or does not need fetching. ---isFetched :: PackageLocation (Maybe FilePath) -> IO Bool+isFetched :: UnresolvedPkgLoc -> IO Bool isFetched loc = case loc of LocalUnpackedPackage _dir -> return True LocalTarballPackage _file -> return True@@ -66,8 +71,8 @@ RepoTarballPackage repo pkgid _ -> doesFileExist (packageFile repo pkgid) -checkFetched :: PackageLocation (Maybe FilePath)- -> IO (Maybe (PackageLocation FilePath))+checkFetched :: UnresolvedPkgLoc+ -> IO (Maybe ResolvedPkgLoc) checkFetched loc = case loc of LocalUnpackedPackage dir -> return (Just $ LocalUnpackedPackage dir)@@ -89,11 +94,11 @@ -- | Fetch a package if we don't have it already. ---fetchPackage :: HttpTransport- -> Verbosity- -> PackageLocation (Maybe FilePath)- -> IO (PackageLocation FilePath)-fetchPackage transport verbosity loc = case loc of+fetchPackage :: Verbosity+ -> RepoContext+ -> UnresolvedPkgLoc+ -> IO ResolvedPkgLoc+fetchPackage verbosity repoCtxt loc = case loc of LocalUnpackedPackage dir -> return (LocalUnpackedPackage dir) LocalTarballPackage file ->@@ -107,10 +112,11 @@ path <- downloadTarballPackage uri return (RemoteTarballPackage uri path) RepoTarballPackage repo pkgid Nothing -> do- local <- fetchRepoTarball transport verbosity repo pkgid+ local <- fetchRepoTarball verbosity repoCtxt repo pkgid return (RepoTarballPackage repo pkgid local) where downloadTarballPackage uri = do+ transport <- repoContextGetTransport repoCtxt transportCheckHttps transport uri notice verbosity ("Downloading " ++ show uri) tmpdir <- getTemporaryDirectory@@ -122,8 +128,8 @@ -- | Fetch a repo package if we don't have it already. ---fetchRepoTarball :: HttpTransport -> Verbosity -> Repo -> PackageId -> IO FilePath-fetchRepoTarball transport verbosity repo pkgid = do+fetchRepoTarball :: Verbosity -> RepoContext -> Repo -> PackageId -> IO FilePath+fetchRepoTarball verbosity repoCtxt repo pkgid = do fetched <- doesFileExist (packageFile repo pkgid) if fetched then do info verbosity $ display pkgid ++ " has already been downloaded."@@ -131,16 +137,26 @@ else do setupMessage verbosity "Downloading" pkgid downloadRepoPackage where- downloadRepoPackage = case repoKind repo of- Right LocalRepo -> return (packageFile repo pkgid)+ downloadRepoPackage = case repo of+ RepoLocal{..} -> return (packageFile repo pkgid) - Left remoteRepo -> do- remoteRepoCheckHttps transport remoteRepo- let uri = packageURI remoteRepo pkgid- dir = packageDir repo pkgid- path = packageFile repo pkgid+ RepoRemote{..} -> do+ transport <- repoContextGetTransport repoCtxt+ remoteRepoCheckHttps transport repoRemote+ let uri = packageURI repoRemote pkgid+ dir = packageDir repo pkgid+ path = packageFile repo pkgid createDirectoryIfMissing True dir _ <- downloadURI transport verbosity uri path+ return path++ RepoSecure{} -> repoContextWithSecureRepo repoCtxt repo $ \rep -> do+ let dir = packageDir repo pkgid+ path = packageFile repo pkgid+ createDirectoryIfMissing True dir+ Sec.uncheckClientErrors $ do+ info verbosity ("writing " ++ path)+ Sec.downloadPackage' rep pkgid path return path -- | Downloads an index file to [config-dir/packages/serv-id].
+ cabal/cabal-install/Distribution/Client/FileMonitor.hs view
@@ -0,0 +1,1101 @@+{-# LANGUAGE CPP, DeriveGeneric, DeriveFunctor, GeneralizedNewtypeDeriving,+ NamedFieldPuns, BangPatterns #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++-- | An abstraction to help with re-running actions when files or other+-- input values they depend on have changed.+--+module Distribution.Client.FileMonitor (++ -- * Declaring files to monitor+ MonitorFilePath(..),+ MonitorKindFile(..),+ MonitorKindDir(..),+ FilePathGlob(..),+ monitorFile,+ monitorFileHashed,+ monitorNonExistentFile,+ monitorDirectory,+ monitorDirectoryExistence,+ monitorFileOrDirectory,+ monitorFileGlob,+ monitorFileSearchPath,+ monitorFileHashedSearchPath,++ -- * Creating and checking sets of monitored files+ FileMonitor(..),+ newFileMonitor,+ MonitorChanged(..),+ MonitorChangedReason(..),+ checkFileMonitorChanged,+ updateFileMonitor,+ MonitorTimestamp,+ beginUpdateFileMonitor,+ ) where+++#if MIN_VERSION_containers(0,5,0)+import Data.Map.Strict (Map)+import qualified Data.Map.Strict as Map+#else+import Data.Map (Map)+import qualified Data.Map as Map+#endif+import qualified Data.ByteString.Lazy as BS+import Distribution.Compat.Binary+import qualified Distribution.Compat.Binary as Binary+import qualified Data.Hashable as Hashable+import Data.List (sort)++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative+#endif+import Control.Monad+import Control.Monad.Trans (MonadIO, liftIO)+import Control.Monad.State (StateT, mapStateT)+import qualified Control.Monad.State as State+import Control.Monad.Except (ExceptT, runExceptT, withExceptT,+ throwError)+import Control.Exception++import Distribution.Client.Compat.Time+import Distribution.Client.Glob+import Distribution.Simple.Utils (handleDoesNotExist, writeFileAtomic)+import Distribution.Client.Utils (mergeBy, MergeResult(..))++import System.FilePath+import System.Directory+import System.IO+import GHC.Generics (Generic)+++------------------------------------------------------------------------------+-- Types for specifying files to monitor+--+++-- | A description of a file (or set of files) to monitor for changes.+--+-- Where file paths are relative they are relative to a common directory+-- (e.g. project root), not necessarily the process current directory.+--+data MonitorFilePath =+ MonitorFile {+ monitorKindFile :: !MonitorKindFile,+ monitorKindDir :: !MonitorKindDir,+ monitorPath :: !FilePath+ }+ | MonitorFileGlob {+ monitorKindFile :: !MonitorKindFile,+ monitorKindDir :: !MonitorKindDir,+ monitorPathGlob :: !FilePathGlob+ }+ deriving (Eq, Show, Generic)++data MonitorKindFile = FileExists+ | FileModTime+ | FileHashed+ | FileNotExists+ deriving (Eq, Show, Generic)++data MonitorKindDir = DirExists+ | DirModTime+ | DirNotExists+ deriving (Eq, Show, Generic)++instance Binary MonitorFilePath+instance Binary MonitorKindFile+instance Binary MonitorKindDir++-- | Monitor a single file for changes, based on its modification time.+-- The monitored file is considered to have changed if it no longer+-- exists or if its modification time has changed.+--+monitorFile :: FilePath -> MonitorFilePath+monitorFile = MonitorFile FileModTime DirNotExists++-- | Monitor a single file for changes, based on its modification time+-- and content hash. The monitored file is considered to have changed if+-- it no longer exists or if its modification time and content hash have+-- changed.+--+monitorFileHashed :: FilePath -> MonitorFilePath+monitorFileHashed = MonitorFile FileHashed DirNotExists++-- | Monitor a single non-existent file for changes. The monitored file+-- is considered to have changed if it exists.+--+monitorNonExistentFile :: FilePath -> MonitorFilePath+monitorNonExistentFile = MonitorFile FileNotExists DirNotExists++-- | Monitor a single directory for changes, based on its modification+-- time. The monitored directory is considered to have changed if it no+-- longer exists or if its modification time has changed.+--+monitorDirectory :: FilePath -> MonitorFilePath+monitorDirectory = MonitorFile FileNotExists DirModTime++-- | Monitor a single directory for existence. The monitored directory is+-- considered to have changed only if it no longer exists.+--+monitorDirectoryExistence :: FilePath -> MonitorFilePath+monitorDirectoryExistence = MonitorFile FileNotExists DirExists++-- | Monitor a single file or directory for changes, based on its modification+-- time. The monitored file is considered to have changed if it no longer+-- exists or if its modification time has changed.+--+monitorFileOrDirectory :: FilePath -> MonitorFilePath+monitorFileOrDirectory = MonitorFile FileModTime DirModTime++-- | Monitor a set of files (or directories) identified by a file glob.+-- The monitored glob is considered to have changed if the set of files+-- matching the glob changes (i.e. creations or deletions), or for files if the+-- modification time and content hash of any matching file has changed.+--+monitorFileGlob :: FilePathGlob -> MonitorFilePath+monitorFileGlob = MonitorFileGlob FileHashed DirExists++-- | Creates a list of files to monitor when you search for a file which+-- unsuccessfully looked in @notFoundAtPaths@ before finding it at+-- @foundAtPath@.+monitorFileSearchPath :: [FilePath] -> FilePath -> [MonitorFilePath]+monitorFileSearchPath notFoundAtPaths foundAtPath =+ monitorFile foundAtPath+ : map monitorNonExistentFile notFoundAtPaths++-- | Similar to 'monitorFileSearchPath', but also instructs us to+-- monitor the hash of the found file.+monitorFileHashedSearchPath :: [FilePath] -> FilePath -> [MonitorFilePath]+monitorFileHashedSearchPath notFoundAtPaths foundAtPath =+ monitorFileHashed foundAtPath+ : map monitorNonExistentFile notFoundAtPaths+++------------------------------------------------------------------------------+-- Implementation types, files status+--++-- | The state necessary to determine whether a set of monitored+-- files has changed. It consists of two parts: a set of specific+-- files to be monitored (index by their path), and a list of+-- globs, which monitor may files at once.+data MonitorStateFileSet+ = MonitorStateFileSet !(Map FilePath MonitorStateFile)+ ![MonitorStateGlob]+ deriving Show++type Hash = Int++-- | The state necessary to determine whether a monitored file has changed.+--+-- This covers all the cases of 'MonitorFilePath' except for globs which is+-- covered separately by 'MonitorStateGlob'.+--+-- The @Maybe ModTime@ is to cover the case where we already consider the+-- file to have changed, either because it had already changed by the time we+-- did the snapshot (i.e. too new, changed since start of update process) or it+-- no longer exists at all.+--+data MonitorStateFile = MonitorStateFile !MonitorKindFile !MonitorKindDir+ !MonitorStateFileStatus+ deriving (Show, Generic)++data MonitorStateFileStatus+ = MonitorStateFileExists+ | MonitorStateFileModTime !ModTime -- ^ cached file mtime+ | MonitorStateFileHashed !ModTime !Hash -- ^ cached mtime and content hash+ | MonitorStateDirExists+ | MonitorStateDirModTime !ModTime -- ^ cached dir mtime+ | MonitorStateNonExistent+ | MonitorStateAlreadyChanged+ deriving (Show, Generic)++instance Binary MonitorStateFile+instance Binary MonitorStateFileStatus++-- | The state necessary to determine whether the files matched by a globbing+-- match have changed.+--+data MonitorStateGlob = MonitorStateGlob !MonitorKindFile !MonitorKindDir+ !FilePathRoot !MonitorStateGlobRel+ deriving (Show, Generic)++data MonitorStateGlobRel+ = MonitorStateGlobDirs+ !Glob !FilePathGlobRel+ !ModTime+ ![(FilePath, MonitorStateGlobRel)] -- invariant: sorted++ | MonitorStateGlobFiles+ !Glob+ !ModTime+ ![(FilePath, MonitorStateFileStatus)] -- invariant: sorted++ | MonitorStateGlobDirTrailing+ deriving (Show, Generic)++instance Binary MonitorStateGlob+instance Binary MonitorStateGlobRel++-- | We can build a 'MonitorStateFileSet' from a set of 'MonitorFilePath' by+-- inspecting the state of the file system, and we can go in the reverse+-- direction by just forgetting the extra info.+--+reconstructMonitorFilePaths :: MonitorStateFileSet -> [MonitorFilePath]+reconstructMonitorFilePaths (MonitorStateFileSet singlePaths globPaths) =+ Map.foldrWithKey (\k x r -> getSinglePath k x : r)+ (map getGlobPath globPaths)+ singlePaths+ where+ getSinglePath filepath (MonitorStateFile kindfile kinddir _) =+ MonitorFile kindfile kinddir filepath++ getGlobPath (MonitorStateGlob kindfile kinddir root gstate) =+ MonitorFileGlob kindfile kinddir $ FilePathGlob root $+ case gstate of+ MonitorStateGlobDirs glob globs _ _ -> GlobDir glob globs+ MonitorStateGlobFiles glob _ _ -> GlobFile glob+ MonitorStateGlobDirTrailing -> GlobDirTrailing++------------------------------------------------------------------------------+-- Checking the status of monitored files+--++-- | A monitor for detecting changes to a set of files. It can be used to+-- efficiently test if any of a set of files (specified individually or by+-- glob patterns) has changed since some snapshot. In addition, it also checks+-- for changes in a value (of type @a@), and when there are no changes in+-- either it returns a saved value (of type @b@).+--+-- The main use case looks like this: suppose we have some expensive action+-- that depends on certain pure inputs and reads some set of files, and+-- produces some pure result. We want to avoid re-running this action when it+-- would produce the same result. So we need to monitor the files the action+-- looked at, the other pure input values, and we need to cache the result.+-- Then at some later point, if the input value didn't change, and none of the+-- files changed, then we can re-use the cached result rather than re-running+-- the action.+--+-- This can be achieved using a 'FileMonitor'. Each 'FileMonitor' instance+-- saves state in a disk file, so the file for that has to be specified,+-- making sure it is unique. The pattern is to use 'checkFileMonitorChanged'+-- to see if there's been any change. If there is, re-run the action, keeping+-- track of the files, then use 'updateFileMonitor' to record the current+-- set of files to monitor, the current input value for the action, and the+-- result of the action.+--+-- The typical occurrence of this pattern is captured by 'rerunIfChanged'+-- and the 'Rebuild' monad. More complicated cases may need to use+-- 'checkFileMonitorChanged' and 'updateFileMonitor' directly.+--+data FileMonitor a b+ = FileMonitor {++ -- | The file where this 'FileMonitor' should store its state.+ --+ fileMonitorCacheFile :: FilePath,++ -- | Compares a new cache key with old one to determine if a+ -- corresponding cached value is still valid.+ --+ -- Typically this is just an equality test, but in some+ -- circumstances it can make sense to do things like subset+ -- comparisons.+ --+ -- The first arg is the new value, the second is the old cached value.+ --+ fileMonitorKeyValid :: a -> a -> Bool,++ -- | When this mode is enabled, if 'checkFileMonitorChanged' returns+ -- 'MonitoredValueChanged' then we have the guarantee that no files+ -- changed, that the value change was the only change. In the default+ -- mode no such guarantee is provided which is slightly faster.+ --+ fileMonitorCheckIfOnlyValueChanged :: Bool+ }++-- | Define a new file monitor.+--+-- It's best practice to define file monitor values once, and then use the+-- same value for 'checkFileMonitorChanged' and 'updateFileMonitor' as this+-- ensures you get the same types @a@ and @b@ for reading and writing.+--+-- The path of the file monitor itself must be unique because it keeps state+-- on disk and these would clash.+--+newFileMonitor :: Eq a => FilePath -- ^ The file to cache the state of the+ -- file monitor. Must be unique.+ -> FileMonitor a b+newFileMonitor path = FileMonitor path (==) False++-- | The result of 'checkFileMonitorChanged': either the monitored files or+-- value changed (and it tells us which it was) or nothing changed and we get+-- the cached result.+--+data MonitorChanged a b =+ -- | The monitored files and value did not change. The cached result is+ -- @b@.+ --+ -- The set of monitored files is also returned. This is useful+ -- for composing or nesting 'FileMonitor's.+ MonitorUnchanged b [MonitorFilePath]++ -- | The monitor found that something changed. The reason is given.+ --+ | MonitorChanged (MonitorChangedReason a)+ deriving Show++-- | What kind of change 'checkFileMonitorChanged' detected.+--+data MonitorChangedReason a =++ -- | One of the files changed (existence, file type, mtime or file+ -- content, depending on the 'MonitorFilePath' in question)+ MonitoredFileChanged FilePath++ -- | The pure input value changed.+ --+ -- The previous cached key value is also returned. This is sometimes+ -- useful when using a 'fileMonitorKeyValid' function that is not simply+ -- '(==)', when invalidation can be partial. In such cases it can make+ -- sense to 'updateFileMonitor' with a key value that's a combination of+ -- the new and old (e.g. set union).+ | MonitoredValueChanged a++ -- | There was no saved monitor state, cached value etc. Ie the file+ -- for the 'FileMonitor' does not exist.+ | MonitorFirstRun++ -- | There was existing state, but we could not read it. This typically+ -- happens when the code has changed compared to an existing 'FileMonitor'+ -- cache file and type of the input value or cached value has changed such+ -- that we cannot decode the values. This is completely benign as we can+ -- treat is just as if there were no cache file and re-run.+ | MonitorCorruptCache+ deriving (Eq, Show, Functor)++-- | Test if the input value or files monitored by the 'FileMonitor' have+-- changed. If not, return the cached value.+--+-- See 'FileMonitor' for a full explanation.+--+checkFileMonitorChanged+ :: (Binary a, Binary b)+ => FileMonitor a b -- ^ cache file path+ -> FilePath -- ^ root directory+ -> a -- ^ guard or key value+ -> IO (MonitorChanged a b) -- ^ did the key or any paths change?+checkFileMonitorChanged+ monitor@FileMonitor { fileMonitorKeyValid,+ fileMonitorCheckIfOnlyValueChanged }+ root currentKey =++ -- Consider it a change if the cache file does not exist,+ -- or we cannot decode it. Sadly ErrorCall can still happen, despite+ -- using decodeFileOrFail, e.g. Data.Char.chr errors++ handleDoesNotExist (MonitorChanged MonitorFirstRun) $+ handleErrorCall (MonitorChanged MonitorCorruptCache) $+ readCacheFile monitor+ >>= either (\_ -> return (MonitorChanged MonitorCorruptCache))+ checkStatusCache++ where+ checkStatusCache (cachedFileStatus, cachedKey, cachedResult) = do+ change <- checkForChanges+ case change of+ Just reason -> return (MonitorChanged reason)+ Nothing -> return (MonitorUnchanged cachedResult monitorFiles)+ where monitorFiles = reconstructMonitorFilePaths cachedFileStatus+ where+ -- In fileMonitorCheckIfOnlyValueChanged mode we want to guarantee that+ -- if we return MonitoredValueChanged that only the value changed.+ -- We do that by checkin for file changes first. Otherwise it makes+ -- more sense to do the cheaper test first.+ checkForChanges+ | fileMonitorCheckIfOnlyValueChanged+ = checkFileChange cachedFileStatus cachedKey cachedResult+ `mplusMaybeT`+ checkValueChange cachedKey++ | otherwise+ = checkValueChange cachedKey+ `mplusMaybeT`+ checkFileChange cachedFileStatus cachedKey cachedResult++ mplusMaybeT :: Monad m => m (Maybe a) -> m (Maybe a) -> m (Maybe a)+ mplusMaybeT ma mb = do+ mx <- ma+ case mx of+ Nothing -> mb+ Just x -> return (Just x)++ -- Check if the guard value has changed+ checkValueChange cachedKey+ | not (fileMonitorKeyValid currentKey cachedKey)+ = return (Just (MonitoredValueChanged cachedKey))+ | otherwise+ = return Nothing++ -- Check if any file has changed+ checkFileChange cachedFileStatus cachedKey cachedResult = do+ res <- probeFileSystem root cachedFileStatus+ case res of+ -- Some monitored file has changed+ Left changedPath ->+ return (Just (MonitoredFileChanged (normalise changedPath)))++ -- No monitored file has changed+ Right (cachedFileStatus', cacheStatus) -> do++ -- But we might still want to update the cache+ whenCacheChanged cacheStatus $+ rewriteCacheFile monitor cachedFileStatus' cachedKey cachedResult++ return Nothing++-- | Helper for reading the cache file.+--+-- This determines the type and format of the binary cache file.+--+readCacheFile :: (Binary a, Binary b)+ => FileMonitor a b+ -> IO (Either String (MonitorStateFileSet, a, b))+readCacheFile FileMonitor {fileMonitorCacheFile} =+ withBinaryFile fileMonitorCacheFile ReadMode $ \hnd ->+ Binary.decodeOrFailIO =<< BS.hGetContents hnd++-- | Helper for writing the cache file.+--+-- This determines the type and format of the binary cache file.+--+rewriteCacheFile :: (Binary a, Binary b)+ => FileMonitor a b+ -> MonitorStateFileSet -> a -> b -> IO ()+rewriteCacheFile FileMonitor {fileMonitorCacheFile} fileset key result =+ writeFileAtomic fileMonitorCacheFile $+ Binary.encode (fileset, key, result)++-- | Probe the file system to see if any of the monitored files have changed.+--+-- It returns Nothing if any file changed, or returns a possibly updated+-- file 'MonitorStateFileSet' plus an indicator of whether it actually changed.+--+-- We may need to update the cache since there may be changes in the filesystem+-- state which don't change any of our affected files.+--+-- Consider the glob @{proj1,proj2}\/\*.cabal@. Say we first run and find a+-- @proj1@ directory containing @proj1.cabal@ yet no @proj2@. If we later run+-- and find @proj2@ was created, yet contains no files matching @*.cabal@ then+-- we want to update the cache despite no changes in our relevant file set.+-- Specifically, we should add an mtime for this directory so we can avoid+-- re-traversing the directory in future runs.+--+probeFileSystem :: FilePath -> MonitorStateFileSet+ -> IO (Either FilePath (MonitorStateFileSet, CacheChanged))+probeFileSystem root (MonitorStateFileSet singlePaths globPaths) =+ runChangedM $ do+ sequence_+ [ probeMonitorStateFileStatus root file status+ | (file, MonitorStateFile _ _ status) <- Map.toList singlePaths ]+ -- The glob monitors can require state changes+ globPaths' <-+ sequence+ [ probeMonitorStateGlob root globPath+ | globPath <- globPaths ]+ return (MonitorStateFileSet singlePaths globPaths')+++-----------------------------------------------+-- Monad for checking for file system changes+--+-- We need to be able to bail out if we detect a change (using ExceptT),+-- but if there's no change we need to be able to rebuild the monitor+-- state. And we want to optimise that rebuilding by keeping track if+-- anything actually changed (using StateT), so that in the typical case+-- we can avoid rewriting the state file.++newtype ChangedM a = ChangedM (StateT CacheChanged (ExceptT FilePath IO) a)+ deriving (Functor, Applicative, Monad, MonadIO)++runChangedM :: ChangedM a -> IO (Either FilePath (a, CacheChanged))+runChangedM (ChangedM action) =+ runExceptT $ State.runStateT action CacheUnchanged++somethingChanged :: FilePath -> ChangedM a+somethingChanged path = ChangedM $ throwError path++cacheChanged :: ChangedM ()+cacheChanged = ChangedM $ State.put CacheChanged++mapChangedFile :: (FilePath -> FilePath) -> ChangedM a -> ChangedM a+mapChangedFile adjust (ChangedM a) =+ ChangedM (mapStateT (withExceptT adjust) a)++data CacheChanged = CacheChanged | CacheUnchanged++whenCacheChanged :: Monad m => CacheChanged -> m () -> m ()+whenCacheChanged CacheChanged action = action+whenCacheChanged CacheUnchanged _ = return ()++----------------------++-- | Probe the file system to see if a single monitored file has changed.+--+probeMonitorStateFileStatus :: FilePath -> FilePath+ -> MonitorStateFileStatus+ -> ChangedM ()+probeMonitorStateFileStatus root file status =+ case status of+ MonitorStateFileExists ->+ probeFileExistence root file++ MonitorStateFileModTime mtime ->+ probeFileModificationTime root file mtime++ MonitorStateFileHashed mtime hash ->+ probeFileModificationTimeAndHash root file mtime hash++ MonitorStateDirExists ->+ probeDirExistence root file++ MonitorStateDirModTime mtime ->+ probeFileModificationTime root file mtime++ MonitorStateNonExistent ->+ probeFileNonExistence root file++ MonitorStateAlreadyChanged ->+ somethingChanged file+++-- | Probe the file system to see if a monitored file glob has changed.+--+probeMonitorStateGlob :: FilePath -- ^ root path+ -> MonitorStateGlob+ -> ChangedM MonitorStateGlob+probeMonitorStateGlob relroot+ (MonitorStateGlob kindfile kinddir globroot glob) = do+ root <- liftIO $ getFilePathRootDirectory globroot relroot+ case globroot of+ FilePathRelative ->+ MonitorStateGlob kindfile kinddir globroot <$>+ probeMonitorStateGlobRel kindfile kinddir root "." glob++ -- for absolute cases, make the changed file we report absolute too+ _ ->+ mapChangedFile (root </>) $+ MonitorStateGlob kindfile kinddir globroot <$>+ probeMonitorStateGlobRel kindfile kinddir root "" glob++probeMonitorStateGlobRel :: MonitorKindFile -> MonitorKindDir+ -> FilePath -- ^ root path+ -> FilePath -- ^ path of the directory we are+ -- looking in relative to @root@+ -> MonitorStateGlobRel+ -> ChangedM MonitorStateGlobRel+probeMonitorStateGlobRel kindfile kinddir root dirName+ (MonitorStateGlobDirs glob globPath mtime children) = do+ change <- liftIO $ checkDirectoryModificationTime (root </> dirName) mtime+ case change of+ Nothing -> do+ children' <- sequence+ [ do fstate' <- probeMonitorStateGlobRel+ kindfile kinddir root+ (dirName </> fname) fstate+ return (fname, fstate')+ | (fname, fstate) <- children ]+ return $! MonitorStateGlobDirs glob globPath mtime children'++ Just mtime' -> do+ -- directory modification time changed:+ -- a matching subdir may have been added or deleted+ matches <- filterM (\entry -> let subdir = root </> dirName </> entry+ in liftIO $ doesDirectoryExist subdir)+ . filter (matchGlob glob)+ =<< liftIO (getDirectoryContents (root </> dirName))++ children' <- mapM probeMergeResult $+ mergeBy (\(path1,_) path2 -> compare path1 path2)+ children+ (sort matches)+ return $! MonitorStateGlobDirs glob globPath mtime' children'+ -- Note that just because the directory has changed, we don't force+ -- a cache rewrite with 'cacheChanged' since that has some cost, and+ -- all we're saving is scanning the directory. But we do rebuild the+ -- cache with the new mtime', so that if the cache is rewritten for+ -- some other reason, we'll take advantage of that.++ where+ probeMergeResult :: MergeResult (FilePath, MonitorStateGlobRel) FilePath+ -> ChangedM (FilePath, MonitorStateGlobRel)++ -- Only in cached (directory deleted)+ probeMergeResult (OnlyInLeft (path, fstate)) = do+ case allMatchingFiles (dirName </> path) fstate of+ [] -> return (path, fstate)+ -- Strictly speaking we should be returning 'CacheChanged' above+ -- as we should prune the now-missing 'MonitorStateGlobRel'. However+ -- we currently just leave these now-redundant entries in the+ -- cache as they cost no IO and keeping them allows us to avoid+ -- rewriting the cache.+ (file:_) -> somethingChanged file++ -- Only in current filesystem state (directory added)+ probeMergeResult (OnlyInRight path) = do+ fstate <- liftIO $ buildMonitorStateGlobRel Nothing Map.empty+ kindfile kinddir root (dirName </> path) globPath+ case allMatchingFiles (dirName </> path) fstate of+ (file:_) -> somethingChanged file+ -- This is the only case where we use 'cacheChanged' because we can+ -- have a whole new dir subtree (of unbounded size and cost), so we+ -- need to save the state of that new subtree in the cache.+ [] -> cacheChanged >> return (path, fstate)++ -- Found in path+ probeMergeResult (InBoth (path, fstate) _) = do+ fstate' <- probeMonitorStateGlobRel kindfile kinddir+ root (dirName </> path) fstate+ return (path, fstate')++ -- | Does a 'MonitorStateGlob' have any relevant files within it?+ allMatchingFiles :: FilePath -> MonitorStateGlobRel -> [FilePath]+ allMatchingFiles dir (MonitorStateGlobFiles _ _ entries) =+ [ dir </> fname | (fname, _) <- entries ]+ allMatchingFiles dir (MonitorStateGlobDirs _ _ _ entries) =+ [ res+ | (subdir, fstate) <- entries+ , res <- allMatchingFiles (dir </> subdir) fstate ]+ allMatchingFiles dir MonitorStateGlobDirTrailing =+ [dir]++probeMonitorStateGlobRel _ _ root dirName+ (MonitorStateGlobFiles glob mtime children) = do+ change <- liftIO $ checkDirectoryModificationTime (root </> dirName) mtime+ mtime' <- case change of+ Nothing -> return mtime+ Just mtime' -> do+ -- directory modification time changed:+ -- a matching file may have been added or deleted+ matches <- return . filter (matchGlob glob)+ =<< liftIO (getDirectoryContents (root </> dirName))++ mapM_ probeMergeResult $+ mergeBy (\(path1,_) path2 -> compare path1 path2)+ children+ (sort matches)+ return mtime'++ -- Check that none of the children have changed+ forM_ children $ \(file, status) ->+ probeMonitorStateFileStatus root (dirName </> file) status+++ return (MonitorStateGlobFiles glob mtime' children)+ -- Again, we don't force a cache rewite with 'cacheChanged', but we do use+ -- the new mtime' if any.+ where+ probeMergeResult :: MergeResult (FilePath, MonitorStateFileStatus) FilePath+ -> ChangedM ()+ probeMergeResult mr = case mr of+ InBoth _ _ -> return ()+ -- this is just to be able to accurately report which file changed:+ OnlyInLeft (path, _) -> somethingChanged (dirName </> path)+ OnlyInRight path -> somethingChanged (dirName </> path)++probeMonitorStateGlobRel _ _ _ _ MonitorStateGlobDirTrailing =+ return MonitorStateGlobDirTrailing++------------------------------------------------------------------------------++-- | Update the input value and the set of files monitored by the+-- 'FileMonitor', plus the cached value that may be returned in future.+--+-- This takes a snapshot of the state of the monitored files right now, so+-- 'checkFileMonitorChanged' will look for file system changes relative to+-- this snapshot.+--+-- This is typically done once the action has been completed successfully and+-- we have the action's result and we know what files it looked at. See+-- 'FileMonitor' for a full explanation.+--+-- If we do take the snapshot after the action has completed then we have a+-- problem. The problem is that files might have changed /while/ the action was+-- running but /after/ the action read them. If we take the snapshot after the+-- action completes then we will miss these changes. The solution is to record+-- a timestamp before beginning execution of the action and then we make the+-- conservative assumption that any file that has changed since then has+-- already changed, ie the file monitor state for these files will be such that+-- 'checkFileMonitorChanged' will report that they have changed.+--+-- So if you do use 'updateFileMonitor' after the action (so you can discover+-- the files used rather than predicting them in advance) then use+-- 'beginUpdateFileMonitor' to get a timestamp and pass that. Alternatively,+-- if you take the snapshot in advance of the action, or you're not monitoring+-- any files then you can use @Nothing@ for the timestamp parameter.+--+updateFileMonitor+ :: (Binary a, Binary b)+ => FileMonitor a b -- ^ cache file path+ -> FilePath -- ^ root directory+ -> Maybe MonitorTimestamp -- ^ timestamp when the update action started+ -> [MonitorFilePath] -- ^ files of interest relative to root+ -> a -- ^ the current key value+ -> b -- ^ the current result value+ -> IO ()+updateFileMonitor monitor root startTime monitorFiles+ cachedKey cachedResult = do+ hashcache <- readCacheFileHashes monitor+ msfs <- buildMonitorStateFileSet startTime hashcache root monitorFiles+ rewriteCacheFile monitor msfs cachedKey cachedResult++-- | A timestamp to help with the problem of file changes during actions.+-- See 'updateFileMonitor' for details.+--+newtype MonitorTimestamp = MonitorTimestamp ModTime++-- | Record a timestamp at the beginning of an action, and when the action+-- completes call 'updateFileMonitor' passing it the timestamp.+-- See 'updateFileMonitor' for details.+--+beginUpdateFileMonitor :: IO MonitorTimestamp+beginUpdateFileMonitor = MonitorTimestamp <$> getCurTime++-- | Take the snapshot of the monitored files. That is, given the+-- specification of the set of files we need to monitor, inspect the state+-- of the file system now and collect the information we'll need later to+-- determine if anything has changed.+--+buildMonitorStateFileSet :: Maybe MonitorTimestamp -- ^ optional: timestamp+ -- of the start of the action+ -> FileHashCache -- ^ existing file hashes+ -> FilePath -- ^ root directory+ -> [MonitorFilePath] -- ^ patterns of interest+ -- relative to root+ -> IO MonitorStateFileSet+buildMonitorStateFileSet mstartTime hashcache root =+ go Map.empty []+ where+ go :: Map FilePath MonitorStateFile -> [MonitorStateGlob]+ -> [MonitorFilePath] -> IO MonitorStateFileSet+ go !singlePaths !globPaths [] =+ return (MonitorStateFileSet singlePaths globPaths)++ go !singlePaths !globPaths+ (MonitorFile kindfile kinddir path : monitors) = do+ monitorState <- MonitorStateFile kindfile kinddir+ <$> buildMonitorStateFile mstartTime hashcache+ kindfile kinddir root path+ go (Map.insert path monitorState singlePaths) globPaths monitors++ go !singlePaths !globPaths+ (MonitorFileGlob kindfile kinddir globPath : monitors) = do+ monitorState <- buildMonitorStateGlob mstartTime hashcache+ kindfile kinddir root globPath+ go singlePaths (monitorState : globPaths) monitors+++buildMonitorStateFile :: Maybe MonitorTimestamp -- ^ start time of update+ -> FileHashCache -- ^ existing file hashes+ -> MonitorKindFile -> MonitorKindDir+ -> FilePath -- ^ the root directory+ -> FilePath+ -> IO MonitorStateFileStatus+buildMonitorStateFile mstartTime hashcache kindfile kinddir root path = do+ let abspath = root </> path+ isFile <- doesFileExist abspath+ isDir <- doesDirectoryExist abspath+ case (isFile, kindfile, isDir, kinddir) of+ (_, FileNotExists, _, DirNotExists) ->+ -- we don't need to care if it exists now, since we check at probe time+ return MonitorStateNonExistent++ (False, _, False, _) ->+ return MonitorStateAlreadyChanged++ (True, FileExists, _, _) ->+ return MonitorStateFileExists++ (True, FileModTime, _, _) ->+ handleIOException MonitorStateAlreadyChanged $ do+ mtime <- getModTime abspath+ if changedDuringUpdate mstartTime mtime+ then return MonitorStateAlreadyChanged+ else return (MonitorStateFileModTime mtime)++ (True, FileHashed, _, _) ->+ handleIOException MonitorStateAlreadyChanged $ do+ mtime <- getModTime abspath+ if changedDuringUpdate mstartTime mtime+ then return MonitorStateAlreadyChanged+ else do hash <- getFileHash hashcache abspath abspath mtime+ return (MonitorStateFileHashed mtime hash)++ (_, _, True, DirExists) ->+ return MonitorStateDirExists++ (_, _, True, DirModTime) ->+ handleIOException MonitorStateAlreadyChanged $ do+ mtime <- getModTime abspath+ if changedDuringUpdate mstartTime mtime+ then return MonitorStateAlreadyChanged+ else return (MonitorStateDirModTime mtime)++ (False, _, True, DirNotExists) -> return MonitorStateAlreadyChanged+ (True, FileNotExists, False, _) -> return MonitorStateAlreadyChanged++-- | If we have a timestamp for the beginning of the update, then any file+-- mtime later than this means that it changed during the update and we ought+-- to consider the file as already changed.+--+changedDuringUpdate :: Maybe MonitorTimestamp -> ModTime -> Bool+changedDuringUpdate (Just (MonitorTimestamp startTime)) mtime+ = mtime > startTime+changedDuringUpdate _ _ = False++-- | Much like 'buildMonitorStateFileSet' but for the somewhat complicated case+-- of a file glob.+--+-- This gets used both by 'buildMonitorStateFileSet' when we're taking the+-- file system snapshot, but also by 'probeGlobStatus' as part of checking+-- the monitored (globed) files for changes when we find a whole new subtree.+--+buildMonitorStateGlob :: Maybe MonitorTimestamp -- ^ start time of update+ -> FileHashCache -- ^ existing file hashes+ -> MonitorKindFile -> MonitorKindDir+ -> FilePath -- ^ the root directory+ -> FilePathGlob -- ^ the matching glob+ -> IO MonitorStateGlob+buildMonitorStateGlob mstartTime hashcache kindfile kinddir relroot+ (FilePathGlob globroot globPath) = do+ root <- liftIO $ getFilePathRootDirectory globroot relroot+ MonitorStateGlob kindfile kinddir globroot <$>+ buildMonitorStateGlobRel+ mstartTime hashcache kindfile kinddir root "." globPath++buildMonitorStateGlobRel :: Maybe MonitorTimestamp -- ^ start time of update+ -> FileHashCache -- ^ existing file hashes+ -> MonitorKindFile -> MonitorKindDir+ -> FilePath -- ^ the root directory+ -> FilePath -- ^ directory we are examining+ -- relative to the root+ -> FilePathGlobRel -- ^ the matching glob+ -> IO MonitorStateGlobRel+buildMonitorStateGlobRel mstartTime hashcache kindfile kinddir root+ dir globPath = do+ let absdir = root </> dir+ dirEntries <- getDirectoryContents absdir+ dirMTime <- getModTime absdir+ case globPath of+ GlobDir glob globPath' -> do+ subdirs <- filterM (\subdir -> doesDirectoryExist (absdir </> subdir))+ $ filter (matchGlob glob) dirEntries+ subdirStates <-+ forM (sort subdirs) $ \subdir -> do+ fstate <- buildMonitorStateGlobRel+ mstartTime hashcache kindfile kinddir root+ (dir </> subdir) globPath'+ return (subdir, fstate)+ return $! MonitorStateGlobDirs glob globPath' dirMTime subdirStates++ GlobFile glob -> do+ let files = filter (matchGlob glob) dirEntries+ filesStates <-+ forM (sort files) $ \file -> do+ fstate <- buildMonitorStateFile+ mstartTime hashcache kindfile kinddir root+ (dir </> file)+ return (file, fstate)+ return $! MonitorStateGlobFiles glob dirMTime filesStates++ GlobDirTrailing ->+ return MonitorStateGlobDirTrailing+++-- | We really want to avoid re-hashing files all the time. We already make+-- the assumption that if a file mtime has not changed then we don't need to+-- bother checking if the content hash has changed. We can apply the same+-- assumption when updating the file monitor state. In the typical case of+-- updating a file monitor the set of files is the same or largely the same so+-- we can grab the previously known content hashes with their corresponding+-- mtimes.+--+type FileHashCache = Map FilePath (ModTime, Hash)++-- | We declare it a cache hit if the mtime of a file is the same as before.+--+lookupFileHashCache :: FileHashCache -> FilePath -> ModTime -> Maybe Hash+lookupFileHashCache hashcache file mtime = do+ (mtime', hash) <- Map.lookup file hashcache+ guard (mtime' == mtime)+ return hash++-- | Either get it from the cache or go read the file+getFileHash :: FileHashCache -> FilePath -> FilePath -> ModTime -> IO Hash+getFileHash hashcache relfile absfile mtime =+ case lookupFileHashCache hashcache relfile mtime of+ Just hash -> return hash+ Nothing -> readFileHash absfile++-- | Build a 'FileHashCache' from the previous 'MonitorStateFileSet'. While+-- in principle we could preserve the structure of the previous state, given+-- that the set of files to monitor can change then it's simpler just to throw+-- away the structure and use a finite map.+--+readCacheFileHashes :: (Binary a, Binary b)+ => FileMonitor a b -> IO FileHashCache+readCacheFileHashes monitor =+ handleDoesNotExist Map.empty $+ handleErrorCall Map.empty $ do+ res <- readCacheFile monitor+ case res of+ Left _ -> return Map.empty+ Right (msfs, _, _) -> return (mkFileHashCache msfs)+ where+ mkFileHashCache :: MonitorStateFileSet -> FileHashCache+ mkFileHashCache (MonitorStateFileSet singlePaths globPaths) =+ collectAllFileHashes singlePaths+ `Map.union` collectAllGlobHashes globPaths++ collectAllFileHashes =+ Map.mapMaybe $ \(MonitorStateFile _ _ fstate) -> case fstate of+ MonitorStateFileHashed mtime hash -> Just (mtime, hash)+ _ -> Nothing++ collectAllGlobHashes globPaths =+ Map.fromList [ (fpath, hash)+ | MonitorStateGlob _ _ _ gstate <- globPaths+ , (fpath, hash) <- collectGlobHashes "" gstate ]++ collectGlobHashes dir (MonitorStateGlobDirs _ _ _ entries) =+ [ res+ | (subdir, fstate) <- entries+ , res <- collectGlobHashes (dir </> subdir) fstate ]++ collectGlobHashes dir (MonitorStateGlobFiles _ _ entries) =+ [ (dir </> fname, (mtime, hash))+ | (fname, MonitorStateFileHashed mtime hash) <- entries ]++ collectGlobHashes _dir MonitorStateGlobDirTrailing =+ []+++------------------------------------------------------------------------------+-- Utils+--++-- | Within the @root@ directory, check if @file@ has its 'ModTime' is+-- the same as @mtime@, short-circuiting if it is different.+probeFileModificationTime :: FilePath -> FilePath -> ModTime -> ChangedM ()+probeFileModificationTime root file mtime = do+ unchanged <- liftIO $ checkModificationTimeUnchanged root file mtime+ unless unchanged (somethingChanged file)++-- | Within the @root@ directory, check if @file@ has its 'ModTime' and+-- 'Hash' is the same as @mtime@ and @hash@, short-circuiting if it is+-- different.+probeFileModificationTimeAndHash :: FilePath -> FilePath -> ModTime -> Hash+ -> ChangedM ()+probeFileModificationTimeAndHash root file mtime hash = do+ unchanged <- liftIO $+ checkFileModificationTimeAndHashUnchanged root file mtime hash+ unless unchanged (somethingChanged file)++-- | Within the @root@ directory, check if @file@ still exists as a file.+-- If it *does not* exist, short-circuit.+probeFileExistence :: FilePath -> FilePath -> ChangedM ()+probeFileExistence root file = do+ existsFile <- liftIO $ doesFileExist (root </> file)+ unless existsFile (somethingChanged file)++-- | Within the @root@ directory, check if @dir@ still exists.+-- If it *does not* exist, short-circuit.+probeDirExistence :: FilePath -> FilePath -> ChangedM ()+probeDirExistence root dir = do+ existsDir <- liftIO $ doesDirectoryExist (root </> dir)+ unless existsDir (somethingChanged dir)++-- | Within the @root@ directory, check if @file@ still does not exist.+-- If it *does* exist, short-circuit.+probeFileNonExistence :: FilePath -> FilePath -> ChangedM ()+probeFileNonExistence root file = do+ existsFile <- liftIO $ doesFileExist (root </> file)+ existsDir <- liftIO $ doesDirectoryExist (root </> file)+ when (existsFile || existsDir) (somethingChanged file)++-- | Returns @True@ if, inside the @root@ directory, @file@ has the same+-- 'ModTime' as @mtime@.+checkModificationTimeUnchanged :: FilePath -> FilePath+ -> ModTime -> IO Bool+checkModificationTimeUnchanged root file mtime =+ handleIOException False $ do+ mtime' <- getModTime (root </> file)+ return (mtime == mtime')++-- | Returns @True@ if, inside the @root@ directory, @file@ has the+-- same 'ModTime' and 'Hash' as @mtime and @chash@.+checkFileModificationTimeAndHashUnchanged :: FilePath -> FilePath+ -> ModTime -> Hash -> IO Bool+checkFileModificationTimeAndHashUnchanged root file mtime chash =+ handleIOException False $ do+ mtime' <- getModTime (root </> file)+ if mtime == mtime'+ then return True+ else do+ chash' <- readFileHash (root </> file)+ return (chash == chash')++-- | Read a non-cryptographic hash of a @file@.+readFileHash :: FilePath -> IO Hash+readFileHash file =+ withBinaryFile file ReadMode $ \hnd ->+ evaluate . Hashable.hash =<< BS.hGetContents hnd++-- | Given a directory @dir@, return @Nothing@ if its 'ModTime'+-- is the same as @mtime@, and the new 'ModTime' if it is not.+checkDirectoryModificationTime :: FilePath -> ModTime -> IO (Maybe ModTime)+checkDirectoryModificationTime dir mtime =+ handleIOException Nothing $ do+ mtime' <- getModTime dir+ if mtime == mtime'+ then return Nothing+ else return (Just mtime')++-- | Run an IO computation, returning @e@ if there is an 'error'+-- call. ('ErrorCall')+handleErrorCall :: a -> IO a -> IO a+handleErrorCall e =+ handle (\(ErrorCall _) -> return e)++-- | Run an IO computation, returning @e@ if there is any 'IOException'.+--+-- This policy is OK in the file monitor code because it just causes the+-- monitor to report that something changed, and then code reacting to that+-- will normally encounter the same IO exception when it re-runs the action+-- that uses the file.+--+handleIOException :: a -> IO a -> IO a+handleIOException e =+ handle (anyIOException e)+ where+ anyIOException :: a -> IOException -> IO a+ anyIOException x _ = return x+++------------------------------------------------------------------------------+-- Instances+--++instance Binary MonitorStateFileSet where+ put (MonitorStateFileSet singlePaths globPaths) = do+ put (1 :: Int) -- version+ put singlePaths+ put globPaths+ get = do+ ver <- get+ if ver == (1 :: Int)+ then do singlePaths <- get+ globPaths <- get+ return $! MonitorStateFileSet singlePaths globPaths+ else fail "MonitorStateFileSet: wrong version"
cabal/cabal-install/Distribution/Client/Freeze.hs view
@@ -13,7 +13,7 @@ -- The cabal freeze command ----------------------------------------------------------------------------- module Distribution.Client.Freeze (- freeze,+ freeze, getFreezePkgs ) where import Distribution.Client.Config ( SavedConfig(..) )@@ -27,10 +27,11 @@ import Distribution.Client.InstallPlan ( InstallPlan, PlanPackage ) import qualified Distribution.Client.InstallPlan as InstallPlan+import Distribution.Client.PkgConfigDb+ ( PkgConfigDb, readPkgConfigDb ) import Distribution.Client.Setup- ( GlobalFlags(..), FreezeFlags(..), ConfigExFlags(..) )-import Distribution.Client.HttpUtils- ( configureTransport )+ ( GlobalFlags(..), FreezeFlags(..), ConfigExFlags(..)+ , RepoContext(..) ) import Distribution.Client.Sandbox.PackageEnvironment ( loadUserConfig, pkgEnvSavedConfig, showPackageEnvironment, userPackageEnvironmentFile )@@ -42,7 +43,6 @@ import Distribution.Simple.Compiler ( Compiler, compilerInfo, PackageDBStack ) import Distribution.Simple.PackageIndex (InstalledPackageIndex)-import qualified Distribution.Simple.PackageIndex as PackageIndex import Distribution.Simple.Program ( ProgramConfiguration ) import Distribution.Simple.Setup@@ -77,7 +77,7 @@ -- freeze :: Verbosity -> PackageDBStack- -> [Repo]+ -> RepoContext -> Compiler -> Platform -> ProgramConfiguration@@ -85,24 +85,12 @@ -> GlobalFlags -> FreezeFlags -> IO ()-freeze verbosity packageDBs repos comp platform conf mSandboxPkgInfo+freeze verbosity packageDBs repoCtxt comp platform conf mSandboxPkgInfo globalFlags freezeFlags = do - installedPkgIndex <- getInstalledPackages verbosity comp packageDBs conf- sourcePkgDb <- getSourcePackages verbosity repos-- transport <- configureTransport verbosity- (flagToMaybe (globalHttpTransport globalFlags))-- pkgSpecifiers <- resolveUserTargets verbosity transport- (fromFlag $ globalWorldFile globalFlags)- (packageIndex sourcePkgDb)- [UserTargetLocalDir "."]-- sanityCheck pkgSpecifiers- pkgs <- planPackages- verbosity comp platform mSandboxPkgInfo freezeFlags- installedPkgIndex sourcePkgDb pkgSpecifiers+ pkgs <- getFreezePkgs+ verbosity packageDBs repoCtxt comp platform conf mSandboxPkgInfo+ globalFlags freezeFlags if null pkgs then notice verbosity $ "No packages to be frozen. "@@ -112,11 +100,40 @@ "The following packages would be frozen:" : formatPkgs pkgs - else freezePackages verbosity pkgs+ else freezePackages verbosity globalFlags pkgs where dryRun = fromFlag (freezeDryRun freezeFlags) +-- | Get the list of packages whose versions would be frozen by the @freeze@+-- command.+getFreezePkgs :: Verbosity+ -> PackageDBStack+ -> RepoContext+ -> Compiler+ -> Platform+ -> ProgramConfiguration+ -> Maybe SandboxPackageInfo+ -> GlobalFlags+ -> FreezeFlags+ -> IO [PlanPackage]+getFreezePkgs verbosity packageDBs repoCtxt comp platform conf mSandboxPkgInfo+ globalFlags freezeFlags = do++ installedPkgIndex <- getInstalledPackages verbosity comp packageDBs conf+ sourcePkgDb <- getSourcePackages verbosity repoCtxt+ pkgConfigDb <- readPkgConfigDb verbosity conf++ pkgSpecifiers <- resolveUserTargets verbosity repoCtxt+ (fromFlag $ globalWorldFile globalFlags)+ (packageIndex sourcePkgDb)+ [UserTargetLocalDir "."]++ sanityCheck pkgSpecifiers+ planPackages+ verbosity comp platform mSandboxPkgInfo freezeFlags+ installedPkgIndex sourcePkgDb pkgConfigDb pkgSpecifiers+ where sanityCheck pkgSpecifiers = do when (not . null $ [n | n@(NamedPackage _ _) <- pkgSpecifiers]) $ die $ "internal error: 'resolveUserTargets' returned "@@ -132,10 +149,11 @@ -> FreezeFlags -> InstalledPackageIndex -> SourcePackageDb- -> [PackageSpecifier SourcePackage]+ -> PkgConfigDb+ -> [PackageSpecifier UnresolvedSourcePackage] -> IO [PlanPackage] planPackages verbosity comp platform mSandboxPkgInfo freezeFlags- installedPkgIndex sourcePkgDb pkgSpecifiers = do+ installedPkgIndex sourcePkgDb pkgConfigDb pkgSpecifiers = do solver <- chooseSolver verbosity (fromFlag (freezeSolver freezeFlags)) (compilerInfo comp)@@ -143,7 +161,7 @@ installPlan <- foldProgress logMsg die return $ resolveDependencies- platform (compilerInfo comp)+ platform (compilerInfo comp) pkgConfigDb solver resolverParams @@ -175,10 +193,8 @@ logMsg message rest = debug verbosity message >> rest - stanzas = concat- [ if testsEnabled then [TestStanzas] else []- , if benchmarksEnabled then [BenchStanzas] else []- ]+ stanzas = [ TestStanzas | testsEnabled ]+ ++ [ BenchStanzas | benchmarksEnabled ] testsEnabled = fromFlagOrDefault False $ freezeTests freezeFlags benchmarksEnabled = fromFlagOrDefault False $ freezeBenchmarks freezeFlags @@ -199,24 +215,24 @@ -- freezing. This is useful for removing previously installed packages -- which are no longer required from the install plan. pruneInstallPlan :: InstallPlan- -> [PackageSpecifier SourcePackage]+ -> [PackageSpecifier UnresolvedSourcePackage] -> [PlanPackage] pruneInstallPlan installPlan pkgSpecifiers =- either (const brokenPkgsErr)- (removeSelf pkgIds . PackageIndex.allPackages) $- InstallPlan.dependencyClosure installPlan pkgIds+ removeSelf pkgIds $+ InstallPlan.dependencyClosure installPlan (map fakeUnitId pkgIds) where- pkgIds = [ packageId pkg | SpecificSourcePackage pkg <- pkgSpecifiers ]- removeSelf [thisPkg] = filter (\pp -> packageId pp /= thisPkg)+ pkgIds = [ packageId pkg+ | SpecificSourcePackage pkg <- pkgSpecifiers ]+ removeSelf [thisPkg] = filter (\pp -> packageId pp /= packageId thisPkg) removeSelf _ = error $ "internal error: 'pruneInstallPlan' given " ++ "unexpected package specifiers!"- brokenPkgsErr = error "planPackages: installPlan contains broken packages" -freezePackages :: Package pkg => Verbosity -> [pkg] -> IO ()-freezePackages verbosity pkgs = do+freezePackages :: Package pkg => Verbosity -> GlobalFlags -> [pkg] -> IO ()+freezePackages verbosity globalFlags pkgs = do+ pkgEnv <- fmap (createPkgEnv . addFrozenConstraints) $- loadUserConfig verbosity ""+ loadUserConfig verbosity "" (flagToMaybe . globalConstraintsFile $ globalFlags) writeFileAtomic userPackageEnvironmentFile $ showPkgEnv pkgEnv where addFrozenConstraints config =@@ -226,7 +242,7 @@ } } constraint pkg =- (pkgIdToConstraint $ packageId pkg, ConstraintSourceUserConfig)+ (pkgIdToConstraint $ packageId pkg, ConstraintSourceUserConfig userPackageEnvironmentFile) where pkgIdToConstraint pkgId = UserConstraintVersion (packageName pkgId)
+ cabal/cabal-install/Distribution/Client/GenBounds.hs view
@@ -0,0 +1,159 @@+-----------------------------------------------------------------------------+-- |+-- Module : Distribution.Client.GenBounds+-- Copyright : (c) Doug Beardsley 2015+-- License : BSD-like+--+-- Maintainer : cabal-devel@gmail.com+-- Stability : provisional+-- Portability : portable+--+-- The cabal gen-bounds command for generating PVP-compliant version bounds.+-----------------------------------------------------------------------------+module Distribution.Client.GenBounds (+ genBounds+ ) where++import Data.Version+ ( Version(..), showVersion )+import Distribution.Client.Init+ ( incVersion )+import Distribution.Client.Freeze+ ( getFreezePkgs )+import Distribution.Client.Sandbox.Types+ ( SandboxPackageInfo(..) )+import Distribution.Client.Setup+ ( GlobalFlags(..), FreezeFlags(..), RepoContext )+import Distribution.Package+ ( Package(..), Dependency(..), PackageName(..)+ , packageName, packageVersion )+import Distribution.PackageDescription+ ( buildDepends )+import Distribution.PackageDescription.Configuration+ ( finalizePackageDescription )+import Distribution.PackageDescription.Parse+ ( readPackageDescription )+import Distribution.Simple.Compiler+ ( Compiler, PackageDBStack, compilerInfo )+import Distribution.Simple.Program+ ( ProgramConfiguration )+import Distribution.Simple.Utils+ ( tryFindPackageDesc )+import Distribution.System+ ( Platform )+import Distribution.Verbosity+ ( Verbosity )+import Distribution.Version+ ( LowerBound(..), UpperBound(..), VersionRange(..), asVersionIntervals+ , orLaterVersion, earlierVersion, intersectVersionRanges )+import System.Directory+ ( getCurrentDirectory )++-- | Does this version range have an upper bound?+hasUpperBound :: VersionRange -> Bool+hasUpperBound vr =+ case asVersionIntervals vr of+ [] -> False+ is -> if snd (last is) == NoUpperBound then False else True++-- | Given a version, return an API-compatible (according to PVP) version range.+--+-- Example: @0.4.1.2@ produces the version range @>= 0.4.1 && < 0.5@.+--+-- This version is slightly different than the one in+-- 'Distribution.Client.Init'. This one uses a.b.c as the lower bound because+-- the user could be using a new function introduced in a.b.c which would make+-- ">= a.b" incorrect.+pvpize :: Version -> VersionRange+pvpize v = orLaterVersion (vn 3)+ `intersectVersionRanges`+ earlierVersion (incVersion 1 (vn 2))+ where+ vn n = (v { versionBranch = take n (versionBranch v) })++-- | Show the PVP-mandated version range for this package. The @padTo@ parameter+-- specifies the width of the package name column.+showBounds :: Package pkg => Int -> pkg -> String+showBounds padTo p = unwords $+ (padAfter padTo $ unPackageName $ packageName p) :+ map showInterval (asVersionIntervals $ pvpize $ packageVersion p)+ where+ padAfter :: Int -> String -> String+ padAfter n str = str ++ replicate (n - length str) ' '++ showInterval :: (LowerBound, UpperBound) -> String+ showInterval (LowerBound _ _, NoUpperBound) =+ error "Error: expected upper bound...this should never happen!"+ showInterval (LowerBound l _, UpperBound u _) =+ unwords [">=", showVersion l, "&& <", showVersion u]++-- | Entry point for the @gen-bounds@ command.+genBounds+ :: Verbosity+ -> PackageDBStack+ -> RepoContext+ -> Compiler+ -> Platform+ -> ProgramConfiguration+ -> Maybe SandboxPackageInfo+ -> GlobalFlags+ -> FreezeFlags+ -> IO ()+genBounds verbosity packageDBs repoCtxt comp platform conf mSandboxPkgInfo+ globalFlags freezeFlags = do++ let cinfo = compilerInfo comp++ cwd <- getCurrentDirectory+ path <- tryFindPackageDesc cwd+ gpd <- readPackageDescription verbosity path+ let epd = finalizePackageDescription [] (const True) platform cinfo [] gpd+ case epd of+ Left _ -> putStrLn "finalizePackageDescription failed"+ Right (pd,_) -> do+ let needBounds = filter (not . hasUpperBound . depVersion) $+ buildDepends pd++ if (null needBounds)+ then putStrLn+ "Congratulations, all your dependencies have upper bounds!"+ else go needBounds+ where+ go needBounds = do+ pkgs <- getFreezePkgs+ verbosity packageDBs repoCtxt comp platform conf+ mSandboxPkgInfo globalFlags freezeFlags++ putStrLn boundsNeededMsg++ let isNeeded pkg = unPackageName (packageName pkg)+ `elem` map depName needBounds+ let thePkgs = filter isNeeded pkgs++ let padTo = maximum $ map (length . unPackageName . packageName) pkgs+ mapM_ (putStrLn . (++",") . showBounds padTo) thePkgs++ depName :: Dependency -> String+ depName (Dependency (PackageName nm) _) = nm++ depVersion :: Dependency -> VersionRange+ depVersion (Dependency _ vr) = vr++-- | The message printed when some dependencies are found to be lacking proper+-- PVP-mandated bounds.+boundsNeededMsg :: String+boundsNeededMsg = unlines+ [ ""+ , "The following packages need bounds and here is a suggested starting point."+ , "You can copy and paste this into the build-depends section in your .cabal"+ , "file and it should work (with the appropriate removal of commas)."+ , ""+ , "Note that version bounds are a statement that you've successfully built and"+ , "tested your package and expect it to work with any of the specified package"+ , "versions (PROVIDED that those packages continue to conform with the PVP)."+ , "Therefore, the version bounds generated here are the most conservative"+ , "based on the versions that you are currently building with. If you know"+ , "your package will work with versions outside the ranges generated here,"+ , "feel free to widen them."+ , ""+ ]
cabal/cabal-install/Distribution/Client/Get.hs view
@@ -21,7 +21,7 @@ import Distribution.Package ( PackageId, packageId, packageName ) import Distribution.Simple.Setup- ( Flag(..), fromFlag, fromFlagOrDefault, flagToMaybe )+ ( Flag(..), fromFlag, fromFlagOrDefault ) import Distribution.Simple.Utils ( notice, die, info, writeFileAtomic ) import Distribution.Verbosity@@ -30,13 +30,11 @@ import qualified Distribution.PackageDescription as PD import Distribution.Client.Setup- ( GlobalFlags(..), GetFlags(..) )+ ( GlobalFlags(..), GetFlags(..), RepoContext(..) ) import Distribution.Client.Types import Distribution.Client.Targets import Distribution.Client.Dependency import Distribution.Client.FetchUtils-import Distribution.Client.HttpUtils- ( configureTransport, HttpTransport(..) ) import qualified Distribution.Client.Tar as Tar (extractTarGzFile) import Distribution.Client.IndexUtils as IndexUtils ( getSourcePackages )@@ -74,7 +72,7 @@ -- | Entry point for the 'cabal get' command. get :: Verbosity- -> [Repo]+ -> RepoContext -> GlobalFlags -> GetFlags -> [UserTarget]@@ -82,7 +80,7 @@ get verbosity _ _ _ [] = notice verbosity "No packages requested. Nothing to do." -get verbosity repos globalFlags getFlags userTargets = do+get verbosity repoCtxt globalFlags getFlags userTargets = do let useFork = case (getSourceRepository getFlags) of NoFlag -> False _ -> True@@ -90,11 +88,9 @@ unless useFork $ mapM_ checkTarget userTargets - sourcePkgDb <- getSourcePackages verbosity repos-- transport <- configureTransport verbosity (flagToMaybe (globalHttpTransport globalFlags))+ sourcePkgDb <- getSourcePackages verbosity repoCtxt - pkgSpecifiers <- resolveUserTargets verbosity transport+ pkgSpecifiers <- resolveUserTargets verbosity repoCtxt (fromFlag $ globalWorldFile globalFlags) (packageIndex sourcePkgDb) userTargets@@ -108,7 +104,7 @@ if useFork then fork pkgs- else unpack transport pkgs+ else unpack pkgs where resolverParams sourcePkgDb pkgSpecifiers =@@ -117,16 +113,16 @@ prefix = fromFlagOrDefault "" (getDestDir getFlags) - fork :: [SourcePackage] -> IO ()+ fork :: [UnresolvedSourcePackage] -> IO () fork pkgs = do let kind = fromFlag . getSourceRepository $ getFlags branchers <- findUsableBranchers mapM_ (forkPackage verbosity branchers prefix kind) pkgs - unpack :: HttpTransport -> [SourcePackage] -> IO ()- unpack transport pkgs = do+ unpack :: [UnresolvedSourcePackage] -> IO ()+ unpack pkgs = do forM_ pkgs $ \pkg -> do- location <- fetchPackage transport verbosity (packageSource pkg)+ location <- fetchPackage verbosity repoCtxt (packageSource pkg) let pkgid = packageId pkg descOverride | usePristine = Nothing | otherwise = packageDescrOverride pkg@@ -230,7 +226,7 @@ -- be created. -> (Maybe PD.RepoKind) -- ^ Which repo to choose.- -> SourcePackage+ -> SourcePackage loc -- ^ The package to fork. -> IO () forkPackage verbosity branchers prefix kind src = do
+ cabal/cabal-install/Distribution/Client/Glob.hs view
@@ -0,0 +1,276 @@+{-# LANGUAGE CPP, DeriveGeneric #-}++--TODO: [code cleanup] plausibly much of this module should be merged with+-- similar functionality in Cabal.+module Distribution.Client.Glob+ ( FilePathGlob(..)+ , FilePathRoot(..)+ , FilePathGlobRel(..)+ , Glob+ , GlobPiece(..)+ , matchFileGlob+ , matchFileGlobRel+ , matchGlob+ , isTrivialFilePathGlob+ , getFilePathRootDirectory+ ) where++import Data.Char (toUpper)+import Data.List (stripPrefix)+#if !MIN_VERSION_base(4,8,0)+import Control.Applicative+#endif+import Control.Monad+import Distribution.Compat.Binary+import GHC.Generics (Generic)++import Distribution.Text+import Distribution.Compat.ReadP (ReadP, (<++), (+++))+import qualified Distribution.Compat.ReadP as Parse+import qualified Text.PrettyPrint as Disp++import System.FilePath+import System.Directory+++-- | A file path specified by globbing+--+data FilePathGlob = FilePathGlob FilePathRoot FilePathGlobRel+ deriving (Eq, Show, Generic)++data FilePathGlobRel+ = GlobDir !Glob !FilePathGlobRel+ | GlobFile !Glob+ | GlobDirTrailing -- ^ trailing dir, a glob ending in @/@+ deriving (Eq, Show, Generic)++-- | A single directory or file component of a globbed path+type Glob = [GlobPiece]++-- | A piece of a globbing pattern+data GlobPiece = WildCard+ | Literal String+ | Union [Glob]+ deriving (Eq, Show, Generic)++data FilePathRoot+ = FilePathRelative+ | FilePathUnixRoot+ | FilePathWinDrive Char+ | FilePathHomeDir+ deriving (Eq, Show, Generic)++instance Binary FilePathGlob+instance Binary FilePathRoot+instance Binary FilePathGlobRel+instance Binary GlobPiece+++-- | Check if a 'FilePathGlob' doesn't actually make use of any globbing and+-- is in fact equivalent to a non-glob 'FilePath'.+--+-- If it is trivial in this sense then the result is the equivalent constant+-- 'FilePath'. On the other hand if it is not trivial (so could in principle+-- match more than one file) then the result is @Nothing@.+--+isTrivialFilePathGlob :: FilePathGlob -> Maybe FilePath+isTrivialFilePathGlob (FilePathGlob root pathglob) =+ case root of+ FilePathRelative -> go [] pathglob+ FilePathUnixRoot -> go ["/"] pathglob+ FilePathWinDrive drive -> go [drive:":"] pathglob+ FilePathHomeDir -> Nothing+ where+ go paths (GlobDir [Literal path] globs) = go (path:paths) globs+ go paths (GlobFile [Literal path]) = Just (joinPath (reverse (path:paths)))+ go paths GlobDirTrailing = Just (addTrailingPathSeparator+ (joinPath (reverse paths)))+ go _ _ = Nothing++-- | Get the 'FilePath' corresponding to a 'FilePathRoot'.+--+-- The 'FilePath' argument is required to supply the path for the+-- 'FilePathRelative' case.+--+getFilePathRootDirectory :: FilePathRoot+ -> FilePath -- ^ root for relative paths+ -> IO FilePath+getFilePathRootDirectory FilePathRelative root = return root+getFilePathRootDirectory FilePathUnixRoot _ = return "/"+getFilePathRootDirectory (FilePathWinDrive drive) _ = return (drive:":")+getFilePathRootDirectory FilePathHomeDir _ = getHomeDirectory+++------------------------------------------------------------------------------+-- Matching+--++-- | Match a 'FilePathGlob' against the file system, starting from a given+-- root directory for relative paths. The results of relative globs are+-- relative to the given root. Matches for absolute globs are absolute.+--+matchFileGlob :: FilePath -> FilePathGlob -> IO [FilePath]+matchFileGlob relroot (FilePathGlob globroot glob) = do+ root <- getFilePathRootDirectory globroot relroot+ matches <- matchFileGlobRel root glob+ case globroot of+ FilePathRelative -> return matches+ _ -> return (map (root </>) matches)++-- | Match a 'FilePathGlobRel' against the file system, starting from a+-- given root directory. The results are all relative to the given root.+--+matchFileGlobRel :: FilePath -> FilePathGlobRel -> IO [FilePath]+matchFileGlobRel root glob0 = go glob0 ""+ where+ go (GlobFile glob) dir = do+ entries <- getDirectoryContents (root </> dir)+ let files = filter (matchGlob glob) entries+ return (map (dir </>) files)++ go (GlobDir glob globPath) dir = do+ entries <- getDirectoryContents (root </> dir)+ subdirs <- filterM (\subdir -> doesDirectoryExist+ (root </> dir </> subdir))+ $ filter (matchGlob glob) entries+ concat <$> mapM (\subdir -> go globPath (dir </> subdir)) subdirs++ go GlobDirTrailing dir = return [dir]+++-- | Match a globbing pattern against a file path component+--+matchGlob :: Glob -> String -> Bool+matchGlob = goStart+ where+ -- From the man page, glob(7):+ -- "If a filename starts with a '.', this character must be+ -- matched explicitly."++ go, goStart :: [GlobPiece] -> String -> Bool++ goStart (WildCard:_) ('.':_) = False+ goStart (Union globs:rest) cs = any (\glob -> goStart (glob ++ rest) cs)+ globs+ goStart rest cs = go rest cs++ go [] "" = True+ go (Literal lit:rest) cs+ | Just cs' <- stripPrefix lit cs+ = go rest cs'+ | otherwise = False+ go [WildCard] "" = True+ go (WildCard:rest) (c:cs) = go rest (c:cs) || go (WildCard:rest) cs+ go (Union globs:rest) cs = any (\glob -> go (glob ++ rest) cs) globs+ go [] (_:_) = False+ go (_:_) "" = False+++------------------------------------------------------------------------------+-- Parsing & printing+--++instance Text FilePathGlob where+ disp (FilePathGlob root pathglob) = disp root Disp.<> disp pathglob+ parse =+ parse >>= \root ->+ (FilePathGlob root <$> parse)+ <++ (when (root == FilePathRelative) Parse.pfail >>+ return (FilePathGlob root GlobDirTrailing))++instance Text FilePathRoot where+ disp FilePathRelative = Disp.empty+ disp FilePathUnixRoot = Disp.char '/'+ disp (FilePathWinDrive c) = Disp.char c+ Disp.<> Disp.char ':'+ Disp.<> Disp.char '\\'+ disp FilePathHomeDir = Disp.char '~'+ Disp.<> Disp.char '/'++ parse =+ ( (Parse.char '/' >> return FilePathUnixRoot)+ +++ (Parse.char '~' >> Parse.char '/' >> return FilePathHomeDir)+ +++ (do drive <- Parse.satisfy (\c -> (c >= 'a' && c <= 'z')+ || (c >= 'A' && c <= 'Z'))+ _ <- Parse.char ':'+ _ <- Parse.char '/' +++ Parse.char '\\'+ return (FilePathWinDrive (toUpper drive)))+ )+ <++ return FilePathRelative++instance Text FilePathGlobRel where+ disp (GlobDir glob pathglob) = dispGlob glob+ Disp.<> Disp.char '/'+ Disp.<> disp pathglob+ disp (GlobFile glob) = dispGlob glob+ disp GlobDirTrailing = Disp.empty++ parse = parsePath+ where+ parsePath :: ReadP r FilePathGlobRel+ parsePath =+ parseGlob >>= \globpieces ->+ asDir globpieces+ <++ asTDir globpieces+ <++ asFile globpieces++ asDir glob = do dirSep+ globs <- parsePath+ return (GlobDir glob globs)+ asTDir glob = do dirSep+ return (GlobDir glob GlobDirTrailing)+ asFile glob = return (GlobFile glob)++ dirSep = (Parse.char '/' >> return ())+ +++ (do _ <- Parse.char '\\'+ -- check this isn't an escape code+ following <- Parse.look+ case following of+ (c:_) | isGlobEscapedChar c -> Parse.pfail+ _ -> return ())+++dispGlob :: Glob -> Disp.Doc+dispGlob = Disp.hcat . map dispPiece+ where+ dispPiece WildCard = Disp.char '*'+ dispPiece (Literal str) = Disp.text (escape str)+ dispPiece (Union globs) = Disp.braces+ (Disp.hcat (Disp.punctuate+ (Disp.char ',')+ (map dispGlob globs)))+ escape [] = []+ escape (c:cs)+ | isGlobEscapedChar c = '\\' : c : escape cs+ | otherwise = c : escape cs++parseGlob :: ReadP r Glob+parseGlob = Parse.many1 parsePiece+ where+ parsePiece = literal +++ wildcard +++ union++ wildcard = Parse.char '*' >> return WildCard++ union = Parse.between (Parse.char '{') (Parse.char '}') $+ fmap Union (Parse.sepBy1 parseGlob (Parse.char ','))++ literal = Literal `fmap` litchars1++ litchar = normal +++ escape++ normal = Parse.satisfy (\c -> not (isGlobEscapedChar c)+ && c /= '/' && c /= '\\')+ escape = Parse.char '\\' >> Parse.satisfy isGlobEscapedChar++ litchars1 :: ReadP r [Char]+ litchars1 = liftM2 (:) litchar litchars++ litchars :: ReadP r [Char]+ litchars = litchars1 <++ return []++isGlobEscapedChar :: Char -> Bool+isGlobEscapedChar '*' = True+isGlobEscapedChar '{' = True+isGlobEscapedChar '}' = True+isGlobEscapedChar ',' = True+isGlobEscapedChar _ = False
+ cabal/cabal-install/Distribution/Client/GlobalFlags.hs view
@@ -0,0 +1,260 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE RecordWildCards #-}+module Distribution.Client.GlobalFlags (+ GlobalFlags(..)+ , defaultGlobalFlags+ , RepoContext(..)+ , withRepoContext+ , withRepoContext'+ ) where++import Distribution.Client.Types+ ( Repo(..), RemoteRepo(..) )+import Distribution.Compat.Semigroup+import Distribution.Simple.Setup+ ( Flag(..), fromFlag, flagToMaybe )+import Distribution.Utils.NubList+ ( NubList, fromNubList )+import Distribution.Client.HttpUtils+ ( HttpTransport, configureTransport )+import Distribution.Verbosity+ ( Verbosity )+import Distribution.Simple.Utils+ ( info )++import Data.Maybe+ ( fromMaybe )+import Control.Concurrent+ ( MVar, newMVar, modifyMVar )+import Control.Exception+ ( throwIO )+import Control.Monad+ ( when )+import System.FilePath+ ( (</>) )+import Network.URI+ ( uriScheme, uriPath )+import Data.Map+ ( Map )+import qualified Data.Map as Map+import GHC.Generics ( Generic )++import qualified Hackage.Security.Client as Sec+import qualified Hackage.Security.Util.Path as Sec+import qualified Hackage.Security.Util.Pretty as Sec+import qualified Hackage.Security.Client.Repository.Cache as Sec+import qualified Hackage.Security.Client.Repository.Local as Sec.Local+import qualified Hackage.Security.Client.Repository.Remote as Sec.Remote+import qualified Distribution.Client.Security.HTTP as Sec.HTTP++-- ------------------------------------------------------------+-- * Global flags+-- ------------------------------------------------------------++-- | Flags that apply at the top level, not to any sub-command.+data GlobalFlags = GlobalFlags {+ globalVersion :: Flag Bool,+ globalNumericVersion :: Flag Bool,+ globalConfigFile :: Flag FilePath,+ globalSandboxConfigFile :: Flag FilePath,+ globalConstraintsFile :: Flag FilePath,+ globalRemoteRepos :: NubList RemoteRepo, -- ^ Available Hackage servers.+ globalCacheDir :: Flag FilePath,+ globalLocalRepos :: NubList FilePath,+ globalLogsDir :: Flag FilePath,+ globalWorldFile :: Flag FilePath,+ globalRequireSandbox :: Flag Bool,+ globalIgnoreSandbox :: Flag Bool,+ globalIgnoreExpiry :: Flag Bool, -- ^ Ignore security expiry dates+ globalHttpTransport :: Flag String+ } deriving Generic++defaultGlobalFlags :: GlobalFlags+defaultGlobalFlags = GlobalFlags {+ globalVersion = Flag False,+ globalNumericVersion = Flag False,+ globalConfigFile = mempty,+ globalSandboxConfigFile = mempty,+ globalConstraintsFile = mempty,+ globalRemoteRepos = mempty,+ globalCacheDir = mempty,+ globalLocalRepos = mempty,+ globalLogsDir = mempty,+ globalWorldFile = mempty,+ globalRequireSandbox = Flag False,+ globalIgnoreSandbox = Flag False,+ globalIgnoreExpiry = Flag False,+ globalHttpTransport = mempty+ }++instance Monoid GlobalFlags where+ mempty = gmempty+ mappend = (<>)++instance Semigroup GlobalFlags where+ (<>) = gmappend++-- ------------------------------------------------------------+-- * Repo context+-- ------------------------------------------------------------++-- | Access to repositories+data RepoContext = RepoContext {+ -- | All user-specified repositories+ repoContextRepos :: [Repo]++ -- | Get the HTTP transport+ --+ -- The transport will be initialized on the first call to this function.+ --+ -- NOTE: It is important that we don't eagerly initialize the transport.+ -- Initializing the transport is not free, and especially in contexts where+ -- we don't know a-priori whether or not we need the transport (for instance+ -- when using cabal in "nix mode") incurring the overhead of transport+ -- initialization on _every_ invocation (eg @cabal build@) is undesirable.+ , repoContextGetTransport :: IO HttpTransport++ -- | Get the (initialized) secure repo+ --+ -- (the 'Repo' type itself is stateless and must remain so, because it+ -- must be serializable)+ , repoContextWithSecureRepo :: forall a.+ Repo+ -> (forall down. Sec.Repository down -> IO a)+ -> IO a++ -- | Should we ignore expiry times (when checking security)?+ , repoContextIgnoreExpiry :: Bool+ }++-- | Wrapper around 'Repository', hiding the type argument+data SecureRepo = forall down. SecureRepo (Sec.Repository down)++withRepoContext :: Verbosity -> GlobalFlags -> (RepoContext -> IO a) -> IO a+withRepoContext verbosity globalFlags =+ withRepoContext'+ verbosity+ (fromNubList (globalRemoteRepos globalFlags))+ (fromNubList (globalLocalRepos globalFlags))+ (fromFlag (globalCacheDir globalFlags))+ (flagToMaybe (globalHttpTransport globalFlags))+ (flagToMaybe (globalIgnoreExpiry globalFlags))++withRepoContext' :: Verbosity -> [RemoteRepo] -> [FilePath]+ -> FilePath -> Maybe String -> Maybe Bool+ -> (RepoContext -> IO a)+ -> IO a+withRepoContext' verbosity remoteRepos localRepos+ sharedCacheDir httpTransport ignoreExpiry = \callback -> do+ transportRef <- newMVar Nothing+ let httpLib = Sec.HTTP.transportAdapter+ verbosity+ (getTransport transportRef)+ initSecureRepos verbosity httpLib secureRemoteRepos $ \secureRepos' ->+ callback RepoContext {+ repoContextRepos = allRemoteRepos+ ++ map RepoLocal localRepos+ , repoContextGetTransport = getTransport transportRef+ , repoContextWithSecureRepo = withSecureRepo secureRepos'+ , repoContextIgnoreExpiry = fromMaybe False ignoreExpiry+ }+ where+ secureRemoteRepos =+ [ (remote, cacheDir) | RepoSecure remote cacheDir <- allRemoteRepos ]+ allRemoteRepos =+ [ (if isSecure then RepoSecure else RepoRemote) remote cacheDir+ | remote <- remoteRepos+ , let cacheDir = sharedCacheDir </> remoteRepoName remote+ isSecure = remoteRepoSecure remote == Just True+ ]++ getTransport :: MVar (Maybe HttpTransport) -> IO HttpTransport+ getTransport transportRef =+ modifyMVar transportRef $ \mTransport -> do+ transport <- case mTransport of+ Just tr -> return tr+ Nothing -> configureTransport verbosity httpTransport+ return (Just transport, transport)++ withSecureRepo :: Map Repo SecureRepo+ -> Repo+ -> (forall down. Sec.Repository down -> IO a)+ -> IO a+ withSecureRepo secureRepos repo callback =+ case Map.lookup repo secureRepos of+ Just (SecureRepo secureRepo) -> callback secureRepo+ Nothing -> throwIO $ userError "repoContextWithSecureRepo: unknown repo"++-- | Initialize the provided secure repositories+--+-- Assumed invariant: `remoteRepoSecure` should be set for all these repos.+initSecureRepos :: forall a. Verbosity+ -> Sec.HTTP.HttpLib+ -> [(RemoteRepo, FilePath)]+ -> (Map Repo SecureRepo -> IO a)+ -> IO a+initSecureRepos verbosity httpLib repos callback = go Map.empty repos+ where+ go :: Map Repo SecureRepo -> [(RemoteRepo, FilePath)] -> IO a+ go !acc [] = callback acc+ go !acc ((r,cacheDir):rs) = do+ cachePath <- Sec.makeAbsolute $ Sec.fromFilePath cacheDir+ initSecureRepo verbosity httpLib r cachePath $ \r' ->+ go (Map.insert (RepoSecure r cacheDir) r' acc) rs++-- | Initialize the given secure repo+--+-- The security library has its own concept of a "local" repository, distinct+-- from @cabal-install@'s; these are secure repositories, but live in the local+-- file system. We use the convention that these repositories are identified by+-- URLs of the form @file:/path/to/local/repo@.+initSecureRepo :: Verbosity+ -> Sec.HTTP.HttpLib+ -> RemoteRepo -- ^ Secure repo ('remoteRepoSecure' assumed)+ -> Sec.Path Sec.Absolute -- ^ Cache dir+ -> (SecureRepo -> IO a) -- ^ Callback+ -> IO a+initSecureRepo verbosity httpLib RemoteRepo{..} cachePath = \callback -> do+ withRepo $ \r -> do+ requiresBootstrap <- Sec.requiresBootstrap r+ when requiresBootstrap $ Sec.uncheckClientErrors $+ Sec.bootstrap r+ (map Sec.KeyId remoteRepoRootKeys)+ (Sec.KeyThreshold (fromIntegral remoteRepoKeyThreshold))+ callback $ SecureRepo r+ where+ -- Initialize local or remote repo depending on the URI+ withRepo :: (forall down. Sec.Repository down -> IO a) -> IO a+ withRepo callback | uriScheme remoteRepoURI == "file:" = do+ dir <- Sec.makeAbsolute $ Sec.fromFilePath (uriPath remoteRepoURI)+ Sec.Local.withRepository dir+ cache+ Sec.hackageRepoLayout+ Sec.hackageIndexLayout+ logTUF+ callback+ withRepo callback =+ Sec.Remote.withRepository httpLib+ [remoteRepoURI]+ Sec.Remote.defaultRepoOpts+ cache+ Sec.hackageRepoLayout+ Sec.hackageIndexLayout+ logTUF+ callback++ cache :: Sec.Cache+ cache = Sec.Cache {+ cacheRoot = cachePath+ , cacheLayout = Sec.cabalCacheLayout+ }++ -- We display any TUF progress only in verbose mode, including any transient+ -- verification errors. If verification fails, then the final exception that+ -- is thrown will of course be shown.+ logTUF :: Sec.LogMessage -> IO ()+ logTUF = info verbosity . Sec.pretty
cabal/cabal-install/Distribution/Client/Haddock.hs view
@@ -17,6 +17,7 @@ where import Data.List (maximumBy)+import Data.Foldable (forM_) import System.Directory (createDirectoryIfMissing, renameFile) import System.FilePath ((</>), splitFileName) import Distribution.Package@@ -40,9 +41,7 @@ regenerateHaddockIndex verbosity pkgs conf index = do (paths, warns) <- haddockPackagePaths pkgs' Nothing let paths' = [ (interface, html) | (interface, Just html) <- paths]- case warns of- Nothing -> return ()- Just m -> debug verbosity m+ forM_ warns (debug verbosity) (confHaddock, _, _) <- requireProgramVersion verbosity haddockProgram
cabal/cabal-install/Distribution/Client/HttpUtils.hs view
@@ -1,11 +1,12 @@ {-# LANGUAGE CPP, BangPatterns #-} -------------------------------------------------------------------------------- | Separate module for HTTP actions, using a proxy server if one exists+-- | Separate module for HTTP actions, using a proxy server if one exists. ----------------------------------------------------------------------------- module Distribution.Client.HttpUtils ( DownloadResult(..), configureTransport, HttpTransport(..),+ HttpCode, downloadURI, transportCheckHttps, remoteRepoCheckHttps,@@ -18,7 +19,7 @@ , Header(..), HeaderName(..), lookupHeader ) import Network.HTTP.Proxy ( Proxy(..), fetchProxy) import Network.URI- ( URI (..), URIAuth (..) )+ ( URI (..), URIAuth (..), uriToString ) import Network.Browser ( browse, setOutHandler, setErrHandler, setProxy , setAuthorityGen, request, setAllowBasicAuth, setUserAgent )@@ -32,7 +33,7 @@ import Data.List ( isPrefixOf, find, intercalate ) import Data.Maybe- ( listToMaybe, maybeToList )+ ( listToMaybe, maybeToList, fromMaybe ) import qualified Paths_cabal_install (version) import Distribution.Verbosity (Verbosity) import Distribution.Simple.Utils@@ -69,7 +70,7 @@ ( IOEncoding(..), getEffectiveEnvironment ) import Numeric (showHex) import System.Directory (canonicalizePath)-import System.IO (hClose, hPutStr)+import System.IO (hClose) import System.FilePath (takeFileName, takeDirectory) import System.Random (randomRIO) import System.Exit (ExitCode(..))@@ -115,7 +116,7 @@ = transport withTempFileName (takeDirectory path) (takeFileName path) $ \tmpFile -> do- result <- getHttp transport' verbosity uri etag tmpFile+ result <- getHttp transport' verbosity uri etag tmpFile [] -- Only write the etag if we get a 200 response code. -- A 304 still sends us an etag header.@@ -196,7 +197,8 @@ isOldHackageURI uri = case uriAuthority uri of Just (URIAuth {uriRegName = "hackage.haskell.org"}) ->- FilePath.Posix.splitDirectories (uriPath uri) == ["/","packages","archive"]+ FilePath.Posix.splitDirectories (uriPath uri)+ == ["/","packages","archive"] _ -> False @@ -208,7 +210,7 @@ -- | GET a URI, with an optional ETag (to do a conditional fetch), -- write the resource to the given file and return the HTTP status code, -- and optional ETag.- getHttp :: Verbosity -> URI -> Maybe ETag -> FilePath+ getHttp :: Verbosity -> URI -> Maybe ETag -> FilePath -> [Header] -> IO (HttpCode, Maybe ETag), -- | POST a resource to a URI, with optional auth (username, password)@@ -222,11 +224,17 @@ postHttpFile :: Verbosity -> URI -> FilePath -> Maybe Auth -> IO (HttpCode, String), + -- | PUT a file resource to a URI, with optional auth+ -- (username, password), extra headers and return the HTTP status code+ -- and any error string.+ putHttpFile :: Verbosity -> URI -> FilePath -> Maybe Auth -> [Header]+ -> IO (HttpCode, String),+ -- | Whether this transport supports https or just http. transportSupportsHttps :: Bool, -- | Whether this transport implementation was specifically chosen by- -- the user via configuration, or whether it was automatically selected. + -- the user via configuration, or whether it was automatically selected. -- Strictly speaking this is not a property of the transport itself but -- about how it was chosen. Nevertheless it's convenient to keep here. transportManuallySelected :: Bool@@ -310,9 +318,9 @@ curlTransport :: ConfiguredProgram -> HttpTransport curlTransport prog =- HttpTransport gethttp posthttp posthttpfile True False+ HttpTransport gethttp posthttp posthttpfile puthttpfile True False where- gethttp verbosity uri etag destPath = do+ gethttp verbosity uri etag destPath reqHeaders = do withTempFile (takeDirectory destPath) "curl-headers.txt" $ \tmpFile tmpHandle -> do hClose tmpHandle@@ -326,6 +334,9 @@ ++ concat [ ["--header", "If-None-Match: " ++ t] | t <- maybeToList etag ]+ ++ concat+ [ ["--header", show name ++ ": " ++ value]+ | Header name value <- reqHeaders ] resp <- getProgramInvocationOutput verbosity (programInvocation prog args)@@ -335,17 +346,41 @@ posthttp = noPostYet + addAuthConfig auth progInvocation = progInvocation+ { progInvokeInput = do+ (uname, passwd) <- auth+ return $ unlines+ [ "--digest"+ , "--user " ++ uname ++ ":" ++ passwd+ ]+ , progInvokeArgs = ["--config", "-"] ++ progInvokeArgs progInvocation+ }+ posthttpfile verbosity uri path auth = do let args = [ show uri , "--form", "package=@"++path- , "--write-out", "%{http_code}"+ , "--write-out", "\n%{http_code}" , "--user-agent", userAgent , "--silent", "--show-error"- , "--header", "Accept: text/plain" ]+ , "--header", "Accept: text/plain"+ ]+ resp <- getProgramInvocationOutput verbosity $ addAuthConfig auth+ (programInvocation prog args)+ (code, err, _etag) <- parseResponse uri resp ""+ return (code, err)++ puthttpfile verbosity uri path auth headers = do+ let args = [ show uri+ , "--request", "PUT", "--data-binary", "@"++path+ , "--write-out", "\n%{http_code}"+ , "--user-agent", userAgent+ , "--silent", "--show-error"+ , "--header", "Accept: text/plain"+ ] ++ concat- [ ["--digest", "--user", uname ++ ":" ++ passwd]- | (uname,passwd) <- maybeToList auth ]- resp <- getProgramInvocationOutput verbosity+ [ ["--header", show name ++ ": " ++ value]+ | Header name value <- headers ]+ resp <- getProgramInvocationOutput verbosity $ addAuthConfig auth (programInvocation prog args) (code, err, _etag) <- parseResponse uri resp "" return (code, err)@@ -357,11 +392,13 @@ case reverse (lines resp) of (codeLine:rerrLines) -> case readMaybe (trim codeLine) of- Just i -> let errstr = unlines (reverse rerrLines)+ Just i -> let errstr = mkErrstr rerrLines in Just (i, errstr) Nothing -> Nothing [] -> Nothing + mkErrstr = unlines . reverse . dropWhile (all isSpace)+ mb_etag :: Maybe ETag mb_etag = listToMaybe $ reverse [ etag@@ -374,15 +411,14 @@ wgetTransport :: ConfiguredProgram -> HttpTransport wgetTransport prog =- HttpTransport gethttp posthttp posthttpfile True False+ HttpTransport gethttp posthttp posthttpfile puthttpfile True False where- gethttp verbosity uri etag destPath = do- resp <- runWGet verbosity args+ gethttp verbosity uri etag destPath reqHeaders = do+ resp <- runWGet verbosity uri args (code, _err, etag') <- parseResponse uri resp return (code, etag') where- args = [ show uri- , "--output-document=" ++ destPath+ args = [ "--output-document=" ++ destPath , "--user-agent=" ++ userAgent , "--tries=5" , "--timeout=15"@@ -390,6 +426,8 @@ ++ concat [ ["--header", "If-None-Match: " ++ t] | t <- maybeToList etag ]+ ++ [ "--header=" ++ show name ++ ": " ++ value+ | Header name value <- reqHeaders ] posthttp = noPostYet @@ -400,25 +438,45 @@ BS.hPut tmpHandle body BS.writeFile "wget.in" body hClose tmpHandle- let args = [ show uri- , "--post-file=" ++ tmpFile+ let args = [ "--post-file=" ++ tmpFile , "--user-agent=" ++ userAgent , "--server-response"- , "--header=Content-type: multipart/form-data; " ++ + , "--header=Content-type: multipart/form-data; " ++ "boundary=" ++ boundary ]- ++ concat- [ [ "--http-user=" ++ uname- , "--http-password=" ++ passwd ]- | (uname,passwd) <- maybeToList auth ]-- resp <- runWGet verbosity args+ resp <- runWGet verbosity (addUriAuth auth uri) args (code, err, _etag) <- parseResponse uri resp return (code, err) - runWGet verbosity args = do+ puthttpfile verbosity uri path auth headers = do+ let args = [ "--method=PUT", "--body-file="++path+ , "--user-agent=" ++ userAgent+ , "--server-response"+ , "--header=Accept: text/plain" ]+ ++ [ "--header=" ++ show name ++ ": " ++ value+ | Header name value <- headers ]++ resp <- runWGet verbosity (addUriAuth auth uri) args+ (code, err, _etag) <- parseResponse uri resp+ return (code, err)++ addUriAuth Nothing uri = uri+ addUriAuth (Just (user, pass)) uri = uri+ { uriAuthority = Just a { uriUserInfo = user ++ ":" ++ pass ++ "@" }+ }+ where+ a = fromMaybe (URIAuth "" "" "") (uriAuthority uri)++ runWGet verbosity uri args = do+ -- We pass the URI via STDIN because it contains the users' credentials+ -- and sensitive data should not be passed via command line arguments.+ let+ invocation = (programInvocation prog ("--input-file=-" : args))+ { progInvokeInput = Just (uriToString id uri "")+ }+ -- wget returns its output on stderr rather than stdout (_, resp, exitCode) <- getProgramInvocationOutputAndErrors verbosity- (programInvocation prog args)+ invocation -- wget returns exit code 8 for server "errors" like "304 not modified" if exitCode == ExitSuccess || exitCode == ExitFailure 8 then return resp@@ -450,95 +508,128 @@ powershellTransport :: ConfiguredProgram -> HttpTransport powershellTransport prog =- HttpTransport gethttp posthttp posthttpfile True False+ HttpTransport gethttp posthttp posthttpfile puthttpfile True False where- gethttp verbosity uri etag destPath =- withTempFile (takeDirectory destPath)- "psScript.ps1" $ \tmpFile tmpHandle -> do- hPutStr tmpHandle script- hClose tmpHandle- let args = ["-InputFormat", "None", "-File", tmpFile]- resp <- getProgramInvocationOutput verbosity- (programInvocation prog args)- parseResponse resp+ gethttp verbosity uri etag destPath reqHeaders = do+ resp <- runPowershellScript verbosity $+ webclientScript+ (setupHeaders ((useragentHeader : etagHeader) ++ reqHeaders))+ [ "$wc.DownloadFile(" ++ escape (show uri)+ ++ "," ++ escape destPath ++ ");"+ , "Write-Host \"200\";"+ , "Write-Host $wc.ResponseHeaders.Item(\"ETag\");"+ ]+ parseResponse resp where- script =- concatMap (++";\n") $- [ "$wc = new-object system.net.webclient"- , "$wc.Headers.Add(\"user-agent\","++escape userAgent++")"]- ++ [ "$wc.Headers.Add(\"If-None-Match\"," ++ t ++ ")"- | t <- maybeToList etag ]- ++ [ "Try {"- , "$wc.DownloadFile("++ escape (show uri) ++- "," ++ escape destPath ++ ")"- , "} Catch {Write-Error $_; Exit(5);}"- , "Write-Host \"200\""- , "Write-Host $wc.ResponseHeaders.Item(\"ETag\")"- , "Exit" ]-- escape x = '"' : x ++ "\"" --TODO write/find real escape.- parseResponse x = case readMaybe . unlines . take 1 . lines $ trim x of Just i -> return (i, Nothing) -- TODO extract real etag Nothing -> statusParseFail uri x+ etagHeader = [ Header HdrIfNoneMatch t | t <- maybeToList etag ] posthttp = noPostYet posthttpfile verbosity uri path auth =- withTempFile (takeDirectory path)- (takeFileName path) $ \tmpFile tmpHandle ->- withTempFile (takeDirectory path)- "psScript.ps1" $ \tmpScriptFile tmpScriptHandle -> do- (body, boundary) <- generateMultipartBody path- BS.hPut tmpHandle body- hClose tmpHandle+ withTempFile (takeDirectory path)+ (takeFileName path) $ \tmpFile tmpHandle -> do+ (body, boundary) <- generateMultipartBody path+ BS.hPut tmpHandle body+ hClose tmpHandle+ fullPath <- canonicalizePath tmpFile - fullPath <- canonicalizePath tmpFile- hPutStr tmpScriptHandle (script fullPath boundary)- hClose tmpScriptHandle- let args = ["-InputFormat", "None", "-File", tmpScriptFile]- resp <- getProgramInvocationOutput verbosity- (programInvocation prog args)- parseResponse resp- where- script fullPath boundary = - concatMap (++";\n") $- [ "$wc = new-object system.net.webclient"- , "$wc.Headers.Add(\"user-agent\","++escape userAgent++")"- , "$wc.Headers.Add(\"Content-type\"," ++- "\"multipart/form-data; " ++- "boundary="++boundary++"\")" ]- ++ [ "$wc.Credentials = new-object System.Net.NetworkCredential("- ++ escape uname ++ "," ++ escape passwd ++ ",\"\")"- | (uname,passwd) <- maybeToList auth ]- ++ [ "Try {"- , "$bytes = [System.IO.File]::ReadAllBytes("++escape fullPath++")"- , "$wc.UploadData("++ escape (show uri) ++ ",$bytes)"- , "} Catch {Write-Error $_; Exit(1);}"- , "Write-Host \"200\""- , "Exit" ]+ let contentHeader = Header HdrContentType+ ("multipart/form-data; boundary=" ++ boundary)+ resp <- runPowershellScript verbosity $ webclientScript+ (setupHeaders (contentHeader : extraHeaders) ++ setupAuth auth)+ (uploadFileAction "POST" uri fullPath)+ parseUploadResponse uri resp - escape x = show x+ puthttpfile verbosity uri path auth headers = do+ fullPath <- canonicalizePath path+ resp <- runPowershellScript verbosity $ webclientScript+ (setupHeaders (extraHeaders ++ headers) ++ setupAuth auth)+ (uploadFileAction "PUT" uri fullPath)+ parseUploadResponse uri resp - parseResponse x = case readMaybe . unlines . take 1 . lines $ trim x of- Just i -> return (i, x) -- TODO extract real etag- Nothing -> statusParseFail uri x+ runPowershellScript verbosity script = do+ let args =+ [ "-InputFormat", "None"+ -- the default execution policy doesn't allow running+ -- unsigned scripts, so we need to tell powershell to bypass it+ , "-ExecutionPolicy", "bypass"+ , "-NoProfile", "-NonInteractive"+ , "-Command", "-"+ ]+ getProgramInvocationOutput verbosity (programInvocation prog args)+ { progInvokeInput = Just (script ++ "\nExit(0);")+ } + escape = show + useragentHeader = Header HdrUserAgent userAgent+ extraHeaders = [Header HdrAccept "text/plain", useragentHeader]++ setupHeaders headers =+ [ "$wc.Headers.Add(" ++ escape (show name) ++ "," ++ escape value ++ ");"+ | Header name value <- headers+ ]++ setupAuth auth =+ [ "$wc.Credentials = new-object System.Net.NetworkCredential("+ ++ escape uname ++ "," ++ escape passwd ++ ",\"\");"+ | (uname,passwd) <- maybeToList auth+ ]++ uploadFileAction method uri fullPath =+ [ "$fileBytes = [System.IO.File]::ReadAllBytes(" ++ escape fullPath ++ ");"+ , "$bodyBytes = $wc.UploadData(" ++ escape (show uri) ++ ","+ ++ show method ++ ", $fileBytes);"+ , "Write-Host \"200\";"+ , "Write-Host (-join [System.Text.Encoding]::UTF8.GetChars($bodyBytes));"+ ]++ parseUploadResponse uri resp = case lines (trim resp) of+ (codeStr : message)+ | Just code <- readMaybe codeStr -> return (code, unlines message)+ _ -> statusParseFail uri resp++ webclientScript setup action = unlines+ [ "$wc = new-object system.net.webclient;"+ , unlines setup+ , "Try {"+ , unlines (map (" " ++) action)+ , "} Catch [System.Net.WebException] {"+ , " $exception = $_.Exception;"+ , " If ($exception.Status -eq "+ ++ "[System.Net.WebExceptionStatus]::ProtocolError) {"+ , " $response = $exception.Response -as [System.Net.HttpWebResponse];"+ , " $reader = new-object "+ ++ "System.IO.StreamReader($response.GetResponseStream());"+ , " Write-Host ($response.StatusCode -as [int]);"+ , " Write-Host $reader.ReadToEnd();"+ , " } Else {"+ , " Write-Host $exception.Message;"+ , " }"+ , "} Catch {"+ , " Write-Host $_.Exception.Message;"+ , "}"+ ]++ ------------------------------------------------------------------------------ -- The builtin plain HttpTransport -- plainHttpTransport :: HttpTransport plainHttpTransport =- HttpTransport gethttp posthttp posthttpfile False False+ HttpTransport gethttp posthttp posthttpfile puthttpfile False False where- gethttp verbosity uri etag destPath = do+ gethttp verbosity uri etag destPath reqHeaders = do let req = Request{ rqURI = uri, rqMethod = GET, rqHeaders = [ Header HdrIfNoneMatch t- | t <- maybeToList etag ],+ | t <- maybeToList etag ]+ ++ reqHeaders, rqBody = BS.empty } (_, resp) <- cabalBrowse verbosity Nothing (request req)@@ -561,6 +652,19 @@ rqURI = uri, rqMethod = POST, rqHeaders = headers,+ rqBody = body+ }+ (_, resp) <- cabalBrowse verbosity auth (request req)+ return (convertRspCode (rspCode resp), rspErrorString resp)++ puthttpfile verbosity uri path auth headers = do+ body <- BS.readFile path+ let req = Request {+ rqURI = uri,+ rqMethod = PUT,+ rqHeaders = Header HdrContentLength (show (BS.length body))+ : Header HdrAccept "text/plain"+ : headers, rqBody = body } (_, resp) <- cabalBrowse verbosity auth (request req)
cabal/cabal-install/Distribution/Client/IndexUtils.hs view
@@ -1,4 +1,7 @@ {-# LANGUAGE CPP #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE GADTs #-} ----------------------------------------------------------------------------- -- | -- Module : Distribution.Client.IndexUtils@@ -14,17 +17,23 @@ module Distribution.Client.IndexUtils ( getIndexFileAge, getInstalledPackages,+ Configure.getInstalledPackagesMonitorFiles, getSourcePackages,- getSourcePackagesStrict,+ getSourcePackagesMonitorFiles, + Index(..),+ PackageEntry(..), parsePackageIndex,- readRepoIndex, updateRepoIndexCache, updatePackageIndexCacheFile,+ readCacheStrict, BuildTreeRefType(..), refTypeFromTypeCode, typeCodeFromRefType ) where +import qualified Codec.Archive.Tar as Tar+import qualified Codec.Archive.Tar.Entry as Tar+import qualified Codec.Archive.Tar.Index as Tar import qualified Distribution.Client.Tar as Tar import Distribution.Client.Types @@ -45,7 +54,7 @@ import Distribution.Simple.Program ( ProgramConfiguration ) import qualified Distribution.Simple.Configure as Configure- ( getInstalledPackages )+ ( getInstalledPackages, getInstalledPackagesMonitorFiles ) import Distribution.ParseUtils ( ParseResult(..) ) import Distribution.Version@@ -56,9 +65,11 @@ ( Verbosity, normal, lessVerbose ) import Distribution.Simple.Utils ( die, warn, info, fromUTF8, ignoreBOM )+import Distribution.Client.Setup+ ( RepoContext(..) ) import Data.Char (isAlphaNum)-import Data.Maybe (mapMaybe)+import Data.Maybe (mapMaybe, catMaybes, maybeToList) import Data.List (isPrefixOf) #if !MIN_VERSION_base(4,8,0) import Data.Monoid (Monoid(..))@@ -75,22 +86,25 @@ , tryFindAddSourcePackageDesc ) import Distribution.Compat.Exception (catchIO) import Distribution.Client.Compat.Time (getFileAge, getModTime)-import System.Directory (doesFileExist)-import System.FilePath ((</>), takeExtension, splitDirectories, normalise)+import System.Directory (doesFileExist, doesDirectoryExist)+import System.FilePath+ ( (</>), takeExtension, replaceExtension, splitDirectories, normalise ) import System.FilePath.Posix as FilePath.Posix ( takeFileName ) import System.IO import System.IO.Unsafe (unsafeInterleaveIO) import System.IO.Error (isDoesNotExistError) +import qualified Hackage.Security.Client as Sec+import qualified Hackage.Security.Util.Some as Sec +-- | Reduced-verbosity version of 'Configure.getInstalledPackages' getInstalledPackages :: Verbosity -> Compiler -> PackageDBStack -> ProgramConfiguration -> IO InstalledPackageIndex getInstalledPackages verbosity comp packageDbs conf = Configure.getInstalledPackages verbosity' comp packageDbs conf where- --FIXME: make getInstalledPackages use sensible verbosity in the first place verbosity' = lessVerbose verbosity ------------------------------------------------------------------------@@ -104,31 +118,17 @@ -- 'Repo'. -- -- This is a higher level wrapper used internally in cabal-install.----getSourcePackages :: Verbosity -> [Repo] -> IO SourcePackageDb-getSourcePackages verbosity repos = getSourcePackages' verbosity repos- ReadPackageIndexLazyIO---- | Like 'getSourcePackages', but reads the package index strictly. Useful if--- you want to write to the package index after having read it.-getSourcePackagesStrict :: Verbosity -> [Repo] -> IO SourcePackageDb-getSourcePackagesStrict verbosity repos = getSourcePackages' verbosity repos- ReadPackageIndexStrict---- | Common implementation used by getSourcePackages and--- getSourcePackagesStrict.-getSourcePackages' :: Verbosity -> [Repo] -> ReadPackageIndexMode- -> IO SourcePackageDb-getSourcePackages' verbosity [] _mode = do+getSourcePackages :: Verbosity -> RepoContext -> IO SourcePackageDb+getSourcePackages verbosity repoCtxt | null (repoContextRepos repoCtxt) = do warn verbosity $ "No remote package servers have been specified. Usually " ++ "you would have one specified in the config file." return SourcePackageDb { packageIndex = mempty, packagePreferences = mempty }-getSourcePackages' verbosity repos mode = do+getSourcePackages verbosity repoCtxt = do info verbosity "Reading available packages..."- pkgss <- mapM (\r -> readRepoIndex verbosity r mode) repos+ pkgss <- mapM (\r -> readRepoIndex verbosity repoCtxt r) (repoContextRepos repoCtxt) let (pkgs, prefs) = mconcat pkgss prefs' = Map.fromListWith intersectVersionRanges [ (name, range) | Dependency name range <- prefs ]@@ -139,6 +139,13 @@ packagePreferences = prefs' } +readCacheStrict :: Verbosity -> Index -> (PackageEntry -> pkg) -> IO ([pkg], [Dependency])+readCacheStrict verbosity index mkPkg = do+ updateRepoIndexCache verbosity index+ cache <- liftM readIndexCache $ BSS.readFile (cacheFile index)+ withFile (indexFile index) ReadMode $ \indexHnd ->+ packageListFromCache mkPkg indexHnd cache ReadPackageIndexStrict+ -- | Read a repository index from disk, from the local file specified by -- the 'Repo'. --@@ -146,16 +153,13 @@ -- -- This is a higher level wrapper used internally in cabal-install. ---readRepoIndex :: Verbosity -> Repo -> ReadPackageIndexMode- -> IO (PackageIndex SourcePackage, [Dependency])-readRepoIndex verbosity repo mode =- let indexFile = repoLocalDir repo </> "00-index.tar"- cacheFile = repoLocalDir repo </> "00-index.cache"- in handleNotFound $ do+readRepoIndex :: Verbosity -> RepoContext -> Repo+ -> IO (PackageIndex UnresolvedSourcePackage, [Dependency])+readRepoIndex verbosity repoCtxt repo =+ handleNotFound $ do warnIfIndexIsOld =<< getIndexFileAge repo- whenCacheOutOfDate indexFile cacheFile $ do- updatePackageIndexCacheFile verbosity indexFile cacheFile- readPackageIndexCacheFile mkAvailablePackage indexFile cacheFile mode+ updateRepoIndexCache verbosity (RepoIndex repoCtxt repo)+ readPackageIndexCacheFile mkAvailablePackage (RepoIndex repoCtxt repo) where mkAvailablePackage pkgEntry =@@ -174,51 +178,59 @@ handleNotFound action = catchIO action $ \e -> if isDoesNotExistError e then do- case repoKind repo of- Left remoteRepo -> warn verbosity $- "The package list for '" ++ remoteRepoName remoteRepo- ++ "' does not exist. Run 'hackport update' to download it."- Right _localRepo -> warn verbosity $- "The package list for the local repo '" ++ repoLocalDir repo+ case repo of+ RepoRemote{..} -> warn verbosity $ errMissingPackageList repoRemote+ RepoSecure{..} -> warn verbosity $ errMissingPackageList repoRemote+ RepoLocal{..} -> warn verbosity $+ "The package list for the local repo '" ++ repoLocalDir ++ "' is missing. The repo is invalid." return mempty else ioError e isOldThreshold = 15 --days warnIfIndexIsOld dt = do- when (dt >= isOldThreshold) $ case repoKind repo of- Left remoteRepo -> warn verbosity $- "The package list for '" ++ remoteRepoName remoteRepo- ++ "' is " ++ shows (floor dt :: Int) " days old.\nRun "- ++ "'hackport update' to get the latest list of available packages."- Right _localRepo -> return ()+ when (dt >= isOldThreshold) $ case repo of+ RepoRemote{..} -> warn verbosity $ errOutdatedPackageList repoRemote dt+ RepoSecure{..} -> warn verbosity $ errOutdatedPackageList repoRemote dt+ RepoLocal{..} -> return () + errMissingPackageList repoRemote =+ "The package list for '" ++ remoteRepoName repoRemote+ ++ "' does not exist. Run 'hackport update' to download it."+ errOutdatedPackageList repoRemote dt =+ "The package list for '" ++ remoteRepoName repoRemote+ ++ "' is " ++ shows (floor dt :: Int) " days old.\nRun "+ ++ "'cabal update' to get the latest list of available packages." -- | Return the age of the index file in days (as a Double). getIndexFileAge :: Repo -> IO Double getIndexFileAge repo = getFileAge $ repoLocalDir repo </> "00-index.tar" +-- | A set of files (or directories) that can be monitored to detect when+-- there might have been a change in the source packages.+--+getSourcePackagesMonitorFiles :: [Repo] -> [FilePath]+getSourcePackagesMonitorFiles repos =+ [ repoLocalDir repo </> "00-index.cache"+ | repo <- repos ] -- | It is not necessary to call this, as the cache will be updated when the -- index is read normally. However you can do the work earlier if you like. ---updateRepoIndexCache :: Verbosity -> Repo -> IO ()-updateRepoIndexCache verbosity repo =- whenCacheOutOfDate indexFile cacheFile $ do- updatePackageIndexCacheFile verbosity indexFile cacheFile- where- indexFile = repoLocalDir repo </> "00-index.tar"- cacheFile = repoLocalDir repo </> "00-index.cache"+updateRepoIndexCache :: Verbosity -> Index -> IO ()+updateRepoIndexCache verbosity index =+ whenCacheOutOfDate index $ do+ updatePackageIndexCacheFile verbosity index -whenCacheOutOfDate :: FilePath -> FilePath -> IO () -> IO ()-whenCacheOutOfDate origFile cacheFile action = do- exists <- doesFileExist cacheFile+whenCacheOutOfDate :: Index -> IO () -> IO ()+whenCacheOutOfDate index action = do+ exists <- doesFileExist $ cacheFile index if not exists then action else do- origTime <- getModTime origFile- cacheTime <- getModTime cacheFile- when (origTime > cacheTime) action+ indexTime <- getModTime $ indexFile index+ cacheTime <- getModTime $ cacheFile index+ when (indexTime > cacheTime) action ------------------------------------------------------------------------ -- Reading the index file@@ -245,8 +257,6 @@ typeCodeFromRefType LinkRef = Tar.buildTreeRefTypeCode typeCodeFromRefType SnapshotRef = Tar.buildTreeSnapshotTypeCode -type MkPackageEntry = IO PackageEntry- instance Package PackageEntry where packageId (NormalPackage pkgid _ _ _) = pkgid packageId (BuildTreeRef _ pkgid _ _ _) = pkgid@@ -261,38 +271,47 @@ data PackageOrDep = Pkg PackageEntry | Dep Dependency -parsePackageIndex :: ByteString- -> [IO PackageOrDep]-parsePackageIndex = accum 0 . Tar.read+-- | Read @00-index.tar.gz@ and extract @.cabal@ and @preferred-versions@ files+--+-- We read the index using 'Tar.read', which gives us a lazily constructed+-- 'TarEntries'. We translate it to a list of entries using 'tarEntriesList',+-- which preserves the lazy nature of 'TarEntries', and finally 'concatMap' a+-- function over this to translate it to a list of IO actions returning+-- 'PackageOrDep's. We can use 'lazySequence' to turn this into a list of+-- 'PackageOrDep's, still maintaining the lazy nature of the original tar read.+parsePackageIndex :: ByteString -> [IO (Maybe PackageOrDep)]+parsePackageIndex = concatMap (uncurry extract) . tarEntriesList . Tar.read where- accum blockNo es = case es of- Tar.Fail err -> error ("parsePackageIndex: " ++ err)- Tar.Done -> []- Tar.Next e es' -> ps ++ accum blockNo' es'- where- ps = extract blockNo e- blockNo' = blockNo + Tar.entrySizeInBlocks e-+ extract :: BlockNo -> Tar.Entry -> [IO (Maybe PackageOrDep)] extract blockNo entry = tryExtractPkg ++ tryExtractPrefs where- maybeToList Nothing = []- maybeToList (Just a) = [a]- tryExtractPkg = do mkPkgEntry <- maybeToList $ extractPkg entry blockNo- return (fmap Pkg mkPkgEntry)+ return $ fmap (fmap Pkg) mkPkgEntry tryExtractPrefs = do- (_,prefs') <- maybeToList $ extractPrefs entry- map (return . Dep) $ prefs'+ prefs' <- maybeToList $ extractPrefs entry+ fmap (return . Just . Dep) prefs' -extractPkg :: Tar.Entry -> BlockNo -> Maybe MkPackageEntry+-- | Turn the 'Entries' data structure from the @tar@ package into a list,+-- and pair each entry with its block number.+--+-- NOTE: This preserves the lazy nature of 'Entries': the tar file is only read+-- as far as the list is evaluated.+tarEntriesList :: Show e => Tar.Entries e -> [(BlockNo, Tar.Entry)]+tarEntriesList = go 0+ where+ go !_ Tar.Done = []+ go !_ (Tar.Fail e) = error ("tarEntriesList: " ++ show e)+ go !n (Tar.Next e es') = (n, e) : go (Tar.nextEntryOffset e n) es'++extractPkg :: Tar.Entry -> BlockNo -> Maybe (IO (Maybe PackageEntry)) extractPkg entry blockNo = case Tar.entryContent entry of Tar.NormalFile content _ | takeExtension fileName == ".cabal" -> case splitDirectories (normalise fileName) of [pkgname,vers,_] -> case simpleParse vers of- Just ver -> Just $ return (NormalPackage pkgid descr content blockNo)+ Just ver -> Just . return $ Just (NormalPackage pkgid descr content blockNo) where pkgid = PackageIdentifier (PackageName pkgname) ver parsed = parsePackageDescription . ignoreBOM . fromUTF8 . BS.Char8.unpack@@ -307,19 +326,22 @@ Tar.OtherEntryType typeCode content _ | Tar.isBuildTreeRefTypeCode typeCode -> Just $ do- let path = byteStringToFilePath content- err = "Error reading package index."- cabalFile <- tryFindAddSourcePackageDesc path err- descr <- PackageDesc.Parse.readPackageDescription normal cabalFile- return $ BuildTreeRef (refTypeFromTypeCode typeCode) (packageId descr)- descr path blockNo+ let path = byteStringToFilePath content+ dirExists <- doesDirectoryExist path+ result <- if not dirExists then return Nothing+ else do+ cabalFile <- tryFindAddSourcePackageDesc path "Error reading package index."+ descr <- PackageDesc.Parse.readPackageDescription normal cabalFile+ return . Just $ BuildTreeRef (refTypeFromTypeCode typeCode) (packageId descr)+ descr path blockNo+ return result _ -> Nothing where fileName = Tar.entryPath entry -extractPrefs :: Tar.Entry -> Maybe (FilePath, [Dependency])+extractPrefs :: Tar.Entry -> Maybe [Dependency] extractPrefs entry = case Tar.entryContent entry of {- -- get rid of hackage's preferred-versions@@ -327,40 +349,105 @@ -- broken packages with improper depends Tar.NormalFile content _ | takeFileName entrypath == "preferred-versions"- -> Just (entrypath, prefs)+ -> Just prefs where entrypath = Tar.entryPath entry- prefs = parsePreferredVersions (BS.Char8.unpack content)+ prefs = parsePreferredVersions content -} _ -> Nothing -parsePreferredVersions :: String -> [Dependency]+parsePreferredVersions :: ByteString -> [Dependency] parsePreferredVersions = mapMaybe simpleParse . filter (not . isPrefixOf "--") . lines+ . BS.Char8.unpack -- TODO: Are we sure no unicode? ------------------------------------------------------------------------ -- Reading and updating the index cache -- +-- | Variation on 'sequence' which evaluates the actions lazily+--+-- Pattern matching on the result list will execute just the first action;+-- more generally pattern matching on the first @n@ '(:)' nodes will execute+-- the first @n@ actions. lazySequence :: [IO a] -> IO [a]-lazySequence [] = return []-lazySequence (x:xs) = unsafeInterleaveIO $ do- x' <- unsafeInterleaveIO x- xs' <- lazySequence xs- return (x':xs')+lazySequence = unsafeInterleaveIO . go+ where+ go [] = return []+ go (x:xs) = do x' <- x+ xs' <- lazySequence xs+ return (x' : xs') -updatePackageIndexCacheFile :: Verbosity -> FilePath -> FilePath -> IO ()-updatePackageIndexCacheFile verbosity indexFile cacheFile = do- info verbosity ("Updating index cache file " ++ cacheFile)- pkgsOrPrefs <- return- . parsePackageIndex- . maybeDecompress- =<< BS.readFile indexFile- entries <- lazySequence pkgsOrPrefs- let cache = map toCache entries- writeFile cacheFile (showIndexCache cache)+-- | Which index do we mean?+data Index =+ -- | The main index for the specified repository+ RepoIndex RepoContext Repo++ -- | A sandbox-local repository+ -- Argument is the location of the index file+ | SandboxIndex FilePath++indexFile :: Index -> FilePath+indexFile (RepoIndex _ctxt repo) = repoLocalDir repo </> "00-index.tar"+indexFile (SandboxIndex index) = index++cacheFile :: Index -> FilePath+cacheFile (RepoIndex _ctxt repo) = repoLocalDir repo </> "00-index.cache"+cacheFile (SandboxIndex index) = index `replaceExtension` "cache"++updatePackageIndexCacheFile :: Verbosity -> Index -> IO ()+updatePackageIndexCacheFile verbosity index = do+ info verbosity ("Updating index cache file " ++ cacheFile index)+ withIndexEntries index $ \entries -> do+ let cache = Cache { cacheEntries = entries }+ writeFile (cacheFile index) (showIndexCache cache)++-- | Read the index (for the purpose of building a cache)+--+-- The callback is provided with list of cache entries, which is guaranteed to+-- be lazily constructed. This list must ONLY be used in the scope of the+-- callback; when the callback is terminated the file handle to the index will+-- be closed and further attempts to read from the list will result in (pure)+-- I/O exceptions.+--+-- In the construction of the index for a secure repo we take advantage of the+-- index built by the @hackage-security@ library to avoid reading the @.tar@+-- file as much as possible (we need to read it only to extract preferred+-- versions). This helps performance, but is also required for correctness:+-- the new @01-index.tar.gz@ may have multiple versions of preferred-versions+-- files, and 'parsePackageIndex' does not correctly deal with that (see #2956);+-- by reading the already-built cache from the security library we will be sure+-- to only read the latest versions of all files.+--+-- TODO: It would be nicer if we actually incrementally updated @cabal@'s+-- cache, rather than reconstruct it from zero on each update. However, this+-- would require a change in the cache format.+withIndexEntries :: Index -> ([IndexCacheEntry] -> IO a) -> IO a+withIndexEntries (RepoIndex repoCtxt repo@RepoSecure{..}) callback =+ repoContextWithSecureRepo repoCtxt repo $ \repoSecure ->+ Sec.withIndex repoSecure $ \Sec.IndexCallbacks{..} -> do+ let mk :: (Sec.DirectoryEntry, fp, Maybe (Sec.Some Sec.IndexFile))+ -> IO [IndexCacheEntry]+ mk (_, _fp, Nothing) =+ return [] -- skip unrecognized file+ mk (_, _fp, Just (Sec.Some (Sec.IndexPkgMetadata _pkgId))) =+ return [] -- skip metadata+ mk (dirEntry, _fp, Just (Sec.Some (Sec.IndexPkgCabal pkgId))) = do+ let blockNo = fromIntegral (Sec.directoryEntryBlockNo dirEntry)+ return [CachePackageId pkgId blockNo]+ mk (dirEntry, _fp, Just (Sec.Some file@(Sec.IndexPkgPrefs _pkgName))) = do+ content <- Sec.indexEntryContent `fmap` indexLookupFileEntry dirEntry file+ return $ map CachePreference (parsePreferredVersions content)+ entriess <- lazySequence $ map mk (Sec.directoryEntries indexDirectory)+ callback $ concat entriess+withIndexEntries index callback = do+ withFile (indexFile index) ReadMode $ \h -> do+ bs <- maybeDecompress `fmap` BS.hGetContents h+ pkgsOrPrefs <- lazySequence $ parsePackageIndex bs+ callback $ map toCache (catMaybes pkgsOrPrefs) where+ toCache :: PackageOrDep -> IndexCacheEntry toCache (Pkg (NormalPackage pkgid _ _ blockNo)) = CachePackageId pkgid blockNo toCache (Pkg (BuildTreeRef refType _ _ _ blockNo)) = CacheBuildTreeRef refType blockNo toCache (Dep d) = CachePreference d@@ -370,35 +457,37 @@ readPackageIndexCacheFile :: Package pkg => (PackageEntry -> pkg)- -> FilePath- -> FilePath- -> ReadPackageIndexMode+ -> Index -> IO (PackageIndex pkg, [Dependency])-readPackageIndexCacheFile mkPkg indexFile cacheFile mode = do- cache <- liftM readIndexCache (BSS.readFile cacheFile)- myWithFile indexFile ReadMode $ \indexHnd ->- packageIndexFromCache mkPkg indexHnd cache mode- where- myWithFile f m act = case mode of- ReadPackageIndexStrict -> withFile f m act- ReadPackageIndexLazyIO -> do indexHnd <- openFile f m- act indexHnd-+readPackageIndexCacheFile mkPkg index = do+ cache <- liftM readIndexCache $ BSS.readFile (cacheFile index)+ indexHnd <- openFile (indexFile index) ReadMode+ packageIndexFromCache mkPkg indexHnd cache ReadPackageIndexLazyIO packageIndexFromCache :: Package pkg => (PackageEntry -> pkg) -> Handle- -> [IndexCacheEntry]+ -> Cache -> ReadPackageIndexMode -> IO (PackageIndex pkg, [Dependency])-packageIndexFromCache mkPkg hnd entrs mode = accum mempty [] entrs+packageIndexFromCache mkPkg hnd cache mode = do+ (pkgs, prefs) <- packageListFromCache mkPkg hnd cache mode+ pkgIndex <- evaluate $ PackageIndex.fromList pkgs+ return (pkgIndex, prefs)++-- | Read package list+--+-- The result packages (though not the preferences) are guaranteed to be listed+-- in the same order as they are in the tar file (because later entries in a tar+-- file mask earlier ones).+packageListFromCache :: (PackageEntry -> pkg)+ -> Handle+ -> Cache+ -> ReadPackageIndexMode+ -> IO ([pkg], [Dependency])+packageListFromCache mkPkg hnd Cache{..} mode = accum mempty [] cacheEntries where- accum srcpkgs prefs [] = do- -- Have to reverse entries, since in a tar file, later entries mask- -- earlier ones, and PackageIndex.fromList does the same, but we- -- accumulate the list of entries in reverse order, so need to reverse.- pkgIndex <- evaluate $ PackageIndex.fromList (reverse srcpkgs)- return (pkgIndex, prefs)+ accum srcpkgs prefs [] = return (reverse srcpkgs, prefs) accum srcpkgs prefs (CachePackageId pkgid blockno : entries) = do -- Given the cache entry, make a package index entry.@@ -433,22 +522,13 @@ getEntryContent :: BlockNo -> IO ByteString getEntryContent blockno = do- hSeek hnd AbsoluteSeek (fromIntegral (blockno * 512))- header <- BS.hGet hnd 512- size <- getEntrySize header- BS.hGet hnd (fromIntegral size)-- getEntrySize :: ByteString -> IO Tar.FileSize- getEntrySize header =- case Tar.read header of- Tar.Next e _ ->- case Tar.entryContent e of- Tar.NormalFile _ size -> return size- Tar.OtherEntryType typecode _ size- | Tar.isBuildTreeRefTypeCode typecode- -> return size- _ -> interror "unexpected tar entry type"- _ -> interror "could not read tar file entry"+ entry <- Tar.hReadEntry hnd blockno+ case Tar.entryContent entry of+ Tar.NormalFile content _size -> return content+ Tar.OtherEntryType typecode content _size+ | Tar.isBuildTreeRefTypeCode typecode+ -> return content+ _ -> interror "unexpected tar entry type" readPackageDescription :: ByteString -> IO GenericPackageDescription readPackageDescription content =@@ -467,15 +547,15 @@ -- | Tar files are block structured with 512 byte blocks. Every header and file -- content starts on a block boundary. ---type BlockNo = Int+type BlockNo = Tar.TarEntryOffset data IndexCacheEntry = CachePackageId PackageId BlockNo | CacheBuildTreeRef BuildTreeRefType BlockNo | CachePreference Dependency deriving (Eq) -packageKey, blocknoKey, buildTreeRefKey, preferredVersionKey :: String-packageKey = "pkg:"+installedUnitId, blocknoKey, buildTreeRefKey, preferredVersionKey :: String+installedUnitId = "pkg:" blocknoKey = "b#" buildTreeRefKey = "build-tree-ref:" preferredVersionKey = "pref-ver:"@@ -484,7 +564,7 @@ readIndexCacheEntry = \line -> case BSS.words line of [key, pkgnamestr, pkgverstr, sep, blocknostr]- | key == BSS.pack packageKey && sep == BSS.pack blocknoKey ->+ | key == BSS.pack installedUnitId && sep == BSS.pack blocknoKey -> case (parseName pkgnamestr, parseVer pkgverstr [], parseBlockNo blocknostr) of (Just pkgname, Just pkgver, Just blockno)@@ -515,8 +595,9 @@ parseBlockNo str = case BSS.readInt str of- Just (blockno, remainder) | BSS.null remainder -> Just blockno- _ -> Nothing+ Just (blockno, remainder)+ | BSS.null remainder -> Just (fromIntegral blockno)+ _ -> Nothing parseRefType str = case BSS.uncons str of@@ -527,7 +608,7 @@ showIndexCacheEntry :: IndexCacheEntry -> String showIndexCacheEntry entry = unwords $ case entry of- CachePackageId pkgid b -> [ packageKey+ CachePackageId pkgid b -> [ installedUnitId , display (packageName pkgid) , display (packageVersion pkgid) , blocknoKey@@ -541,8 +622,15 @@ , display dep ] -readIndexCache :: BSS.ByteString -> [IndexCacheEntry]-readIndexCache = mapMaybe readIndexCacheEntry . BSS.lines+-- | Cabal caches various information about the Hackage index+data Cache = Cache {+ cacheEntries :: [IndexCacheEntry]+ } -showIndexCache :: [IndexCacheEntry] -> String-showIndexCache = unlines . map showIndexCacheEntry+readIndexCache :: BSS.ByteString -> Cache+readIndexCache bs = Cache {+ cacheEntries = mapMaybe readIndexCacheEntry $ BSS.lines bs+ }++showIndexCache :: Cache -> String+showIndexCache Cache{..} = unlines $ map showIndexCacheEntry cacheEntries
cabal/cabal-install/Distribution/Client/Init.hs view
@@ -18,6 +18,8 @@ -- * Commands initCabal+ , pvpize+ , incVersion ) where @@ -98,19 +100,21 @@ import Distribution.Client.IndexUtils ( getSourcePackages ) import Distribution.Client.Types- ( SourcePackageDb(..), Repo )+ ( SourcePackageDb(..) )+import Distribution.Client.Setup+ ( RepoContext(..) ) initCabal :: Verbosity -> PackageDBStack- -> [Repo]+ -> RepoContext -> Compiler -> ProgramConfiguration -> InitFlags -> IO ()-initCabal verbosity packageDBs repos comp conf initFlags = do+initCabal verbosity packageDBs repoCtxt comp conf initFlags = do installedPkgIndex <- getInstalledPackages verbosity comp packageDBs conf- sourcePkgDb <- getSourcePackages verbosity repos+ sourcePkgDb <- getSourcePackages verbosity repoCtxt hSetBuffering stdout NoBuffering @@ -359,7 +363,7 @@ getSrcDir flags = do srcDirs <- return (sourceDirs flags) ?>> fmap (:[]) `fmap` guessSourceDir flags- ?>> fmap (fmap ((:[]) . either id id) . join) (maybePrompt+ ?>> fmap (>>= fmap ((:[]) . either id id)) (maybePrompt flags (promptListOptional' "Source directory" ["src"] id)) @@ -465,12 +469,17 @@ return $ P.Dependency (P.pkgName . head $ pids) (pvpize . maximum . map P.pkgVersion $ pids) - pvpize :: Version -> VersionRange- pvpize v = orLaterVersion v'- `intersectVersionRanges`- earlierVersion (incVersion 1 v')- where v' = (v { versionBranch = take 2 (versionBranch v) })+-- | Given a version, return an API-compatible (according to PVP) version range.+--+-- Example: @0.4.1@ produces the version range @>= 0.4 && < 0.5@ (which is the+-- same as @0.4.*@).+pvpize :: Version -> VersionRange+pvpize v = orLaterVersion v'+ `intersectVersionRanges`+ earlierVersion (incVersion 1 v')+ where v' = (v { versionBranch = take 2 (versionBranch v) }) +-- | Increment the nth version component (counting from 0). incVersion :: Int -> Version -> Version incVersion n (Version vlist tags) = Version (incVersion' n vlist) tags where
cabal/cabal-install/Distribution/Client/Init/Types.hs view
@@ -1,4 +1,5 @@-{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveGeneric #-}+ ----------------------------------------------------------------------------- -- | -- Module : Distribution.Client.Init.Types@@ -17,6 +18,7 @@ import Distribution.Simple.Setup ( Flag(..) ) +import Distribution.Compat.Semigroup import Distribution.Version import Distribution.Verbosity import qualified Distribution.Package as P@@ -28,9 +30,7 @@ import qualified Distribution.Compat.ReadP as Parse import Distribution.Text -#if !MIN_VERSION_base(4,8,0)-import Data.Monoid-#endif+import GHC.Generics ( Generic ) -- | InitFlags is really just a simple type to represent certain -- portions of a .cabal file. Rather than have a flag for EVERY@@ -71,7 +71,7 @@ , initVerbosity :: Flag Verbosity , overwrite :: Flag Bool }- deriving (Show)+ deriving (Show, Generic) -- the Monoid instance for Flag has later values override earlier -- ones, which is why we want Maybe [foo] for collecting foo values,@@ -85,63 +85,11 @@ parse = Parse.choice $ map (fmap read . Parse.string . show) [Library, Executable] instance Monoid InitFlags where- mempty = InitFlags- { nonInteractive = mempty- , quiet = mempty- , packageDir = mempty- , noComments = mempty- , minimal = mempty- , packageName = mempty- , version = mempty- , cabalVersion = mempty- , license = mempty- , author = mempty- , email = mempty- , homepage = mempty- , synopsis = mempty- , category = mempty- , extraSrc = mempty- , packageType = mempty- , mainIs = mempty- , language = mempty- , exposedModules = mempty- , otherModules = mempty- , otherExts = mempty- , dependencies = mempty- , sourceDirs = mempty- , buildTools = mempty- , initVerbosity = mempty- , overwrite = mempty- }- mappend a b = InitFlags- { nonInteractive = combine nonInteractive- , quiet = combine quiet- , packageDir = combine packageDir- , noComments = combine noComments- , minimal = combine minimal- , packageName = combine packageName- , version = combine version- , cabalVersion = combine cabalVersion- , license = combine license- , author = combine author- , email = combine email- , homepage = combine homepage- , synopsis = combine synopsis- , category = combine category- , extraSrc = combine extraSrc- , packageType = combine packageType- , mainIs = combine mainIs- , language = combine language- , exposedModules = combine exposedModules- , otherModules = combine otherModules- , otherExts = combine otherExts- , dependencies = combine dependencies- , sourceDirs = combine sourceDirs- , buildTools = combine buildTools- , initVerbosity = combine initVerbosity- , overwrite = combine overwrite- }- where combine field = field a `mappend` field b+ mempty = gmempty+ mappend = (<>)++instance Semigroup InitFlags where+ (<>) = gmappend -- | Some common package categories. data Category
cabal/cabal-install/Distribution/Client/Install.hs view
@@ -33,6 +33,7 @@ ( traverse_ ) import Data.List ( isPrefixOf, unfoldr, nub, sort, (\\) )+import qualified Data.Map as Map import qualified Data.Set as S import Data.Maybe ( catMaybes, isJust, isNothing, fromMaybe, mapMaybe )@@ -73,14 +74,14 @@ ( Solver(..), ConstraintSource(..), LabeledPackageConstraint(..) ) import Distribution.Client.FetchUtils import Distribution.Client.HttpUtils- ( configureTransport, HttpTransport (..) )+ ( HttpTransport (..) ) import qualified Distribution.Client.Haddock as Haddock (regenerateHaddockIndex) import Distribution.Client.IndexUtils as IndexUtils ( getSourcePackages, getInstalledPackages ) import qualified Distribution.Client.InstallPlan as InstallPlan import Distribution.Client.InstallPlan (InstallPlan) import Distribution.Client.Setup- ( GlobalFlags(..)+ ( GlobalFlags(..), RepoContext(..) , ConfigFlags(..), configureCommand, filterConfigureFlags , ConfigExFlags(..), InstallFlags(..) ) import Distribution.Client.Config@@ -118,9 +119,12 @@ import qualified Distribution.Simple.InstallDirs as InstallDirs import qualified Distribution.Simple.PackageIndex as PackageIndex import Distribution.Simple.PackageIndex (InstalledPackageIndex)+import Distribution.Simple.LocalBuildInfo (ComponentName(CLibName))+import qualified Distribution.Simple.Configure as Configure import Distribution.Simple.Setup ( haddockCommand, HaddockFlags(..) , buildCommand, BuildFlags(..), emptyBuildFlags+ , AllowNewer(..) , toFlag, fromFlag, fromFlagOrDefault, flagToMaybe, defaultDistPref ) import qualified Distribution.Simple.Setup as Cabal ( Flag(..)@@ -135,16 +139,18 @@ , initialPathTemplateEnv, installDirsTemplateEnv ) import Distribution.Package ( PackageIdentifier(..), PackageId, packageName, packageVersion- , Package(..), LibraryName+ , Package(..) , Dependency(..), thisPackageVersion- , InstalledPackageId, installedPackageId- , HasInstalledPackageId(..) )+ , UnitId(..), mkUnitId+ , HasUnitId(..) ) import qualified Distribution.PackageDescription as PackageDescription import Distribution.PackageDescription ( PackageDescription, GenericPackageDescription(..), Flag(..) , FlagName(..), FlagAssignment ) import Distribution.PackageDescription.Configuration ( finalizePackageDescription )+import Distribution.Client.PkgConfigDb+ ( PkgConfigDb, readPkgConfigDb ) import Distribution.ParseUtils ( showPWarning ) import Distribution.Version@@ -153,7 +159,7 @@ ( notice, info, warn, debug, debugNoWrap, die , intercalate, withTempDirectory ) import Distribution.Client.Utils- ( determineNumJobs, inDir, mergeBy, MergeResult(..)+ ( determineNumJobs, inDir, logDirChange, mergeBy, MergeResult(..) , tryCanonicalizePath ) import Distribution.System ( Platform, OS(Windows), buildOS )@@ -184,7 +190,7 @@ install :: Verbosity -> PackageDBStack- -> [Repo]+ -> RepoContext -> Compiler -> Platform -> ProgramConfiguration@@ -230,14 +236,15 @@ -- TODO: Make InstallContext a proper data type with documented fields. -- | Common context for makeInstallPlan and processInstallPlan. type InstallContext = ( InstalledPackageIndex, SourcePackageDb- , [UserTarget], [PackageSpecifier SourcePackage]+ , PkgConfigDb+ , [UserTarget], [PackageSpecifier UnresolvedSourcePackage] , HttpTransport ) -- TODO: Make InstallArgs a proper data type with documented fields or just get -- rid of it completely. -- | Initial arguments given to 'install' or 'makeInstallContext'. type InstallArgs = ( PackageDBStack- , [Repo]+ , RepoContext , Compiler , Platform , ProgramConfiguration@@ -253,15 +260,16 @@ makeInstallContext :: Verbosity -> InstallArgs -> Maybe [UserTarget] -> IO InstallContext makeInstallContext verbosity- (packageDBs, repos, comp, _, conf,_,_,+ (packageDBs, repoCtxt, comp, _, conf,_,_, globalFlags, _, configExFlags, _, _) mUserTargets = do installedPkgIndex <- getInstalledPackages verbosity comp packageDBs conf- sourcePkgDb <- getSourcePackages verbosity repos+ sourcePkgDb <- getSourcePackages verbosity repoCtxt+ pkgConfigDb <- readPkgConfigDb verbosity conf+ checkConfigExFlags verbosity installedPkgIndex (packageIndex sourcePkgDb) configExFlags- transport <- configureTransport verbosity- (flagToMaybe (globalHttpTransport globalFlags))+ transport <- repoContextGetTransport repoCtxt (userTargets, pkgSpecifiers) <- case mUserTargets of Nothing ->@@ -275,13 +283,13 @@ let userTargets | null userTargets0 = [UserTargetLocalDir "."] | otherwise = userTargets0 - pkgSpecifiers <- resolveUserTargets verbosity transport+ pkgSpecifiers <- resolveUserTargets verbosity repoCtxt (fromFlag $ globalWorldFile globalFlags) (packageIndex sourcePkgDb) userTargets return (userTargets, pkgSpecifiers) - return (installedPkgIndex, sourcePkgDb, userTargets+ return (installedPkgIndex, sourcePkgDb, pkgConfigDb, userTargets ,pkgSpecifiers, transport) -- | Make an install plan given install context and install arguments.@@ -291,7 +299,7 @@ (_, _, comp, platform, _, _, mSandboxPkgInfo, _, configFlags, configExFlags, installFlags, _)- (installedPkgIndex, sourcePkgDb,+ (installedPkgIndex, sourcePkgDb, pkgConfigDb, _, pkgSpecifiers, _) = do solver <- chooseSolver verbosity (fromFlag (configSolver configExFlags))@@ -299,7 +307,7 @@ notice verbosity "Resolving dependencies..." return $ planPackages comp platform mSandboxPkgInfo solver configFlags configExFlags installFlags- installedPkgIndex sourcePkgDb pkgSpecifiers+ installedPkgIndex sourcePkgDb pkgConfigDb pkgSpecifiers -- | Given an install plan, perform the actual installations. processInstallPlan :: Verbosity -> InstallArgs -> InstallContext@@ -307,7 +315,7 @@ -> IO () processInstallPlan verbosity args@(_,_, _, _, _, _, _, _, _, _, installFlags, _)- (installedPkgIndex, sourcePkgDb,+ (installedPkgIndex, sourcePkgDb, _, userTargets, pkgSpecifiers, _) installPlan = do checkPrintPlan verbosity installedPkgIndex installPlan sourcePkgDb installFlags pkgSpecifiers@@ -333,14 +341,15 @@ -> InstallFlags -> InstalledPackageIndex -> SourcePackageDb- -> [PackageSpecifier SourcePackage]+ -> PkgConfigDb+ -> [PackageSpecifier UnresolvedSourcePackage] -> Progress String String InstallPlan planPackages comp platform mSandboxPkgInfo solver configFlags configExFlags installFlags- installedPkgIndex sourcePkgDb pkgSpecifiers =+ installedPkgIndex sourcePkgDb pkgConfigDb pkgSpecifiers = resolveDependencies- platform (compilerInfo comp)+ platform (compilerInfo comp) pkgConfigDb solver resolverParams @@ -400,23 +409,22 @@ $ standardInstallPolicy installedPkgIndex sourcePkgDb pkgSpecifiers - stanzas = concat- [ if testsEnabled then [TestStanzas] else []- , if benchmarksEnabled then [BenchStanzas] else []- ]- testsEnabled = fromFlagOrDefault False $ configTests configFlags+ stanzas = [ TestStanzas | testsEnabled ]+ ++ [ BenchStanzas | benchmarksEnabled ]+ testsEnabled = fromFlagOrDefault False $ configTests configFlags benchmarksEnabled = fromFlagOrDefault False $ configBenchmarks configFlags - reinstall = fromFlag (installReinstall installFlags)- reorderGoals = fromFlag (installReorderGoals installFlags)- independentGoals = fromFlag (installIndependentGoals installFlags)- avoidReinstalls = fromFlag (installAvoidReinstalls installFlags)- shadowPkgs = fromFlag (installShadowPkgs installFlags)- strongFlags = fromFlag (installStrongFlags installFlags)- maxBackjumps = fromFlag (installMaxBackjumps installFlags)- upgradeDeps = fromFlag (installUpgradeDeps installFlags)- onlyDeps = fromFlag (installOnlyDeps installFlags)- allowNewer = fromFlag (configAllowNewer configExFlags)+ reinstall = fromFlag (installOverrideReinstall installFlags) ||+ fromFlag (installReinstall installFlags)+ reorderGoals = fromFlag (installReorderGoals installFlags)+ independentGoals = fromFlag (installIndependentGoals installFlags)+ avoidReinstalls = fromFlag (installAvoidReinstalls installFlags)+ shadowPkgs = fromFlag (installShadowPkgs installFlags)+ strongFlags = fromFlag (installStrongFlags installFlags)+ maxBackjumps = fromFlag (installMaxBackjumps installFlags)+ upgradeDeps = fromFlag (installUpgradeDeps installFlags)+ onlyDeps = fromFlag (installOnlyDeps installFlags)+ allowNewer = fromMaybe AllowNewerNone (configAllowNewer configFlags) -- | Remove the provided targets from the install plan. pruneInstallPlan :: Package targetpkg@@ -459,7 +467,7 @@ -> InstallPlan -> SourcePackageDb -> InstallFlags- -> [PackageSpecifier SourcePackage]+ -> [PackageSpecifier UnresolvedSourcePackage] -> IO () checkPrintPlan verbosity installed installPlan sourcePkgDb installFlags pkgSpecifiers = do@@ -483,16 +491,16 @@ let reinstalledPkgs = concatMap (extractReinstalls . snd) lPlan -- Packages that are already broken. let oldBrokenPkgs =- map Installed.installedPackageId+ map Installed.installedUnitId . PackageIndex.reverseDependencyClosure installed- . map (Installed.installedPackageId . fst)+ . map (Installed.installedUnitId . fst) . PackageIndex.brokenPackages $ installed let excluded = reinstalledPkgs ++ oldBrokenPkgs -- Packages that are reverse dependencies of replaced packages are very -- likely to be broken. We exclude packages that are already broken. let newBrokenPkgs =- filter (\ p -> not (Installed.installedPackageId p `elem` excluded))+ filter (\ p -> not (Installed.installedUnitId p `elem` excluded)) (PackageIndex.reverseDependencyClosure installed reinstalledPkgs) let containsReinstalls = not (null reinstalledPkgs) let breaksPkgs = not (null newBrokenPkgs)@@ -529,9 +537,8 @@ -- are already fetched. let offline = fromFlagOrDefault False (installOfflineMode installFlags) when offline $ do- let pkgs = [ sourcePkg- | InstallPlan.Configured (ConfiguredPackage sourcePkg _ _ _)- <- InstallPlan.toList installPlan ]+ let pkgs = [ confPkgSource cpkg+ | InstallPlan.Configured cpkg <- InstallPlan.toList installPlan ] notFetched <- fmap (map packageInfoId) . filterM (fmap isNothing . checkFetched . packageSource) $ pkgs@@ -547,7 +554,14 @@ dryRun = fromFlag (installDryRun installFlags) overrideReinstall = fromFlag (installOverrideReinstall installFlags) ---TODO: this type is too specific+-- | Given an 'InstallPlan', perform a dry run, producing the sequence+-- of 'ReadyPackage's which would be compiled in order to carry+-- out this plan. This function is not actually used to execute a plan;+-- presently, it is used only to (1) determine if the installation+-- plan would cause reinstalls and (2) to print out what would be+-- installed.+--+-- TODO: this type is too specific linearizeInstallPlan :: InstalledPackageIndex -> InstallPlan -> [(ReadyPackage, PackageStatus)]@@ -558,11 +572,11 @@ [] -> Nothing (pkg:_) -> Just ((pkg, status), plan'') where- pkgid = installedPackageId pkg+ pkgid = installedUnitId pkg status = packageStatus installedPkgIndex pkg ipkg = Installed.emptyInstalledPackageInfo {- Installed.sourcePackageId = packageId pkg,- Installed.installedPackageId = pkgid+ Installed.sourcePackageId = packageId pkg,+ Installed.installedUnitId = pkgid } plan'' = InstallPlan.completed pkgid (Just ipkg) (BuildOk DocsNotTried TestsNotTried (Just ipkg))@@ -570,15 +584,16 @@ --FIXME: This is a bit of a hack, -- pretending that each package is installed -- It's doubly a hack because the installed package ID- -- didn't get updated...+ -- didn't get updated. But it doesn't really matter+ -- because we're not going to use this for anything real. data PackageStatus = NewPackage | NewVersion [Version]- | Reinstall [InstalledPackageId] [PackageChange]+ | Reinstall [UnitId] [PackageChange] type PackageChange = MergeResult PackageIdentifier PackageIdentifier -extractReinstalls :: PackageStatus -> [InstalledPackageId]+extractReinstalls :: PackageStatus -> [UnitId] extractReinstalls (Reinstall ipids _) = ipids extractReinstalls _ = [] @@ -592,7 +607,7 @@ ps -> case filter ((== packageId cpkg) . Installed.sourcePackageId) (concatMap snd ps) of [] -> NewVersion (map fst ps)- pkgs@(pkg:_) -> Reinstall (map Installed.installedPackageId pkgs)+ pkgs@(pkg:_) -> Reinstall (map Installed.installedUnitId pkgs) (changes pkg cpkg) where@@ -608,13 +623,13 @@ (resolveInstalledIds $ CD.nonSetupDeps (depends pkg')) -- convert to source pkg ids via index- resolveInstalledIds :: [InstalledPackageId] -> [PackageIdentifier]+ resolveInstalledIds :: [UnitId] -> [PackageIdentifier] resolveInstalledIds = nub . sort . map Installed.sourcePackageId . catMaybes- . map (PackageIndex.lookupInstalledPackageId installedPkgIndex)+ . map (PackageIndex.lookupUnitId installedPkgIndex) changed (InBoth pkgid pkgid') = pkgid /= pkgid' changed _ = True@@ -627,7 +642,7 @@ printPlan dryRun verbosity plan sourcePkgDb = case plan of [] -> return () pkgs- | verbosity >= Verbosity.verbose -> notice verbosity $ unlines $+ | verbosity >= Verbosity.verbose -> putStr $ unlines $ ("In order, the following " ++ wouldWill ++ " be installed:") : map showPkgAndReason pkgs | otherwise -> notice verbosity $ unlines $@@ -641,16 +656,18 @@ showPkg (pkg, _) = display (packageId pkg) ++ showLatest (pkg) - showPkgAndReason (ReadyPackage pkg' _, pr) = display (packageId pkg') +++ showPkgAndReason (ReadyPackage pkg', pr) = display (packageId pkg') ++ showLatest pkg' ++ showFlagAssignment (nonDefaultFlags pkg') ++- showStanzas (stanzas pkg') ++ " " +++ showStanzas (confPkgStanzas pkg') +++ showDep pkg' ++ case pr of- NewPackage -> "(new package)"- NewVersion _ -> "(new version)"- Reinstall _ cs -> "(reinstall)" ++ case cs of+ NewPackage -> " (new package)"+ NewVersion _ -> " (new version)"+ Reinstall _ cs -> " (reinstall)" ++ case cs of [] -> ""- diff -> " changes: " ++ intercalate ", " (map change diff)+ diff -> " (changes: " ++ intercalate ", " (map change diff)+ ++ ")" showLatest :: Package srcpkg => srcpkg -> String showLatest pkg = case mLatestVersion of@@ -670,22 +687,18 @@ toFlagAssignment :: [Flag] -> FlagAssignment toFlagAssignment = map (\ f -> (flagName f, flagDefault f)) - nonDefaultFlags :: ConfiguredPackage -> FlagAssignment- nonDefaultFlags (ConfiguredPackage spkg fa _ _) =+ nonDefaultFlags :: ConfiguredPackage loc -> FlagAssignment+ nonDefaultFlags cpkg = let defaultAssignment = toFlagAssignment- (genPackageFlags (Source.packageDescription spkg))- in fa \\ defaultAssignment-- stanzas :: ConfiguredPackage -> [OptionalStanza]- stanzas (ConfiguredPackage _ _ sts _) = sts+ (genPackageFlags (Source.packageDescription (confPkgSource cpkg)))+ in confPkgFlags cpkg \\ defaultAssignment showStanzas :: [OptionalStanza] -> String showStanzas = concatMap ((' ' :) . showStanza) showStanza TestStanzas = "*test" showStanza BenchStanzas = "*bench" - -- FIXME: this should be a proper function in a proper place showFlagAssignment :: FlagAssignment -> String showFlagAssignment = concatMap ((' ' :) . showFlagValue) showFlagValue (f, True) = '+' : showFlagName f@@ -697,6 +710,18 @@ ++ display (packageVersion pkgid') change (OnlyInRight pkgid') = display pkgid' ++ " added" + showDep pkg | Just rdeps <- Map.lookup (packageId pkg) revDeps+ = " (via: " ++ unwords (map display rdeps) ++ ")"+ | otherwise = ""++ revDepGraphEdges :: [(PackageId, PackageId)]+ revDepGraphEdges = [ (rpid, packageId cpkg)+ | (ReadyPackage cpkg, _) <- plan+ , ConfiguredId rpid _ <- CD.flatDeps (confPkgDeps cpkg) ]++ revDeps :: Map.Map PackageId [PackageId]+ revDeps = Map.fromListWith (++) (map (fmap (:[])) revDepGraphEdges)+ -- ------------------------------------------------------------ -- * Post installation stuff -- ------------------------------------------------------------@@ -708,7 +733,7 @@ reportPlanningFailure verbosity (_, _, comp, platform, _, _, _ ,_, configFlags, _, installFlags, _)- (_, sourcePkgDb, _, pkgSpecifiers, _)+ (_, sourcePkgDb, _, _, pkgSpecifiers, _) message = do when reportFailure $ do@@ -736,7 +761,7 @@ case logFile of Nothing -> return () Just template -> forM_ pkgids $ \pkgid ->- let env = initialPathTemplateEnv pkgid dummyLibraryName+ let env = initialPathTemplateEnv pkgid dummyIpid (compilerInfo comp) platform path = fromPathTemplate $ substPathTemplate env template in writeFile path message@@ -745,10 +770,10 @@ reportFailure = fromFlag (installReportPlanningFailure installFlags) logFile = flagToMaybe (installLogFile installFlags) - -- A LibraryName is calculated from the transitive closure of+ -- A IPID is calculated from the transitive closure of -- dependencies, but when the solver fails we don't have that. -- So we fail.- dummyLibraryName = error "reportPlanningFailure: library name not available"+ dummyIpid = error "reportPlanningFailure: installed package ID not available" -- | If a 'PackageSpecifier' refers to a single package, return Just that -- package.@@ -836,7 +861,8 @@ createDirectoryIfMissing True reportsDir -- FIXME writeFile reportFile (show (BuildReports.show report, buildLog)) - | (report, Just Repo { repoKind = Left remoteRepo }) <- reports+ | (report, Just repo) <- reports+ , Just remoteRepo <- [maybeRepoRemote repo] , isLikelyToHaveLogFile (BuildReports.installOutcome report) ] where@@ -1003,7 +1029,7 @@ withUpdateTimestamps sandboxDir (compilerId comp) platform $ \_ -> do let allInstalled = [ pkg | InstallPlan.Installed pkg _ _ <- InstallPlan.toList installPlan ]- allSrcPkgs = [ pkg | ReadyPackage (ConfiguredPackage pkg _ _ _) _+ allSrcPkgs = [ confPkgSource cpkg | ReadyPackage cpkg <- allInstalled ] allPaths = [ pth | LocalUnpackedPackage pth <- map packageSource allSrcPkgs]@@ -1023,7 +1049,7 @@ -- | If logging is enabled, contains location of the log file and the verbosity -- level for logging.-type UseLogFile = Maybe (PackageIdentifier -> LibraryName -> FilePath, Verbosity)+type UseLogFile = Maybe (PackageIdentifier -> UnitId -> FilePath, Verbosity) performInstallations :: Verbosity -> InstallArgs@@ -1031,7 +1057,7 @@ -> InstallPlan -> IO InstallPlan performInstallations verbosity- (packageDBs, _, comp, platform, conf, useSandbox, _,+ (packageDBs, repoCtxt, comp, platform, conf, useSandbox, _, globalFlags, configFlags, configExFlags, installFlags, haddockFlags) installedPkgIndex installPlan = do @@ -1048,23 +1074,18 @@ installLock <- newLock -- serialise installation cacheLock <- newLock -- serialise access to setup exe cache - transport <- configureTransport verbosity- (flagToMaybe (globalHttpTransport globalFlags))- executeInstallPlan verbosity comp jobControl useLogFile installPlan $ \rpkg ->- -- Calculate the package key (ToDo: Is this right for source install)- let libname = readyLibraryName comp rpkg in installReadyPackage platform cinfo configFlags rpkg $ \configFlags' src pkg pkgoverride ->- fetchSourcePackage transport verbosity fetchLimit src $ \src' ->+ fetchSourcePackage verbosity repoCtxt fetchLimit src $ \src' -> installLocalPackage verbosity buildLimit (packageId pkg) src' distPref $ \mpath ->- installUnpackedPackage verbosity buildLimit installLock numJobs libname+ installUnpackedPackage verbosity buildLimit installLock numJobs (setupScriptOptions installedPkgIndex cacheLock rpkg) miscOptions configFlags' installFlags haddockFlags- cinfo platform pkg pkgoverride mpath useLogFile+ cinfo platform pkg rpkg pkgoverride mpath useLogFile where cinfo = compilerInfo comp@@ -1082,7 +1103,7 @@ platform conf distPref- (chooseCabalVersion configExFlags (libVersion miscOptions))+ (chooseCabalVersion configFlags (libVersion miscOptions)) (Just lock) parallelInstall index@@ -1128,12 +1149,12 @@ | parallelInstall = False | otherwise = False - substLogFileName :: PathTemplate -> PackageIdentifier -> LibraryName- -> FilePath- substLogFileName template pkg libname = fromPathTemplate- . substPathTemplate env- $ template- where env = initialPathTemplateEnv (packageId pkg) libname+ substLogFileName :: PathTemplate -> PackageIdentifier -> UnitId -> FilePath+ substLogFileName template pkg ipid = fromPathTemplate+ . substPathTemplate env+ $ template+ where env = initialPathTemplateEnv (packageId pkg)+ ipid (compilerInfo comp) platform miscOptions = InstallMisc {@@ -1148,12 +1169,12 @@ executeInstallPlan :: Verbosity -> Compiler- -> JobControl IO (PackageId, LibraryName, BuildResult)+ -> JobControl IO (PackageId, UnitId, BuildResult) -> UseLogFile -> InstallPlan -> (ReadyPackage -> IO BuildResult) -> IO InstallPlan-executeInstallPlan verbosity comp jobCtl useLogFile plan0 installPkg =+executeInstallPlan verbosity _comp jobCtl useLogFile plan0 installPkg = tryNewTasks 0 plan0 where tryNewTasks taskCount plan = do@@ -1165,10 +1186,13 @@ [ do info verbosity $ "Ready to install " ++ display pkgid spawnJob jobCtl $ do buildResult <- installPkg pkg- return (packageId pkg, libname, buildResult)+ let ipid = case buildResult of+ Right (BuildOk _ _ (Just ipi)) ->+ Installed.installedUnitId ipi+ _ -> mkUnitId (display (packageId pkg))+ return (packageId pkg, ipid, buildResult) | pkg <- pkgs- , let pkgid = packageId pkg- libname = readyLibraryName comp pkg ]+ , let pkgid = packageId pkg ] let taskCount' = taskCount + length pkgs plan' = InstallPlan.processing pkgs plan@@ -1176,8 +1200,8 @@ waitForTasks taskCount plan = do info verbosity $ "Waiting for install task to finish..."- (pkgid, libname, buildResult) <- collectJob jobCtl- printBuildResult pkgid libname buildResult+ (pkgid, ipid, buildResult) <- collectJob jobCtl+ printBuildResult pkgid ipid buildResult let taskCount' = taskCount-1 plan' = updatePlan pkgid buildResult plan tryNewTasks taskCount' plan'@@ -1185,11 +1209,11 @@ updatePlan :: PackageIdentifier -> BuildResult -> InstallPlan -> InstallPlan updatePlan pkgid (Right buildSuccess@(BuildOk _ _ mipkg)) =- InstallPlan.completed (Source.fakeInstalledPackageId pkgid)+ InstallPlan.completed (Source.fakeUnitId pkgid) mipkg buildSuccess updatePlan pkgid (Left buildFailure) =- InstallPlan.failed (Source.fakeInstalledPackageId pkgid)+ InstallPlan.failed (Source.fakeUnitId pkgid) buildFailure depsFailure where depsFailure = DependentFailed pkgid@@ -1200,8 +1224,8 @@ -- Print build log if something went wrong, and 'Installed $PKGID' -- otherwise.- printBuildResult :: PackageId -> LibraryName -> BuildResult -> IO ()- printBuildResult pkgid libname buildResult = case buildResult of+ printBuildResult :: PackageId -> UnitId -> BuildResult -> IO ()+ printBuildResult pkgid ipid buildResult = case buildResult of (Right _) -> notice verbosity $ "Installed " ++ display pkgid (Left _) -> do notice verbosity $ "Failed to install " ++ display pkgid@@ -1209,7 +1233,7 @@ case useLogFile of Nothing -> return () Just (mkLogFileName, _) -> do- let logName = mkLogFileName pkgid libname+ let logName = mkLogFileName pkgid ipid putStr $ "Build log ( " ++ logName ++ " ):\n" printFile logName @@ -1227,7 +1251,7 @@ installReadyPackage :: Platform -> CompilerInfo -> ConfigFlags -> ReadyPackage- -> (ConfigFlags -> PackageLocation (Maybe FilePath)+ -> (ConfigFlags -> UnresolvedPkgLoc -> PackageDescription -> PackageDescriptionOverride -> a)@@ -1235,19 +1259,17 @@ installReadyPackage platform cinfo configFlags (ReadyPackage (ConfiguredPackage (SourcePackage _ gpkg source pkgoverride)- flags stanzas _)- deps)+ flags stanzas deps)) installPkg = installPkg configFlags { configConfigurationsFlags = flags, -- We generate the legacy constraints as well as the new style precise deps. -- In the end only one set gets passed to Setup.hs configure, depending on -- the Cabal version we are talking to.- configConstraints = [ thisPackageVersion (packageId deppkg)- | deppkg <- CD.nonSetupDeps deps ],- configDependencies = [ (packageName (Installed.sourcePackageId deppkg),- Installed.installedPackageId deppkg)- | deppkg <- CD.nonSetupDeps deps ],+ configConstraints = [ thisPackageVersion srcid+ | ConfiguredId srcid _uid <- CD.nonSetupDeps deps ],+ configDependencies = [ (packageName srcid, uid)+ | ConfiguredId srcid uid <- CD.nonSetupDeps deps ], -- Use '--exact-configuration' if supported. configExactConfiguration = toFlag True, configBenchmarks = toFlag False,@@ -1261,26 +1283,26 @@ Right (desc, _) -> desc fetchSourcePackage- :: HttpTransport- -> Verbosity+ :: Verbosity+ -> RepoContext -> JobLimit- -> PackageLocation (Maybe FilePath)- -> (PackageLocation FilePath -> IO BuildResult)+ -> UnresolvedPkgLoc+ -> (ResolvedPkgLoc -> IO BuildResult) -> IO BuildResult-fetchSourcePackage transport verbosity fetchLimit src installPkg = do+fetchSourcePackage verbosity repoCtxt fetchLimit src installPkg = do fetched <- checkFetched src case fetched of Just src' -> installPkg src' Nothing -> onFailure DownloadFailed $ do loc <- withJobLimit fetchLimit $- fetchPackage transport verbosity src+ fetchPackage verbosity repoCtxt src installPkg loc installLocalPackage :: Verbosity -> JobLimit- -> PackageIdentifier -> PackageLocation FilePath -> FilePath+ -> PackageIdentifier -> ResolvedPkgLoc -> FilePath -> (Maybe FilePath -> IO BuildResult) -> IO BuildResult installLocalPackage verbosity jobLimit pkgid location distPref installPkg =@@ -1362,7 +1384,6 @@ -> JobLimit -> Lock -> Int- -> LibraryName -> SetupScriptOptions -> InstallMisc -> ConfigFlags@@ -1371,14 +1392,15 @@ -> CompilerInfo -> Platform -> PackageDescription+ -> ReadyPackage -> PackageDescriptionOverride -> Maybe FilePath -- ^ Directory to change to before starting the installation. -> UseLogFile -- ^ File to log output to (if any) -> IO BuildResult-installUnpackedPackage verbosity buildLimit installLock numJobs libname+installUnpackedPackage verbosity buildLimit installLock numJobs scriptOptions miscOptions configFlags installFlags haddockFlags- cinfo platform pkg pkgoverride workingDir useLogFile = do+ cinfo platform pkg rpkg pkgoverride workingDir useLogFile = do -- Override the .cabal file if necessary case pkgoverride of@@ -1391,9 +1413,20 @@ ++ " with the latest revision from the index." writeFileAtomic descFilePath pkgtxt + -- Compute the IPID of the *library*+ let flags (ReadyPackage cpkg) = confPkgFlags cpkg+ pkg_name = pkgName (PackageDescription.package pkg)+ cid = Configure.computeComponentId+ Cabal.NoFlag -- This would let us override the computation+ (PackageDescription.package pkg)+ (CLibName (display pkg_name))+ (map (\(SimpleUnitId cid0) -> cid0) (CD.libraryDeps (depends rpkg)))+ (flags rpkg)+ ipid = SimpleUnitId cid+ -- Make sure that we pass --libsubdir etc to 'setup configure' (necessary if -- the setup script was compiled against an old version of the Cabal lib).- configFlags' <- addDefaultInstallDirs configFlags+ configFlags' <- addDefaultInstallDirs ipid configFlags -- Filter out flags not supported by the old versions of the Cabal lib. let configureFlags :: Version -> ConfigFlags configureFlags = filterConfigureFlags configFlags' {@@ -1401,52 +1434,55 @@ } -- Path to the optional log file.- mLogPath <- maybeLogPath-- -- Configure phase- onFailure ConfigureFailed $ withJobLimit buildLimit $ do- when (numJobs > 1) $ notice verbosity $- "Configuring " ++ display pkgid ++ "..."- setup configureCommand configureFlags mLogPath+ mLogPath <- maybeLogPath ipid - -- Build phase- onFailure BuildFailed $ do+ logDirChange (maybe putStr appendFile mLogPath) workingDir $ do+ -- Configure phase+ onFailure ConfigureFailed $ withJobLimit buildLimit $ do when (numJobs > 1) $ notice verbosity $- "Building " ++ display pkgid ++ "..."- setup buildCommand' buildFlags mLogPath+ "Configuring " ++ display pkgid ++ "..."+ setup configureCommand configureFlags mLogPath - -- Doc generation phase- docsResult <- if shouldHaddock- then (do setup haddockCommand haddockFlags' mLogPath- return DocsOk)- `catchIO` (\_ -> return DocsFailed)- `catchExit` (\_ -> return DocsFailed)- else return DocsNotTried+ -- Build phase+ onFailure BuildFailed $ do+ when (numJobs > 1) $ notice verbosity $+ "Building " ++ display pkgid ++ "..."+ setup buildCommand' buildFlags mLogPath - -- Tests phase- onFailure TestsFailed $ do- when (testsEnabled && PackageDescription.hasTests pkg) $- setup Cabal.testCommand testFlags mLogPath+ -- Doc generation phase+ docsResult <- if shouldHaddock+ then (do setup haddockCommand haddockFlags' mLogPath+ return DocsOk)+ `catchIO` (\_ -> return DocsFailed)+ `catchExit` (\_ -> return DocsFailed)+ else return DocsNotTried - let testsResult | testsEnabled = TestsOk- | otherwise = TestsNotTried+ -- Tests phase+ onFailure TestsFailed $ do+ when (testsEnabled && PackageDescription.hasTests pkg) $+ setup Cabal.testCommand testFlags mLogPath - -- Install phase- onFailure InstallFailed $ criticalSection installLock $ do- -- Capture installed package configuration file- maybePkgConf <- maybeGenPkgConf mLogPath+ let testsResult | testsEnabled = TestsOk+ | otherwise = TestsNotTried - -- Actual installation- withWin32SelfUpgrade verbosity libname configFlags- cinfo platform pkg $ do- case rootCmd miscOptions of- (Just cmd) -> reexec cmd- Nothing -> do- setup Cabal.copyCommand copyFlags mLogPath- when shouldRegister $ do- setup Cabal.registerCommand registerFlags mLogPath- return (Right (BuildOk docsResult testsResult maybePkgConf))+ -- Install phase+ onFailure InstallFailed $ criticalSection installLock $ do+ -- Actual installation+ withWin32SelfUpgrade verbosity ipid configFlags+ cinfo platform pkg $ do+ case rootCmd miscOptions of+ (Just cmd) -> reexec cmd+ Nothing -> do+ setup Cabal.copyCommand copyFlags mLogPath+ when shouldRegister $ do+ setup Cabal.registerCommand registerFlags mLogPath + -- Capture installed package configuration file+ -- TODO: Why do we need this?+ maybePkgConf <- maybeGenPkgConf mLogPath++ return (Right (BuildOk docsResult testsResult maybePkgConf))+ where pkgid = packageId pkg buildCommand' = buildCommand defaultProgramConfiguration@@ -1477,8 +1513,8 @@ verbosity' = maybe verbosity snd useLogFile tempTemplate name = name ++ "-" ++ display pkgid - addDefaultInstallDirs :: ConfigFlags -> IO ConfigFlags- addDefaultInstallDirs configFlags' = do+ addDefaultInstallDirs :: UnitId -> ConfigFlags -> IO ConfigFlags+ addDefaultInstallDirs ipid configFlags' = do defInstallDirs <- InstallDirs.defaultInstallDirs flavor userInstall False return $ configFlags' { configInstallDirs = fmap Cabal.Flag .@@ -1488,7 +1524,7 @@ } where CompilerId flavor _ = compilerInfoId cinfo- env = initialPathTemplateEnv pkgid libname cinfo platform+ env = initialPathTemplateEnv pkgid ipid cinfo platform userInstall = fromFlagOrDefault defaultUserInstall (configUserInstall configFlags') @@ -1517,12 +1553,12 @@ die $ "Couldn't parse the output of 'setup register --gen-pkg-config':" ++ show perror - maybeLogPath :: IO (Maybe FilePath)- maybeLogPath =+ maybeLogPath :: UnitId -> IO (Maybe FilePath)+ maybeLogPath ipid = case useLogFile of Nothing -> return Nothing Just (mkLogFileName, _) -> do- let logFileName = mkLogFileName (packageId pkg) libname+ let logFileName = mkLogFileName (packageId pkg) ipid logDir = takeDirectory logFileName unless (null logDir) $ createDirectoryIfMissing True logDir logFileExists <- doesFileExist logFileName@@ -1570,14 +1606,14 @@ -- ------------------------------------------------------------ withWin32SelfUpgrade :: Verbosity- -> LibraryName+ -> UnitId -> ConfigFlags -> CompilerInfo -> Platform -> PackageDescription -> IO a -> IO a withWin32SelfUpgrade _ _ _ _ _ _ action | buildOS /= Windows = action-withWin32SelfUpgrade verbosity libname configFlags cinfo platform pkg action = do+withWin32SelfUpgrade verbosity ipid configFlags cinfo platform pkg action = do defaultDirs <- InstallDirs.defaultInstallDirs compFlavor@@ -1605,10 +1641,10 @@ templateDirs = InstallDirs.combineInstallDirs fromFlagOrDefault defaultDirs (configInstallDirs configFlags) absoluteDirs = InstallDirs.absoluteInstallDirs- pkgid libname+ pkgid ipid cinfo InstallDirs.NoCopyDest platform templateDirs substTemplate = InstallDirs.fromPathTemplate . InstallDirs.substPathTemplate env- where env = InstallDirs.initialPathTemplateEnv pkgid libname+ where env = InstallDirs.initialPathTemplateEnv pkgid ipid cinfo platform
cabal/cabal-install/Distribution/Client/InstallPlan.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE DeriveGeneric #-} ----------------------------------------------------------------------------- -- | -- Module : Distribution.Client.InstallPlan@@ -21,11 +22,16 @@ -- * Operations on 'InstallPlan's new, toList,+ mapPreservingGraph,+ ready, processing, completed, failed, remove,+ preexisting,+ preinstalled,+ showPlanIndex, showInstallPlan, @@ -42,17 +48,21 @@ -- ** Querying the install plan dependencyClosure,+ reverseDependencyClosure,+ topologicalOrder,+ reverseTopologicalOrder, ) where import Distribution.InstalledPackageInfo ( InstalledPackageInfo ) import Distribution.Package ( PackageIdentifier(..), PackageName(..), Package(..)- , InstalledPackageId, HasInstalledPackageId(..) )+ , HasUnitId(..), UnitId(..) ) import Distribution.Client.Types ( BuildSuccess, BuildFailure , PackageFixedDeps(..), ConfiguredPackage- , GenericReadyPackage(..), fakeInstalledPackageId )+ , UnresolvedPkgLoc+ , GenericReadyPackage(..), fakeUnitId ) import Distribution.Version ( Version ) import Distribution.Client.ComponentDeps (ComponentDeps)@@ -67,14 +77,16 @@ ( display ) import Data.List- ( intercalate )+ ( foldl', intercalate ) import Data.Maybe- ( fromMaybe, maybeToList )+ ( fromMaybe, catMaybes ) import qualified Data.Graph as Graph import Data.Graph (Graph)+import qualified Data.Tree as Tree+import Distribution.Compat.Binary (Binary(..))+import GHC.Generics import Control.Exception ( assert )-import Data.Maybe (catMaybes) import qualified Data.Map as Map import qualified Data.Traversable as T @@ -135,12 +147,16 @@ data GenericPlanPackage ipkg srcpkg iresult ifailure = PreExisting ipkg | Configured srcpkg- | Processing (GenericReadyPackage srcpkg ipkg)- | Installed (GenericReadyPackage srcpkg ipkg) (Maybe ipkg) iresult+ | Processing (GenericReadyPackage srcpkg)+ | Installed (GenericReadyPackage srcpkg) (Maybe ipkg) iresult | Failed srcpkg ifailure+ deriving (Eq, Show, Generic) +instance (Binary ipkg, Binary srcpkg, Binary iresult, Binary ifailure)+ => Binary (GenericPlanPackage ipkg srcpkg iresult ifailure)+ type PlanPackage = GenericPlanPackage- InstalledPackageInfo ConfiguredPackage+ InstalledPackageInfo (ConfiguredPackage UnresolvedPkgLoc) BuildSuccess BuildFailure instance (Package ipkg, Package srcpkg) =>@@ -152,7 +168,7 @@ packageId (Failed spkg _) = packageId spkg instance (PackageFixedDeps srcpkg,- PackageFixedDeps ipkg, HasInstalledPackageId ipkg) =>+ PackageFixedDeps ipkg) => PackageFixedDeps (GenericPlanPackage ipkg srcpkg iresult ifailure) where depends (PreExisting pkg) = depends pkg depends (Configured pkg) = depends pkg@@ -160,58 +176,114 @@ depends (Installed pkg _ _) = depends pkg depends (Failed pkg _) = depends pkg -instance (HasInstalledPackageId ipkg, HasInstalledPackageId srcpkg) =>- HasInstalledPackageId+instance (HasUnitId ipkg, HasUnitId srcpkg) =>+ HasUnitId (GenericPlanPackage ipkg srcpkg iresult ifailure) where- installedPackageId (PreExisting ipkg ) = installedPackageId ipkg- installedPackageId (Configured spkg) = installedPackageId spkg- installedPackageId (Processing rpkg) = installedPackageId rpkg+ installedUnitId (PreExisting ipkg ) = installedUnitId ipkg+ installedUnitId (Configured spkg) = installedUnitId spkg+ installedUnitId (Processing rpkg) = installedUnitId rpkg -- NB: defer to the actual installed package info in this case- installedPackageId (Installed _ (Just ipkg) _) = installedPackageId ipkg- installedPackageId (Installed rpkg _ _) = installedPackageId rpkg- installedPackageId (Failed spkg _) = installedPackageId spkg+ installedUnitId (Installed _ (Just ipkg) _) = installedUnitId ipkg+ installedUnitId (Installed rpkg _ _) = installedUnitId rpkg+ installedUnitId (Failed spkg _) = installedUnitId spkg data GenericInstallPlan ipkg srcpkg iresult ifailure = GenericInstallPlan {- planIndex :: (PlanIndex ipkg srcpkg iresult ifailure),- planFakeMap :: FakeMap,+ planIndex :: !(PlanIndex ipkg srcpkg iresult ifailure),+ planFakeMap :: !FakeMap,+ planIndepGoals :: !Bool,++ -- | Cached (lazily) graph+ --+ -- The 'Graph' representation works in terms of integer node ids, so we+ -- have to keep mapping to and from our meaningful nodes, which of course+ -- are package ids.+ -- planGraph :: Graph,- planGraphRev :: Graph,- planPkgOf :: Graph.Vertex- -> GenericPlanPackage ipkg srcpkg iresult ifailure,- planVertexOf :: InstalledPackageId -> Graph.Vertex,- planIndepGoals :: Bool+ planGraphRev :: Graph, -- ^ Reverse deps, transposed+ planPkgIdOf :: Graph.Vertex -> UnitId, -- ^ mapping back to package ids+ planVertexOf :: UnitId -> Graph.Vertex -- ^ mapping into node ids } +-- | Much like 'planPkgIdOf', but mapping back to full packages.+planPkgOf :: GenericInstallPlan ipkg srcpkg iresult ifailure+ -> Graph.Vertex+ -> GenericPlanPackage ipkg srcpkg iresult ifailure+planPkgOf plan v =+ case PackageIndex.lookupUnitId (planIndex plan)+ (planPkgIdOf plan v) of+ Just pkg -> pkg+ Nothing -> error "InstallPlan: internal error: planPkgOf lookup failed"++ -- | 'GenericInstallPlan' specialised to most commonly used types. type InstallPlan = GenericInstallPlan- InstalledPackageInfo ConfiguredPackage+ InstalledPackageInfo (ConfiguredPackage UnresolvedPkgLoc) BuildSuccess BuildFailure type PlanIndex ipkg srcpkg iresult ifailure = PackageIndex (GenericPlanPackage ipkg srcpkg iresult ifailure) -invariant :: (HasInstalledPackageId ipkg, PackageFixedDeps ipkg,- HasInstalledPackageId srcpkg, PackageFixedDeps srcpkg)+invariant :: (HasUnitId ipkg, PackageFixedDeps ipkg,+ HasUnitId srcpkg, PackageFixedDeps srcpkg) => GenericInstallPlan ipkg srcpkg iresult ifailure -> Bool invariant plan = valid (planFakeMap plan) (planIndepGoals plan) (planIndex plan) +-- | Smart constructor that deals with caching the 'Graph' representation.+--+mkInstallPlan :: (HasUnitId ipkg, PackageFixedDeps ipkg,+ HasUnitId srcpkg, PackageFixedDeps srcpkg)+ => PlanIndex ipkg srcpkg iresult ifailure+ -> FakeMap+ -> Bool+ -> GenericInstallPlan ipkg srcpkg iresult ifailure+mkInstallPlan index fakeMap indepGoals =+ GenericInstallPlan {+ planIndex = index,+ planFakeMap = fakeMap,+ planIndepGoals = indepGoals,++ -- lazily cache the graph stuff:+ planGraph = graph,+ planGraphRev = Graph.transposeG graph,+ planPkgIdOf = vertexToPkgId,+ planVertexOf = fromMaybe noSuchPkgId . pkgIdToVertex+ }+ where+ (graph, vertexToPkgId, pkgIdToVertex) =+ PlanIndex.dependencyGraph fakeMap index+ noSuchPkgId = internalError "package is not in the graph"+ internalError :: String -> a internalError msg = error $ "InstallPlan: internal error: " ++ msg -showPlanIndex :: (HasInstalledPackageId ipkg, HasInstalledPackageId srcpkg)+instance (HasUnitId ipkg, PackageFixedDeps ipkg,+ HasUnitId srcpkg, PackageFixedDeps srcpkg,+ Binary ipkg, Binary srcpkg, Binary iresult, Binary ifailure)+ => Binary (GenericInstallPlan ipkg srcpkg iresult ifailure) where+ put GenericInstallPlan {+ planIndex = index,+ planFakeMap = fakeMap,+ planIndepGoals = indepGoals+ } = put (index, fakeMap, indepGoals)++ get = do+ (index, fakeMap, indepGoals) <- get+ return $! mkInstallPlan index fakeMap indepGoals++showPlanIndex :: (HasUnitId ipkg, HasUnitId srcpkg) => PlanIndex ipkg srcpkg iresult ifailure -> String showPlanIndex index = intercalate "\n" (map showPlanPackage (PackageIndex.allPackages index)) where showPlanPackage p = showPlanPackageTag p ++ " " ++ display (packageId p) ++ " ("- ++ display (installedPackageId p) ++ ")"+ ++ display (installedUnitId p) ++ ")" -showInstallPlan :: (HasInstalledPackageId ipkg, HasInstalledPackageId srcpkg)+showInstallPlan :: (HasUnitId ipkg, HasUnitId srcpkg) => GenericInstallPlan ipkg srcpkg iresult ifailure -> String showInstallPlan plan = showPlanIndex (planIndex plan) ++ "\n" ++@@ -228,8 +300,8 @@ -- | Build an installation plan from a valid set of resolved packages. ---new :: (HasInstalledPackageId ipkg, PackageFixedDeps ipkg,- HasInstalledPackageId srcpkg, PackageFixedDeps srcpkg)+new :: (HasUnitId ipkg, PackageFixedDeps ipkg,+ HasUnitId srcpkg, PackageFixedDeps srcpkg) => Bool -> PlanIndex ipkg srcpkg iresult ifailure -> Either [PlanProblem ipkg srcpkg iresult ifailure]@@ -240,23 +312,12 @@ let isPreExisting (PreExisting _) = True isPreExisting _ = False fakeMap = Map.fromList- . map (\p -> (fakeInstalledPackageId (packageId p)- ,installedPackageId p))+ . map (\p -> (fakeUnitId (packageId p)+ ,installedUnitId p)) . filter isPreExisting $ PackageIndex.allPackages index in case problems fakeMap indepGoals index of- [] -> Right GenericInstallPlan {- planIndex = index,- planFakeMap = fakeMap,- planGraph = graph,- planGraphRev = Graph.transposeG graph,- planPkgOf = vertexToPkgId,- planVertexOf = fromMaybe noSuchPkgId . pkgIdToVertex,- planIndepGoals = indepGoals- }- where (graph, vertexToPkgId, pkgIdToVertex) =- PlanIndex.dependencyGraph fakeMap index- noSuchPkgId = internalError "package is not in the graph"+ [] -> Right (mkInstallPlan index fakeMap indepGoals) probs -> Left probs toList :: GenericInstallPlan ipkg srcpkg iresult ifailure@@ -269,8 +330,8 @@ -- the dependencies of a package or set of packages without actually -- installing the package itself, as when doing development. ---remove :: (HasInstalledPackageId ipkg, PackageFixedDeps ipkg,- HasInstalledPackageId srcpkg, PackageFixedDeps srcpkg)+remove :: (HasUnitId ipkg, PackageFixedDeps ipkg,+ HasUnitId srcpkg, PackageFixedDeps srcpkg) => (GenericPlanPackage ipkg srcpkg iresult ifailure -> Bool) -> GenericInstallPlan ipkg srcpkg iresult ifailure -> Either [PlanProblem ipkg srcpkg iresult ifailure]@@ -287,7 +348,7 @@ -- ready :: forall ipkg srcpkg iresult ifailure. PackageFixedDeps srcpkg => GenericInstallPlan ipkg srcpkg iresult ifailure- -> [GenericReadyPackage srcpkg ipkg]+ -> [GenericReadyPackage srcpkg] ready plan = assert check readyPackages where check = if null readyPackages && null processingPackages@@ -296,22 +357,27 @@ configuredPackages = [ pkg | Configured pkg <- toList plan ] processingPackages = [ pkg | Processing pkg <- toList plan] - readyPackages :: [GenericReadyPackage srcpkg ipkg]- readyPackages =- [ ReadyPackage srcpkg deps- | srcpkg <- configuredPackages- -- select only the package that have all of their deps installed:- , deps <- maybeToList (hasAllInstalledDeps srcpkg)- ]+ readyPackages :: [GenericReadyPackage srcpkg]+ readyPackages = catMaybes (map (lookupReadyPackage plan) configuredPackages) +lookupReadyPackage :: forall ipkg srcpkg iresult ifailure.+ PackageFixedDeps srcpkg+ => GenericInstallPlan ipkg srcpkg iresult ifailure+ -> srcpkg+ -> Maybe (GenericReadyPackage srcpkg)+lookupReadyPackage plan pkg = do+ _ <- hasAllInstalledDeps pkg+ return (ReadyPackage pkg)+ where+ hasAllInstalledDeps :: srcpkg -> Maybe (ComponentDeps [ipkg]) hasAllInstalledDeps = T.mapM (mapM isInstalledDep) . depends - isInstalledDep :: InstalledPackageId -> Maybe ipkg+ isInstalledDep :: UnitId -> Maybe ipkg isInstalledDep pkgid = -- NB: Need to check if the ID has been updated in planFakeMap, in which -- case we might be dealing with an old pointer- case PlanIndex.fakeLookupInstalledPackageId+ case PlanIndex.fakeLookupUnitId (planFakeMap plan) (planIndex plan) pkgid of Just (PreExisting ipkg) -> Just ipkg@@ -329,9 +395,9 @@ -- -- * The package must exist in the graph and be in the configured state. ---processing :: (HasInstalledPackageId ipkg, PackageFixedDeps ipkg,- HasInstalledPackageId srcpkg, PackageFixedDeps srcpkg)- => [GenericReadyPackage srcpkg ipkg]+processing :: (HasUnitId ipkg, PackageFixedDeps ipkg,+ HasUnitId srcpkg, PackageFixedDeps srcpkg)+ => [GenericReadyPackage srcpkg] -> GenericInstallPlan ipkg srcpkg iresult ifailure -> GenericInstallPlan ipkg srcpkg iresult ifailure processing pkgs plan = assert (invariant plan') plan'@@ -347,9 +413,9 @@ -- * The package must exist in the graph and be in the processing state. -- * The package must have had no uninstalled dependent packages. ---completed :: (HasInstalledPackageId ipkg, PackageFixedDeps ipkg,- HasInstalledPackageId srcpkg, PackageFixedDeps srcpkg)- => InstalledPackageId+completed :: (HasUnitId ipkg, PackageFixedDeps ipkg,+ HasUnitId srcpkg, PackageFixedDeps srcpkg)+ => UnitId -> Maybe ipkg -> iresult -> GenericInstallPlan ipkg srcpkg iresult ifailure -> GenericInstallPlan ipkg srcpkg iresult ifailure@@ -361,13 +427,13 @@ planFakeMap = insert_fake_mapping mipkg $ planFakeMap plan, planIndex = PackageIndex.insert installed- . PackageIndex.deleteInstalledPackageId pkgid+ . PackageIndex.deleteUnitId pkgid $ planIndex plan } -- ...but be sure to use the *old* IPID for the lookup for the -- preexisting record installed = Installed (lookupProcessingPackage plan pkgid) mipkg buildResult- insert_fake_mapping (Just ipkg) = Map.insert pkgid (installedPackageId ipkg)+ insert_fake_mapping (Just ipkg) = Map.insert pkgid (installedUnitId ipkg) insert_fake_mapping _ = id -- | Marks a package in the graph as having failed. It also marks all the@@ -376,9 +442,9 @@ -- * The package must exist in the graph and be in the processing -- state. ---failed :: (HasInstalledPackageId ipkg, PackageFixedDeps ipkg,- HasInstalledPackageId srcpkg, PackageFixedDeps srcpkg)- => InstalledPackageId -- ^ The id of the package that failed to install+failed :: (HasUnitId ipkg, PackageFixedDeps ipkg,+ HasUnitId srcpkg, PackageFixedDeps srcpkg)+ => UnitId -- ^ The id of the package that failed to install -> ifailure -- ^ The build result to use for the failed package -> ifailure -- ^ The build result to use for its dependencies -> GenericInstallPlan ipkg srcpkg iresult ifailure@@ -389,7 +455,7 @@ plan' = plan { planIndex = PackageIndex.merge (planIndex plan) failures }- ReadyPackage srcpkg _deps = lookupProcessingPackage plan pkgid+ ReadyPackage srcpkg = lookupProcessingPackage plan pkgid failures = PackageIndex.fromList $ Failed srcpkg buildResult : [ Failed pkg' buildResult'@@ -399,7 +465,7 @@ -- | Lookup the reachable packages in the reverse dependency graph. -- packagesThatDependOn :: GenericInstallPlan ipkg srcpkg iresult ifailure- -> InstalledPackageId+ -> UnitId -> [GenericPlanPackage ipkg srcpkg iresult ifailure] packagesThatDependOn plan pkgid = map (planPkgOf plan) . tail@@ -410,12 +476,12 @@ -- | Lookup a package that we expect to be in the processing state. -- lookupProcessingPackage :: GenericInstallPlan ipkg srcpkg iresult ifailure- -> InstalledPackageId- -> GenericReadyPackage srcpkg ipkg+ -> UnitId+ -> GenericReadyPackage srcpkg lookupProcessingPackage plan pkgid = -- NB: processing packages are guaranteed to not indirect through -- planFakeMap- case PackageIndex.lookupInstalledPackageId (planIndex plan) pkgid of+ case PackageIndex.lookupUnitId (planIndex plan) pkgid of Just (Processing pkg) -> pkg _ -> internalError $ "not in processing state or no such pkg " ++ display pkgid@@ -430,6 +496,97 @@ checkConfiguredPackage pkg = internalError $ "not configured or no such pkg " ++ display (packageId pkg) +-- | Replace a ready package with a pre-existing one. The pre-existing one+-- must have exactly the same dependencies as the source one was configured+-- with.+--+preexisting :: (HasUnitId ipkg, PackageFixedDeps ipkg,+ HasUnitId srcpkg, PackageFixedDeps srcpkg)+ => UnitId+ -> ipkg+ -> GenericInstallPlan ipkg srcpkg iresult ifailure+ -> GenericInstallPlan ipkg srcpkg iresult ifailure+preexisting pkgid ipkg plan = assert (invariant plan') plan'+ where+ plan' = plan {+ -- NB: installation can change the IPID, so better+ -- record it in the fake mapping...+ planFakeMap = Map.insert pkgid+ (installedUnitId ipkg)+ (planFakeMap plan),+ planIndex = PackageIndex.insert (PreExisting ipkg)+ -- ...but be sure to use the *old* IPID for the lookup for+ -- the preexisting record+ . PackageIndex.deleteUnitId pkgid+ $ planIndex plan+ }++-- | Replace a ready package with an installed one. The installed one+-- must have exactly the same dependencies as the source one was configured+-- with.+--+preinstalled :: (HasUnitId ipkg, PackageFixedDeps ipkg,+ HasUnitId srcpkg, PackageFixedDeps srcpkg)+ => UnitId+ -> Maybe ipkg -> iresult+ -> GenericInstallPlan ipkg srcpkg iresult ifailure+ -> GenericInstallPlan ipkg srcpkg iresult ifailure+preinstalled pkgid mipkg buildResult plan = assert (invariant plan') plan'+ where+ plan' = plan { planIndex = PackageIndex.insert installed (planIndex plan) }+ Just installed = do+ Configured pkg <- PackageIndex.lookupUnitId (planIndex plan) pkgid+ rpkg <- lookupReadyPackage plan pkg+ return (Installed rpkg mipkg buildResult)++-- | Transform an install plan by mapping a function over all the packages in+-- the plan. It can consistently change the 'UnitId' of all the packages,+-- while preserving the same overall graph structure.+--+-- The mapping function has a few constraints on it for correct operation.+-- The mapping function /may/ change the 'UnitId' of the package, but it+-- /must/ also remap the 'UnitId's of its dependencies using ths supplied+-- remapping function. Apart from this consistent remapping it /may not/+-- change the structure of the dependencies.+--+mapPreservingGraph :: (HasUnitId ipkg,+ HasUnitId srcpkg,+ HasUnitId ipkg', PackageFixedDeps ipkg',+ HasUnitId srcpkg', PackageFixedDeps srcpkg')+ => ( (UnitId -> UnitId)+ -> GenericPlanPackage ipkg srcpkg iresult ifailure+ -> GenericPlanPackage ipkg' srcpkg' iresult' ifailure')+ -> GenericInstallPlan ipkg srcpkg iresult ifailure+ -> GenericInstallPlan ipkg' srcpkg' iresult' ifailure'+mapPreservingGraph f plan =+ mkInstallPlan (PackageIndex.fromList pkgs')+ Map.empty -- empty fakeMap+ (planIndepGoals plan)+ where+ -- The package mapping function may change the UnitId. So we+ -- walk over the packages in dependency order keeping track of these+ -- package id changes and use it to supply the correct set of package+ -- dependencies as an extra input to the package mapping function.+ --+ -- Having fully remapped all the deps this also means we can use an empty+ -- FakeMap for the resulting install plan.++ (_, pkgs') = foldl' f' (Map.empty, []) (reverseTopologicalOrder plan)++ f' (ipkgidMap, pkgs) pkg = (ipkgidMap', pkg' : pkgs)+ where+ pkg' = f (mapDep ipkgidMap) pkg++ ipkgidMap'+ | ipkgid /= ipkgid' = Map.insert ipkgid ipkgid' ipkgidMap+ | otherwise = ipkgidMap+ where+ ipkgid = installedUnitId pkg+ ipkgid' = installedUnitId pkg'++ mapDep ipkgidMap ipkgid = Map.findWithDefault ipkgid ipkgid ipkgidMap++ -- ------------------------------------------------------------ -- * Checking validity of plans -- ------------------------------------------------------------@@ -440,8 +597,8 @@ -- -- * if the result is @False@ use 'problems' to get a detailed list. ---valid :: (HasInstalledPackageId ipkg, PackageFixedDeps ipkg,- HasInstalledPackageId srcpkg, PackageFixedDeps srcpkg)+valid :: (HasUnitId ipkg, PackageFixedDeps ipkg,+ HasUnitId srcpkg, PackageFixedDeps srcpkg) => FakeMap -> Bool -> PlanIndex ipkg srcpkg iresult ifailure -> Bool@@ -492,8 +649,8 @@ -- error messages. This is mainly intended for debugging purposes. -- Use 'showPlanProblem' for a human readable explanation. ---problems :: (HasInstalledPackageId ipkg, PackageFixedDeps ipkg,- HasInstalledPackageId srcpkg, PackageFixedDeps srcpkg)+problems :: (HasUnitId ipkg, PackageFixedDeps ipkg,+ HasUnitId srcpkg, PackageFixedDeps srcpkg) => FakeMap -> Bool -> PlanIndex ipkg srcpkg iresult ifailure -> [PlanProblem ipkg srcpkg iresult ifailure]@@ -502,7 +659,7 @@ [ PackageMissingDeps pkg (catMaybes (map- (fmap packageId . PlanIndex.fakeLookupInstalledPackageId fakeMap index)+ (fmap packageId . PlanIndex.fakeLookupUnitId fakeMap index) missingDeps)) | (pkg, missingDeps) <- PlanIndex.brokenPackages fakeMap index ] @@ -515,8 +672,8 @@ ++ [ PackageStateInvalid pkg pkg' | pkg <- PackageIndex.allPackages index- , Just pkg' <- map (PlanIndex.fakeLookupInstalledPackageId fakeMap index)- (CD.nonSetupDeps (depends pkg))+ , Just pkg' <- map (PlanIndex.fakeLookupUnitId fakeMap index)+ (CD.flatDeps (depends pkg)) , not (stateDependencyRelation pkg pkg') ] -- | The graph of packages (nodes) and dependencies (edges) must be acyclic.@@ -524,8 +681,8 @@ -- * if the result is @False@ use 'PackageIndex.dependencyCycles' to find out -- which packages are involved in dependency cycles. ---acyclic :: (HasInstalledPackageId ipkg, PackageFixedDeps ipkg,- HasInstalledPackageId srcpkg, PackageFixedDeps srcpkg)+acyclic :: (HasUnitId ipkg, PackageFixedDeps ipkg,+ HasUnitId srcpkg, PackageFixedDeps srcpkg) => FakeMap -> PlanIndex ipkg srcpkg iresult ifailure -> Bool acyclic fakeMap = null . PlanIndex.dependencyCycles fakeMap @@ -536,7 +693,7 @@ -- * if the result is @False@ use 'PackageIndex.brokenPackages' to find out -- which packages depend on packages not in the index. ---closed :: (HasInstalledPackageId ipkg, PackageFixedDeps ipkg,+closed :: (PackageFixedDeps ipkg, PackageFixedDeps srcpkg) => FakeMap -> PlanIndex ipkg srcpkg iresult ifailure -> Bool closed fakeMap = null . PlanIndex.brokenPackages fakeMap@@ -557,8 +714,8 @@ -- * if the result is @False@ use 'PackageIndex.dependencyInconsistencies' to -- find out which packages are. ---consistent :: (HasInstalledPackageId ipkg, PackageFixedDeps ipkg,- HasInstalledPackageId srcpkg, PackageFixedDeps srcpkg)+consistent :: (HasUnitId ipkg, PackageFixedDeps ipkg,+ HasUnitId srcpkg, PackageFixedDeps srcpkg) => FakeMap -> PlanIndex ipkg srcpkg iresult ifailure -> Bool consistent fakeMap = null . PlanIndex.dependencyInconsistencies fakeMap False @@ -594,22 +751,39 @@ stateDependencyRelation _ _ = False --- | Compute the dependency closure of a _source_ package in a install plan+-- | Compute the dependency closure of a package in a install plan ----- See `Distribution.Client.PlanIndex.dependencyClosure`-dependencyClosure :: (HasInstalledPackageId ipkg, PackageFixedDeps ipkg,- HasInstalledPackageId srcpkg, PackageFixedDeps srcpkg)- => GenericInstallPlan ipkg srcpkg iresult ifailure- -> [PackageIdentifier]- -> Either [(GenericPlanPackage ipkg srcpkg iresult ifailure,- [InstalledPackageId])]- (PackageIndex- (GenericPlanPackage ipkg srcpkg iresult ifailure))-dependencyClosure installPlan pids =- PlanIndex.dependencyClosure- (planFakeMap installPlan)- (planIndex installPlan)- (map (resolveFakeId . fakeInstalledPackageId) pids)- where- resolveFakeId :: InstalledPackageId -> InstalledPackageId- resolveFakeId ipid = Map.findWithDefault ipid ipid (planFakeMap installPlan)+dependencyClosure :: GenericInstallPlan ipkg srcpkg iresult ifailure+ -> [UnitId]+ -> [GenericPlanPackage ipkg srcpkg iresult ifailure]+dependencyClosure plan =+ map (planPkgOf plan)+ . concatMap Tree.flatten+ . Graph.dfs (planGraph plan)+ . map (planVertexOf plan)+++reverseDependencyClosure :: GenericInstallPlan ipkg srcpkg iresult ifailure+ -> [UnitId]+ -> [GenericPlanPackage ipkg srcpkg iresult ifailure]+reverseDependencyClosure plan =+ map (planPkgOf plan)+ . concatMap Tree.flatten+ . Graph.dfs (planGraphRev plan)+ . map (planVertexOf plan)+++topologicalOrder :: GenericInstallPlan ipkg srcpkg iresult ifailure+ -> [GenericPlanPackage ipkg srcpkg iresult ifailure]+topologicalOrder plan =+ map (planPkgOf plan)+ . Graph.topSort+ $ planGraph plan+++reverseTopologicalOrder :: GenericInstallPlan ipkg srcpkg iresult ifailure+ -> [GenericPlanPackage ipkg srcpkg iresult ifailure]+reverseTopologicalOrder plan =+ map (planPkgOf plan)+ . Graph.topSort+ $ planGraphRev plan
cabal/cabal-install/Distribution/Client/InstallSymlink.hs view
@@ -40,19 +40,17 @@ import Distribution.Client.Types ( SourcePackage(..) , GenericReadyPackage(..), ReadyPackage, enableStanzas- , ConfiguredPackage(..) )+ , ConfiguredPackage(..) , fakeUnitId) import Distribution.Client.Setup ( InstallFlags(installSymlinkBinDir) ) import qualified Distribution.Client.InstallPlan as InstallPlan import Distribution.Client.InstallPlan (InstallPlan) import Distribution.Package- ( PackageIdentifier, Package(packageId), mkPackageKey- , packageKeyLibraryName, LibraryName )+ ( PackageIdentifier, Package(packageId), UnitId(..) ) import Distribution.Compiler ( CompilerId(..) ) import qualified Distribution.PackageDescription as PackageDescription-import qualified Distribution.Client.ComponentDeps as CD import Distribution.PackageDescription ( PackageDescription ) import Distribution.PackageDescription.Configuration@@ -60,9 +58,8 @@ import Distribution.Simple.Setup ( ConfigFlags(..), fromFlag, fromFlagOrDefault, flagToMaybe ) import qualified Distribution.Simple.InstallDirs as InstallDirs-import qualified Distribution.InstalledPackageInfo as Installed import Distribution.Simple.Compiler- ( Compiler, compilerInfo, CompilerInfo(..), packageKeySupported )+ ( Compiler, compilerInfo, CompilerInfo(..) ) import Distribution.System ( Platform ) @@ -118,7 +115,7 @@ -- TODO: do we want to do this here? : -- createDirectoryIfMissing True publicBinDir fmap catMaybes $ sequence- [ do privateBinDir <- pkgBinDir pkg libname+ [ do privateBinDir <- pkgBinDir pkg ipid ok <- symlinkBinary publicBinDir privateBinDir publicExeName privateExeName@@ -126,16 +123,14 @@ then return Nothing else return (Just (pkgid, publicExeName, privateBinDir </> privateExeName))- | (ReadyPackage (ConfiguredPackage _ _flags _ _) deps, pkg, exe) <- exes+ | (ReadyPackage _cpkg, pkg, exe) <- exes , let pkgid = packageId pkg- pkg_key = mkPackageKey (packageKeySupported comp) pkgid- (map Installed.libraryName- (CD.nonSetupDeps deps))- libname = packageKeyLibraryName pkgid pkg_key+ -- This is a bit dodgy; probably won't work for Backpack packages+ ipid = fakeUnitId pkgid publicExeName = PackageDescription.exeName exe privateExeName = prefix ++ publicExeName ++ suffix- prefix = substTemplate pkgid libname prefixTemplate- suffix = substTemplate pkgid libname suffixTemplate ]+ prefix = substTemplate pkgid ipid prefixTemplate+ suffix = substTemplate pkgid ipid suffixTemplate ] where exes = [ (cpkg, pkg, exe)@@ -147,8 +142,7 @@ pkgDescription :: ReadyPackage -> PackageDescription pkgDescription (ReadyPackage (ConfiguredPackage (SourcePackage _ pkg _ _)- flags stanzas _)- _) =+ flags stanzas _)) = case finalizePackageDescription flags (const True) platform cinfo [] (enableStanzas stanzas pkg) of@@ -157,8 +151,8 @@ -- This is sadly rather complicated. We're kind of re-doing part of the -- configuration for the package. :-(- pkgBinDir :: PackageDescription -> LibraryName -> IO FilePath- pkgBinDir pkg libname = do+ pkgBinDir :: PackageDescription -> UnitId -> IO FilePath+ pkgBinDir pkg ipid = do defaultDirs <- InstallDirs.defaultInstallDirs compilerFlavor (fromFlag (configUserInstall configFlags))@@ -166,14 +160,14 @@ let templateDirs = InstallDirs.combineInstallDirs fromFlagOrDefault defaultDirs (configInstallDirs configFlags) absoluteDirs = InstallDirs.absoluteInstallDirs- (packageId pkg) libname+ (packageId pkg) ipid cinfo InstallDirs.NoCopyDest platform templateDirs canonicalizePath (InstallDirs.bindir absoluteDirs) - substTemplate pkgid libname = InstallDirs.fromPathTemplate- . InstallDirs.substPathTemplate env- where env = InstallDirs.initialPathTemplateEnv pkgid libname+ substTemplate pkgid ipid = InstallDirs.fromPathTemplate+ . InstallDirs.substPathTemplate env+ where env = InstallDirs.initialPathTemplateEnv pkgid ipid cinfo platform fromFlagTemplate = fromFlagOrDefault (InstallDirs.toPathTemplate "")
cabal/cabal-install/Distribution/Client/List.hs view
@@ -16,7 +16,7 @@ import Distribution.Package ( PackageName(..), Package(..), packageName, packageVersion , Dependency(..), simplifyDependency- , InstalledPackageId )+ , UnitId ) import Distribution.ModuleName (ModuleName) import Distribution.License (License) import qualified Distribution.InstalledPackageInfo as Installed@@ -31,7 +31,7 @@ import Distribution.Simple.Program (ProgramConfiguration) import Distribution.Simple.Utils ( equating, comparing, die, notice )-import Distribution.Simple.Setup (fromFlag, flagToMaybe)+import Distribution.Simple.Setup (fromFlag) import Distribution.Simple.PackageIndex (InstalledPackageIndex) import qualified Distribution.Simple.PackageIndex as InstalledPackageIndex import qualified Distribution.Client.PackageIndex as PackageIndex@@ -43,21 +43,21 @@ ( Text(disp), display ) import Distribution.Client.Types- ( SourcePackage(..), Repo, SourcePackageDb(..) )+ ( SourcePackage(..), SourcePackageDb(..)+ , UnresolvedSourcePackage ) import Distribution.Client.Dependency.Types ( PackageConstraint(..) ) import Distribution.Client.Targets ( UserTarget, resolveUserTargets, PackageSpecifier(..) ) import Distribution.Client.Setup- ( GlobalFlags(..), ListFlags(..), InfoFlags(..) )+ ( GlobalFlags(..), ListFlags(..), InfoFlags(..)+ , RepoContext(..) ) import Distribution.Client.Utils ( mergeBy, MergeResult(..) ) import Distribution.Client.IndexUtils as IndexUtils ( getSourcePackages, getInstalledPackages ) import Distribution.Client.FetchUtils ( isFetched )-import Distribution.Client.HttpUtils- ( configureTransport ) import Data.List ( sortBy, groupBy, sort, nub, intersperse, maximumBy, partition )@@ -77,21 +77,21 @@ -- | Return a list of packages matching given search strings. getPkgList :: Verbosity -> PackageDBStack- -> [Repo]+ -> RepoContext -> Compiler -> ProgramConfiguration -> ListFlags -> [String] -> IO [PackageDisplayInfo]-getPkgList verbosity packageDBs repos comp conf listFlags pats = do+getPkgList verbosity packageDBs repoCtxt comp conf listFlags pats = do installedPkgIndex <- getInstalledPackages verbosity comp packageDBs conf- sourcePkgDb <- getSourcePackages verbosity repos+ sourcePkgDb <- getSourcePackages verbosity repoCtxt let sourcePkgIndex = packageIndex sourcePkgDb prefs name = fromMaybe anyVersion (Map.lookup name (packagePreferences sourcePkgDb)) pkgsInfo ::- [(PackageName, [Installed.InstalledPackageInfo], [SourcePackage])]+ [(PackageName, [Installed.InstalledPackageInfo], [UnresolvedSourcePackage])] pkgsInfo -- gather info for all packages | null pats = mergePackages@@ -102,7 +102,7 @@ | otherwise = pkgsInfoMatching pkgsInfoMatching ::- [(PackageName, [Installed.InstalledPackageInfo], [SourcePackage])]+ [(PackageName, [Installed.InstalledPackageInfo], [UnresolvedSourcePackage])] pkgsInfoMatching = let matchingInstalled = matchingPackages InstalledPackageIndex.searchByNameSubstring@@ -133,7 +133,7 @@ -- | Show information about packages. list :: Verbosity -> PackageDBStack- -> [Repo]+ -> RepoContext -> Compiler -> ProgramConfiguration -> ListFlags@@ -163,7 +163,7 @@ info :: Verbosity -> PackageDBStack- -> [Repo]+ -> RepoContext -> Compiler -> ProgramConfiguration -> GlobalFlags@@ -173,11 +173,11 @@ info verbosity _ _ _ _ _ _ [] = notice verbosity "No packages requested. Nothing to do." -info verbosity packageDBs repos comp conf+info verbosity packageDBs repoCtxt comp conf globalFlags _listFlags userTargets = do installedPkgIndex <- getInstalledPackages verbosity comp packageDBs conf- sourcePkgDb <- getSourcePackages verbosity repos+ sourcePkgDb <- getSourcePackages verbosity repoCtxt let sourcePkgIndex = packageIndex sourcePkgDb prefs name = fromMaybe anyVersion (Map.lookup name (packagePreferences sourcePkgDb))@@ -190,8 +190,7 @@ (InstalledPackageIndex.allPackages installedPkgIndex) ++ map packageId (PackageIndex.allPackages sourcePkgIndex)- transport <- configureTransport verbosity (flagToMaybe (globalHttpTransport globalFlags))- pkgSpecifiers <- resolveUserTargets verbosity transport+ pkgSpecifiers <- resolveUserTargets verbosity repoCtxt (fromFlag $ globalWorldFile globalFlags) sourcePkgs' userTargets @@ -208,8 +207,8 @@ where gatherPkgInfo :: (PackageName -> VersionRange) -> InstalledPackageIndex ->- PackageIndex.PackageIndex SourcePackage ->- PackageSpecifier SourcePackage ->+ PackageIndex.PackageIndex UnresolvedSourcePackage ->+ PackageSpecifier UnresolvedSourcePackage -> Either String PackageDisplayInfo gatherPkgInfo prefs installedPkgIndex sourcePkgIndex (NamedPackage name constraints)@@ -253,8 +252,8 @@ (PackageName -> VersionRange) -> PackageName -> InstalledPackageIndex- -> PackageIndex.PackageIndex SourcePackage- -> (VersionRange, [Installed.InstalledPackageInfo], [SourcePackage])+ -> PackageIndex.PackageIndex UnresolvedSourcePackage+ -> (VersionRange, [Installed.InstalledPackageInfo], [UnresolvedSourcePackage]) sourcePkgsInfo prefs name installedPkgIndex sourcePkgIndex = (pref, installedPkgs, sourcePkgs) where@@ -270,7 +269,7 @@ data PackageDisplayInfo = PackageDisplayInfo { pkgName :: PackageName, selectedVersion :: Maybe Version,- selectedSourcePkg :: Maybe SourcePackage,+ selectedSourcePkg :: Maybe UnresolvedSourcePackage, installedVersions :: [Version], sourceVersions :: [Version], preferredVersions :: VersionRange,@@ -296,7 +295,7 @@ -- | Covers source dependencies and installed dependencies in -- one type. data ExtDependency = SourceDependency Dependency- | InstalledDependency InstalledPackageId+ | InstalledDependency UnitId showPackageSummaryInfo :: PackageDisplayInfo -> String showPackageSummaryInfo pkginfo =@@ -419,8 +418,8 @@ -- mergePackageInfo :: VersionRange -> [Installed.InstalledPackageInfo]- -> [SourcePackage]- -> Maybe SourcePackage+ -> [UnresolvedSourcePackage]+ -> Maybe UnresolvedSourcePackage -> Bool -> PackageDisplayInfo mergePackageInfo versionPref installedPkgs sourcePkgs selectedPkg showVer =@@ -458,13 +457,13 @@ flags = maybe [] Source.genPackageFlags sourceGeneric, hasLib = isJust installed || fromMaybe False- (fmap (isJust . Source.condLibrary) sourceGeneric),+ (fmap (not . null . Source.condLibraries) sourceGeneric), hasExe = fromMaybe False (fmap (not . null . Source.condExecutables) sourceGeneric), executables = map fst (maybe [] Source.condExecutables sourceGeneric), modules = combine (map Installed.exposedName . Installed.exposedModules) installed- (maybe [] getListOfExposedModules . Source.library)+ (concatMap getListOfExposedModules . Source.libraries) source, dependencies = combine (map (SourceDependency . simplifyDependency)@@ -522,10 +521,10 @@ -- both be empty. -- mergePackages :: [Installed.InstalledPackageInfo]- -> [SourcePackage]+ -> [UnresolvedSourcePackage] -> [( PackageName , [Installed.InstalledPackageInfo]- , [SourcePackage] )]+ , [UnresolvedSourcePackage] )] mergePackages installedPkgs sourcePkgs = map collect $ mergeBy (\i a -> fst i `compare` fst a)
+ cabal/cabal-install/Distribution/Client/Manpage.hs view
@@ -0,0 +1,171 @@+{-# LANGUAGE CPP #-}+-----------------------------------------------------------------------------+-- |+-- Module : Distribution.Client.Manpage+-- Copyright : (c) Maciek Makowski 2015+-- License : BSD-like+--+-- Maintainer : cabal-devel@haskell.org+-- Stability : provisional+-- Portability : portable+--+-- Functions for building the manual page.++module Distribution.Client.Manpage+ ( -- * Manual page generation+ manpage+ ) where++import Distribution.Simple.Command+import Distribution.Client.Setup (globalCommand)++import Data.Char (toUpper)+import Data.List (intercalate)++data FileInfo = FileInfo String String -- ^ path, description++-- | A list of files that should be documented in the manual page.+files :: [FileInfo]+files =+ [ (FileInfo "~/.cabal/config" "The defaults that can be overridden with command-line options.")+ , (FileInfo "~/.cabal/world" "A list of all packages whose installation has been explicitly requested.")+ ]++-- | Produces a manual page with @troff@ markup.+manpage :: String -> [CommandSpec a] -> String+manpage pname commands = unlines $+ [ ".TH " ++ map toUpper pname ++ " 1"+ , ".SH NAME"+ , pname ++ " \\- a system for building and packaging Haskell libraries and programs"+ , ".SH SYNOPSIS"+ , ".B " ++ pname+ , ".I command"+ , ".RI < arguments |[ options ]>..."+ , ""+ , "Where the"+ , ".I commands"+ , "are"+ , ""+ ] +++ concatMap (commandSynopsisLines pname) commands +++ [ ".SH DESCRIPTION"+ , "Cabal is the standard package system for Haskell software. It helps people to configure, "+ , "build and install Haskell software and to distribute it easily to other users and developers."+ , ""+ , "The command line " ++ pname ++ " tool (also referred to as cabal-install) helps with "+ , "installing existing packages and developing new packages. "+ , "It can be used to work with local packages or to install packages from online package archives, "+ , "including automatically installing dependencies. By default it is configured to use Hackage, "+ , "which is Haskell’s central package archive that contains thousands of libraries and applications "+ , "in the Cabal package format."+ , ".SH OPTIONS"+ , "Global options:"+ , ""+ ] +++ optionsLines (globalCommand []) +++ [ ".SH COMMANDS"+ ] +++ concatMap (commandDetailsLines pname) commands +++ [ ".SH FILES"+ ] +++ concatMap fileLines files +++ [ ".SH BUGS"+ , "To browse the list of known issues or report a new one please see "+ , "https://github.com/haskell/cabal/labels/cabal-install."+ ]++commandSynopsisLines :: String -> CommandSpec action -> [String]+commandSynopsisLines pname (CommandSpec ui _ NormalCommand) =+ [ ".B " ++ pname ++ " " ++ (commandName ui)+ , ".R - " ++ commandSynopsis ui+ , ".br"+ ]+commandSynopsisLines _ (CommandSpec _ _ HiddenCommand) = []++commandDetailsLines :: String -> CommandSpec action -> [String]+commandDetailsLines pname (CommandSpec ui _ NormalCommand) =+ [ ".B " ++ pname ++ " " ++ (commandName ui)+ , ""+ , commandUsage ui pname+ , ""+ ] +++ optional commandDescription +++ optional commandNotes +++ [ "Flags:"+ , ".RS"+ ] +++ optionsLines ui +++ [ ".RE"+ , ""+ ]+ where+ optional field =+ case field ui of+ Just text -> [text pname, ""]+ Nothing -> []+commandDetailsLines _ (CommandSpec _ _ HiddenCommand) = []++optionsLines :: CommandUI flags -> [String]+optionsLines command = concatMap optionLines (concatMap optionDescr (commandOptions command ParseArgs))++data ArgumentRequired = Optional | Required+type OptionArg = (ArgumentRequired, ArgPlaceHolder)++optionLines :: OptDescr flags -> [String]+optionLines (ReqArg description (optionChars, optionStrings) placeHolder _ _) =+ argOptionLines description optionChars optionStrings (Required, placeHolder)+optionLines (OptArg description (optionChars, optionStrings) placeHolder _ _ _) =+ argOptionLines description optionChars optionStrings (Optional, placeHolder)+optionLines (BoolOpt description (trueChars, trueStrings) (falseChars, falseStrings) _ _) =+ optionLinesIfPresent trueChars trueStrings +++ optionLinesIfPresent falseChars falseStrings +++ optionDescriptionLines description+optionLines (ChoiceOpt options) =+ concatMap choiceLines options+ where+ choiceLines (description, (optionChars, optionStrings), _, _) =+ [ optionsLine optionChars optionStrings ] +++ optionDescriptionLines description++argOptionLines :: String -> [Char] -> [String] -> OptionArg -> [String]+argOptionLines description optionChars optionStrings arg =+ [ optionsLine optionChars optionStrings+ , optionArgLine arg+ ] +++ optionDescriptionLines description++optionLinesIfPresent :: [Char] -> [String] -> [String]+optionLinesIfPresent optionChars optionStrings =+ if null optionChars && null optionStrings then []+ else [ optionsLine optionChars optionStrings, ".br" ]++optionDescriptionLines :: String -> [String]+optionDescriptionLines description =+ [ ".RS"+ , description+ , ".RE"+ , ""+ ]++optionsLine :: [Char] -> [String] -> String+optionsLine optionChars optionStrings =+ intercalate ", " (shortOptions optionChars ++ longOptions optionStrings)++shortOptions :: [Char] -> [String]+shortOptions = map (\c -> "\\-" ++ [c])++longOptions :: [String] -> [String]+longOptions = map (\s -> "\\-\\-" ++ s)++optionArgLine :: OptionArg -> String+optionArgLine (Required, placeHolder) = ".I " ++ placeHolder+optionArgLine (Optional, placeHolder) = ".RI [ " ++ placeHolder ++ " ]"++fileLines :: FileInfo -> [String]+fileLines (FileInfo path description) =+ [ path+ , ".RS"+ , description+ , ".RE"+ , ""+ ]
+ cabal/cabal-install/Distribution/Client/PackageHash.hs view
@@ -0,0 +1,226 @@+{-# LANGUAGE RecordWildCards, NamedFieldPuns, GeneralizedNewtypeDeriving #-}++-- | Functions to calculate nix-style hashes for package ids.+--+-- The basic idea is simple, hash the combination of:+--+-- * the package tarball+-- * the ids of all the direct dependencies+-- * other local configuration (flags, profiling, etc)+--+module Distribution.Client.PackageHash (+ -- * Calculating package hashes+ PackageHashInputs(..),+ PackageHashConfigInputs(..),+ PackageSourceHash,+ hashedInstalledPackageId,+ hashPackageHashInputs,+ renderPackageHashInputs,++ -- * Low level hash choice+ HashValue,+ hashValue,+ showHashValue,+ readFileHashValue+ ) where++import Distribution.Package+ ( PackageId, mkUnitId )+import Distribution.System+ ( Platform )+import Distribution.PackageDescription+ ( FlagName(..), FlagAssignment )+import Distribution.Simple.Compiler+ ( CompilerId, OptimisationLevel(..), DebugInfoLevel(..)+ , ProfDetailLevel(..), showProfDetailLevel )+import Distribution.Simple.InstallDirs+ ( PathTemplate, fromPathTemplate )+import Distribution.Text+ ( display )+import Distribution.Client.Types+ ( InstalledPackageId )++import qualified Data.ByteString.Char8 as BS+import qualified Data.ByteString.Lazy.Char8 as LBS+import qualified Crypto.Hash as Hash+import qualified Data.Byteable as Hash+import qualified Data.Set as Set+import Data.Set (Set)++import Data.Maybe (catMaybes)+import Data.List (sortBy, intercalate)+import Data.Function (on)+import Distribution.Compat.Binary (Binary(..))+import Control.Exception (evaluate)+import System.IO (withBinaryFile, IOMode(..))+++-------------------------------+-- Calculating package hashes+--++-- | Calculate a 'InstalledPackageId' for a package using our nix-style+-- inputs hashing method.+--+hashedInstalledPackageId :: PackageHashInputs -> InstalledPackageId+hashedInstalledPackageId pkghashinputs@PackageHashInputs{pkgHashPkgId} =+ mkUnitId $+ display pkgHashPkgId -- to be a bit user friendly+ ++ "-"+ ++ showHashValue (hashPackageHashInputs pkghashinputs)++-- | All the information that contribues to a package's hash, and thus its+-- 'InstalledPackageId'.+--+data PackageHashInputs = PackageHashInputs {+ pkgHashPkgId :: PackageId,+ pkgHashSourceHash :: PackageSourceHash,+ pkgHashDirectDeps :: Set InstalledPackageId,+ pkgHashOtherConfig :: PackageHashConfigInputs+ }++type PackageSourceHash = HashValue++-- | Those parts of the package configuration that contribute to the+-- package hash.+--+data PackageHashConfigInputs = PackageHashConfigInputs {+ pkgHashCompilerId :: CompilerId,+ pkgHashPlatform :: Platform,+ pkgHashFlagAssignment :: FlagAssignment, -- complete not partial+ pkgHashConfigureScriptArgs :: [String], -- just ./configure for build-type Configure+ pkgHashVanillaLib :: Bool,+ pkgHashSharedLib :: Bool,+ pkgHashDynExe :: Bool,+ pkgHashGHCiLib :: Bool,+ pkgHashProfLib :: Bool,+ pkgHashProfExe :: Bool,+ pkgHashProfLibDetail :: ProfDetailLevel,+ pkgHashProfExeDetail :: ProfDetailLevel,+ pkgHashCoverage :: Bool,+ pkgHashOptimization :: OptimisationLevel,+ pkgHashSplitObjs :: Bool,+ pkgHashStripLibs :: Bool,+ pkgHashStripExes :: Bool,+ pkgHashDebugInfo :: DebugInfoLevel,+ pkgHashExtraLibDirs :: [FilePath],+ pkgHashExtraFrameworkDirs :: [FilePath],+ pkgHashExtraIncludeDirs :: [FilePath],+ pkgHashProgPrefix :: Maybe PathTemplate,+ pkgHashProgSuffix :: Maybe PathTemplate++-- TODO: [required eventually] pkgHashToolsVersions ?+-- TODO: [required eventually] pkgHashToolsExtraOptions ?+-- TODO: [research required] and what about docs?+ }+ deriving Show+++-- | Calculate the overall hash to be used for an 'InstalledPackageId'.+--+hashPackageHashInputs :: PackageHashInputs -> HashValue+hashPackageHashInputs = hashValue . renderPackageHashInputs++-- | Render a textual representation of the 'PackageHashInputs'.+--+-- The 'hashValue' of this text is the overall package hash.+--+renderPackageHashInputs :: PackageHashInputs -> LBS.ByteString+renderPackageHashInputs PackageHashInputs{+ pkgHashPkgId,+ pkgHashSourceHash,+ pkgHashDirectDeps,+ pkgHashOtherConfig =+ PackageHashConfigInputs{..}+ } =+ -- The purpose of this somewhat laboured rendering (e.g. why not just+ -- use show?) is so that existing package hashes do not change+ -- unnecessarily when new configuration inputs are added into the hash.++ -- In particular, the assumption is that when a new configuration input+ -- is included into the hash, that existing packages will typically get+ -- the default value for that feature. So if we avoid adding entries with+ -- the default value then most of the time adding new features will not+ -- change the hashes of existing packages and so fewer packages will need+ -- to be rebuilt. ++ --TODO: [nice to have] ultimately we probably want to put this config info+ -- into the ghc-pkg db. At that point this should probably be changed to+ -- use the config file infrastructure so it can be read back in again.+ LBS.pack $ unlines $ catMaybes+ [ entry "pkgid" display pkgHashPkgId+ , entry "src" showHashValue pkgHashSourceHash+ , entry "deps" (intercalate ", " . map display+ . Set.toList) pkgHashDirectDeps+ -- and then all the config+ , entry "compilerid" display pkgHashCompilerId+ , entry "platform" display pkgHashPlatform+ , opt "flags" [] showFlagAssignment pkgHashFlagAssignment+ , opt "configure-script" [] unwords pkgHashConfigureScriptArgs+ , opt "vanilla-lib" True display pkgHashVanillaLib+ , opt "shared-lib" False display pkgHashSharedLib+ , opt "dynamic-exe" False display pkgHashDynExe+ , opt "ghci-lib" False display pkgHashGHCiLib+ , opt "prof-lib" False display pkgHashProfLib+ , opt "prof-exe" False display pkgHashProfExe+ , opt "prof-lib-detail" ProfDetailDefault showProfDetailLevel pkgHashProfLibDetail + , opt "prof-exe-detail" ProfDetailDefault showProfDetailLevel pkgHashProfExeDetail + , opt "hpc" False display pkgHashCoverage+ , opt "optimisation" NormalOptimisation (show . fromEnum) pkgHashOptimization+ , opt "split-objs" False display pkgHashSplitObjs+ , opt "stripped-lib" False display pkgHashStripLibs+ , opt "stripped-exe" True display pkgHashStripExes+ , opt "debug-info" NormalDebugInfo (show . fromEnum) pkgHashDebugInfo+ , opt "extra-lib-dirs" [] unwords pkgHashExtraLibDirs+ , opt "extra-framework-dirs" [] unwords pkgHashExtraFrameworkDirs+ , opt "extra-include-dirs" [] unwords pkgHashExtraIncludeDirs+ , opt "prog-prefix" Nothing (maybe "" fromPathTemplate) pkgHashProgPrefix+ , opt "prog-suffix" Nothing (maybe "" fromPathTemplate) pkgHashProgSuffix+ ]+ where+ entry key format value = Just (key ++ ": " ++ format value)+ opt key def format value+ | value == def = Nothing+ | otherwise = entry key format value++ showFlagAssignment = unwords . map showEntry . sortBy (compare `on` fst)+ where+ showEntry (FlagName name, False) = '-' : name+ showEntry (FlagName name, True) = '+' : name++-----------------------------------------------+-- The specific choice of hash implementation+--++-- Is a crypto hash necessary here? One thing to consider is who controls the+-- inputs and what's the result of a hash collision. Obviously we should not+-- install packages we don't trust because they can run all sorts of code, but+-- if I've checked there's no TH, no custom Setup etc, is there still a+-- problem? If someone provided us a tarball that hashed to the same value as+-- some other package and we installed it, we could end up re-using that+-- installed package in place of another one we wanted. So yes, in general+-- there is some value in preventing intentional hash collisions in installed+-- package ids.++newtype HashValue = HashValue (Hash.Digest Hash.SHA256)+ deriving (Eq, Show)++instance Binary HashValue where+ put (HashValue digest) = put (Hash.toBytes digest)+ get = do+ bs <- get+ case Hash.digestFromByteString bs of+ Nothing -> fail "HashValue: bad digest"+ Just digest -> return (HashValue digest)++hashValue :: LBS.ByteString -> HashValue+hashValue = HashValue . Hash.hashlazy++showHashValue :: HashValue -> String+showHashValue (HashValue digest) = BS.unpack (Hash.digestToHexByteString digest)++readFileHashValue :: FilePath -> IO HashValue+readFileHashValue tarball =+ withBinaryFile tarball ReadMode $ \hnd ->+ evaluate . hashValue =<< LBS.hGetContents hnd+
cabal/cabal-install/Distribution/Client/PackageIndex.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveGeneric #-} ----------------------------------------------------------------------------- -- | -- Module : Distribution.Client.PackageIndex@@ -54,6 +55,9 @@ import Data.Monoid (Monoid(..)) #endif import Data.Maybe (isJust, fromMaybe)+import GHC.Generics (Generic)+import Distribution.Compat.Binary (Binary)+import Distribution.Compat.Semigroup (Semigroup((<>))) import Distribution.Package ( PackageName(..), PackageIdentifier(..)@@ -78,14 +82,20 @@ -- (Map PackageName [pkg]) - deriving (Show, Read, Functor)+ deriving (Eq, Show, Read, Functor, Generic)+--FIXME: the Functor instance here relies on no package id changes +instance Package pkg => Semigroup (PackageIndex pkg) where+ (<>) = merge+ instance Package pkg => Monoid (PackageIndex pkg) where mempty = PackageIndex Map.empty- mappend = merge+ mappend = (<>) --save one mappend with empty in the common case: mconcat [] = mempty mconcat xs = foldr1 mappend xs++instance Binary pkg => Binary (PackageIndex pkg) invariant :: Package pkg => PackageIndex pkg -> Bool invariant (PackageIndex m) = all (uncurry goodBucket) (Map.toList m)
cabal/cabal-install/Distribution/Client/PackageUtils.hs view
@@ -15,11 +15,11 @@ ) where import Distribution.Package- ( packageVersion, packageName, Dependency(..) )+ ( packageVersion, packageName, Dependency(..), PackageName(..) ) import Distribution.PackageDescription- ( PackageDescription(..) )+ ( PackageDescription(..), libName ) import Distribution.Version- ( withinRange )+ ( withinRange, isAnyVersion ) -- | The list of dependencies that refer to external packages -- rather than internal package components.@@ -30,5 +30,7 @@ -- True if this dependency is an internal one (depends on a library -- defined in the same package). internal (Dependency depName versionRange) =- depName == packageName pkg &&- packageVersion pkg `withinRange` versionRange+ (depName == packageName pkg &&+ packageVersion pkg `withinRange` versionRange) ||+ (unPackageName depName `elem` map libName (libraries pkg) &&+ isAnyVersion versionRange)
cabal/cabal-install/Distribution/Client/ParseUtils.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE ExistentialQuantification, NamedFieldPuns #-}+ ----------------------------------------------------------------------------- -- | -- Module : Distribution.Client.ParseUtils@@ -7,13 +9,41 @@ -- Parsing utilities. ----------------------------------------------------------------------------- -module Distribution.Client.ParseUtils ( parseFields, ppFields, ppSection )+module Distribution.Client.ParseUtils (++ -- * Fields and field utilities+ FieldDescr(..),+ liftField,+ liftFields,+ filterFields,+ mapFieldNames,+ commandOptionToField,+ commandOptionsToFields,++ -- * Sections and utilities+ SectionDescr(..),+ liftSection,++ -- * Parsing and printing flat config+ parseFields,+ ppFields,+ ppSection,++ -- * Parsing and printing config with sections and subsections+ parseFieldsAndSections,+ ppFieldsAndSections,++ -- ** Top level of config files+ parseConfig,+ showConfig,+ ) where import Distribution.ParseUtils- ( FieldDescr(..), ParseResult(..), warning, lineNo )-import qualified Distribution.ParseUtils as ParseUtils- ( Field(..) )+ ( FieldDescr(..), ParseResult(..), warning, LineNo, lineNo+ , Field(..), liftField, readFieldsFlat )+import Distribution.Simple.Command+ ( OptionField, viewAsFieldDescr ) import Control.Monad ( foldM ) import Text.PrettyPrint ( (<>), (<+>), ($+$) )@@ -21,18 +51,101 @@ import qualified Text.PrettyPrint as Disp ( Doc, text, colon, vcat, empty, isEmpty, nest ) ---FIXME: replace this with something better-parseFields :: [FieldDescr a] -> a -> [ParseUtils.Field] -> ParseResult a-parseFields fields = foldM setField++-------------------------+-- FieldDescr utilities+--++liftFields :: (b -> a)+ -> (a -> b -> b)+ -> [FieldDescr a]+ -> [FieldDescr b]+liftFields get set = map (liftField get set)+++-- | Given a collection of field descriptions, keep only a given list of them,+-- identified by name.+--+filterFields :: [String] -> [FieldDescr a] -> [FieldDescr a]+filterFields includeFields = filter ((`elem` includeFields) . fieldName)++-- | Apply a name mangling function to the field names of all the field+-- descriptions. The typical use case is to apply some prefix.+--+mapFieldNames :: (String -> String) -> [FieldDescr a] -> [FieldDescr a]+mapFieldNames mangleName =+ map (\descr -> descr { fieldName = mangleName (fieldName descr) })+++-- | Reuse a command line 'OptionField' as a config file 'FieldDescr'.+--+commandOptionToField :: OptionField a -> FieldDescr a+commandOptionToField = viewAsFieldDescr++-- | Reuse a bunch of command line 'OptionField's as config file 'FieldDescr's.+--+commandOptionsToFields :: [OptionField a] -> [FieldDescr a]+commandOptionsToFields = map viewAsFieldDescr+++------------------------------------------+-- SectionDescr definition and utilities+--++-- | The description of a section in a config file. It can contains both+-- fields and optionally further subsections. See also 'FieldDescr'.+--+data SectionDescr a = forall b. SectionDescr {+ sectionName :: String,+ sectionFields :: [FieldDescr b],+ sectionSubsections :: [SectionDescr b],+ sectionGet :: a -> [(String, b)],+ sectionSet :: LineNo -> String -> b -> a -> ParseResult a,+ sectionEmpty :: b+ }++-- | To help construction of config file descriptions in a modular way it is+-- useful to define fields and sections on local types and then hoist them+-- into the parent types when combining them in bigger descriptions.+--+-- This is essentially a lens operation for 'SectionDescr' to help embedding+-- one inside another.+--+liftSection :: (b -> a)+ -> (a -> b -> b)+ -> SectionDescr a+ -> SectionDescr b+liftSection get' set' (SectionDescr name fields sections get set empty) =+ let sectionGet' = get . get'+ sectionSet' lineno param x y = do+ x' <- set lineno param x (get' y)+ return (set' x' y)+ in SectionDescr name fields sections sectionGet' sectionSet' empty+++-------------------------------------+-- Parsing and printing flat config+--++-- | Parse a bunch of semi-parsed 'Field's according to a set of field+-- descriptions. It accumulates the result on top of a given initial value.+--+-- This only covers the case of flat configuration without subsections. See+-- also 'parseFieldsAndSections'.+--+parseFields :: [FieldDescr a] -> a -> [Field] -> ParseResult a+parseFields fieldDescrs =+ foldM setField where- fieldMap = Map.fromList- [ (name, f) | f@(FieldDescr name _ _) <- fields ]- setField accum (ParseUtils.F line name value) =+ fieldMap = Map.fromList [ (fieldName f, f) | f <- fieldDescrs ]++ setField accum (F line name value) = case Map.lookup name fieldMap of Just (FieldDescr _ _ set) -> set line value accum Nothing -> do warning $ "Unrecognized field " ++ name ++ " on line " ++ show line return accum+ setField accum f = do warning $ "Unrecognized stanza on line " ++ show (lineNo f) return accum@@ -41,8 +154,9 @@ -- that also optionally print default values for empty fields as comments. -- ppFields :: [FieldDescr a] -> (Maybe a) -> a -> Disp.Doc-ppFields fields def cur = Disp.vcat [ ppField name (fmap getter def) (getter cur)- | FieldDescr name getter _ <- fields]+ppFields fields def cur =+ Disp.vcat [ ppField name (fmap getter def) (getter cur)+ | FieldDescr name getter _ <- fields] ppField :: String -> (Maybe Disp.Doc) -> Disp.Doc -> Disp.Doc ppField name mdef cur@@ -51,6 +165,11 @@ <> Disp.colon <+> def) mdef | otherwise = Disp.text name <> Disp.colon <+> cur +-- | Pretty print a section.+--+-- Since 'ppFields' does not cover subsections you can use this to add them.+-- Or alternatively use a 'SectionDescr' and use 'ppFieldsAndSections'.+-- ppSection :: String -> String -> [FieldDescr a] -> (Maybe a) -> a -> Disp.Doc ppSection name arg fields def cur | Disp.isEmpty fieldsDoc = Disp.empty@@ -60,3 +179,101 @@ fieldsDoc = ppFields fields def cur argDoc | arg == "" = Disp.empty | otherwise = Disp.text arg+++-----------------------------------------+-- Parsing and printing non-flat config+--++-- | Much like 'parseFields' but it also allows subsections. The permitted+-- subsections are given by a list of 'SectionDescr's.+--+parseFieldsAndSections :: [FieldDescr a] -> [SectionDescr a] -> a+ -> [Field] -> ParseResult a+parseFieldsAndSections fieldDescrs sectionDescrs =+ foldM setField+ where+ fieldMap = Map.fromList [ (fieldName f, f) | f <- fieldDescrs ]+ sectionMap = Map.fromList [ (sectionName s, s) | s <- sectionDescrs ]++ setField a (F line name value) =+ case Map.lookup name fieldMap of+ Just (FieldDescr _ _ set) -> set line value a+ Nothing -> do+ warning $ "Unrecognized field '" ++ name+ ++ "' on line " ++ show line+ return a++ setField a (Section line name param fields) =+ case Map.lookup name sectionMap of+ Just (SectionDescr _ fieldDescrs' sectionDescrs' _ set sectionEmpty) -> do+ b <- parseFieldsAndSections fieldDescrs' sectionDescrs' sectionEmpty fields+ set line param b a+ Nothing -> do+ warning $ "Unrecognized section '" ++ name+ ++ "' on line " ++ show line+ return a++ setField accum (block@IfBlock {}) = do+ warning $ "Unrecognized stanza on line " ++ show (lineNo block)+ return accum++-- | Much like 'ppFields' but also pretty prints any subsections. Subsection+-- are only shown if they are non-empty.+--+-- Note that unlike 'ppFields', at present it does not support printing+-- default values. If needed, adding such support would be quite reasonable.+--+ppFieldsAndSections :: [FieldDescr a] -> [SectionDescr a] -> a -> Disp.Doc+ppFieldsAndSections fieldDescrs sectionDescrs val =+ ppFields fieldDescrs Nothing val+ $+$+ Disp.vcat+ [ Disp.text "" $+$ sectionDoc+ | SectionDescr {+ sectionName, sectionGet,+ sectionFields, sectionSubsections+ } <- sectionDescrs+ , (param, x) <- sectionGet val+ , let sectionDoc = ppSectionAndSubsections+ sectionName param+ sectionFields sectionSubsections x+ , not (Disp.isEmpty sectionDoc)+ ]++-- | Unlike 'ppSection' which has to be called directly, this gets used via+-- 'ppFieldsAndSections' and so does not need to be exported.+--+ppSectionAndSubsections :: String -> String+ -> [FieldDescr a] -> [SectionDescr a] -> a -> Disp.Doc+ppSectionAndSubsections name arg fields sections cur+ | Disp.isEmpty fieldsDoc = Disp.empty+ | otherwise = Disp.text name <+> argDoc+ $+$ (Disp.nest 2 fieldsDoc)+ where+ fieldsDoc = showConfig fields sections cur+ argDoc | arg == "" = Disp.empty+ | otherwise = Disp.text arg+++-----------------------------------------------+-- Top level config file parsing and printing+--++-- | Parse a string in the config file syntax into a value, based on a+-- description of the configuration file in terms of its fields and sections.+--+-- It accumulates the result on top of a given initial (typically empty) value.+--+parseConfig :: [FieldDescr a] -> [SectionDescr a] -> a+ -> String -> ParseResult a+parseConfig fieldDescrs sectionDescrs empty str =+ parseFieldsAndSections fieldDescrs sectionDescrs empty+ =<< readFieldsFlat str++-- | Render a value in the config file syntax, based on a description of the+-- configuration file in terms of its fields and sections.+--+showConfig :: [FieldDescr a] -> [SectionDescr a] -> a -> Disp.Doc+showConfig = ppFieldsAndSections+
+ cabal/cabal-install/Distribution/Client/PkgConfigDb.hs view
@@ -0,0 +1,103 @@+{-# LANGUAGE CPP #-}+-----------------------------------------------------------------------------+-- |+-- Module : Distribution.Client.PkgConfigDb+-- Copyright : (c) Iñaki García Etxebarria 2016+-- License : BSD-like+--+-- Maintainer : cabal-devel@haskell.org+-- Portability : portable+--+-- Read the list of packages available to pkg-config.+-----------------------------------------------------------------------------+module Distribution.Client.PkgConfigDb+ (+ PkgConfigDb+ , readPkgConfigDb+ , pkgConfigDbFromList+ , pkgConfigPkgIsPresent+ ) where++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative ((<$>))+#endif++import Control.Exception (IOException, handle)+import Data.Char (isSpace)+import qualified Data.Map as M+import Data.Version (parseVersion)+import Text.ParserCombinators.ReadP (readP_to_S)++import Distribution.Package+ ( PackageName(..) )+import Distribution.Verbosity+ ( Verbosity )+import Distribution.Version+ ( Version, VersionRange, withinRange )++import Distribution.Simple.Program+ ( ProgramConfiguration, pkgConfigProgram, getProgramOutput,+ requireProgram )+import Distribution.Simple.Utils+ ( info )++-- | The list of packages installed in the system visible to+-- @pkg-config@. This is an opaque datatype, to be constructed with+-- `readPkgConfigDb` and queried with `pkgConfigPkgPresent`.+data PkgConfigDb = PkgConfigDb (M.Map PackageName (Maybe Version))+ -- ^ If an entry is `Nothing`, this means that the+ -- package seems to be present, but we don't know the+ -- exact version (because parsing of the version+ -- number failed).+ | NoPkgConfigDb+ -- ^ For when we could not run pkg-config successfully.+ deriving (Show)++-- | Query pkg-config for the list of installed packages, together+-- with their versions. Return a `PkgConfigDb` encapsulating this+-- information.+readPkgConfigDb :: Verbosity -> ProgramConfiguration -> IO PkgConfigDb+readPkgConfigDb verbosity conf = handle ioErrorHandler $ do+ (pkgConfig, _) <- requireProgram verbosity pkgConfigProgram conf+ pkgList <- lines <$> getProgramOutput verbosity pkgConfig ["--list-all"]+ -- The output of @pkg-config --list-all@ also includes a description+ -- for each package, which we do not need.+ let pkgNames = map (takeWhile (not . isSpace)) pkgList+ pkgVersions <- lines <$> getProgramOutput verbosity pkgConfig+ ("--modversion" : pkgNames)+ (return . pkgConfigDbFromList . zip pkgNames) pkgVersions+ where+ -- For when pkg-config invocation fails (possibly because of a+ -- too long command line).+ ioErrorHandler :: IOException -> IO PkgConfigDb+ ioErrorHandler e = do+ info verbosity ("Failed to query pkg-config, Cabal will continue"+ ++ " without solving for pkg-config constraints: "+ ++ show e)+ return NoPkgConfigDb++-- | Create a `PkgConfigDb` from a list of @(packageName, version)@ pairs.+pkgConfigDbFromList :: [(String, String)] -> PkgConfigDb+pkgConfigDbFromList pairs = (PkgConfigDb . M.fromList . map convert) pairs+ where+ convert :: (String, String) -> (PackageName, Maybe Version)+ convert (n,vs) = (PackageName n,+ case (reverse . readP_to_S parseVersion) vs of+ (v, "") : _ -> Just v+ _ -> Nothing -- Version not (fully)+ -- understood.+ )++-- | Check whether a given package range is satisfiable in the given+-- @pkg-config@ database.+pkgConfigPkgIsPresent :: PkgConfigDb -> PackageName -> VersionRange -> Bool+pkgConfigPkgIsPresent (PkgConfigDb db) pn vr =+ case M.lookup pn db of+ Nothing -> False -- Package not present in the DB.+ Just Nothing -> True -- Package present, but version unknown.+ Just (Just v) -> withinRange v vr+-- If we could not read the pkg-config database successfully we allow+-- the check to succeed. The plan found by the solver may fail to be+-- executed later on, but we have no grounds for rejecting the plan at+-- this stage.+pkgConfigPkgIsPresent NoPkgConfigDb _ _ = True
cabal/cabal-install/Distribution/Client/PlanIndex.hs view
@@ -8,25 +8,20 @@ -- * FakeMap and related operations FakeMap , fakeDepends- , fakeLookupInstalledPackageId+ , fakeLookupUnitId -- * Graph traversal functions , brokenPackages- , dependencyClosure , dependencyCycles , dependencyGraph , dependencyInconsistencies- , reverseDependencyClosure- , reverseTopologicalOrder- , topologicalOrder ) where import Prelude hiding (lookup) import qualified Data.Map as Map-import qualified Data.Tree as Tree import qualified Data.Graph as Graph import Data.Array ((!)) import Data.Map (Map)-import Data.Maybe (isNothing, fromMaybe, fromJust)+import Data.Maybe (isNothing) import Data.Either (rights) #if !MIN_VERSION_base(4,8,0)@@ -34,7 +29,7 @@ #endif import Distribution.Package- ( PackageName(..), PackageIdentifier(..), InstalledPackageId(..)+ ( PackageName(..), PackageIdentifier(..), UnitId(..) , Package(..), packageName, packageVersion ) import Distribution.Version@@ -45,59 +40,54 @@ import Distribution.Client.Types ( PackageFixedDeps(..) ) import Distribution.Simple.PackageIndex- ( PackageIndex, allPackages, insert, lookupInstalledPackageId )+ ( PackageIndex, allPackages, insert, lookupUnitId ) import Distribution.Package- ( HasInstalledPackageId(..), PackageId )+ ( HasUnitId(..), PackageId ) -- Note [FakeMap] -------------------- We'd like to use the PackageIndex defined in this module for--- cabal-install's InstallPlan. However, at the moment, this--- data structure is indexed by InstalledPackageId, which we don't--- know until after we've compiled a package (whereas InstallPlan--- needs to store not-compiled packages in the index.) Eventually,--- an InstalledPackageId will be calculatable prior to actually--- building the package (making it something of a misnomer), but--- at the moment, the "fake installed package ID map" is a workaround--- to solve this problem while reusing PackageIndex. The basic idea--- is that, since we don't know what an InstalledPackageId is--- beforehand, we just fake up one based on the package ID (it only--- needs to be unique for the particular install plan), and fill--- it out with the actual generated InstalledPackageId after the--- package is successfully compiled.+-- We'd like to use the PackageIndex defined in this module for cabal-install's+-- InstallPlan. However, at the moment, this data structure is indexed by+-- UnitId, which we don't know until after we've compiled a package+-- (whereas InstallPlan needs to store not-compiled packages in the index.)+-- Eventually, an UnitId will be calculatable prior to actually building+-- the package, but at the moment, the "fake installed package ID map" is a+-- workaround to solve this problem while reusing PackageIndex. The basic idea+-- is that, since we don't know what an UnitId is beforehand, we just fake+-- up one based on the package ID (it only needs to be unique for the particular+-- install plan), and fill it out with the actual generated UnitId after+-- the package is successfully compiled. ----- However, there is a problem: in the index there may be--- references using the old package ID, which are now dangling if--- we update the InstalledPackageId. We could map over the entire--- index to update these pointers as well (a costly operation), but--- instead, we've chosen to parametrize a variety of important functions--- by a FakeMap, which records what a fake installed package ID was--- actually resolved to post-compilation. If we do a lookup, we first--- check and see if it's a fake ID in the FakeMap.+-- However, there is a problem: in the index there may be references using the+-- old package ID, which are now dangling if we update the UnitId. We+-- could map over the entire index to update these pointers as well (a costly+-- operation), but instead, we've chosen to parametrize a variety of important+-- functions by a FakeMap, which records what a fake installed package ID was+-- actually resolved to post-compilation. If we do a lookup, we first check and+-- see if it's a fake ID in the FakeMap. ----- It's a bit grungy, but we expect this to only be temporary anyway.--- (Another possible workaround would have been to *not* update--- the installed package ID, but I decided this would be hard to--- understand.)+-- It's a bit grungy, but we expect this to only be temporary anyway. (Another+-- possible workaround would have been to *not* update the installed package ID,+-- but I decided this would be hard to understand.) --- | Map from fake installed package IDs to real ones. See Note [FakeMap]-type FakeMap = Map InstalledPackageId InstalledPackageId+-- | Map from fake package keys to real ones. See Note [FakeMap]+type FakeMap = Map UnitId UnitId -- | Variant of `depends` which accepts a `FakeMap` -- -- Analogous to `fakeInstalledDepends`. See Note [FakeMap].-fakeDepends :: PackageFixedDeps pkg => FakeMap -> pkg -> ComponentDeps [InstalledPackageId]+fakeDepends :: PackageFixedDeps pkg => FakeMap -> pkg -> ComponentDeps [UnitId] fakeDepends fakeMap = fmap (map resolveFakeId) . depends where- resolveFakeId :: InstalledPackageId -> InstalledPackageId+ resolveFakeId :: UnitId -> UnitId resolveFakeId ipid = Map.findWithDefault ipid ipid fakeMap ---- | Variant of 'lookupInstalledPackageId' which accepts a 'FakeMap'. See Note+--- | Variant of 'lookupUnitId' which accepts a 'FakeMap'. See Note --- [FakeMap].-fakeLookupInstalledPackageId :: FakeMap -> PackageIndex a -> InstalledPackageId+fakeLookupUnitId :: FakeMap -> PackageIndex a -> UnitId -> Maybe a-fakeLookupInstalledPackageId fakeMap index pkg =- lookupInstalledPackageId index (Map.findWithDefault pkg pkg fakeMap)+fakeLookupUnitId fakeMap index pkg =+ lookupUnitId index (Map.findWithDefault pkg pkg fakeMap) -- | All packages that have dependencies that are not in the index. --@@ -106,13 +96,13 @@ brokenPackages :: (PackageFixedDeps pkg) => FakeMap -> PackageIndex pkg- -> [(pkg, [InstalledPackageId])]+ -> [(pkg, [UnitId])] brokenPackages fakeMap index = [ (pkg, missing) | pkg <- allPackages index , let missing =- [ pkg' | pkg' <- CD.nonSetupDeps (depends pkg)- , isNothing (fakeLookupInstalledPackageId fakeMap index pkg') ]+ [ pkg' | pkg' <- CD.flatDeps (depends pkg)+ , isNothing (fakeLookupUnitId fakeMap index pkg') ] , not (null missing) ] -- | Compute all roots of the install plan, and verify that the transitive@@ -122,7 +112,7 @@ -- may be absent from the subplans even if the larger plan contains a dependency -- cycle. Such cycles may or may not be an issue; either way, we don't check -- for them here.-dependencyInconsistencies :: forall pkg. (PackageFixedDeps pkg, HasInstalledPackageId pkg)+dependencyInconsistencies :: forall pkg. (PackageFixedDeps pkg, HasUnitId pkg) => FakeMap -> Bool -> PackageIndex pkg@@ -141,8 +131,8 @@ -- This is the set of all top-level library roots (taken together normally, or -- as singletons sets if we are considering them as independent goals), along -- with all setup dependencies of all packages.-rootSets :: (PackageFixedDeps pkg, HasInstalledPackageId pkg)- => FakeMap -> Bool -> PackageIndex pkg -> [[InstalledPackageId]]+rootSets :: (PackageFixedDeps pkg, HasUnitId pkg)+ => FakeMap -> Bool -> PackageIndex pkg -> [[UnitId]] rootSets fakeMap indepGoals index = if indepGoals then map (:[]) libRoots else [libRoots] ++ setupRoots index@@ -153,10 +143,10 @@ -- -- The library roots are the set of packages with no reverse dependencies -- (no reverse library dependencies but also no reverse setup dependencies).-libraryRoots :: (PackageFixedDeps pkg, HasInstalledPackageId pkg)- => FakeMap -> PackageIndex pkg -> [InstalledPackageId]+libraryRoots :: (PackageFixedDeps pkg, HasUnitId pkg)+ => FakeMap -> PackageIndex pkg -> [UnitId] libraryRoots fakeMap index =- map (installedPackageId . toPkgId) roots+ map toPkgId roots where (graph, toPkgId, _) = dependencyGraph fakeMap index indegree = Graph.indegree graph@@ -164,7 +154,7 @@ isRoot v = indegree ! v == 0 -- | The setup dependencies of each package in the plan-setupRoots :: PackageFixedDeps pkg => PackageIndex pkg -> [[InstalledPackageId]]+setupRoots :: PackageFixedDeps pkg => PackageIndex pkg -> [[UnitId]] setupRoots = filter (not . null) . map (CD.setupDeps . depends) . allPackages@@ -180,7 +170,7 @@ -- distinct. -- dependencyInconsistencies' :: forall pkg.- (PackageFixedDeps pkg, HasInstalledPackageId pkg)+ (PackageFixedDeps pkg, HasUnitId pkg) => FakeMap -> PackageIndex pkg -> [(PackageName, [(PackageIdentifier, Version)])]@@ -195,7 +185,7 @@ -- and each installed ID of that that package -- the associated package instance -- and a list of reverse dependencies (as source IDs)- inverseIndex :: Map PackageName (Map InstalledPackageId (pkg, [PackageId]))+ inverseIndex :: Map PackageName (Map UnitId (pkg, [PackageId])) inverseIndex = Map.fromListWith (Map.unionWith (\(a,b) (_,b') -> (a,b++b'))) [ (packageName dep, Map.fromList [(ipid,(dep,[packageId pkg]))]) | -- For each package @pkg@@@ -203,7 +193,7 @@ -- Find out which @ipid@ @pkg@ depends on , ipid <- CD.nonSetupDeps (fakeDepends fakeMap pkg) -- And look up those @ipid@ (i.e., @ipid@ is the ID of @dep@)- , Just dep <- [fakeLookupInstalledPackageId fakeMap index ipid]+ , Just dep <- [fakeLookupUnitId fakeMap index ipid] ] -- If, in a single install plan, we depend on more than one version of a@@ -215,15 +205,14 @@ reallyIsInconsistent [] = False reallyIsInconsistent [_p] = False reallyIsInconsistent [p1, p2] =- let pid1 = installedPackageId p1- pid2 = installedPackageId p2+ let pid1 = installedUnitId p1+ pid2 = installedUnitId p2 in Map.findWithDefault pid1 pid1 fakeMap `notElem` CD.nonSetupDeps (fakeDepends fakeMap p2) && Map.findWithDefault pid2 pid2 fakeMap `notElem` CD.nonSetupDeps (fakeDepends fakeMap p1) reallyIsInconsistent _ = True - -- | Find if there are any cycles in the dependency graph. If there are no -- cycles the result is @[]@. --@@ -231,14 +220,15 @@ -- list of groups of packages where within each group they all depend on each -- other, directly or indirectly. ---dependencyCycles :: (PackageFixedDeps pkg, HasInstalledPackageId pkg)+dependencyCycles :: (PackageFixedDeps pkg, HasUnitId pkg) => FakeMap -> PackageIndex pkg -> [[pkg]] dependencyCycles fakeMap index = [ vs | Graph.CyclicSCC vs <- Graph.stronglyConnComp adjacencyList ] where- adjacencyList = [ (pkg, installedPackageId pkg, CD.nonSetupDeps (fakeDepends fakeMap pkg))+ adjacencyList = [ (pkg, installedUnitId pkg,+ CD.flatDeps (fakeDepends fakeMap pkg)) | pkg <- allPackages index ] @@ -249,11 +239,11 @@ -- -- * Note that if the result is @Right []@ it is because at least one of -- the original given 'PackageIdentifier's do not occur in the index.-dependencyClosure :: (PackageFixedDeps pkg, HasInstalledPackageId pkg)+dependencyClosure :: (PackageFixedDeps pkg, HasUnitId pkg) => FakeMap -> PackageIndex pkg- -> [InstalledPackageId]- -> Either [(pkg, [InstalledPackageId])]+ -> [UnitId]+ -> Either [(pkg, [UnitId])] (PackageIndex pkg) dependencyClosure fakeMap index pkgids0 = case closure mempty [] pkgids0 of (completed, []) -> Right completed@@ -261,79 +251,39 @@ where closure completed failed [] = (completed, failed) closure completed failed (pkgid:pkgids) =- case fakeLookupInstalledPackageId fakeMap index pkgid of+ case fakeLookupUnitId fakeMap index pkgid of Nothing -> closure completed (pkgid:failed) pkgids Just pkg ->- case fakeLookupInstalledPackageId fakeMap completed- (installedPackageId pkg) of+ case fakeLookupUnitId fakeMap completed+ (installedUnitId pkg) of Just _ -> closure completed failed pkgids Nothing -> closure completed' failed pkgids' where completed' = insert pkg completed pkgids' = CD.nonSetupDeps (depends pkg) ++ pkgids -topologicalOrder :: (PackageFixedDeps pkg, HasInstalledPackageId pkg)- => FakeMap -> PackageIndex pkg -> [pkg]-topologicalOrder fakeMap index = map toPkgId- . Graph.topSort- $ graph- where (graph, toPkgId, _) = dependencyGraph fakeMap index---reverseTopologicalOrder :: (PackageFixedDeps pkg, HasInstalledPackageId pkg)- => FakeMap -> PackageIndex pkg -> [pkg]-reverseTopologicalOrder fakeMap index = map toPkgId- . Graph.topSort- . Graph.transposeG- $ graph- where (graph, toPkgId, _) = dependencyGraph fakeMap index----- | Takes the transitive closure of the packages reverse dependencies.------ * The given 'PackageIdentifier's must be in the index.----reverseDependencyClosure :: (PackageFixedDeps pkg, HasInstalledPackageId pkg)- => FakeMap- -> PackageIndex pkg- -> [InstalledPackageId]- -> [pkg]-reverseDependencyClosure fakeMap index =- map vertexToPkg- . concatMap Tree.flatten- . Graph.dfs reverseDepGraph- . map (fromMaybe noSuchPkgId . pkgIdToVertex)-- where- (depGraph, vertexToPkg, pkgIdToVertex) = dependencyGraph fakeMap index- reverseDepGraph = Graph.transposeG depGraph- noSuchPkgId = error "reverseDependencyClosure: package is not in the graph"--- -- | Builds a graph of the package dependencies. -- -- Dependencies on other packages that are not in the index are discarded. -- You can check if there are any such dependencies with 'brokenPackages'. ---dependencyGraph :: (PackageFixedDeps pkg, HasInstalledPackageId pkg)+dependencyGraph :: (PackageFixedDeps pkg, HasUnitId pkg) => FakeMap -> PackageIndex pkg -> (Graph.Graph,- Graph.Vertex -> pkg,- InstalledPackageId -> Maybe Graph.Vertex)+ Graph.Vertex -> UnitId,+ UnitId -> Maybe Graph.Vertex) dependencyGraph fakeMap index = (graph, vertexToPkg, idToVertex) where (graph, vertexToPkg', idToVertex) = Graph.graphFromEdges edges- vertexToPkg = fromJust- . (\((), key, _targets) -> lookupInstalledPackageId index key)- . vertexToPkg'+ vertexToPkg v = case vertexToPkg' v of+ ((), pkgid, _targets) -> pkgid pkgs = allPackages index edges = map edgesFrom pkgs resolve pid = Map.findWithDefault pid pid fakeMap edgesFrom pkg = ( ()- , resolve (installedPackageId pkg)- , CD.nonSetupDeps (fakeDepends fakeMap pkg)+ , resolve (installedUnitId pkg)+ , CD.flatDeps (fakeDepends fakeMap pkg) )
+ cabal/cabal-install/Distribution/Client/ProjectBuilding.hs view
@@ -0,0 +1,1284 @@+{-# LANGUAGE CPP, BangPatterns, RecordWildCards, NamedFieldPuns,+ DeriveGeneric, DeriveDataTypeable, GeneralizedNewtypeDeriving,+ ScopedTypeVariables #-}++-- | +--+module Distribution.Client.ProjectBuilding (+ BuildStatus(..),+ BuildStatusMap,+ BuildStatusRebuild(..),+ BuildReason(..),+ MonitorChangedReason(..),+ rebuildTargetsDryRun,+ rebuildTargets+ ) where++import Distribution.Client.PackageHash (renderPackageHashInputs)+import Distribution.Client.RebuildMonad+import Distribution.Client.ProjectConfig+import Distribution.Client.ProjectPlanning++import Distribution.Client.Types+ ( PackageLocation(..), GenericReadyPackage(..)+ , PackageFixedDeps(..)+ , InstalledPackageId, installedPackageId )+import Distribution.Client.InstallPlan+ ( GenericInstallPlan, GenericPlanPackage )+import qualified Distribution.Client.InstallPlan as InstallPlan+import qualified Distribution.Client.ComponentDeps as CD+import Distribution.Client.ComponentDeps (ComponentDeps)+import Distribution.Client.DistDirLayout+import Distribution.Client.FileMonitor+import Distribution.Client.SetupWrapper+import Distribution.Client.JobControl+import Distribution.Client.FetchUtils+import Distribution.Client.GlobalFlags (RepoContext)+import qualified Distribution.Client.Tar as Tar+import Distribution.Client.Setup (filterConfigureFlags)+import Distribution.Client.Utils (removeExistingFile)++import Distribution.Package hiding (InstalledPackageId, installedPackageId)+import Distribution.InstalledPackageInfo (InstalledPackageInfo)+import qualified Distribution.InstalledPackageInfo as Installed+import Distribution.Simple.Program+import qualified Distribution.Simple.Setup as Cabal+import Distribution.Simple.Command (CommandUI)+import qualified Distribution.Simple.Register as Cabal+import qualified Distribution.Simple.InstallDirs as InstallDirs+import Distribution.Simple.LocalBuildInfo (ComponentName)+import qualified Distribution.Simple.Program.HcPkg as HcPkg++import Distribution.Simple.Utils hiding (matchFileGlob)+import Distribution.Version+import Distribution.Verbosity+import Distribution.Text+import Distribution.ParseUtils ( showPWarning )++import Data.Map (Map)+import qualified Data.Map as Map+import Data.Set (Set)+import qualified Data.Set as Set+import qualified Data.ByteString.Lazy as LBS++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative+#endif+import Control.Monad+import Control.Exception+import Control.Concurrent.Async+import Control.Concurrent.MVar+import Data.List+import Data.Maybe++import System.FilePath+import System.IO+import System.Directory+import System.Exit (ExitCode)+++------------------------------------------------------------------------------+-- * Overall building strategy.+------------------------------------------------------------------------------+--+-- We start with an 'ElaboratedInstallPlan' that has already been improved by+-- reusing packages from the store. So the remaining packages in the+-- 'InstallPlan.Configured' state are ones we either need to build or rebuild.+--+-- First, we do a preliminary dry run phase where we work out which packages+-- we really need to (re)build, and for the ones we do need to build which+-- build phase to start at.+++------------------------------------------------------------------------------+-- * Dry run: what bits of the 'ElaboratedInstallPlan' will we execute?+------------------------------------------------------------------------------++-- We split things like this for a couple reasons. Firstly we need to be able+-- to do dry runs, and these need to be reasonably accurate in terms of+-- letting users know what (and why) things are going to be (re)built.+--+-- Given that we need to be able to do dry runs, it would not be great if+-- we had to repeat all the same work when we do it for real. Not only is+-- it duplicate work, but it's duplicate code which is likely to get out of+-- sync. So we do things only once. We preserve info we discover in the dry+-- run phase and rely on it later when we build things for real. This also+-- somewhat simplifies the build phase. So this way the dry run can't so+-- easily drift out of sync with the real thing since we're relying on the+-- info it produces.+--+-- An additional advantage is that it makes it easier to debug rebuild+-- errors (ie rebuilding too much or too little), since all the rebuild+-- decisions are made without making any state changes at the same time+-- (that would make it harder to reproduce the problem sitation).+++-- | The 'BuildStatus' of every package in the 'ElaboratedInstallPlan'+--+type BuildStatusMap = Map InstalledPackageId BuildStatus++-- | The build status for an individual package. That is, the state that the+-- package is in prior to initiating a (re)build.+--+-- It serves two purposes:+--+-- * For dry-run output, it lets us explain to the user if and why a package+-- is going to be (re)built.+--+-- * It tell us what step to start or resume building from, and carries+-- enough information for us to be able to do so.+--+data BuildStatus =++ -- | The package is in the 'InstallPlan.PreExisting' state, so does not+ -- need building.+ BuildStatusPreExisting++ -- | The package has not been downloaded yet, so it will have to be+ -- downloaded, unpacked and built.+ | BuildStatusDownload++ -- | The package has not been unpacked yet, so it will have to be+ -- unpacked and built.+ | BuildStatusUnpack FilePath++ -- | The package exists in a local dir already, and just needs building+ -- or rebuilding. So this can only happen for 'BuildInplaceOnly' style+ -- packages.+ | BuildStatusRebuild FilePath BuildStatusRebuild++ -- | The package exists in a local dir already, and is fully up to date.+ -- So this package can be put into the 'InstallPlan.Installed' state+ -- and it does not need to be built.+ | BuildStatusUpToDate (Maybe InstalledPackageInfo) BuildSuccess++-- | For a package that is going to be built or rebuilt, the state it's in now.+--+-- So again, this tells us why a package needs to be rebuilt and what build+-- phases need to be run. The 'MonitorChangedReason' gives us details like+-- which file changed, which is mainly for high verbosity debug output.+--+data BuildStatusRebuild =++ -- | The package configuration changed, so the configure and build phases+ -- needs to be (re)run.+ BuildStatusConfigure (MonitorChangedReason ())++ -- | The configuration has not changed but the build phase needs to be+ -- rerun. We record the reason the (re)build is needed.+ --+ -- The optional registration info here tells us if we've registered the+ -- package already, or if we stil need to do that after building.+ --+ | BuildStatusBuild (Maybe (Maybe InstalledPackageInfo)) BuildReason++data BuildReason =+ -- | The depencencies of this package have been (re)built so the build+ -- phase needs to be rerun.+ --+ -- The optional registration info here tells us if we've registered the+ -- package already, or if we stil need to do that after building.+ --+ BuildReasonDepsRebuilt++ -- | Changes in files within the package (or first run or corrupt cache) + | BuildReasonFilesChanged (MonitorChangedReason ())++ -- | An important special case is that no files have changed but the+ -- set of components the /user asked to build/ has changed. We track the+ -- set of components /we have built/, which of course only grows (until+ -- some other change resets it).+ --+ -- The @Set 'ComponentName'@ is the set of components we have built+ -- previously. When we update the monitor we take the union of the ones+ -- we have built previously with the ones the user has asked for this+ -- time and save those. See 'updatePackageBuildFileMonitor'.+ --+ | BuildReasonExtraTargets (Set ComponentName)++ -- | Although we're not going to build any additional targets as a whole,+ -- we're going to build some part of a component or run a repl or any+ -- other action that does not result in additional persistent artifacts.+ -- + | BuildReasonEphemeralTargets++-- | Which 'BuildStatus' values indicate we'll have to do some build work of+-- some sort. In particular we use this as part of checking if any of a+-- package's deps have changed.+--+buildStatusRequiresBuild :: BuildStatus -> Bool+buildStatusRequiresBuild BuildStatusPreExisting = False+buildStatusRequiresBuild BuildStatusUpToDate {} = False+buildStatusRequiresBuild _ = True++-- | Do the dry run pass. This is a prerequisite of 'rebuildTargets'.+--+-- It gives us the 'BuildStatusMap' and also gives us an improved version of+-- the 'ElaboratedInstallPlan' with packages switched to the+-- 'InstallPlan.Installed' state when we find that they're already up to date.+--+rebuildTargetsDryRun :: DistDirLayout+ -> ElaboratedInstallPlan+ -> IO (ElaboratedInstallPlan, BuildStatusMap)+rebuildTargetsDryRun distDirLayout@DistDirLayout{..} = \installPlan -> do++ -- Do the various checks to work out the 'BuildStatus' of each package+ pkgsBuildStatus <- foldMInstallPlanDepOrder installPlan dryRunPkg++ -- For 'BuildStatusUpToDate' packages, improve the plan by marking them as+ -- 'InstallPlan.Installed'.+ let installPlan' = improveInstallPlanWithUpToDatePackages+ installPlan pkgsBuildStatus++ return (installPlan', pkgsBuildStatus)+ where+ dryRunPkg :: ElaboratedPlanPackage+ -> ComponentDeps [BuildStatus]+ -> IO BuildStatus+ dryRunPkg (InstallPlan.PreExisting _pkg) _depsBuildStatus =+ return BuildStatusPreExisting++ dryRunPkg (InstallPlan.Configured pkg) depsBuildStatus = do+ mloc <- checkFetched (pkgSourceLocation pkg)+ case mloc of+ Nothing -> return BuildStatusDownload++ Just (LocalUnpackedPackage srcdir) ->+ -- For the case of a user-managed local dir, irrespective of the+ -- build style, we build from that directory and put build+ -- artifacts under the shared dist directory.+ dryRunLocalPkg pkg depsBuildStatus srcdir++ -- The three tarball cases are handled the same as each other,+ -- though depending on the build style.+ Just (LocalTarballPackage tarball) ->+ dryRunTarballPkg pkg depsBuildStatus tarball++ Just (RemoteTarballPackage _ tarball) ->+ dryRunTarballPkg pkg depsBuildStatus tarball++ Just (RepoTarballPackage _ _ tarball) ->+ dryRunTarballPkg pkg depsBuildStatus tarball++ dryRunPkg (InstallPlan.Processing {}) _ = unexpectedState+ dryRunPkg (InstallPlan.Installed {}) _ = unexpectedState+ dryRunPkg (InstallPlan.Failed {}) _ = unexpectedState++ unexpectedState = error "rebuildTargetsDryRun: unexpected package state"++ dryRunTarballPkg :: ElaboratedConfiguredPackage+ -> ComponentDeps [BuildStatus]+ -> FilePath+ -> IO BuildStatus+ dryRunTarballPkg pkg depsBuildStatus tarball =+ case pkgBuildStyle pkg of+ BuildAndInstall -> return (BuildStatusUnpack tarball)+ BuildInplaceOnly -> do+ -- TODO: [nice to have] use a proper file monitor rather than this dir exists test+ exists <- doesDirectoryExist srcdir+ if exists+ then dryRunLocalPkg pkg depsBuildStatus srcdir+ else return (BuildStatusUnpack tarball)+ where+ srcdir = distUnpackedSrcDirectory (packageId pkg)++ dryRunLocalPkg :: ElaboratedConfiguredPackage+ -> ComponentDeps [BuildStatus]+ -> FilePath+ -> IO BuildStatus+ dryRunLocalPkg pkg depsBuildStatus srcdir = do+ -- Go and do lots of I/O, reading caches and probing files to work out+ -- if anything has changed+ change <- checkPackageFileMonitorChanged+ packageFileMonitor pkg srcdir depsBuildStatus+ case change of+ -- It did change, giving us 'BuildStatusRebuild' info on why+ Left rebuild ->+ return (BuildStatusRebuild srcdir rebuild)++ -- No changes, the package is up to date. Use the saved build results.+ Right (mipkg, buildSuccess) ->+ return (BuildStatusUpToDate mipkg buildSuccess)+ where+ packageFileMonitor =+ newPackageFileMonitor distDirLayout (packageId pkg)+++-- | A specialised traversal over the packages in an install plan.+--+-- The packages are visited in dependency order, starting with packages with no+-- depencencies. The result for each package is accumulated into a 'Map' and+-- returned as the final result. In addition, when visting a package, the+-- visiting function is passed the results for all the immediate package+-- depencencies. This can be used to propagate information from depencencies.+--+foldMInstallPlanDepOrder+ :: forall m ipkg srcpkg iresult ifailure b.+ (Monad m,+ HasUnitId ipkg, PackageFixedDeps ipkg,+ HasUnitId srcpkg, PackageFixedDeps srcpkg)+ => GenericInstallPlan ipkg srcpkg iresult ifailure+ -> (GenericPlanPackage ipkg srcpkg iresult ifailure ->+ ComponentDeps [b] -> m b)+ -> m (Map InstalledPackageId b)+foldMInstallPlanDepOrder plan0 visit =+ go Map.empty (InstallPlan.reverseTopologicalOrder plan0)+ where+ go :: Map InstalledPackageId b+ -> [GenericPlanPackage ipkg srcpkg iresult ifailure]+ -> m (Map InstalledPackageId b)+ go !results [] = return results++ go !results (pkg : pkgs) = do+ -- we go in the right order so the results map has entries for all deps+ let depresults :: ComponentDeps [b]+ depresults =+ fmap (map (\ipkgid -> let Just result = Map.lookup ipkgid results+ in result))+ (depends pkg)+ result <- visit pkg depresults+ let results' = Map.insert (installedPackageId pkg) result results+ go results' pkgs++improveInstallPlanWithUpToDatePackages :: ElaboratedInstallPlan+ -> BuildStatusMap+ -> ElaboratedInstallPlan+improveInstallPlanWithUpToDatePackages installPlan pkgsBuildStatus =+ replaceWithPreInstalled installPlan+ [ (installedPackageId pkg, mipkg, buildSuccess)+ | InstallPlan.Configured pkg+ <- InstallPlan.reverseTopologicalOrder installPlan+ , let ipkgid = installedPackageId pkg+ Just pkgBuildStatus = Map.lookup ipkgid pkgsBuildStatus+ , BuildStatusUpToDate mipkg buildSuccess <- [pkgBuildStatus]+ ]+ where+ replaceWithPreInstalled =+ foldl' (\plan (ipkgid, mipkg, buildSuccess) ->+ InstallPlan.preinstalled ipkgid mipkg buildSuccess plan)+++-----------------------------+-- Package change detection+--++-- | As part of the dry run for local unpacked packages we have to check if the+-- package config or files have changed. That is the purpose of+-- 'PackageFileMonitor' and 'checkPackageFileMonitorChanged'.+--+-- When a package is (re)built, the monitor must be updated to reflect the new+-- state of the package. Because we sometimes build without reconfiguring the+-- state updates are split into two, one for package config changes and one+-- for other changes. This is the purpose of 'updatePackageConfigFileMonitor'+-- and 'updatePackageBuildFileMonitor'.+--+data PackageFileMonitor = PackageFileMonitor {+ pkgFileMonitorConfig :: FileMonitor ElaboratedConfiguredPackage (),+ pkgFileMonitorBuild :: FileMonitor (Set ComponentName) BuildSuccess,+ pkgFileMonitorReg :: FileMonitor () (Maybe InstalledPackageInfo)+ }++newPackageFileMonitor :: DistDirLayout -> PackageId -> PackageFileMonitor+newPackageFileMonitor DistDirLayout{distPackageCacheFile} pkgid =+ PackageFileMonitor {+ pkgFileMonitorConfig =+ newFileMonitor (distPackageCacheFile pkgid "config"),++ pkgFileMonitorBuild =+ FileMonitor {+ fileMonitorCacheFile = distPackageCacheFile pkgid "build",+ fileMonitorKeyValid = \componentsToBuild componentsAlreadyBuilt ->+ componentsToBuild `Set.isSubsetOf` componentsAlreadyBuilt,+ fileMonitorCheckIfOnlyValueChanged = True+ },++ pkgFileMonitorReg =+ newFileMonitor (distPackageCacheFile pkgid "registration")+ }++-- | Helper function for 'checkPackageFileMonitorChanged',+-- 'updatePackageConfigFileMonitor' and 'updatePackageBuildFileMonitor'.+--+-- It selects the info from a 'ElaboratedConfiguredPackage' that are used by+-- the 'FileMonitor's (in the 'PackageFileMonitor') to detect value changes.+--+packageFileMonitorKeyValues :: ElaboratedConfiguredPackage+ -> (ElaboratedConfiguredPackage, Set ComponentName)+packageFileMonitorKeyValues pkg =+ (pkgconfig, buildComponents)+ where+ -- The first part is the value used to guard (re)configuring the package.+ -- That is, if this value changes then we will reconfigure.+ -- The ElaboratedConfiguredPackage consists mostly (but not entirely) of+ -- information that affects the (re)configure step. But those parts that+ -- do not affect the configure step need to be nulled out. Those parts are+ -- the specific targets that we're going to build.+ --+ pkgconfig = pkg {+ pkgBuildTargets = [],+ pkgReplTarget = Nothing,+ pkgBuildHaddocks = False+ }++ -- The second part is the value used to guard the build step. So this is+ -- more or less the opposite of the first part, as it's just the info about+ -- what targets we're going to build.+ --+ buildComponents = pkgBuildTargetWholeComponents pkg++-- | Do all the checks on whether a package has changed and thus needs either+-- rebuilding or reconfiguring and rebuilding.+--+checkPackageFileMonitorChanged :: PackageFileMonitor+ -> ElaboratedConfiguredPackage+ -> FilePath+ -> ComponentDeps [BuildStatus]+ -> IO (Either BuildStatusRebuild+ (Maybe InstalledPackageInfo,+ BuildSuccess))+checkPackageFileMonitorChanged PackageFileMonitor{..}+ pkg srcdir depsBuildStatus = do+ --TODO: [nice to have] some debug-level message about file changes, like rerunIfChanged+ configChanged <- checkFileMonitorChanged+ pkgFileMonitorConfig srcdir pkgconfig+ case configChanged of+ MonitorChanged monitorReason ->+ return (Left (BuildStatusConfigure monitorReason'))+ where+ monitorReason' = fmap (const ()) monitorReason++ MonitorUnchanged () _+ -- The configChanged here includes the identity of the dependencies,+ -- so depsBuildStatus is just needed for the changes in the content+ -- of depencencies.+ | any buildStatusRequiresBuild (CD.flatDeps depsBuildStatus) -> do+ regChanged <- checkFileMonitorChanged pkgFileMonitorReg srcdir ()+ let mreg = changedToMaybe regChanged+ return (Left (BuildStatusBuild mreg BuildReasonDepsRebuilt))++ | otherwise -> do+ buildChanged <- checkFileMonitorChanged+ pkgFileMonitorBuild srcdir buildComponents+ regChanged <- checkFileMonitorChanged+ pkgFileMonitorReg srcdir ()+ let mreg = changedToMaybe regChanged+ case (buildChanged, regChanged) of+ (MonitorChanged (MonitoredValueChanged prevBuildComponents), _) ->+ return (Left (BuildStatusBuild mreg buildReason))+ where+ buildReason = BuildReasonExtraTargets prevBuildComponents++ (MonitorChanged monitorReason, _) ->+ return (Left (BuildStatusBuild mreg buildReason))+ where+ buildReason = BuildReasonFilesChanged monitorReason'+ monitorReason' = fmap (const ()) monitorReason++ (MonitorUnchanged _ _, MonitorChanged monitorReason) ->+ -- this should only happen if the file is corrupt or been+ -- manually deleted. We don't want to bother with another+ -- phase just for this, so we'll reregister by doing a build.+ return (Left (BuildStatusBuild Nothing buildReason))+ where+ buildReason = BuildReasonFilesChanged monitorReason'+ monitorReason' = fmap (const ()) monitorReason++ (MonitorUnchanged _ _, MonitorUnchanged _ _)+ | pkgHasEphemeralBuildTargets pkg ->+ return (Left (BuildStatusBuild mreg buildReason))+ where+ buildReason = BuildReasonEphemeralTargets++ (MonitorUnchanged buildSuccess _, MonitorUnchanged mipkg _) ->+ return (Right (mipkg, buildSuccess))+ where+ (pkgconfig, buildComponents) = packageFileMonitorKeyValues pkg+ changedToMaybe (MonitorChanged _) = Nothing+ changedToMaybe (MonitorUnchanged x _) = Just x+++updatePackageConfigFileMonitor :: PackageFileMonitor+ -> FilePath+ -> ElaboratedConfiguredPackage+ -> IO ()+updatePackageConfigFileMonitor PackageFileMonitor{pkgFileMonitorConfig}+ srcdir pkg =+ updateFileMonitor pkgFileMonitorConfig srcdir Nothing+ [] pkgconfig ()+ where+ (pkgconfig, _buildComponents) = packageFileMonitorKeyValues pkg++updatePackageBuildFileMonitor :: PackageFileMonitor+ -> FilePath+ -> MonitorTimestamp+ -> ElaboratedConfiguredPackage+ -> BuildStatusRebuild+ -> [FilePath]+ -> BuildSuccess+ -> IO ()+updatePackageBuildFileMonitor PackageFileMonitor{pkgFileMonitorBuild}+ srcdir timestamp pkg pkgBuildStatus+ allSrcFiles buildSuccess =+ updateFileMonitor pkgFileMonitorBuild srcdir (Just timestamp)+ (map monitorFileHashed allSrcFiles)+ buildComponents' buildSuccess+ where+ (_pkgconfig, buildComponents) = packageFileMonitorKeyValues pkg++ -- If the only thing that's changed is that we're now building extra+ -- components, then we can avoid later unnecessary rebuilds by saving the+ -- total set of components that have been built, namely the union of the+ -- existing ones plus the new ones. If files also changed this would be+ -- the wrong thing to do. Note that we rely on the+ -- fileMonitorCheckIfOnlyValueChanged = True mode to get this guarantee+ -- that it's /only/ the value that changed not any files that changed.+ buildComponents' =+ case pkgBuildStatus of+ BuildStatusBuild _ (BuildReasonExtraTargets prevBuildComponents)+ -> buildComponents `Set.union` prevBuildComponents+ _ -> buildComponents++updatePackageRegFileMonitor :: PackageFileMonitor+ -> FilePath+ -> Maybe InstalledPackageInfo+ -> IO ()+updatePackageRegFileMonitor PackageFileMonitor{pkgFileMonitorReg}+ srcdir mipkg =+ updateFileMonitor pkgFileMonitorReg srcdir Nothing+ [] () mipkg++invalidatePackageRegFileMonitor :: PackageFileMonitor -> IO ()+invalidatePackageRegFileMonitor PackageFileMonitor{pkgFileMonitorReg} =+ removeExistingFile (fileMonitorCacheFile pkgFileMonitorReg)+++------------------------------------------------------------------------------+-- * Doing it: executing an 'ElaboratedInstallPlan'+------------------------------------------------------------------------------+++-- | Build things for real.+--+-- It requires the 'BuildStatusMap' gatthered by 'rebuildTargetsDryRun'.+--+rebuildTargets :: Verbosity+ -> DistDirLayout+ -> ElaboratedInstallPlan+ -> ElaboratedSharedConfig+ -> BuildStatusMap+ -> BuildTimeSettings+ -> IO ElaboratedInstallPlan+rebuildTargets verbosity+ distDirLayout@DistDirLayout{..}+ installPlan+ sharedPackageConfig+ pkgsBuildStatus+ buildSettings@BuildTimeSettings{buildSettingNumJobs} = do++ -- Concurrency control: create the job controller and concurrency limits+ -- for downloading, building and installing.+ jobControl <- if isParallelBuild then newParallelJobControl+ else newSerialJobControl+ buildLimit <- newJobLimit buildSettingNumJobs+ installLock <- newLock -- serialise installation+ cacheLock <- newLock -- serialise access to setup exe cache+ --TODO: [code cleanup] eliminate setup exe cache++ createDirectoryIfMissingVerbose verbosity False distBuildRootDirectory+ createDirectoryIfMissingVerbose verbosity False distTempDirectory++ -- Before traversing the install plan, pre-emptively find all packages that+ -- will need to be downloaded and start downloading them.+ asyncDownloadPackages verbosity withRepoCtx+ installPlan pkgsBuildStatus $ \downloadMap ->++ -- For each package in the plan, in dependency order, but in parallel...+ executeInstallPlan verbosity jobControl installPlan $ \pkg ->+ handle (return . BuildFailure) $ --TODO: review exception handling++ let ipkgid = installedPackageId pkg+ Just pkgBuildStatus = Map.lookup ipkgid pkgsBuildStatus in++ rebuildTarget+ verbosity+ distDirLayout+ buildSettings downloadMap+ buildLimit installLock cacheLock + sharedPackageConfig+ pkg+ pkgBuildStatus+ where+ isParallelBuild = buildSettingNumJobs >= 2+ withRepoCtx = projectConfigWithBuilderRepoContext verbosity + buildSettings++-- | Given all the context and resources, (re)build an individual package.+--+rebuildTarget :: Verbosity+ -> DistDirLayout+ -> BuildTimeSettings+ -> AsyncDownloadMap+ -> JobLimit -> Lock -> Lock+ -> ElaboratedSharedConfig+ -> ElaboratedReadyPackage+ -> BuildStatus+ -> IO BuildResult+rebuildTarget verbosity+ distDirLayout@DistDirLayout{distBuildDirectory}+ buildSettings downloadMap+ buildLimit installLock cacheLock+ sharedPackageConfig+ rpkg@(ReadyPackage pkg)+ pkgBuildStatus =++ -- We rely on the 'BuildStatus' to decide which phase to start from:+ case pkgBuildStatus of+ BuildStatusDownload -> downloadPhase+ BuildStatusUnpack tarball -> unpackTarballPhase tarball+ BuildStatusRebuild srcdir status -> rebuildPhase status srcdir++ -- TODO: perhaps re-nest the types to make these impossible+ BuildStatusPreExisting {} -> unexpectedState+ BuildStatusUpToDate {} -> unexpectedState+ where+ unexpectedState = error "rebuildTarget: unexpected package status"++ downloadPhase = do+ downsrcloc <- waitAsyncPackageDownload verbosity downloadMap pkg+ case downsrcloc of+ DownloadedTarball tarball -> unpackTarballPhase tarball+ --TODO: [nice to have] git/darcs repos etc+++ unpackTarballPhase tarball =+ withJobLimit buildLimit $+ withTarballLocalDirectory+ verbosity distDirLayout tarball+ (packageId pkg) (pkgBuildStyle pkg)+ (pkgDescriptionOverride pkg) $++ case pkgBuildStyle pkg of+ BuildAndInstall -> buildAndInstall+ BuildInplaceOnly -> buildInplace buildStatus+ where+ buildStatus = BuildStatusConfigure MonitorFirstRun++ -- Note that this really is rebuild, not build. It can only happen for+ -- 'BuildInplaceOnly' style packages. 'BuildAndInstall' style packages+ -- would only start from download or unpack phases.+ --+ rebuildPhase buildStatus srcdir =+ assert (pkgBuildStyle pkg == BuildInplaceOnly) $++ withJobLimit buildLimit $+ buildInplace buildStatus srcdir builddir+ where+ builddir = distBuildDirectory (packageId pkg)++ buildAndInstall srcdir builddir =+ buildAndInstallUnpackedPackage+ verbosity distDirLayout+ buildSettings installLock cacheLock+ sharedPackageConfig+ rpkg+ srcdir builddir'+ where+ builddir' = makeRelative srcdir builddir+ --TODO: [nice to have] ^^ do this relative stuff better++ buildInplace buildStatus srcdir builddir =+ --TODO: [nice to have] use a relative build dir rather than absolute+ buildInplaceUnpackedPackage+ verbosity distDirLayout+ buildSettings cacheLock+ sharedPackageConfig+ rpkg+ buildStatus+ srcdir builddir++--TODO: [nice to have] do we need to use a with-style for the temp files for downloading http+-- packages, or are we going to cache them persistently?++type AsyncDownloadMap = Map (PackageLocation (Maybe FilePath))+ (MVar DownloadedSourceLocation)++data DownloadedSourceLocation = DownloadedTarball FilePath+ --TODO: [nice to have] git/darcs repos etc++downloadedSourceLocation :: PackageLocation FilePath+ -> Maybe DownloadedSourceLocation+downloadedSourceLocation pkgloc =+ case pkgloc of+ RemoteTarballPackage _ tarball -> Just (DownloadedTarball tarball)+ RepoTarballPackage _ _ tarball -> Just (DownloadedTarball tarball)+ _ -> Nothing++-- | Given the current 'InstallPlan' and 'BuildStatusMap', select all the+-- packages we have to download and fork off an async action to download them.+-- We download them in dependency order so that the one's we'll need+-- first are the ones we will start downloading first.+--+-- The body action is passed a map from those packages (identified by their+-- location) to a completion var for that package. So the body action should+-- lookup the location and use 'waitAsyncPackageDownload' to get the result.+--+asyncDownloadPackages :: Verbosity+ -> ((RepoContext -> IO ()) -> IO ())+ -> ElaboratedInstallPlan+ -> BuildStatusMap+ -> (AsyncDownloadMap -> IO a)+ -> IO a+asyncDownloadPackages verbosity withRepoCtx installPlan pkgsBuildStatus body+ | null pkgsToDownload = body Map.empty+ | otherwise = do+ --TODO: [research required] use parallel downloads? if so, use the fetchLimit++ asyncDownloadVars <- mapM (\loc -> (,) loc <$> newEmptyMVar) pkgsToDownload++ let downloadAction :: IO ()+ downloadAction =+ withRepoCtx $ \repoctx ->+ forM_ asyncDownloadVars $ \(pkgloc, var) -> do+ Just scrloc <- downloadedSourceLocation <$>+ fetchPackage verbosity repoctx pkgloc+ putMVar var scrloc++ withAsync downloadAction $ \_ ->+ body (Map.fromList asyncDownloadVars)+ where+ pkgsToDownload = + [ pkgSourceLocation pkg+ | InstallPlan.Configured pkg+ <- InstallPlan.reverseTopologicalOrder installPlan+ , let ipkgid = installedPackageId pkg+ Just pkgBuildStatus = Map.lookup ipkgid pkgsBuildStatus+ , BuildStatusDownload <- [pkgBuildStatus]+ ]+++-- | Check if a package needs downloading, and if so expect to find a download+-- in progress in the given 'AsyncDownloadMap' and wait on it to finish.+--+waitAsyncPackageDownload :: Verbosity+ -> AsyncDownloadMap+ -> ElaboratedConfiguredPackage+ -> IO DownloadedSourceLocation+waitAsyncPackageDownload verbosity downloadMap pkg =+ case Map.lookup (pkgSourceLocation pkg) downloadMap of+ Just hnd -> do+ debug verbosity $+ "Waiting for download of " ++ display (packageId pkg) ++ " to finish"+ --TODO: [required eventually] do the exception handling on download stuff+ takeMVar hnd+ Nothing ->+ fail "waitAsyncPackageDownload: package not being download"+++executeInstallPlan+ :: forall ipkg srcpkg iresult.+ (HasUnitId ipkg, PackageFixedDeps ipkg,+ HasUnitId srcpkg, PackageFixedDeps srcpkg)+ => Verbosity+ -> JobControl IO ( GenericReadyPackage srcpkg+ , GenericBuildResult ipkg iresult BuildFailure )+ -> GenericInstallPlan ipkg srcpkg iresult BuildFailure+ -> ( GenericReadyPackage srcpkg+ -> IO (GenericBuildResult ipkg iresult BuildFailure))+ -> IO (GenericInstallPlan ipkg srcpkg iresult BuildFailure)+executeInstallPlan verbosity jobCtl plan0 installPkg =+ tryNewTasks 0 plan0+ where+ tryNewTasks taskCount plan = do+ case InstallPlan.ready plan of+ [] | taskCount == 0 -> return plan+ | otherwise -> waitForTasks taskCount plan+ pkgs -> do+ sequence_+ [ do debug verbosity $ "Ready to install " ++ display pkgid+ spawnJob jobCtl $ do+ buildResult <- installPkg pkg+ return (pkg, buildResult)+ | pkg <- pkgs+ , let pkgid = packageId pkg+ ]++ let taskCount' = taskCount + length pkgs+ plan' = InstallPlan.processing pkgs plan+ waitForTasks taskCount' plan'++ waitForTasks taskCount plan = do+ debug verbosity $ "Waiting for install task to finish..."+ (pkg, buildResult) <- collectJob jobCtl+ let taskCount' = taskCount-1+ plan' = updatePlan pkg buildResult plan+ tryNewTasks taskCount' plan'++ updatePlan :: GenericReadyPackage srcpkg+ -> GenericBuildResult ipkg iresult BuildFailure+ -> GenericInstallPlan ipkg srcpkg iresult BuildFailure+ -> GenericInstallPlan ipkg srcpkg iresult BuildFailure+ updatePlan pkg (BuildSuccess mipkg buildSuccess) =+ InstallPlan.completed (installedPackageId pkg) mipkg buildSuccess++ updatePlan pkg (BuildFailure buildFailure) =+ InstallPlan.failed (installedPackageId pkg) buildFailure depsFailure+ where+ depsFailure = DependentFailed (packageId pkg)+ -- So this first pkgid failed for whatever reason (buildFailure).+ -- All the other packages that depended on this pkgid, which we+ -- now cannot build, we mark as failing due to 'DependentFailed'+ -- which kind of means it was not their fault.+++-- | Ensure that the package is unpacked in an appropriate directory, either+-- a temporary one or a persistent one under the shared dist directory. +--+withTarballLocalDirectory+ :: Verbosity+ -> DistDirLayout+ -> FilePath+ -> PackageId+ -> BuildStyle+ -> Maybe CabalFileText+ -> (FilePath -> FilePath -> IO a)+ -> IO a+withTarballLocalDirectory verbosity distDirLayout@DistDirLayout{..}+ tarball pkgid buildstyle pkgTextOverride+ buildPkg =+ case buildstyle of+ -- In this case we make a temp dir, unpack the tarball to there and+ -- build and install it from that temp dir.+ BuildAndInstall ->+ withTempDirectory verbosity distTempDirectory+ (display (packageName pkgid)) $ \tmpdir -> do+ unpackPackageTarball verbosity tarball tmpdir+ pkgid pkgTextOverride+ let srcdir = tmpdir </> display pkgid+ builddir = srcdir </> "dist"+ buildPkg srcdir builddir++ -- In this case we make sure the tarball has been unpacked to the+ -- appropriate location under the shared dist dir, and then build it+ -- inplace there+ BuildInplaceOnly -> do+ let srcrootdir = distUnpackedSrcRootDirectory+ srcdir = distUnpackedSrcDirectory pkgid+ builddir = distBuildDirectory pkgid+ -- TODO: [nice to have] use a proper file monitor rather than this dir exists test+ exists <- doesDirectoryExist srcdir+ unless exists $ do+ createDirectoryIfMissingVerbose verbosity False srcrootdir+ unpackPackageTarball verbosity tarball srcrootdir+ pkgid pkgTextOverride+ moveTarballShippedDistDirectory verbosity distDirLayout+ srcrootdir pkgid+ buildPkg srcdir builddir+++unpackPackageTarball :: Verbosity -> FilePath -> FilePath+ -> PackageId -> Maybe CabalFileText+ -> IO ()+unpackPackageTarball verbosity tarball parentdir pkgid pkgTextOverride =+ --TODO: [nice to have] switch to tar package and catch tar exceptions+ annotateFailure UnpackFailed $ do++ -- Unpack the tarball+ --+ info verbosity $ "Extracting " ++ tarball ++ " to " ++ parentdir ++ "..."+ Tar.extractTarGzFile parentdir pkgsubdir tarball++ -- Sanity check+ --+ exists <- doesFileExist cabalFile+ when (not exists) $+ die $ "Package .cabal file not found in the tarball: " ++ cabalFile++ -- Overwrite the .cabal with the one from the index, when appropriate+ --+ case pkgTextOverride of+ Nothing -> return ()+ Just pkgtxt -> do+ info verbosity $ "Updating " ++ display pkgname <.> "cabal"+ ++ " with the latest revision from the index."+ writeFileAtomic cabalFile pkgtxt++ where+ cabalFile = parentdir </> pkgsubdir+ </> display pkgname <.> "cabal"+ pkgsubdir = display pkgid+ pkgname = packageName pkgid+++-- | This is a bit of a hacky workaround. A number of packages ship+-- pre-processed .hs files in a dist directory inside the tarball. We don't+-- use the standard 'dist' location so unless we move this dist dir to the+-- right place then we'll miss the shipped pre-procssed files. This hacky+-- approach to shipped pre-procssed files ought to be replaced by a proper+-- system, though we'll still need to keep this hack for older packages.+--+moveTarballShippedDistDirectory :: Verbosity -> DistDirLayout+ -> FilePath -> PackageId -> IO ()+moveTarballShippedDistDirectory verbosity DistDirLayout{distBuildDirectory}+ parentdir pkgid = do+ distDirExists <- doesDirectoryExist tarballDistDir+ when distDirExists $ do+ debug verbosity $ "Moving '" ++ tarballDistDir ++ "' to '"+ ++ targetDistDir ++ "'"+ --TODO: [nice to have] or perhaps better to copy, and use a file monitor+ renameDirectory tarballDistDir targetDistDir+ where+ tarballDistDir = parentdir </> display pkgid </> "dist"+ targetDistDir = distBuildDirectory pkgid+++buildAndInstallUnpackedPackage :: Verbosity+ -> DistDirLayout+ -> BuildTimeSettings -> Lock -> Lock+ -> ElaboratedSharedConfig+ -> ElaboratedReadyPackage+ -> FilePath -> FilePath+ -> IO BuildResult+buildAndInstallUnpackedPackage verbosity+ DistDirLayout{distTempDirectory}+ BuildTimeSettings {+ buildSettingNumJobs,+ buildSettingLogFile+ }+ installLock cacheLock+ pkgshared@ElaboratedSharedConfig {+ pkgConfigCompiler = compiler,+ pkgConfigPlatform = platform,+ pkgConfigProgramDb = progdb+ }+ rpkg@(ReadyPackage pkg)+ srcdir builddir = do++ createDirectoryIfMissingVerbose verbosity False builddir+ initLogFile++ --TODO: [code cleanup] deal consistently with talking to older Setup.hs versions, much like+ -- we do for ghc, with a proper options type and rendering step+ -- which will also let us call directly into the lib, rather than always+ -- going via the lib's command line interface, which would also allow+ -- passing data like installed packages, compiler, and program db for a+ -- quicker configure.++ --TODO: [required feature] docs and tests+ --TODO: [required feature] sudo re-exec++ -- Configure phase+ when isParallelBuild $+ notice verbosity $ "Configuring " ++ display pkgid ++ "..."+ annotateFailure ConfigureFailed $+ setup configureCommand configureFlags++ -- Build phase+ when isParallelBuild $+ notice verbosity $ "Building " ++ display pkgid ++ "..."+ annotateFailure BuildFailed $+ setup buildCommand buildFlags++ -- Install phase+ mipkg <-+ criticalSection installLock $ + annotateFailure InstallFailed $ do+ --TODO: [research required] do we need the installLock for copying? can we not do that in+ -- parallel? Isn't it just registering that we have to lock for?++ --TODO: [required eventually] need to lock installing this ipkig so other processes don't+ -- stomp on our files, since we don't have ABI compat, not safe to replace++ -- TODO: [required eventually] note that for nix-style installations it is not necessary to do+ -- the 'withWin32SelfUpgrade' dance, but it would be necessary for a+ -- shared bin dir.++ -- Actual installation+ setup Cabal.copyCommand copyFlags+ + LBS.writeFile+ (InstallDirs.prefix (pkgInstallDirs pkg) </> "cabal-hash.txt") $+ (renderPackageHashInputs (packageHashInputs pkgshared pkg))++ -- here's where we could keep track of the installed files ourselves if+ -- we wanted by calling copy to an image dir and then we would make a+ -- manifest and move it to its final location++ --TODO: [nice to have] we should actually have it make an image in store/incomming and+ -- then when it's done, move it to its final location, to reduce problems+ -- with installs failing half-way. Could also register and then move.++ -- For libraries, grab the package configuration file+ -- and register it ourselves+ if pkgRequiresRegistration pkg+ then do+ ipkg <- generateInstalledPackageInfo+ -- We register ourselves rather than via Setup.hs. We need to+ -- grab and modify the InstalledPackageInfo. We decide what+ -- the installed package id is, not the build system.+ let ipkg' = ipkg { Installed.installedUnitId = ipkgid }+ Cabal.registerPackage verbosity compiler progdb+ HcPkg.MultiInstance+ (pkgRegisterPackageDBStack pkg) ipkg'+ return (Just ipkg')+ else return Nothing++ --TODO: [required feature] docs and test phases+ let docsResult = DocsNotTried+ testsResult = TestsNotTried++ return (BuildSuccess mipkg (BuildOk docsResult testsResult))++ where+ pkgid = packageId rpkg+ ipkgid = installedPackageId rpkg++ isParallelBuild = buildSettingNumJobs >= 2++ configureCommand = Cabal.configureCommand defaultProgramConfiguration+ configureFlags v = flip filterConfigureFlags v $+ setupHsConfigureFlags rpkg pkgshared+ verbosity builddir++ buildCommand = Cabal.buildCommand defaultProgramConfiguration+ buildFlags _ = setupHsBuildFlags pkg pkgshared verbosity builddir++ generateInstalledPackageInfo :: IO InstalledPackageInfo+ generateInstalledPackageInfo =+ withTempInstalledPackageInfoFile+ verbosity distTempDirectory $ \pkgConfFile -> do+ -- make absolute since setup changes dir+ pkgConfFile' <- canonicalizePath pkgConfFile+ let registerFlags _ = setupHsRegisterFlags+ pkg pkgshared+ verbosity builddir+ pkgConfFile'+ setup Cabal.registerCommand registerFlags++ copyFlags _ = setupHsCopyFlags pkg pkgshared verbosity builddir++ scriptOptions = setupHsScriptOptions rpkg pkgshared srcdir builddir+ isParallelBuild cacheLock++ setup :: CommandUI flags -> (Version -> flags) -> IO ()+ setup cmd flags =+ withLogging $ \mLogFileHandle -> + setupWrapper+ verbosity+ scriptOptions { useLoggingHandle = mLogFileHandle }+ (Just (pkgDescription pkg))+ cmd flags []++ mlogFile =+ case buildSettingLogFile of+ Nothing -> Nothing+ Just mkLogFile -> Just (mkLogFile compiler platform pkgid ipkgid)++ initLogFile =+ case mlogFile of+ Nothing -> return ()+ Just logFile -> do+ createDirectoryIfMissing True (takeDirectory logFile)+ exists <- doesFileExist logFile+ when exists $ removeFile logFile++ withLogging action =+ case mlogFile of+ Nothing -> action Nothing+ Just logFile -> withFile logFile AppendMode (action . Just)+++buildInplaceUnpackedPackage :: Verbosity+ -> DistDirLayout+ -> BuildTimeSettings -> Lock+ -> ElaboratedSharedConfig+ -> ElaboratedReadyPackage+ -> BuildStatusRebuild+ -> FilePath -> FilePath+ -> IO BuildResult+buildInplaceUnpackedPackage verbosity+ distDirLayout@DistDirLayout {+ distTempDirectory,+ distPackageCacheDirectory+ }+ BuildTimeSettings{buildSettingNumJobs}+ cacheLock+ pkgshared@ElaboratedSharedConfig {+ pkgConfigCompiler = compiler,+ pkgConfigProgramDb = progdb+ }+ rpkg@(ReadyPackage pkg)+ buildStatus+ srcdir builddir = do++ --TODO: [code cleanup] there is duplication between the distdirlayout and the builddir here+ -- builddir is not enough, we also need the per-package cachedir+ createDirectoryIfMissingVerbose verbosity False builddir+ createDirectoryIfMissingVerbose verbosity False (distPackageCacheDirectory pkgid)+ createPackageDBIfMissing verbosity compiler progdb (pkgBuildPackageDBStack pkg)++ -- Configure phase+ --+ whenReConfigure $ do+ setup configureCommand configureFlags []+ invalidatePackageRegFileMonitor packageFileMonitor+ updatePackageConfigFileMonitor packageFileMonitor srcdir pkg++ -- Build phase+ --+ let docsResult = DocsNotTried+ testsResult = TestsNotTried++ buildSuccess :: BuildSuccess+ buildSuccess = BuildOk docsResult testsResult++ whenRebuild $ do+ timestamp <- beginUpdateFileMonitor+ setup buildCommand buildFlags buildArgs++ --TODO: [required eventually] temporary hack. We need to look at the package description+ -- and work out the exact file monitors to use+ allSrcFiles <- filter (not . ("dist-newstyle" `isPrefixOf`))+ <$> getDirectoryContentsRecursive srcdir++ updatePackageBuildFileMonitor packageFileMonitor srcdir timestamp+ pkg buildStatus+ allSrcFiles buildSuccess++ mipkg <- whenReRegister $ do+ -- Register locally+ mipkg <- if pkgRequiresRegistration pkg+ then do+ ipkg <- generateInstalledPackageInfo+ -- We register ourselves rather than via Setup.hs. We need to+ -- grab and modify the InstalledPackageInfo. We decide what+ -- the installed package id is, not the build system.+ let ipkg' = ipkg { Installed.installedUnitId = ipkgid }+ Cabal.registerPackage verbosity compiler progdb HcPkg.NoMultiInstance+ (pkgRegisterPackageDBStack pkg)+ ipkg'+ return (Just ipkg')++ else return Nothing++ updatePackageRegFileMonitor packageFileMonitor srcdir mipkg+ return mipkg++ -- Repl phase+ --+ whenRepl $+ setup replCommand replFlags replArgs++ -- Haddock phase+ whenHaddock $+ setup haddockCommand haddockFlags []++ return (BuildSuccess mipkg buildSuccess)++ where+ pkgid = packageId rpkg+ ipkgid = installedPackageId rpkg++ isParallelBuild = buildSettingNumJobs >= 2++ packageFileMonitor = newPackageFileMonitor distDirLayout pkgid++ whenReConfigure action = case buildStatus of+ BuildStatusConfigure _ -> action+ _ -> return ()++ whenRebuild action+ | null (pkgBuildTargets pkg) = return ()+ | otherwise = action++ whenRepl action+ | isNothing (pkgReplTarget pkg) = return ()+ | otherwise = action++ whenHaddock action+ | pkgBuildHaddocks pkg = action+ | otherwise = return ()++ whenReRegister action = case buildStatus of+ BuildStatusConfigure _ -> action+ BuildStatusBuild Nothing _ -> action+ BuildStatusBuild (Just mipkg) _ -> return mipkg++ configureCommand = Cabal.configureCommand defaultProgramConfiguration+ configureFlags v = flip filterConfigureFlags v $+ setupHsConfigureFlags rpkg pkgshared+ verbosity builddir++ buildCommand = Cabal.buildCommand defaultProgramConfiguration+ buildFlags _ = setupHsBuildFlags pkg pkgshared+ verbosity builddir+ buildArgs = setupHsBuildArgs pkg++ replCommand = Cabal.replCommand defaultProgramConfiguration+ replFlags _ = setupHsReplFlags pkg pkgshared+ verbosity builddir+ replArgs = setupHsReplArgs pkg++ haddockCommand = Cabal.haddockCommand+ haddockFlags _ = setupHsHaddockFlags pkg pkgshared+ verbosity builddir++ scriptOptions = setupHsScriptOptions rpkg pkgshared+ srcdir builddir+ isParallelBuild cacheLock++ setup :: CommandUI flags -> (Version -> flags) -> [String] -> IO ()+ setup cmd flags args =+ setupWrapper verbosity+ scriptOptions+ (Just (pkgDescription pkg))+ cmd flags args++ generateInstalledPackageInfo :: IO InstalledPackageInfo+ generateInstalledPackageInfo =+ withTempInstalledPackageInfoFile+ verbosity distTempDirectory $ \pkgConfFile -> do+ -- make absolute since setup changes dir+ pkgConfFile' <- canonicalizePath pkgConfFile+ let registerFlags _ = setupHsRegisterFlags+ pkg pkgshared+ verbosity builddir+ pkgConfFile'+ setup Cabal.registerCommand registerFlags []+++-- helper+annotateFailure :: (String -> BuildFailure) -> IO a -> IO a+annotateFailure annotate action =+ action `catches`+ [ Handler $ \ioe -> handler (ioe :: IOException)+ , Handler $ \exit -> handler (exit :: ExitCode)+ ]+ where+ handler :: Exception e => e -> IO a+ handler = throwIO . annotate . show+ --TODO: [nice to have] use displayException when available+++withTempInstalledPackageInfoFile :: Verbosity -> FilePath+ -> (FilePath -> IO ())+ -> IO InstalledPackageInfo+withTempInstalledPackageInfoFile verbosity tempdir action =+ withTempFile tempdir "package-registration-" $ \pkgConfFile hnd -> do+ hClose hnd+ action pkgConfFile++ (warns, ipkg) <- withUTF8FileContents pkgConfFile $ \pkgConfStr ->+ case Installed.parseInstalledPackageInfo pkgConfStr of+ Installed.ParseFailed perror -> pkgConfParseFailed perror+ Installed.ParseOk warns ipkg -> return (warns, ipkg)++ unless (null warns) $+ warn verbosity $ unlines (map (showPWarning pkgConfFile) warns)++ return ipkg+ where+ pkgConfParseFailed :: Installed.PError -> IO a+ pkgConfParseFailed perror =+ die $ "Couldn't parse the output of 'setup register --gen-pkg-config':"+ ++ show perror+
+ cabal/cabal-install/Distribution/Client/ProjectConfig.hs view
@@ -0,0 +1,676 @@+{-# LANGUAGE CPP, RecordWildCards, NamedFieldPuns, DeriveDataTypeable #-}++-- | Handling project configuration.+--+module Distribution.Client.ProjectConfig (++ -- * Types for project config+ ProjectConfig(..),+ ProjectConfigBuildOnly(..),+ ProjectConfigShared(..),+ PackageConfig(..),++ -- * Project config files+ findProjectRoot,+ readProjectConfig,+ writeProjectLocalExtraConfig,+ writeProjectConfigFile,+ commandLineFlagsToProjectConfig,++ -- * Packages within projects+ ProjectPackageLocation(..),+ BadPackageLocation(..),+ BadPackageLocationMatch(..),+ findProjectPackages,+ readSourcePackage,++ -- * Resolving configuration+ lookupLocalPackageConfig,+ projectConfigWithBuilderRepoContext,+ projectConfigWithSolverRepoContext,+ SolverSettings(..),+ resolveSolverSettings,+ BuildTimeSettings(..),+ resolveBuildTimeSettings,+ ) where++import Distribution.Client.ProjectConfig.Types+import Distribution.Client.ProjectConfig.Legacy+import Distribution.Client.RebuildMonad+import Distribution.Client.Glob+ ( isTrivialFilePathGlob )++import Distribution.Client.Types+import Distribution.Client.DistDirLayout+ ( CabalDirLayout(..) )+import Distribution.Client.GlobalFlags+ ( RepoContext(..), withRepoContext' )+import Distribution.Client.BuildReports.Types+ ( ReportLevel(..) )+import Distribution.Client.Config+ ( loadConfig, defaultConfigFile )++import Distribution.Package+ ( PackageName, PackageId, packageId, UnitId, Dependency )+import Distribution.System+ ( Platform )+import Distribution.PackageDescription+ ( SourceRepo(..) )+import Distribution.PackageDescription.Parse+ ( readPackageDescription )+import Distribution.Simple.Compiler+ ( Compiler, compilerInfo )+import Distribution.Simple.Setup+ ( Flag(Flag), toFlag, flagToMaybe, flagToList+ , fromFlag, AllowNewer(..) )+import Distribution.Client.Setup+ ( defaultSolver, defaultMaxBackjumps, )+import Distribution.Simple.InstallDirs+ ( PathTemplate, fromPathTemplate+ , toPathTemplate, substPathTemplate, initialPathTemplateEnv )+import Distribution.Simple.Utils+ ( die, warn )+import Distribution.Client.Utils+ ( determineNumJobs )+import Distribution.Utils.NubList+ ( fromNubList )+import Distribution.Verbosity+ ( Verbosity, verbose )+import Distribution.Text+import Distribution.ParseUtils+ ( ParseResult(..), locatedErrorMsg, showPWarning )++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative+#endif+import Control.Monad+import Control.Monad.Trans (liftIO)+import Control.Exception+import Data.Typeable+import Data.Maybe+import Data.Either+import qualified Data.Map as Map+import Distribution.Compat.Semigroup+import System.FilePath hiding (combine)+import System.Directory+import Network.URI (URI(..), URIAuth(..), parseAbsoluteURI)+++----------------------------------------+-- Resolving configuration to settings+--++-- | Look up a 'PackageConfig' field in the 'ProjectConfig' for a specific+-- 'PackageName'. This returns the configuration that applies to all local+-- packages plus any package-specific configuration for this package.+--+lookupLocalPackageConfig :: (Semigroup a, Monoid a)+ => (PackageConfig -> a)+ -> ProjectConfig+ -> PackageName -> a+lookupLocalPackageConfig field ProjectConfig {+ projectConfigLocalPackages,+ projectConfigSpecificPackage+ } pkgname =+ field projectConfigLocalPackages+ <> maybe mempty field (Map.lookup pkgname projectConfigSpecificPackage)+++-- | Use a 'RepoContext' based on the 'BuildTimeSettings'.+--+projectConfigWithBuilderRepoContext :: Verbosity+ -> BuildTimeSettings+ -> (RepoContext -> IO a) -> IO a+projectConfigWithBuilderRepoContext verbosity BuildTimeSettings{..} =+ withRepoContext'+ verbosity+ buildSettingRemoteRepos+ buildSettingLocalRepos+ buildSettingCacheDir+ buildSettingHttpTransport+ (Just buildSettingIgnoreExpiry)+++-- | Use a 'RepoContext', but only for the solver. The solver does not use the+-- full facilities of the 'RepoContext' so we can get away with making one+-- that doesn't have an http transport. And that avoids having to have access+-- to the 'BuildTimeSettings'+--+projectConfigWithSolverRepoContext :: Verbosity+ -> FilePath+ -> ProjectConfigShared+ -> ProjectConfigBuildOnly+ -> (RepoContext -> IO a) -> IO a+projectConfigWithSolverRepoContext verbosity downloadCacheRootDir+ ProjectConfigShared{..}+ ProjectConfigBuildOnly{..} =+ withRepoContext'+ verbosity+ (fromNubList projectConfigRemoteRepos)+ (fromNubList projectConfigLocalRepos)+ downloadCacheRootDir+ (flagToMaybe projectConfigHttpTransport)+ (flagToMaybe projectConfigIgnoreExpiry)+++-- | Resolve the project configuration, with all its optional fields, into+-- 'SolverSettings' with no optional fields (by applying defaults).+--+resolveSolverSettings :: ProjectConfigShared -> SolverSettings+resolveSolverSettings projectConfig =+ SolverSettings {..}+ where+ solverSettingRemoteRepos = fromNubList projectConfigRemoteRepos+ solverSettingLocalRepos = fromNubList projectConfigLocalRepos+ solverSettingConstraints = projectConfigConstraints+ solverSettingPreferences = projectConfigPreferences+ solverSettingFlagAssignment = projectConfigFlagAssignment+ solverSettingCabalVersion = flagToMaybe projectConfigCabalVersion+ solverSettingSolver = fromFlag projectConfigSolver+ solverSettingAllowNewer = fromJust projectConfigAllowNewer+ solverSettingMaxBackjumps = case fromFlag projectConfigMaxBackjumps of+ n | n < 0 -> Nothing+ | otherwise -> Just n+ solverSettingReorderGoals = fromFlag projectConfigReorderGoals+ solverSettingStrongFlags = fromFlag projectConfigStrongFlags+ --solverSettingIndependentGoals = fromFlag projectConfigIndependentGoals+ --solverSettingShadowPkgs = fromFlag projectConfigShadowPkgs+ --solverSettingReinstall = fromFlag projectConfigReinstall+ --solverSettingAvoidReinstalls = fromFlag projectConfigAvoidReinstalls+ --solverSettingOverrideReinstall = fromFlag projectConfigOverrideReinstall+ --solverSettingUpgradeDeps = fromFlag projectConfigUpgradeDeps++ ProjectConfigShared {..} = defaults <> projectConfig++ defaults = mempty {+ projectConfigSolver = Flag defaultSolver,+ projectConfigAllowNewer = Just AllowNewerNone,+ projectConfigMaxBackjumps = Flag defaultMaxBackjumps,+ projectConfigReorderGoals = Flag False,+ projectConfigStrongFlags = Flag False+ --projectConfigIndependentGoals = Flag False,+ --projectConfigShadowPkgs = Flag False,+ --projectConfigReinstall = Flag False,+ --projectConfigAvoidReinstalls = Flag False,+ --projectConfigOverrideReinstall = Flag False,+ --projectConfigUpgradeDeps = Flag False+ }+++-- | Resolve the project configuration, with all its optional fields, into+-- 'BuildTimeSettings' with no optional fields (by applying defaults).+--+resolveBuildTimeSettings :: Verbosity+ -> CabalDirLayout+ -> ProjectConfigShared+ -> ProjectConfigBuildOnly+ -> ProjectConfigBuildOnly+ -> BuildTimeSettings+resolveBuildTimeSettings verbosity+ CabalDirLayout {+ cabalLogsDirectory,+ cabalPackageCacheDirectory+ }+ ProjectConfigShared {+ projectConfigRemoteRepos,+ projectConfigLocalRepos+ }+ fromProjectFile+ fromCommandLine =+ BuildTimeSettings {..}+ where+ buildSettingDryRun = fromFlag projectConfigDryRun+ buildSettingOnlyDeps = fromFlag projectConfigOnlyDeps+ buildSettingSummaryFile = fromNubList projectConfigSummaryFile+ --buildSettingLogFile -- defined below, more complicated+ --buildSettingLogVerbosity -- defined below, more complicated+ buildSettingBuildReports = fromFlag projectConfigBuildReports+ buildSettingSymlinkBinDir = flagToList projectConfigSymlinkBinDir+ buildSettingOneShot = fromFlag projectConfigOneShot+ buildSettingNumJobs = determineNumJobs projectConfigNumJobs+ buildSettingOfflineMode = fromFlag projectConfigOfflineMode+ buildSettingKeepTempFiles = fromFlag projectConfigKeepTempFiles+ buildSettingRemoteRepos = fromNubList projectConfigRemoteRepos+ buildSettingLocalRepos = fromNubList projectConfigLocalRepos+ buildSettingCacheDir = cabalPackageCacheDirectory+ buildSettingHttpTransport = flagToMaybe projectConfigHttpTransport+ buildSettingIgnoreExpiry = fromFlag projectConfigIgnoreExpiry+ buildSettingReportPlanningFailure+ = fromFlag projectConfigReportPlanningFailure+ buildSettingRootCmd = flagToMaybe projectConfigRootCmd++ ProjectConfigBuildOnly{..} = defaults+ <> fromProjectFile+ <> fromCommandLine++ defaults = mempty {+ projectConfigDryRun = toFlag False,+ projectConfigOnlyDeps = toFlag False,+ projectConfigBuildReports = toFlag NoReports,+ projectConfigReportPlanningFailure = toFlag False,+ projectConfigOneShot = toFlag False,+ projectConfigOfflineMode = toFlag False,+ projectConfigKeepTempFiles = toFlag False,+ projectConfigIgnoreExpiry = toFlag False+ }++ -- The logging logic: what log file to use and what verbosity.+ --+ -- If the user has specified --remote-build-reporting=detailed, use the+ -- default log file location. If the --build-log option is set, use the+ -- provided location. Otherwise don't use logging, unless building in+ -- parallel (in which case the default location is used).+ --+ buildSettingLogFile :: Maybe (Compiler -> Platform+ -> PackageId -> UnitId -> FilePath)+ buildSettingLogFile+ | useDefaultTemplate = Just (substLogFileName defaultTemplate)+ | otherwise = fmap substLogFileName givenTemplate++ defaultTemplate = toPathTemplate $+ cabalLogsDirectory </> "$pkgid" <.> "log"+ givenTemplate = flagToMaybe projectConfigLogFile++ useDefaultTemplate+ | buildSettingBuildReports == DetailedReports = True+ | isJust givenTemplate = False+ | isParallelBuild = True+ | otherwise = False++ isParallelBuild = buildSettingNumJobs >= 2++ substLogFileName :: PathTemplate+ -> Compiler -> Platform+ -> PackageId -> UnitId -> FilePath+ substLogFileName template compiler platform pkgid uid =+ fromPathTemplate (substPathTemplate env template)+ where+ env = initialPathTemplateEnv+ pkgid uid (compilerInfo compiler) platform++ -- If the user has specified --remote-build-reporting=detailed or+ -- --build-log, use more verbose logging.+ --+ buildSettingLogVerbosity+ | overrideVerbosity = max verbose verbosity+ | otherwise = verbosity++ overrideVerbosity+ | buildSettingBuildReports == DetailedReports = True+ | isJust givenTemplate = True+ | isParallelBuild = False+ | otherwise = False+++---------------------------------------------+-- Reading and writing project config files+--++-- | Find the root of this project.+--+-- Searches for an explicit @cabal.project@ file, in the current directory or+-- parent directories. If no project file is found then the current dir is the+-- project root (and the project will use an implicit config).+--+findProjectRoot :: IO FilePath+findProjectRoot = do++ curdir <- getCurrentDirectory+ homedir <- getHomeDirectory++ -- Search upwards. If we get to the users home dir or the filesystem root,+ -- then use the current dir+ let probe dir | isDrive dir || dir == homedir+ = return curdir -- implicit project root+ probe dir = do+ exists <- doesFileExist (dir </> "cabal.project")+ if exists+ then return dir -- explicit project root+ else probe (takeDirectory dir)++ probe curdir+ --TODO: [nice to have] add compat support for old style sandboxes+++-- | Read all the config relevant for a project. This includes the project+-- file if any, plus other global config.+--+readProjectConfig :: Verbosity -> FilePath -> Rebuild ProjectConfig+readProjectConfig verbosity projectRootDir = do+ global <- readGlobalConfig verbosity+ local <- readProjectLocalConfig verbosity projectRootDir+ extra <- readProjectLocalExtraConfig verbosity projectRootDir+ return (global <> local <> extra)+++-- | Reads an explicit @cabal.project@ file in the given project root dir,+-- or returns the default project config for an implicitly defined project.+--+readProjectLocalConfig :: Verbosity -> FilePath -> Rebuild ProjectConfig+readProjectLocalConfig verbosity projectRootDir = do+ usesExplicitProjectRoot <- liftIO $ doesFileExist projectFile+ if usesExplicitProjectRoot+ then do+ monitorFiles [monitorFileHashed projectFile]+ liftIO readProjectFile+ else do+ monitorFiles [monitorNonExistentFile projectFile]+ return defaultImplicitProjectConfig++ where+ projectFile = projectRootDir </> "cabal.project"+ readProjectFile =+ reportParseResult verbosity "project file" projectFile+ . parseProjectConfig+ =<< readFile projectFile++ defaultImplicitProjectConfig :: ProjectConfig+ defaultImplicitProjectConfig =+ mempty {+ -- We expect a package in the current directory.+ projectPackages = [ "./*.cabal" ],++ -- This is to automatically pick up deps that we unpack locally.+ projectPackagesOptional = [ "./*/*.cabal" ]+ }+++-- | Reads a @cabal.project.extra@ file in the given project root dir,+-- or returns empty. This file gets written by @cabal configure@, or in+-- principle can be edited manually or by other tools.+--+readProjectLocalExtraConfig :: Verbosity -> FilePath -> Rebuild ProjectConfig+readProjectLocalExtraConfig verbosity projectRootDir = do+ hasExtraConfig <- liftIO $ doesFileExist projectExtraConfigFile+ if hasExtraConfig+ then do monitorFiles [monitorFileHashed projectExtraConfigFile]+ liftIO readProjectExtraConfigFile+ else do monitorFiles [monitorNonExistentFile projectExtraConfigFile]+ return mempty+ where+ projectExtraConfigFile = projectRootDir </> "cabal.project.extra"++ readProjectExtraConfigFile =+ reportParseResult verbosity "project extra configuration file"+ projectExtraConfigFile+ . parseProjectConfig+ =<< readFile projectExtraConfigFile+++-- | Parse the 'ProjectConfig' format.+--+-- For the moment this is implemented in terms of parsers for legacy+-- configuration types, plus a conversion.+--+parseProjectConfig :: String -> ParseResult ProjectConfig+parseProjectConfig content =+ convertLegacyProjectConfig <$>+ parseLegacyProjectConfig content+++-- | Render the 'ProjectConfig' format.+--+-- For the moment this is implemented in terms of a pretty printer for the+-- legacy configuration types, plus a conversion.+--+showProjectConfig :: ProjectConfig -> String+showProjectConfig =+ showLegacyProjectConfig . convertToLegacyProjectConfig+++-- | Write a @cabal.project.extra@ file in the given project root dir.+--+writeProjectLocalExtraConfig :: FilePath -> ProjectConfig -> IO ()+writeProjectLocalExtraConfig projectRootDir =+ writeProjectConfigFile projectExtraConfigFile+ where+ projectExtraConfigFile = projectRootDir </> "cabal.project.extra"+++-- | Write in the @cabal.project@ format to the given file.+--+writeProjectConfigFile :: FilePath -> ProjectConfig -> IO ()+writeProjectConfigFile file =+ writeFile file . showProjectConfig+++-- | Read the user's @~/.cabal/config@ file.+--+readGlobalConfig :: Verbosity -> Rebuild ProjectConfig+readGlobalConfig verbosity = do+ config <- liftIO (loadConfig verbosity mempty)+ configFile <- liftIO defaultConfigFile+ monitorFiles [monitorFileHashed configFile]+ return (convertLegacyGlobalConfig config)+ --TODO: do this properly, there's several possible locations+ -- and env vars, and flags for selecting the global config+++reportParseResult :: Verbosity -> String -> FilePath -> ParseResult a -> IO a+reportParseResult verbosity _filetype filename (ParseOk warnings x) = do+ unless (null warnings) $+ let msg = unlines (map (showPWarning filename) warnings)+ in warn verbosity msg+ return x+reportParseResult _verbosity filetype filename (ParseFailed err) =+ let (line, msg) = locatedErrorMsg err+ in die $ "Error parsing " ++ filetype ++ " " ++ filename+ ++ maybe "" (\n -> ':' : show n) line ++ ":\n" ++ msg+++---------------------------------------------+-- Reading packages in the project+--++data ProjectPackageLocation =+ ProjectPackageLocalCabalFile FilePath+ | ProjectPackageLocalDirectory FilePath FilePath -- dir and .cabal file+ | ProjectPackageLocalTarball FilePath+ | ProjectPackageRemoteTarball URI+ | ProjectPackageRemoteRepo SourceRepo+ | ProjectPackageNamed Dependency+ deriving Show+++-- | Exception thrown by 'findProjectPackages'.+--+newtype BadPackageLocations = BadPackageLocations [BadPackageLocation]+ deriving (Show, Typeable)++instance Exception BadPackageLocations+--TODO: [required eventually] displayException for nice rendering+--TODO: [nice to have] custom exception subclass for Doc rendering, colour etc++data BadPackageLocation+ = BadPackageLocationFile BadPackageLocationMatch+ | BadLocGlobEmptyMatch String+ | BadLocGlobBadMatches String [BadPackageLocationMatch]+ | BadLocUnexpectedUriScheme String+ | BadLocUnrecognisedUri String+ | BadLocUnrecognised String+ deriving Show++data BadPackageLocationMatch+ = BadLocUnexpectedFile String+ | BadLocNonexistantFile String+ | BadLocDirNoCabalFile String+ | BadLocDirManyCabalFiles String+ deriving Show+++-- | Given the project config,+--+-- Throws 'BadPackageLocations'.+--+findProjectPackages :: FilePath -> ProjectConfig+ -> Rebuild [ProjectPackageLocation]+findProjectPackages projectRootDir ProjectConfig{..} = do++ requiredPkgs <- findPackageLocations True projectPackages+ optionalPkgs <- findPackageLocations False projectPackagesOptional+ let repoPkgs = map ProjectPackageRemoteRepo projectPackagesRepo+ namedPkgs = map ProjectPackageNamed projectPackagesNamed++ return (concat [requiredPkgs, optionalPkgs, repoPkgs, namedPkgs])+ where+ findPackageLocations required pkglocstr = do+ (problems, pkglocs) <-+ partitionEithers <$> mapM (findPackageLocation required) pkglocstr+ unless (null problems) $+ liftIO $ throwIO $ BadPackageLocations problems+ return (concat pkglocs)+++ findPackageLocation :: Bool -> String+ -> Rebuild (Either BadPackageLocation+ [ProjectPackageLocation])+ findPackageLocation _required@True pkglocstr =+ -- strategy: try first as a file:// or http(s):// URL.+ -- then as a file glob (usually encompassing single file)+ -- finally as a single file, for files that fail to parse as globs+ checkIsUriPackage pkglocstr+ `mplusMaybeT` checkIsFileGlobPackage pkglocstr+ `mplusMaybeT` checkIsSingleFilePackage pkglocstr+ >>= maybe (return (Left (BadLocUnrecognised pkglocstr))) return+++ findPackageLocation _required@False pkglocstr = do+ -- just globs for optional case+ res <- checkIsFileGlobPackage pkglocstr+ case res of+ Nothing -> return (Left (BadLocUnrecognised pkglocstr))+ Just (Left _) -> return (Right []) -- it's optional+ Just (Right pkglocs) -> return (Right pkglocs)+++ checkIsUriPackage, checkIsFileGlobPackage, checkIsSingleFilePackage+ :: String -> Rebuild (Maybe (Either BadPackageLocation+ [ProjectPackageLocation]))+ checkIsUriPackage pkglocstr =+ return $!+ case parseAbsoluteURI pkglocstr of+ Just uri@URI {+ uriScheme = scheme,+ uriAuthority = Just URIAuth { uriRegName = host }+ }+ | recognisedScheme && not (null host) ->+ Just (Right [ProjectPackageRemoteTarball uri])++ | not recognisedScheme && not (null host) ->+ Just (Left (BadLocUnexpectedUriScheme pkglocstr))++ | recognisedScheme && null host ->+ Just (Left (BadLocUnrecognisedUri pkglocstr))+ where+ recognisedScheme = scheme == "http:" || scheme == "https:"+ || scheme == "file:"++ _ -> Nothing+++ checkIsFileGlobPackage pkglocstr =+ case simpleParse pkglocstr of+ Nothing -> return Nothing+ Just glob -> liftM Just $ do+ matches <- matchFileGlob projectRootDir glob+ case matches of+ [] | isJust (isTrivialFilePathGlob glob)+ -> return (Left (BadPackageLocationFile+ (BadLocNonexistantFile pkglocstr)))++ [] -> return (Left (BadLocGlobEmptyMatch pkglocstr))++ _ -> do+ (failures, pkglocs) <- partitionEithers <$>+ mapM checkFilePackageMatch matches+ if null pkglocs+ then return (Left (BadLocGlobBadMatches pkglocstr failures))+ else return (Right pkglocs)+++ checkIsSingleFilePackage pkglocstr = do+ let filename = projectRootDir </> pkglocstr+ isFile <- liftIO $ doesFileExist filename+ isDir <- liftIO $ doesDirectoryExist filename+ if isFile || isDir+ then checkFilePackageMatch pkglocstr+ >>= either (return . Just . Left . BadPackageLocationFile)+ (return . Just . Right . (\x->[x]))+ else return Nothing+++ checkFilePackageMatch :: String -> Rebuild (Either BadPackageLocationMatch+ ProjectPackageLocation)+ checkFilePackageMatch pkglocstr = do+ let filename = projectRootDir </> pkglocstr+ isDir <- liftIO $ doesDirectoryExist filename+ parentDirExists <- case takeDirectory filename of+ [] -> return False+ dir -> liftIO $ doesDirectoryExist dir+ case () of+ _ | isDir+ -> do let dirname = filename -- now we know its a dir+ glob = globStarDotCabal pkglocstr+ matches <- matchFileGlob projectRootDir glob+ case matches of+ [match]+ -> return (Right (ProjectPackageLocalDirectory+ dirname cabalFile))+ where+ cabalFile = dirname </> match+ [] -> return (Left (BadLocDirNoCabalFile pkglocstr))+ _ -> return (Left (BadLocDirManyCabalFiles pkglocstr))++ | extensionIsTarGz filename+ -> return (Right (ProjectPackageLocalTarball filename))++ | takeExtension filename == ".cabal"+ -> return (Right (ProjectPackageLocalCabalFile filename))++ | parentDirExists+ -> return (Left (BadLocNonexistantFile pkglocstr))++ | otherwise+ -> return (Left (BadLocUnexpectedFile pkglocstr))+++ extensionIsTarGz f = takeExtension f == ".gz"+ && takeExtension (dropExtension f) == ".tar"+++globStarDotCabal :: FilePath -> FilePathGlob+globStarDotCabal =+ FilePathGlob FilePathRelative+ . foldr (\dirpart -> GlobDir [Literal dirpart])+ (GlobFile [WildCard, Literal ".cabal"])+ . splitDirectories+++--TODO: [code cleanup] use sufficiently recent transformers package+mplusMaybeT :: Monad m => m (Maybe a) -> m (Maybe a) -> m (Maybe a)+mplusMaybeT ma mb = do+ mx <- ma+ case mx of+ Nothing -> mb+ Just x -> return (Just x)+++readSourcePackage :: Verbosity -> ProjectPackageLocation+ -> Rebuild UnresolvedSourcePackage+readSourcePackage verbosity (ProjectPackageLocalCabalFile cabalFile) =+ readSourcePackage verbosity (ProjectPackageLocalDirectory dir cabalFile)+ where+ dir = takeDirectory cabalFile++readSourcePackage verbosity (ProjectPackageLocalDirectory dir cabalFile) = do+ -- no need to monitorFiles because findProjectCabalFiles did it already+ pkgdesc <- liftIO $ readPackageDescription verbosity cabalFile+ return SourcePackage {+ packageInfoId = packageId pkgdesc,+ packageDescription = pkgdesc,+ packageSource = LocalUnpackedPackage dir,+ packageDescrOverride = Nothing+ }+readSourcePackage _verbosity _ =+ fail $ "TODO: add support for fetching and reading local tarballs, remote "+ ++ "tarballs, remote repos and passing named packages through"
+ cabal/cabal-install/Distribution/Client/ProjectConfig/Legacy.hs view
@@ -0,0 +1,1251 @@+{-# LANGUAGE CPP, RecordWildCards, NamedFieldPuns, DeriveGeneric #-}++-- | Project configuration, implementation in terms of legacy types.+--+module Distribution.Client.ProjectConfig.Legacy (++ -- * Project config in terms of legacy types+ LegacyProjectConfig,+ parseLegacyProjectConfig,+ showLegacyProjectConfig,++ -- * Conversion to and from legacy config types+ commandLineFlagsToProjectConfig,+ convertLegacyProjectConfig,+ convertLegacyGlobalConfig,+ convertToLegacyProjectConfig,++ -- * Internals, just for tests+ parsePackageLocationTokenQ,+ renderPackageLocationToken,+ ) where++import Distribution.Client.ProjectConfig.Types+import Distribution.Client.Types+ ( RemoteRepo(..), emptyRemoteRepo )+import Distribution.Client.Dependency.Types+ ( ConstraintSource(..) )+import Distribution.Client.Config+ ( SavedConfig(..), remoteRepoFields )++import Distribution.Package+import Distribution.PackageDescription+ ( SourceRepo(..), RepoKind(..) )+import Distribution.PackageDescription.Parse+ ( sourceRepoFieldDescrs )+import Distribution.Simple.Compiler+ ( OptimisationLevel(..), DebugInfoLevel(..) )+import Distribution.Simple.Setup+ ( Flag(Flag), toFlag, fromFlagOrDefault+ , ConfigFlags(..), configureOptions+ , HaddockFlags(..), haddockOptions, defaultHaddockFlags+ , programConfigurationPaths', splitArgs+ , AllowNewer(..) )+import Distribution.Client.Setup+ ( GlobalFlags(..), globalCommand+ , ConfigExFlags(..), configureExOptions, defaultConfigExFlags+ , InstallFlags(..), installOptions, defaultInstallFlags )+import Distribution.Simple.Program+ ( programName, knownPrograms )+import Distribution.Simple.Program.Db+ ( ProgramDb, defaultProgramDb )+import Distribution.Client.Targets+ ( dispFlagAssignment, parseFlagAssignment )+import Distribution.Simple.Utils+ ( lowercase )+import Distribution.Utils.NubList+ ( toNubList, fromNubList, overNubList )+import Distribution.Simple.LocalBuildInfo+ ( toPathTemplate, fromPathTemplate )++import Distribution.Text+import qualified Distribution.Compat.ReadP as Parse+import Distribution.Compat.ReadP+ ( ReadP, (+++), (<++) )+import qualified Text.Read as Read+import qualified Text.PrettyPrint as Disp+import Text.PrettyPrint+ ( Doc, ($+$) )+import qualified Distribution.ParseUtils as ParseUtils (field)+import Distribution.ParseUtils+ ( ParseResult(..), PError(..), syntaxError, PWarning(..), warning+ , simpleField, commaNewLineListField+ , showToken )+import Distribution.Client.ParseUtils+import Distribution.Simple.Command+ ( CommandUI(commandOptions), ShowOrParseArgs(..)+ , OptionField, option, reqArg' )++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative+#endif+import Control.Monad+import Data.Map (Map)+import qualified Data.Map as Map+import Data.Char (isSpace)+import Distribution.Compat.Semigroup+import GHC.Generics (Generic)++------------------------------------------------------------------+-- Representing the project config file in terms of legacy types+--++-- | We already have parsers\/pretty-printers for almost all the fields in the+-- project config file, but they're in terms of the types used for the command+-- line flags for Setup.hs or cabal commands. We don't want to redefine them+-- all, at least not yet so for the moment we use the parsers at the old types+-- and use conversion functions.+--+-- Ultimately if\/when this project-based approach becomes the default then we+-- can redefine the parsers directly for the new types.+--+data LegacyProjectConfig = LegacyProjectConfig {+ legacyPackages :: [String],+ legacyPackagesOptional :: [String],+ legacyPackagesRepo :: [SourceRepo],+ legacyPackagesNamed :: [Dependency],++ legacySharedConfig :: LegacySharedConfig,+ legacyLocalConfig :: LegacyPackageConfig,+ legacySpecificConfig :: Map PackageName LegacyPackageConfig+ } deriving Generic++instance Monoid LegacyProjectConfig where+ mempty = gmempty+ mappend = (<>)++instance Semigroup LegacyProjectConfig where+ (<>) = gmappend++data LegacyPackageConfig = LegacyPackageConfig {+ legacyConfigureFlags :: ConfigFlags,+ legacyInstallPkgFlags :: InstallFlags,+ legacyHaddockFlags :: HaddockFlags+ } deriving Generic++instance Monoid LegacyPackageConfig where+ mempty = gmempty+ mappend = (<>)++instance Semigroup LegacyPackageConfig where+ (<>) = gmappend++data LegacySharedConfig = LegacySharedConfig {+ legacyGlobalFlags :: GlobalFlags,+ legacyConfigureShFlags :: ConfigFlags,+ legacyConfigureExFlags :: ConfigExFlags,+ legacyInstallFlags :: InstallFlags+ } deriving Generic++instance Monoid LegacySharedConfig where+ mempty = gmempty+ mappend = (<>)++instance Semigroup LegacySharedConfig where+ (<>) = gmappend+++------------------------------------------------------------------+-- Converting from and to the legacy types+--++-- | Convert configuration from the @cabal configure@ or @cabal build@ command+-- line into a 'ProjectConfig' value that can combined with configuration from+-- other sources.+--+-- At the moment this uses the legacy command line flag types. See+-- 'LegacyProjectConfig' for an explanation.+--+commandLineFlagsToProjectConfig :: GlobalFlags+ -> ConfigFlags -> ConfigExFlags+ -> InstallFlags -> HaddockFlags+ -> ProjectConfig+commandLineFlagsToProjectConfig globalFlags configFlags configExFlags+ installFlags haddockFlags =+ mempty {+ projectConfigBuildOnly = convertLegacyBuildOnlyFlags+ globalFlags configFlags+ installFlags haddockFlags,+ projectConfigShared = convertLegacyAllPackageFlags+ globalFlags configFlags+ configExFlags installFlags,+ projectConfigLocalPackages = convertLegacyPerPackageFlags+ configFlags installFlags haddockFlags+ }+++-- | Convert from the types currently used for the user-wide @~/.cabal/config@+-- file into the 'ProjectConfig' type.+--+-- Only a subset of the 'ProjectConfig' can be represented in the user-wide+-- config. In particular it does not include packages that are in the project,+-- and it also doesn't support package-specific configuration (only+-- configuration that applies to all packages).+--+convertLegacyGlobalConfig :: SavedConfig -> ProjectConfig+convertLegacyGlobalConfig+ SavedConfig {+ savedGlobalFlags = globalFlags,+ savedInstallFlags = installFlags,+ savedConfigureFlags = configFlags,+ savedConfigureExFlags = configExFlags,+ savedUserInstallDirs = _,+ savedGlobalInstallDirs = _,+ savedUploadFlags = _,+ savedReportFlags = _,+ savedHaddockFlags = haddockFlags+ } =+ mempty {+ projectConfigShared = configAllPackages,+ projectConfigLocalPackages = configLocalPackages,+ projectConfigBuildOnly = configBuildOnly+ }+ where+ --TODO: [code cleanup] eliminate use of default*Flags here and specify the+ -- defaults in the various resolve functions in terms of the new types.+ configExFlags' = defaultConfigExFlags <> configExFlags+ installFlags' = defaultInstallFlags <> installFlags+ haddockFlags' = defaultHaddockFlags <> haddockFlags++ configLocalPackages = convertLegacyPerPackageFlags+ configFlags installFlags' haddockFlags'+ configAllPackages = convertLegacyAllPackageFlags+ globalFlags configFlags+ configExFlags' installFlags'+ configBuildOnly = convertLegacyBuildOnlyFlags+ globalFlags configFlags+ installFlags' haddockFlags'+++-- | Convert the project config from the legacy types to the 'ProjectConfig'+-- and associated types. See 'LegacyProjectConfig' for an explanation of the+-- approach.+--+convertLegacyProjectConfig :: LegacyProjectConfig -> ProjectConfig+convertLegacyProjectConfig+ LegacyProjectConfig {+ legacyPackages,+ legacyPackagesOptional,+ legacyPackagesRepo,+ legacyPackagesNamed,+ legacySharedConfig = LegacySharedConfig globalFlags configShFlags+ configExFlags installSharedFlags,+ legacyLocalConfig = LegacyPackageConfig configFlags installPerPkgFlags+ haddockFlags,+ legacySpecificConfig+ } =++ ProjectConfig {+ projectPackages = legacyPackages,+ projectPackagesOptional = legacyPackagesOptional,+ projectPackagesRepo = legacyPackagesRepo,+ projectPackagesNamed = legacyPackagesNamed,++ projectConfigBuildOnly = configBuildOnly,+ projectConfigShared = configAllPackages,+ projectConfigLocalPackages = configLocalPackages,+ projectConfigSpecificPackage = fmap perPackage legacySpecificConfig+ }+ where+ configLocalPackages = convertLegacyPerPackageFlags+ configFlags installPerPkgFlags haddockFlags+ configAllPackages = convertLegacyAllPackageFlags+ globalFlags (configFlags <> configShFlags)+ configExFlags installSharedFlags+ configBuildOnly = convertLegacyBuildOnlyFlags+ globalFlags configShFlags+ installSharedFlags haddockFlags++ perPackage (LegacyPackageConfig perPkgConfigFlags perPkgInstallFlags+ perPkgHaddockFlags) =+ convertLegacyPerPackageFlags+ perPkgConfigFlags perPkgInstallFlags perPkgHaddockFlags+++-- | Helper used by other conversion functions that returns the+-- 'ProjectConfigShared' subset of the 'ProjectConfig'.+--+convertLegacyAllPackageFlags :: GlobalFlags -> ConfigFlags+ -> ConfigExFlags -> InstallFlags+ -> ProjectConfigShared+convertLegacyAllPackageFlags globalFlags configFlags+ configExFlags installFlags =+ ProjectConfigShared{..}+ where+ GlobalFlags {+ globalConfigFile = _, -- TODO: [required feature]+ globalSandboxConfigFile = _, -- ??+ globalRemoteRepos = projectConfigRemoteRepos,+ globalLocalRepos = projectConfigLocalRepos+ } = globalFlags++ ConfigFlags {+ configProgramPaths,+ configProgramArgs,+ configProgramPathExtra = projectConfigProgramPathExtra,+ configHcFlavor = projectConfigHcFlavor,+ configHcPath = projectConfigHcPath,+ configHcPkg = projectConfigHcPkg,+ --configInstallDirs = projectConfigInstallDirs,+ --configUserInstall = projectConfigUserInstall,+ --configPackageDBs = projectConfigPackageDBs,+ configConfigurationsFlags = projectConfigFlagAssignment,+ configAllowNewer = projectConfigAllowNewer+ } = configFlags+ projectConfigProgramPaths = Map.fromList configProgramPaths+ projectConfigProgramArgs = Map.fromList configProgramArgs++ ConfigExFlags {+ configCabalVersion = projectConfigCabalVersion,+ configExConstraints = projectConfigConstraints,+ configPreferences = projectConfigPreferences,+ configSolver = projectConfigSolver+ } = configExFlags++ InstallFlags {+ installHaddockIndex = projectConfigHaddockIndex,+ --installReinstall = projectConfigReinstall,+ --installAvoidReinstalls = projectConfigAvoidReinstalls,+ --installOverrideReinstall = projectConfigOverrideReinstall,+ installMaxBackjumps = projectConfigMaxBackjumps,+ --installUpgradeDeps = projectConfigUpgradeDeps,+ installReorderGoals = projectConfigReorderGoals,+ --installIndependentGoals = projectConfigIndependentGoals,+ --installShadowPkgs = projectConfigShadowPkgs,+ installStrongFlags = projectConfigStrongFlags+ } = installFlags++++-- | Helper used by other conversion functions that returns the+-- 'PackageConfig' subset of the 'ProjectConfig'.+--+convertLegacyPerPackageFlags :: ConfigFlags -> InstallFlags -> HaddockFlags+ -> PackageConfig+convertLegacyPerPackageFlags configFlags installFlags haddockFlags =+ PackageConfig{..}+ where+ ConfigFlags {+ configVanillaLib = packageConfigVanillaLib,+ configProfLib = packageConfigProfLib,+ configSharedLib = packageConfigSharedLib,+ configDynExe = packageConfigDynExe,+ configProfExe = packageConfigProfExe,+ configProf = packageConfigProf,+ configProfDetail = packageConfigProfDetail,+ configProfLibDetail = packageConfigProfLibDetail,+ configConfigureArgs = packageConfigConfigureArgs,+ configOptimization = packageConfigOptimization,+ configProgPrefix = packageConfigProgPrefix,+ configProgSuffix = packageConfigProgSuffix,+ configGHCiLib = packageConfigGHCiLib,+ configSplitObjs = packageConfigSplitObjs,+ configStripExes = packageConfigStripExes,+ configStripLibs = packageConfigStripLibs,+ configExtraLibDirs = packageConfigExtraLibDirs,+ configExtraFrameworkDirs = packageConfigExtraFrameworkDirs,+ configExtraIncludeDirs = packageConfigExtraIncludeDirs,+ configConfigurationsFlags = _projectConfigFlagAssignment, --TODO: should be per pkg+ configTests = packageConfigTests,+ configBenchmarks = packageConfigBenchmarks,+ configCoverage = coverage,+ configLibCoverage = libcoverage, --deprecated+ configDebugInfo = packageConfigDebugInfo,+ configRelocatable = packageConfigRelocatable+ } = configFlags++ packageConfigCoverage = coverage <> libcoverage+ --TODO: defer this merging to the resolve phase++ InstallFlags {+ installDocumentation = packageConfigDocumentation,+ installRunTests = packageConfigRunTests+ } = installFlags++ HaddockFlags {+ haddockHoogle = packageConfigHaddockHoogle,+ haddockHtml = packageConfigHaddockHtml,+ haddockHtmlLocation = packageConfigHaddockHtmlLocation,+ haddockExecutables = packageConfigHaddockExecutables,+ haddockTestSuites = packageConfigHaddockTestSuites,+ haddockBenchmarks = packageConfigHaddockBenchmarks,+ haddockInternal = packageConfigHaddockInternal,+ haddockCss = packageConfigHaddockCss,+ haddockHscolour = packageConfigHaddockHscolour,+ haddockHscolourCss = packageConfigHaddockHscolourCss,+ haddockContents = packageConfigHaddockContents+ } = haddockFlags++++-- | Helper used by other conversion functions that returns the+-- 'ProjectConfigBuildOnly' subset of the 'ProjectConfig'.+--+convertLegacyBuildOnlyFlags :: GlobalFlags -> ConfigFlags+ -> InstallFlags -> HaddockFlags+ -> ProjectConfigBuildOnly+convertLegacyBuildOnlyFlags globalFlags configFlags+ installFlags haddockFlags =+ ProjectConfigBuildOnly{..}+ where+ GlobalFlags {+ globalCacheDir = projectConfigCacheDir,+ globalLogsDir = projectConfigLogsDir,+ globalWorldFile = projectConfigWorldFile,+ globalHttpTransport = projectConfigHttpTransport,+ globalIgnoreExpiry = projectConfigIgnoreExpiry+ } = globalFlags++ ConfigFlags {+ configVerbosity = projectConfigVerbosity+ } = configFlags++ InstallFlags {+ installDryRun = projectConfigDryRun,+ installOnly = _,+ installOnlyDeps = projectConfigOnlyDeps,+ installRootCmd = projectConfigRootCmd,+ installSummaryFile = projectConfigSummaryFile,+ installLogFile = projectConfigLogFile,+ installBuildReports = projectConfigBuildReports,+ installReportPlanningFailure = projectConfigReportPlanningFailure,+ installSymlinkBinDir = projectConfigSymlinkBinDir,+ installOneShot = projectConfigOneShot,+ installNumJobs = projectConfigNumJobs,+ installOfflineMode = projectConfigOfflineMode+ } = installFlags++ HaddockFlags {+ haddockKeepTempFiles = projectConfigKeepTempFiles --TODO: this ought to live elsewhere+ } = haddockFlags+++convertToLegacyProjectConfig :: ProjectConfig -> LegacyProjectConfig+convertToLegacyProjectConfig+ projectConfig@ProjectConfig {+ projectPackages,+ projectPackagesOptional,+ projectPackagesRepo,+ projectPackagesNamed,+ projectConfigLocalPackages,+ projectConfigSpecificPackage+ } =+ LegacyProjectConfig {+ legacyPackages = projectPackages,+ legacyPackagesOptional = projectPackagesOptional,+ legacyPackagesRepo = projectPackagesRepo,+ legacyPackagesNamed = projectPackagesNamed,+ legacySharedConfig = convertToLegacySharedConfig projectConfig,+ legacyLocalConfig = convertToLegacyAllPackageConfig projectConfig+ <> convertToLegacyPerPackageConfig+ projectConfigLocalPackages,+ legacySpecificConfig = fmap convertToLegacyPerPackageConfig+ projectConfigSpecificPackage+ }++convertToLegacySharedConfig :: ProjectConfig -> LegacySharedConfig+convertToLegacySharedConfig+ ProjectConfig {+ projectConfigBuildOnly = ProjectConfigBuildOnly {..},+ projectConfigShared = ProjectConfigShared {..}+ } =++ LegacySharedConfig {+ legacyGlobalFlags = globalFlags,+ legacyConfigureShFlags = configFlags,+ legacyConfigureExFlags = configExFlags,+ legacyInstallFlags = installFlags+ }+ where+ globalFlags = GlobalFlags {+ globalVersion = mempty,+ globalNumericVersion = mempty,+ globalConfigFile = mempty,+ globalSandboxConfigFile = mempty,+ globalConstraintsFile = mempty,+ globalRemoteRepos = projectConfigRemoteRepos,+ globalCacheDir = projectConfigCacheDir,+ globalLocalRepos = projectConfigLocalRepos,+ globalLogsDir = projectConfigLogsDir,+ globalWorldFile = projectConfigWorldFile,+ globalRequireSandbox = mempty,+ globalIgnoreSandbox = mempty,+ globalIgnoreExpiry = projectConfigIgnoreExpiry,+ globalHttpTransport = projectConfigHttpTransport+ }++ configFlags = mempty {+ configVerbosity = projectConfigVerbosity,+ configAllowNewer = projectConfigAllowNewer+ }++ configExFlags = ConfigExFlags {+ configCabalVersion = projectConfigCabalVersion,+ configExConstraints = projectConfigConstraints,+ configPreferences = projectConfigPreferences,+ configSolver = projectConfigSolver+ }++ installFlags = InstallFlags {+ installDocumentation = mempty,+ installHaddockIndex = projectConfigHaddockIndex,+ installDryRun = projectConfigDryRun,+ installReinstall = mempty, --projectConfigReinstall,+ installAvoidReinstalls = mempty, --projectConfigAvoidReinstalls,+ installOverrideReinstall = mempty, --projectConfigOverrideReinstall,+ installMaxBackjumps = projectConfigMaxBackjumps,+ installUpgradeDeps = mempty, --projectConfigUpgradeDeps,+ installReorderGoals = projectConfigReorderGoals,+ installIndependentGoals = mempty, --projectConfigIndependentGoals,+ installShadowPkgs = mempty, --projectConfigShadowPkgs,+ installStrongFlags = projectConfigStrongFlags,+ installOnly = mempty,+ installOnlyDeps = projectConfigOnlyDeps,+ installRootCmd = projectConfigRootCmd,+ installSummaryFile = projectConfigSummaryFile,+ installLogFile = projectConfigLogFile,+ installBuildReports = projectConfigBuildReports,+ installReportPlanningFailure = projectConfigReportPlanningFailure,+ installSymlinkBinDir = projectConfigSymlinkBinDir,+ installOneShot = projectConfigOneShot,+ installNumJobs = projectConfigNumJobs,+ installRunTests = mempty,+ installOfflineMode = projectConfigOfflineMode+ }+++convertToLegacyAllPackageConfig :: ProjectConfig -> LegacyPackageConfig+convertToLegacyAllPackageConfig+ ProjectConfig {+ projectConfigBuildOnly = ProjectConfigBuildOnly {..},+ projectConfigShared = ProjectConfigShared {..}+ } =++ LegacyPackageConfig {+ legacyConfigureFlags = configFlags,+ legacyInstallPkgFlags= mempty,+ legacyHaddockFlags = haddockFlags+ }+ where+ configFlags = ConfigFlags {+ configPrograms_ = mempty,+ configProgramPaths = Map.toList projectConfigProgramPaths,+ configProgramArgs = Map.toList projectConfigProgramArgs,+ configProgramPathExtra = projectConfigProgramPathExtra,+ configHcFlavor = projectConfigHcFlavor,+ configHcPath = projectConfigHcPath,+ configHcPkg = projectConfigHcPkg,+ configVanillaLib = mempty,+ configProfLib = mempty,+ configSharedLib = mempty,+ configDynExe = mempty,+ configProfExe = mempty,+ configProf = mempty,+ configProfDetail = mempty,+ configProfLibDetail = mempty,+ configConfigureArgs = mempty,+ configOptimization = mempty,+ configProgPrefix = mempty,+ configProgSuffix = mempty,+ configInstallDirs = mempty,+ configScratchDir = mempty,+ configDistPref = mempty,+ configVerbosity = mempty,+ configUserInstall = mempty, --projectConfigUserInstall,+ configPackageDBs = mempty, --projectConfigPackageDBs,+ configGHCiLib = mempty,+ configSplitObjs = mempty,+ configStripExes = mempty,+ configStripLibs = mempty,+ configExtraLibDirs = mempty,+ configExtraFrameworkDirs = mempty,+ configConstraints = mempty,+ configDependencies = mempty,+ configExtraIncludeDirs = mempty,+ configIPID = mempty,+ configConfigurationsFlags = projectConfigFlagAssignment,+ configTests = mempty,+ configCoverage = mempty, --TODO: don't merge+ configLibCoverage = mempty, --TODO: don't merge+ configExactConfiguration = mempty,+ configBenchmarks = mempty,+ configFlagError = mempty, --TODO: ???+ configRelocatable = mempty,+ configDebugInfo = mempty,+ configAllowNewer = mempty+ }++ haddockFlags = mempty {+ haddockKeepTempFiles = projectConfigKeepTempFiles+ }+++convertToLegacyPerPackageConfig :: PackageConfig -> LegacyPackageConfig+convertToLegacyPerPackageConfig PackageConfig {..} =+ LegacyPackageConfig {+ legacyConfigureFlags = configFlags,+ legacyInstallPkgFlags = installFlags,+ legacyHaddockFlags = haddockFlags+ }+ where+ configFlags = ConfigFlags {+ configPrograms_ = configPrograms_ mempty,+ configProgramPaths = mempty,+ configProgramArgs = mempty,+ configProgramPathExtra = mempty,+ configHcFlavor = mempty,+ configHcPath = mempty,+ configHcPkg = mempty,+ configVanillaLib = packageConfigVanillaLib,+ configProfLib = packageConfigProfLib,+ configSharedLib = packageConfigSharedLib,+ configDynExe = packageConfigDynExe,+ configProfExe = packageConfigProfExe,+ configProf = packageConfigProf,+ configProfDetail = packageConfigProfDetail,+ configProfLibDetail = packageConfigProfLibDetail,+ configConfigureArgs = packageConfigConfigureArgs,+ configOptimization = packageConfigOptimization,+ configProgPrefix = packageConfigProgPrefix,+ configProgSuffix = packageConfigProgSuffix,+ configInstallDirs = mempty,+ configScratchDir = mempty,+ configDistPref = mempty,+ configVerbosity = mempty,+ configUserInstall = mempty,+ configPackageDBs = mempty,+ configGHCiLib = packageConfigGHCiLib,+ configSplitObjs = packageConfigSplitObjs,+ configStripExes = packageConfigStripExes,+ configStripLibs = packageConfigStripLibs,+ configExtraLibDirs = packageConfigExtraLibDirs,+ configExtraFrameworkDirs = packageConfigExtraFrameworkDirs,+ configConstraints = mempty,+ configDependencies = mempty,+ configExtraIncludeDirs = packageConfigExtraIncludeDirs,+ configIPID = mempty,+ configConfigurationsFlags = mempty,+ configTests = packageConfigTests,+ configCoverage = packageConfigCoverage, --TODO: don't merge+ configLibCoverage = packageConfigCoverage, --TODO: don't merge+ configExactConfiguration = mempty,+ configBenchmarks = packageConfigBenchmarks,+ configFlagError = mempty, --TODO: ???+ configRelocatable = packageConfigRelocatable,+ configDebugInfo = packageConfigDebugInfo,+ configAllowNewer = mempty+ }++ installFlags = mempty {+ installDocumentation = packageConfigDocumentation,+ installRunTests = packageConfigRunTests+ }++ haddockFlags = HaddockFlags {+ haddockProgramPaths = mempty,+ haddockProgramArgs = mempty,+ haddockHoogle = packageConfigHaddockHoogle,+ haddockHtml = packageConfigHaddockHtml,+ haddockHtmlLocation = packageConfigHaddockHtmlLocation,+ haddockForHackage = mempty, --TODO: added recently+ haddockExecutables = packageConfigHaddockExecutables,+ haddockTestSuites = packageConfigHaddockTestSuites,+ haddockBenchmarks = packageConfigHaddockBenchmarks,+ haddockInternal = packageConfigHaddockInternal,+ haddockCss = packageConfigHaddockCss,+ haddockHscolour = packageConfigHaddockHscolour,+ haddockHscolourCss = packageConfigHaddockHscolourCss,+ haddockContents = packageConfigHaddockContents,+ haddockDistPref = mempty,+ haddockKeepTempFiles = mempty,+ haddockVerbosity = mempty+ }+++------------------------------------------------+-- Parsing and showing the project config file+--++parseLegacyProjectConfig :: String -> ParseResult LegacyProjectConfig+parseLegacyProjectConfig =+ parseConfig legacyProjectConfigFieldDescrs+ legacyPackageConfigSectionDescrs+ mempty++showLegacyProjectConfig :: LegacyProjectConfig -> String+showLegacyProjectConfig config =+ Disp.render $+ showConfig legacyProjectConfigFieldDescrs+ legacyPackageConfigSectionDescrs+ config+ $+$+ Disp.text ""+++legacyProjectConfigFieldDescrs :: [FieldDescr LegacyProjectConfig]+legacyProjectConfigFieldDescrs =++ [ newLineListField "packages"+ (Disp.text . renderPackageLocationToken) parsePackageLocationTokenQ+ legacyPackages+ (\v flags -> flags { legacyPackages = v })+ , newLineListField "optional-packages"+ (Disp.text . renderPackageLocationToken) parsePackageLocationTokenQ+ legacyPackagesOptional+ (\v flags -> flags { legacyPackagesOptional = v })+ , commaNewLineListField "extra-packages"+ disp parse+ legacyPackagesNamed+ (\v flags -> flags { legacyPackagesNamed = v })+ ]++ ++ map (liftField+ legacySharedConfig+ (\flags conf -> conf { legacySharedConfig = flags }))+ legacySharedConfigFieldDescrs++ ++ map (liftField+ legacyLocalConfig+ (\flags conf -> conf { legacyLocalConfig = flags }))+ legacyPackageConfigFieldDescrs++-- | This is a bit tricky since it has to cover globs which have embedded @,@+-- chars. But we don't just want to parse strictly as a glob since we want to+-- allow http urls which don't parse as globs, and possibly some+-- system-dependent file paths. So we parse fairly liberally as a token, but+-- we allow @,@ inside matched @{}@ braces.+--+parsePackageLocationTokenQ :: ReadP r String+parsePackageLocationTokenQ = parseHaskellString+ Parse.<++ parsePackageLocationToken+ where+ parsePackageLocationToken :: ReadP r String+ parsePackageLocationToken = fmap fst (Parse.gather outerTerm)+ where+ outerTerm = alternateEither1 outerToken (braces innerTerm)+ innerTerm = alternateEither innerToken (braces innerTerm)+ outerToken = Parse.munch1 outerChar >> return ()+ innerToken = Parse.munch1 innerChar >> return ()+ outerChar c = not (isSpace c || c == '{' || c == '}' || c == ',')+ innerChar c = not (isSpace c || c == '{' || c == '}')+ braces = Parse.between (Parse.char '{') (Parse.char '}')++ alternateEither, alternateEither1,+ alternatePQs, alternate1PQs, alternateQsP, alternate1QsP+ :: ReadP r () -> ReadP r () -> ReadP r ()++ alternateEither1 p q = alternate1PQs p q +++ alternate1QsP q p+ alternateEither p q = alternateEither1 p q +++ return ()+ alternate1PQs p q = p >> alternateQsP q p+ alternatePQs p q = alternate1PQs p q +++ return ()+ alternate1QsP q p = Parse.many1 q >> alternatePQs p q+ alternateQsP q p = alternate1QsP q p +++ return ()++renderPackageLocationToken :: String -> String+renderPackageLocationToken s | needsQuoting = show s+ | otherwise = s+ where+ needsQuoting = not (ok 0 s)+ || s == "." -- . on its own on a line has special meaning+ || take 2 s == "--" -- on its own line is comment syntax+ --TODO: [code cleanup] these "." and "--" escaping issues+ -- ought to be dealt with systematically in ParseUtils.+ ok :: Int -> String -> Bool+ ok n [] = n == 0+ ok _ ('"':_) = False+ ok n ('{':cs) = ok (n+1) cs+ ok n ('}':cs) = ok (n-1) cs+ ok n (',':cs) = (n > 0) && ok n cs+ ok _ (c:_)+ | isSpace c = False+ ok n (_ :cs) = ok n cs+++legacySharedConfigFieldDescrs :: [FieldDescr LegacySharedConfig]+legacySharedConfigFieldDescrs =++ ( liftFields+ legacyGlobalFlags+ (\flags conf -> conf { legacyGlobalFlags = flags })+ . addFields+ [ newLineListField "local-repo"+ showTokenQ parseTokenQ+ (fromNubList . globalLocalRepos)+ (\v conf -> conf { globalLocalRepos = toNubList v })+ ]+ . filterFields+ [ "remote-repo-cache"+ , "logs-dir", "world-file", "ignore-expiry", "http-transport"+ ]+ . commandOptionsToFields+ ) (commandOptions (globalCommand []) ParseArgs)+ +++ ( liftFields+ legacyConfigureShFlags+ (\flags conf -> conf { legacyConfigureShFlags = flags })+ . addFields+ [ simpleField "allow-newer"+ (maybe mempty dispAllowNewer) (fmap Just parseAllowNewer)+ configAllowNewer (\v conf -> conf { configAllowNewer = v })+ ]+ . filterFields ["verbose"]+ . commandOptionsToFields+ ) (configureOptions ParseArgs)+ +++ ( liftFields+ legacyConfigureExFlags+ (\flags conf -> conf { legacyConfigureExFlags = flags })+ . addFields+ [ commaNewLineListField "constraints"+ (disp . fst) (fmap (\constraint -> (constraint, constraintSrc)) parse)+ configExConstraints (\v conf -> conf { configExConstraints = v })++ , commaNewLineListField "preferences"+ disp parse+ configPreferences (\v conf -> conf { configPreferences = v })+ ]+ . filterFields+ [ "cabal-lib-version", "solver"+ -- not "constraint" or "preference", we use our own plural ones above+ ]+ . commandOptionsToFields+ ) (configureExOptions ParseArgs constraintSrc)+ +++ ( liftFields+ legacyInstallFlags+ (\flags conf -> conf { legacyInstallFlags = flags })+ . addFields+ [ newLineListField "build-summary"+ (showTokenQ . fromPathTemplate) (fmap toPathTemplate parseTokenQ)+ (fromNubList . installSummaryFile)+ (\v conf -> conf { installSummaryFile = toNubList v })+ ]+ . filterFields+ [ "doc-index-file"+ , "root-cmd", "symlink-bindir"+ , "build-log"+ , "remote-build-reporting", "report-planning-failure"+ , "one-shot", "jobs", "offline"+ -- solver flags:+ , "max-backjumps", "reorder-goals", "strong-flags"+ ]+ . commandOptionsToFields+ ) (installOptions ParseArgs)+ where+ constraintSrc = ConstraintSourceProjectConfig "TODO"++parseAllowNewer :: ReadP r AllowNewer+parseAllowNewer =+ ((const AllowNewerNone <$> (Parse.string "none" +++ Parse.string "None"))+ +++ (const AllowNewerAll <$> (Parse.string "all" +++ Parse.string "All")))+ <++ ( AllowNewerSome <$> parseOptCommaList parse)++dispAllowNewer :: AllowNewer -> Doc+dispAllowNewer AllowNewerNone = Disp.text "None"+dispAllowNewer (AllowNewerSome pkgs) = Disp.fsep . Disp.punctuate Disp.comma+ . map disp $ pkgs+dispAllowNewer AllowNewerAll = Disp.text "All"+++legacyPackageConfigFieldDescrs :: [FieldDescr LegacyPackageConfig]+legacyPackageConfigFieldDescrs =+ ( liftFields+ legacyConfigureFlags+ (\flags conf -> conf { legacyConfigureFlags = flags })+ . addFields+ [ newLineListField "extra-include-dirs"+ showTokenQ parseTokenQ+ configExtraIncludeDirs+ (\v conf -> conf { configExtraIncludeDirs = v })+ , newLineListField "extra-lib-dirs"+ showTokenQ parseTokenQ+ configExtraLibDirs+ (\v conf -> conf { configExtraLibDirs = v })+ , newLineListField "extra-framework-dirs"+ showTokenQ parseTokenQ+ configExtraFrameworkDirs+ (\v conf -> conf { configExtraFrameworkDirs = v })+ , newLineListField "extra-prog-path"+ showTokenQ parseTokenQ+ (fromNubList . configProgramPathExtra)+ (\v conf -> conf { configProgramPathExtra = toNubList v })+ , newLineListField "configure-options"+ showTokenQ parseTokenQ+ configConfigureArgs+ (\v conf -> conf { configConfigureArgs = v })+ , simpleField "flags"+ dispFlagAssignment parseFlagAssignment+ configConfigurationsFlags+ (\v conf -> conf { configConfigurationsFlags = v })+ ]+ . filterFields+ [ "compiler", "with-compiler", "with-hc-pkg"+ , "program-prefix", "program-suffix"+ , "library-vanilla", "library-profiling"+ , "shared", "executable-dynamic"+ , "profiling", "executable-profiling"+ , "profiling-detail", "library-profiling-detail"+ , "optimization", "debug-info", "library-for-ghci", "split-objs"+ , "executable-stripping", "library-stripping"+ , "tests", "benchmarks"+ , "coverage", "library-coverage"+ , "relocatable"+ -- not "extra-include-dirs", "extra-lib-dirs", "extra-framework-dirs"+ -- or "extra-prog-path". We use corrected ones above that parse+ -- as list fields.+ ]+ . commandOptionsToFields+ ) (configureOptions ParseArgs)+ +++ liftFields+ legacyConfigureFlags+ (\flags conf -> conf { legacyConfigureFlags = flags })+ [ overrideFieldCompiler+ , overrideFieldOptimization+ , overrideFieldDebugInfo+ ]+ +++ ( liftFields+ legacyInstallPkgFlags+ (\flags conf -> conf { legacyInstallPkgFlags = flags })+ . filterFields+ [ "documentation", "run-tests"+ ]+ . commandOptionsToFields+ ) (installOptions ParseArgs)+ +++ ( liftFields+ legacyHaddockFlags+ (\flags conf -> conf { legacyHaddockFlags = flags })+ . mapFieldNames+ ("haddock-"++)+ . filterFields+ [ "hoogle", "html", "html-location"+ , "executables", "tests", "benchmarks", "all", "internal", "css"+ , "hyperlink-source", "hscolour-css"+ , "contents-location", "keep-temp-files"+ ]+ . commandOptionsToFields+ ) (haddockOptions ParseArgs)++ where+ overrideFieldCompiler =+ simpleField "compiler"+ (fromFlagOrDefault Disp.empty . fmap disp)+ (Parse.option mempty (fmap toFlag parse))+ configHcFlavor (\v flags -> flags { configHcFlavor = v })+++ -- TODO: [code cleanup] The following is a hack. The "optimization" and+ -- "debug-info" fields are OptArg, and viewAsFieldDescr fails on that.+ -- Instead of a hand-written parser and printer, we should handle this case+ -- properly in the library.++ overrideFieldOptimization =+ liftField configOptimization+ (\v flags -> flags { configOptimization = v }) $+ let name = "optimization" in+ FieldDescr name+ (\f -> case f of+ Flag NoOptimisation -> Disp.text "False"+ Flag NormalOptimisation -> Disp.text "True"+ Flag MaximumOptimisation -> Disp.text "2"+ _ -> Disp.empty)+ (\line str _ -> case () of+ _ | str == "False" -> ParseOk [] (Flag NoOptimisation)+ | str == "True" -> ParseOk [] (Flag NormalOptimisation)+ | str == "0" -> ParseOk [] (Flag NoOptimisation)+ | str == "1" -> ParseOk [] (Flag NormalOptimisation)+ | str == "2" -> ParseOk [] (Flag MaximumOptimisation)+ | lstr == "false" -> ParseOk [caseWarning] (Flag NoOptimisation)+ | lstr == "true" -> ParseOk [caseWarning] (Flag NormalOptimisation)+ | otherwise -> ParseFailed (NoParse name line)+ where+ lstr = lowercase str+ caseWarning = PWarning $+ "The '" ++ name ++ "' field is case sensitive, use 'True' or 'False'.")++ overrideFieldDebugInfo =+ liftField configDebugInfo (\v flags -> flags { configDebugInfo = v }) $+ let name = "debug-info" in+ FieldDescr name+ (\f -> case f of+ Flag NoDebugInfo -> Disp.text "False"+ Flag MinimalDebugInfo -> Disp.text "1"+ Flag NormalDebugInfo -> Disp.text "True"+ Flag MaximalDebugInfo -> Disp.text "3"+ _ -> Disp.empty)+ (\line str _ -> case () of+ _ | str == "False" -> ParseOk [] (Flag NoDebugInfo)+ | str == "True" -> ParseOk [] (Flag NormalDebugInfo)+ | str == "0" -> ParseOk [] (Flag NoDebugInfo)+ | str == "1" -> ParseOk [] (Flag MinimalDebugInfo)+ | str == "2" -> ParseOk [] (Flag NormalDebugInfo)+ | str == "3" -> ParseOk [] (Flag MaximalDebugInfo)+ | lstr == "false" -> ParseOk [caseWarning] (Flag NoDebugInfo)+ | lstr == "true" -> ParseOk [caseWarning] (Flag NormalDebugInfo)+ | otherwise -> ParseFailed (NoParse name line)+ where+ lstr = lowercase str+ caseWarning = PWarning $+ "The '" ++ name ++ "' field is case sensitive, use 'True' or 'False'.")+++legacyPackageConfigSectionDescrs :: [SectionDescr LegacyProjectConfig]+legacyPackageConfigSectionDescrs =+ [ packageRepoSectionDescr+ , packageSpecificOptionsSectionDescr+ , liftSection+ legacyLocalConfig+ (\flags conf -> conf { legacyLocalConfig = flags })+ programOptionsSectionDescr+ , liftSection+ legacyLocalConfig+ (\flags conf -> conf { legacyLocalConfig = flags })+ programLocationsSectionDescr+ , liftSection+ legacySharedConfig+ (\flags conf -> conf { legacySharedConfig = flags }) $+ liftSection+ legacyGlobalFlags+ (\flags conf -> conf { legacyGlobalFlags = flags })+ remoteRepoSectionDescr+ ]++packageRepoSectionDescr :: SectionDescr LegacyProjectConfig+packageRepoSectionDescr =+ SectionDescr {+ sectionName = "source-repository-package",+ sectionFields = sourceRepoFieldDescrs,+ sectionSubsections = [],+ sectionGet = map (\x->("", x))+ . legacyPackagesRepo,+ sectionSet =+ \lineno unused pkgrepo projconf -> do+ unless (null unused) $+ syntaxError lineno "the section 'source-repository-package' takes no arguments"+ return projconf {+ legacyPackagesRepo = legacyPackagesRepo projconf ++ [pkgrepo]+ },+ sectionEmpty = SourceRepo {+ repoKind = RepoThis, -- hopefully unused+ repoType = Nothing,+ repoLocation = Nothing,+ repoModule = Nothing,+ repoBranch = Nothing,+ repoTag = Nothing,+ repoSubdir = Nothing+ }+ }++packageSpecificOptionsSectionDescr :: SectionDescr LegacyProjectConfig+packageSpecificOptionsSectionDescr =+ SectionDescr {+ sectionName = "package",+ sectionFields = legacyPackageConfigFieldDescrs+ ++ programOptionsFieldDescrs+ (configProgramArgs . legacyConfigureFlags)+ (\args pkgconf -> pkgconf {+ legacyConfigureFlags = (legacyConfigureFlags pkgconf) {+ configProgramArgs = args+ }+ }+ ),+ sectionSubsections = [],+ sectionGet = \projconf ->+ [ (display pkgname, pkgconf)+ | (pkgname, pkgconf) <- Map.toList (legacySpecificConfig projconf) ],+ sectionSet =+ \lineno pkgnamestr pkgconf projconf -> do+ pkgname <- case simpleParse pkgnamestr of+ Just pkgname -> return pkgname+ Nothing -> syntaxError lineno $+ "a 'package' section requires a package name "+ ++ "as an argument"+ return projconf {+ legacySpecificConfig =+ Map.insertWith mappend pkgname pkgconf+ (legacySpecificConfig projconf)+ },+ sectionEmpty = mempty+ }++programOptionsFieldDescrs :: (a -> [(String, [String])])+ -> ([(String, [String])] -> a -> a)+ -> [FieldDescr a]+programOptionsFieldDescrs get set =+ commandOptionsToFields+ $ programConfigurationOptions+ defaultProgramDb+ ParseArgs get set++programOptionsSectionDescr :: SectionDescr LegacyPackageConfig+programOptionsSectionDescr =+ SectionDescr {+ sectionName = "program-options",+ sectionFields = programOptionsFieldDescrs+ configProgramArgs+ (\args conf -> conf { configProgramArgs = args }),+ sectionSubsections = [],+ sectionGet = (\x->[("", x)])+ . legacyConfigureFlags,+ sectionSet =+ \lineno unused confflags pkgconf -> do+ unless (null unused) $+ syntaxError lineno "the section 'program-options' takes no arguments"+ return pkgconf {+ legacyConfigureFlags = legacyConfigureFlags pkgconf <> confflags+ },+ sectionEmpty = mempty+ }++programLocationsFieldDescrs :: [FieldDescr ConfigFlags]+programLocationsFieldDescrs =+ commandOptionsToFields+ $ programConfigurationPaths'+ (++ "-location")+ defaultProgramDb+ ParseArgs+ configProgramPaths+ (\paths conf -> conf { configProgramPaths = paths })++programLocationsSectionDescr :: SectionDescr LegacyPackageConfig+programLocationsSectionDescr =+ SectionDescr {+ sectionName = "program-locations",+ sectionFields = programLocationsFieldDescrs,+ sectionSubsections = [],+ sectionGet = (\x->[("", x)])+ . legacyConfigureFlags,+ sectionSet =+ \lineno unused confflags pkgconf -> do+ unless (null unused) $+ syntaxError lineno "the section 'program-locations' takes no arguments"+ return pkgconf {+ legacyConfigureFlags = legacyConfigureFlags pkgconf <> confflags+ },+ sectionEmpty = mempty+ }+++-- | For each known program @PROG@ in 'progConf', produce a @PROG-options@+-- 'OptionField'.+programConfigurationOptions+ :: ProgramDb+ -> ShowOrParseArgs+ -> (flags -> [(String, [String])])+ -> ([(String, [String])] -> (flags -> flags))+ -> [OptionField flags]+programConfigurationOptions progConf showOrParseArgs get set =+ case showOrParseArgs of+ -- we don't want a verbose help text list so we just show a generic one:+ ShowArgs -> [programOptions "PROG"]+ ParseArgs -> map (programOptions . programName . fst)+ (knownPrograms progConf)+ where+ programOptions prog =+ option "" [prog ++ "-options"]+ ("give extra options to " ++ prog)+ get set+ (reqArg' "OPTS" (\args -> [(prog, splitArgs args)])+ (\progArgs -> [ joinsArgs args+ | (prog', args) <- progArgs, prog==prog' ]))+++ joinsArgs = unwords . map escape+ escape arg | any isSpace arg = "\"" ++ arg ++ "\""+ | otherwise = arg+++remoteRepoSectionDescr :: SectionDescr GlobalFlags+remoteRepoSectionDescr =+ SectionDescr {+ sectionName = "repository",+ sectionFields = remoteRepoFields,+ sectionSubsections = [],+ sectionGet = map (\x->(remoteRepoName x, x)) . fromNubList+ . globalRemoteRepos,+ sectionSet =+ \lineno reponame repo0 conf -> do+ when (null reponame) $+ syntaxError lineno $ "a 'repository' section requires the "+ ++ "repository name as an argument"+ let repo = repo0 { remoteRepoName = reponame }+ when (remoteRepoKeyThreshold repo+ > length (remoteRepoRootKeys repo)) $+ warning $ "'key-threshold' for repository "+ ++ show (remoteRepoName repo)+ ++ " higher than number of keys"+ when (not (null (remoteRepoRootKeys repo))+ && remoteRepoSecure repo /= Just True) $+ warning $ "'root-keys' for repository "+ ++ show (remoteRepoName repo)+ ++ " non-empty, but 'secure' not set to True."+ return conf {+ globalRemoteRepos = overNubList (++[repo]) (globalRemoteRepos conf)+ },+ sectionEmpty = emptyRemoteRepo ""+ }+++-------------------------------+-- Local field utils+--++--TODO: [code cleanup] all these utils should move to Distribution.ParseUtils+-- either augmenting or replacing the ones there++--TODO: [code cleanup] this is a different definition from listField, like+-- commaNewLineListField it pretty prints on multiple lines+newLineListField :: String -> (a -> Doc) -> ReadP [a] a+ -> (b -> [a]) -> ([a] -> b -> b) -> FieldDescr b+newLineListField = listFieldWithSep Disp.sep++--TODO: [code cleanup] local copy purely so we can use the fixed version+-- of parseOptCommaList below+listFieldWithSep :: ([Doc] -> Doc) -> String -> (a -> Doc) -> ReadP [a] a+ -> (b -> [a]) -> ([a] -> b -> b) -> FieldDescr b+listFieldWithSep separator name showF readF get set =+ liftField get set' $+ ParseUtils.field name showF' (parseOptCommaList readF)+ where+ set' xs b = set (get b ++ xs) b+ showF' = separator . map showF++--TODO: [code cleanup] local redefinition that should replace the version in+-- D.ParseUtils. This version avoid parse ambiguity for list element parsers+-- that have multiple valid parses of prefixes.+parseOptCommaList :: ReadP r a -> ReadP r [a]+parseOptCommaList p = Parse.sepBy p sep+ where+ -- The separator must not be empty or it introduces ambiguity+ sep = (Parse.skipSpaces >> Parse.char ',' >> Parse.skipSpaces)+ +++ (Parse.satisfy isSpace >> Parse.skipSpaces)++--TODO: [code cleanup] local redefinition that should replace the version in+-- D.ParseUtils called showFilePath. This version escapes "." and "--" which+-- otherwise are special syntax.+showTokenQ :: String -> Doc+showTokenQ "" = Disp.empty+showTokenQ x@('-':'-':_) = Disp.text (show x)+showTokenQ x@('.':[]) = Disp.text (show x)+showTokenQ x = showToken x++-- This is just a copy of parseTokenQ, using the fixed parseHaskellString+parseTokenQ :: ReadP r String+parseTokenQ = parseHaskellString+ <++ Parse.munch1 (\x -> not (isSpace x) && x /= ',')++--TODO: [code cleanup] use this to replace the parseHaskellString in+-- Distribution.ParseUtils. It turns out Read instance for String accepts+-- the ['a', 'b'] syntax, which we do not want. In particular it messes+-- up any token starting with [].+parseHaskellString :: ReadP r String+parseHaskellString =+ Parse.readS_to_P $+ Read.readPrec_to_S (do Read.String s <- Read.lexP; return s) 0++-- Handy util+addFields :: [FieldDescr a]+ -> ([FieldDescr a] -> [FieldDescr a])+addFields = (++)
+ cabal/cabal-install/Distribution/Client/ProjectConfig/Types.hs view
@@ -0,0 +1,333 @@+{-# LANGUAGE DeriveGeneric, DeriveDataTypeable #-}++-- | Handling project configuration, types.+--+module Distribution.Client.ProjectConfig.Types (++ -- * Types for project config+ ProjectConfig(..),+ ProjectConfigBuildOnly(..),+ ProjectConfigShared(..),+ PackageConfig(..),++ -- * Resolving configuration+ SolverSettings(..),+ BuildTimeSettings(..),++ ) where++import Distribution.Client.Types+ ( RemoteRepo )+import Distribution.Client.Dependency.Types+ ( PreSolver, ConstraintSource )+import Distribution.Client.Targets+ ( UserConstraint )+import Distribution.Client.BuildReports.Types + ( ReportLevel(..) )++import Distribution.Package+ ( PackageName, PackageId, UnitId, Dependency )+import Distribution.Version+ ( Version )+import Distribution.System+ ( Platform )+import Distribution.PackageDescription+ ( FlagAssignment, SourceRepo(..) )+import Distribution.Simple.Compiler+ ( Compiler, CompilerFlavor+ , OptimisationLevel(..), ProfDetailLevel, DebugInfoLevel(..) )+import Distribution.Simple.Setup+ ( Flag, AllowNewer(..) )+import Distribution.Simple.InstallDirs+ ( PathTemplate )+import Distribution.Utils.NubList+ ( NubList )+import Distribution.Verbosity+ ( Verbosity )++import Data.Map (Map)+import Distribution.Compat.Binary (Binary)+import Distribution.Compat.Semigroup+import GHC.Generics (Generic)+++-------------------------------+-- Project config types+--++-- | This type corresponds directly to what can be written in the+-- @cabal.project@ file. Other sources of configuration can also be injected+-- into this type, such as the user-wide @~/.cabal/config@ file and the+-- command line of @cabal configure@ or @cabal build@.+--+-- Since it corresponds to the external project file it is an instance of+-- 'Monoid' and all the fields can be empty. This also means there has to+-- be a step where we resolve configuration. At a minimum resolving means+-- applying defaults but it can also mean merging information from multiple+-- sources. For example for package-specific configuration the project file+-- can specify configuration that applies to all local packages, and then+-- additional configuration for a specific package.+--+-- Future directions: multiple profiles, conditionals. If we add these+-- features then the gap between configuration as written in the config file+-- and resolved settings we actually use will become even bigger.+--+data ProjectConfig+ = ProjectConfig {++ -- | Packages in this project, including local dirs, local .cabal files+ -- local and remote tarballs. Where these are file globs, they must+ -- match something.+ projectPackages :: [String],++ -- | Like 'projectConfigPackageGlobs' but /optional/ in the sense that+ -- file globs are allowed to match nothing. The primary use case for+ -- this is to be able to say @optional-packages: */@ to automagically+ -- pick up deps that we unpack locally.+ projectPackagesOptional :: [String],++ -- | Packages in this project from remote source repositories.+ projectPackagesRepo :: [SourceRepo],++ -- | Packages in this project from hackage repositories.+ projectPackagesNamed :: [Dependency],++ projectConfigBuildOnly :: ProjectConfigBuildOnly,+ projectConfigShared :: ProjectConfigShared,+ projectConfigLocalPackages :: PackageConfig,+ projectConfigSpecificPackage :: Map PackageName PackageConfig+ }+ deriving (Eq, Show, Generic)++-- | That part of the project configuration that only affects /how/ we build+-- and not the /value/ of the things we build. This means this information+-- does not need to be tracked for changes since it does not affect the+-- outcome.+--+data ProjectConfigBuildOnly+ = ProjectConfigBuildOnly {+ projectConfigVerbosity :: Flag Verbosity,+ projectConfigDryRun :: Flag Bool,+ projectConfigOnlyDeps :: Flag Bool,+ projectConfigSummaryFile :: NubList PathTemplate,+ projectConfigLogFile :: Flag PathTemplate,+ projectConfigBuildReports :: Flag ReportLevel,+ projectConfigReportPlanningFailure :: Flag Bool,+ projectConfigSymlinkBinDir :: Flag FilePath,+ projectConfigOneShot :: Flag Bool,+ projectConfigNumJobs :: Flag (Maybe Int),+ projectConfigOfflineMode :: Flag Bool,+ projectConfigKeepTempFiles :: Flag Bool,+ projectConfigHttpTransport :: Flag String,+ projectConfigIgnoreExpiry :: Flag Bool,+ projectConfigCacheDir :: Flag FilePath,+ projectConfigLogsDir :: Flag FilePath,+ projectConfigWorldFile :: Flag FilePath,+ projectConfigRootCmd :: Flag String+ }+ deriving (Eq, Show, Generic)+++-- | Project configuration that is shared between all packages in the project.+-- In particular this includes configuration that affects the solver.+--+data ProjectConfigShared+ = ProjectConfigShared {+ projectConfigProgramPaths :: Map String FilePath,+ projectConfigProgramArgs :: Map String [String],+ projectConfigProgramPathExtra :: NubList FilePath,+ projectConfigHcFlavor :: Flag CompilerFlavor,+ projectConfigHcPath :: Flag FilePath,+ projectConfigHcPkg :: Flag FilePath,+ projectConfigHaddockIndex :: Flag PathTemplate,++ -- Things that only make sense for manual mode, not --local mode+ -- too much control!+ --projectConfigUserInstall :: Flag Bool,+ --projectConfigInstallDirs :: InstallDirs (Flag PathTemplate),+ --TODO: [required eventually] decide what to do with InstallDirs+ -- currently we don't allow it to be specified in the config file+ --projectConfigPackageDBs :: [Maybe PackageDB],++ -- configuration used both by the solver and other phases+ projectConfigRemoteRepos :: NubList RemoteRepo, -- ^ Available Hackage servers.+ projectConfigLocalRepos :: NubList FilePath,++ -- solver configuration+ projectConfigConstraints :: [(UserConstraint, ConstraintSource)],+ projectConfigPreferences :: [Dependency],+ projectConfigFlagAssignment :: FlagAssignment, --TODO: [required eventually] must be per-package, not global+ projectConfigCabalVersion :: Flag Version, --TODO: [required eventually] unused+ projectConfigSolver :: Flag PreSolver,+ projectConfigAllowNewer :: Maybe AllowNewer,+ projectConfigMaxBackjumps :: Flag Int,+ projectConfigReorderGoals :: Flag Bool,+ projectConfigStrongFlags :: Flag Bool++ -- More things that only make sense for manual mode, not --local mode+ -- too much control!+ --projectConfigIndependentGoals :: Flag Bool,+ --projectConfigShadowPkgs :: Flag Bool,+ --projectConfigReinstall :: Flag Bool,+ --projectConfigAvoidReinstalls :: Flag Bool,+ --projectConfigOverrideReinstall :: Flag Bool,+ --projectConfigUpgradeDeps :: Flag Bool+ }+ deriving (Eq, Show, Generic)+++-- | Project configuration that is specific to each package, that is where we+-- can in principle have different values for different packages in the same+-- project.+--+data PackageConfig+ = PackageConfig {+ packageConfigVanillaLib :: Flag Bool,+ packageConfigSharedLib :: Flag Bool,+ packageConfigDynExe :: Flag Bool,+ packageConfigProf :: Flag Bool, --TODO: [code cleanup] sort out+ packageConfigProfLib :: Flag Bool, -- this duplication+ packageConfigProfExe :: Flag Bool, -- and consistency+ packageConfigProfDetail :: Flag ProfDetailLevel,+ packageConfigProfLibDetail :: Flag ProfDetailLevel,+ packageConfigConfigureArgs :: [String],+ packageConfigOptimization :: Flag OptimisationLevel,+ packageConfigProgPrefix :: Flag PathTemplate,+ packageConfigProgSuffix :: Flag PathTemplate,+ packageConfigExtraLibDirs :: [FilePath],+ packageConfigExtraFrameworkDirs :: [FilePath],+ packageConfigExtraIncludeDirs :: [FilePath],+ packageConfigGHCiLib :: Flag Bool,+ packageConfigSplitObjs :: Flag Bool,+ packageConfigStripExes :: Flag Bool,+ packageConfigStripLibs :: Flag Bool,+ packageConfigTests :: Flag Bool,+ packageConfigBenchmarks :: Flag Bool,+ packageConfigCoverage :: Flag Bool,+ packageConfigRelocatable :: Flag Bool,+ packageConfigDebugInfo :: Flag DebugInfoLevel,+ packageConfigRunTests :: Flag Bool, --TODO: [required eventually] use this+ packageConfigDocumentation :: Flag Bool, --TODO: [required eventually] use this+ packageConfigHaddockHoogle :: Flag Bool, --TODO: [required eventually] use this+ packageConfigHaddockHtml :: Flag Bool, --TODO: [required eventually] use this+ packageConfigHaddockHtmlLocation :: Flag String, --TODO: [required eventually] use this+ packageConfigHaddockExecutables :: Flag Bool, --TODO: [required eventually] use this+ packageConfigHaddockTestSuites :: Flag Bool, --TODO: [required eventually] use this+ packageConfigHaddockBenchmarks :: Flag Bool, --TODO: [required eventually] use this+ packageConfigHaddockInternal :: Flag Bool, --TODO: [required eventually] use this+ packageConfigHaddockCss :: Flag FilePath, --TODO: [required eventually] use this+ packageConfigHaddockHscolour :: Flag Bool, --TODO: [required eventually] use this+ packageConfigHaddockHscolourCss :: Flag FilePath, --TODO: [required eventually] use this+ packageConfigHaddockContents :: Flag PathTemplate --TODO: [required eventually] use this+ }+ deriving (Eq, Show, Generic)++instance Binary ProjectConfig+instance Binary ProjectConfigBuildOnly+instance Binary ProjectConfigShared+instance Binary PackageConfig+++instance Monoid ProjectConfig where+ mempty = gmempty+ mappend = (<>)++instance Semigroup ProjectConfig where+ (<>) = gmappend+++instance Monoid ProjectConfigBuildOnly where+ mempty = gmempty+ mappend = (<>)++instance Semigroup ProjectConfigBuildOnly where+ (<>) = gmappend+++instance Monoid ProjectConfigShared where+ mempty = gmempty+ mappend = (<>)++instance Semigroup ProjectConfigShared where+ (<>) = gmappend+++instance Monoid PackageConfig where+ mempty = gmempty+ mappend = (<>)++instance Semigroup PackageConfig where+ (<>) = gmappend++----------------------------------------+-- Resolving configuration to settings+--++-- | Resolved configuration for the solver. The idea is that this is easier to+-- use than the raw configuration because in the raw configuration everything+-- is optional (monoidial). In the 'BuildTimeSettings' every field is filled+-- in, if only with the defaults.+--+-- Use 'resolveSolverSettings' to make one from the project config (by+-- applying defaults etc).+--+data SolverSettings+ = SolverSettings {+ solverSettingRemoteRepos :: [RemoteRepo], -- ^ Available Hackage servers.+ solverSettingLocalRepos :: [FilePath],+ solverSettingConstraints :: [(UserConstraint, ConstraintSource)],+ solverSettingPreferences :: [Dependency],+ solverSettingFlagAssignment :: FlagAssignment, --TODO: [required eventually] must be per-package, not global+ solverSettingCabalVersion :: Maybe Version, --TODO: [required eventually] unused+ solverSettingSolver :: PreSolver,+ solverSettingAllowNewer :: AllowNewer,+ solverSettingMaxBackjumps :: Maybe Int,+ solverSettingReorderGoals :: Bool,+ solverSettingStrongFlags :: Bool+ -- Things that only make sense for manual mode, not --local mode+ -- too much control!+ --solverSettingIndependentGoals :: Bool,+ --solverSettingShadowPkgs :: Bool,+ --solverSettingReinstall :: Bool,+ --solverSettingAvoidReinstalls :: Bool,+ --solverSettingOverrideReinstall :: Bool,+ --solverSettingUpgradeDeps :: Bool+ }+ deriving (Eq, Show, Generic)++instance Binary SolverSettings+++-- | Resolved configuration for things that affect how we build and not the+-- value of the things we build. The idea is that this is easier to use than+-- the raw configuration because in the raw configuration everything is+-- optional (monoidial). In the 'BuildTimeSettings' every field is filled in,+-- if only with the defaults.+--+-- Use 'resolveBuildTimeSettings' to make one from the project config (by+-- applying defaults etc).+--+data BuildTimeSettings+ = BuildTimeSettings {+ buildSettingDryRun :: Bool,+ buildSettingOnlyDeps :: Bool,+ buildSettingSummaryFile :: [PathTemplate],+ buildSettingLogFile :: Maybe (Compiler -> Platform+ -> PackageId -> UnitId+ -> FilePath),+ buildSettingLogVerbosity :: Verbosity,+ buildSettingBuildReports :: ReportLevel,+ buildSettingReportPlanningFailure :: Bool,+ buildSettingSymlinkBinDir :: [FilePath],+ buildSettingOneShot :: Bool,+ buildSettingNumJobs :: Int,+ buildSettingOfflineMode :: Bool,+ buildSettingKeepTempFiles :: Bool,+ buildSettingRemoteRepos :: [RemoteRepo],+ buildSettingLocalRepos :: [FilePath],+ buildSettingCacheDir :: FilePath,+ buildSettingHttpTransport :: Maybe String,+ buildSettingIgnoreExpiry :: Bool,+ buildSettingRootCmd :: Maybe String+ }+
+ cabal/cabal-install/Distribution/Client/ProjectPlanning.hs view
@@ -0,0 +1,2333 @@+{-# LANGUAGE CPP, RecordWildCards, NamedFieldPuns,+ DeriveGeneric, DeriveDataTypeable,+ RankNTypes, ScopedTypeVariables #-}++-- | Planning how to build everything in a project.+--+module Distribution.Client.ProjectPlanning (+ -- * elaborated install plan types+ ElaboratedInstallPlan,+ ElaboratedConfiguredPackage(..),+ ElaboratedPlanPackage,+ ElaboratedSharedConfig(..),+ ElaboratedReadyPackage,+ BuildStyle(..),+ CabalFileText,++ --TODO: [code cleanup] these types should live with execution, not with+ -- plan definition. Need to better separate InstallPlan definition.+ GenericBuildResult(..),+ BuildResult,+ BuildSuccess(..),+ BuildFailure(..),+ DocsResult(..),+ TestsResult(..),++ -- * Producing the elaborated install plan+ rebuildInstallPlan,++ -- * Build targets+ PackageTarget(..),+ ComponentTarget(..),+ SubComponentTarget(..),+ showComponentTarget,++ -- * Selecting a plan subset+ pruneInstallPlanToTargets,++ -- * Utils required for building+ pkgHasEphemeralBuildTargets,+ pkgBuildTargetWholeComponents,++ -- * Setup.hs CLI flags for building+ setupHsScriptOptions,+ setupHsConfigureFlags,+ setupHsBuildFlags,+ setupHsBuildArgs,+ setupHsReplFlags,+ setupHsReplArgs,+ setupHsCopyFlags,+ setupHsRegisterFlags,+ setupHsHaddockFlags,++ packageHashInputs,++ -- TODO: [code cleanup] utils that should live in some shared place?+ createPackageDBIfMissing+ ) where++import Distribution.Client.PackageHash+import Distribution.Client.RebuildMonad+import Distribution.Client.ProjectConfig++import Distribution.Client.Types hiding+ (BuildResult,BuildSuccess(..), BuildFailure(..), DocsResult(..)+ ,TestsResult(..))+import Distribution.Client.InstallPlan+ ( GenericInstallPlan, InstallPlan, GenericPlanPackage )+import qualified Distribution.Client.InstallPlan as InstallPlan+import Distribution.Client.Dependency+import Distribution.Client.Dependency.Types+import qualified Distribution.Client.ComponentDeps as CD+import Distribution.Client.ComponentDeps (ComponentDeps)+import qualified Distribution.Client.IndexUtils as IndexUtils+import qualified Distribution.Client.PackageIndex as SourcePackageIndex+import Distribution.Client.Targets (userToPackageConstraint)+import Distribution.Client.DistDirLayout+import Distribution.Client.SetupWrapper+import Distribution.Client.JobControl+import Distribution.Client.FetchUtils+import Distribution.Client.PkgConfigDb+import Distribution.Client.Setup hiding (packageName, cabalVersion)+import Distribution.Utils.NubList (toNubList)++import Distribution.Package hiding+ (InstalledPackageId, installedPackageId)+import Distribution.System+import qualified Distribution.PackageDescription as Cabal+import qualified Distribution.PackageDescription as PD+import qualified Distribution.PackageDescription.Configuration as PD+import Distribution.InstalledPackageInfo (InstalledPackageInfo)+import Distribution.Simple.PackageIndex (InstalledPackageIndex)+import qualified Distribution.Simple.PackageIndex as PackageIndex+import Distribution.Simple.Compiler hiding (Flag)+import qualified Distribution.Simple.GHC as GHC --TODO: [code cleanup] eliminate+import qualified Distribution.Simple.GHCJS as GHCJS --TODO: [code cleanup] eliminate+import Distribution.Simple.Program+import Distribution.Simple.Program.Db+import Distribution.Simple.Program.Find+import qualified Distribution.Simple.Setup as Cabal+import Distribution.Simple.Setup+ (Flag, toFlag, flagToMaybe, flagToList, fromFlagOrDefault)+import qualified Distribution.Simple.Configure as Cabal+import Distribution.ModuleName (ModuleName)+import qualified Distribution.Simple.LocalBuildInfo as Cabal+import Distribution.Simple.LocalBuildInfo (ComponentName(..))+import qualified Distribution.Simple.Register as Cabal+import qualified Distribution.Simple.InstallDirs as InstallDirs+import Distribution.Simple.InstallDirs (PathTemplate)+import qualified Distribution.Simple.BuildTarget as Cabal++import Distribution.Simple.Utils hiding (matchFileGlob)+import Distribution.Version+import Distribution.Verbosity+import Distribution.Text++import Data.Map (Map)+import qualified Data.Map as Map+import Data.Set (Set)+import qualified Data.Set as Set+import qualified Data.Graph as Graph+import qualified Data.Tree as Tree+import qualified Data.ByteString.Lazy as LBS++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative+#endif+import Control.Monad+import Control.Monad.State as State+import Control.Exception+import Data.List+import Data.Maybe+import Data.Monoid+import Data.Function++import Distribution.Compat.Binary+import GHC.Generics (Generic)+import Data.Typeable (Typeable)++import System.FilePath+++------------------------------------------------------------------------------+-- * Elaborated install plan+------------------------------------------------------------------------------++-- "Elaborated" -- worked out with great care and nicety of detail;+-- executed with great minuteness: elaborate preparations;+-- elaborate care.+--+-- So here's the idea:+--+-- Rather than a miscellaneous collection of 'ConfigFlags', 'InstallFlags' etc+-- all passed in as separate args and which are then further selected,+-- transformed etc during the execution of the build. Instead we construct+-- an elaborated install plan that includes everything we will need, and then+-- during the execution of the plan we do as little transformation of this+-- info as possible.+--+-- So we're trying to split the work into two phases: construction of the+-- elaborated install plan (which as far as possible should be pure) and+-- then simple execution of that plan without any smarts, just doing what the+-- plan says to do.+--+-- So that means we need a representation of this fully elaborated install+-- plan. The representation consists of two parts:+--+-- * A 'ElaboratedInstallPlan'. This is a 'GenericInstallPlan' with a+-- representation of source packages that includes a lot more detail about+-- that package's individual configuration+--+-- * A 'ElaboratedSharedConfig'. Some package configuration is the same for+-- every package in a plan. Rather than duplicate that info every entry in+-- the 'GenericInstallPlan' we keep that separately.+--+-- The division between the shared and per-package config is /not set in stone+-- for all time/. For example if we wanted to generalise the install plan to+-- describe a situation where we want to build some packages with GHC and some+-- with GHCJS then the platform and compiler would no longer be shared between+-- all packages but would have to be per-package (probably with some sanity+-- condition on the graph structure).+--++-- | The combination of an elaborated install plan plus a+-- 'ElaboratedSharedConfig' contains all the details necessary to be able+-- to execute the plan without having to make further policy decisions.+--+-- It does not include dynamic elements such as resources (such as http+-- connections).+--+type ElaboratedInstallPlan+ = GenericInstallPlan InstalledPackageInfo+ ElaboratedConfiguredPackage+ BuildSuccess BuildFailure++type ElaboratedPlanPackage+ = GenericPlanPackage InstalledPackageInfo+ ElaboratedConfiguredPackage+ BuildSuccess BuildFailure++type SolverInstallPlan+ = InstallPlan --TODO: [code cleanup] redefine locally or move def to solver interface++--TODO: [code cleanup] decide if we really need this, there's not much in it, and in principle+-- even platform and compiler could be different if we're building things+-- like a server + client with ghc + ghcjs+data ElaboratedSharedConfig+ = ElaboratedSharedConfig {++ pkgConfigPlatform :: Platform,+ pkgConfigCompiler :: Compiler, --TODO: [code cleanup] replace with CompilerInfo+ pkgConfigProgramDb :: ProgramDb --TODO: [code cleanup] no Eq instance+ --TODO: [code cleanup] binary instance does not preserve the prog paths+ -- perhaps should keep the configured progs separately+ }+ deriving (Show, Generic)++instance Binary ElaboratedSharedConfig++data ElaboratedConfiguredPackage+ = ElaboratedConfiguredPackage {++ pkgInstalledId :: InstalledPackageId,+ pkgSourceId :: PackageId,++ -- | TODO: [code cleanup] we don't need this, just a few bits from it:+ -- build type, spec version+ pkgDescription :: Cabal.PackageDescription,++ -- | A total flag assignment for the package+ pkgFlagAssignment :: Cabal.FlagAssignment,++ -- | The original default flag assignment, used only for reporting.+ pkgFlagDefaults :: Cabal.FlagAssignment,++ -- | The exact dependencies (on other plan packages)+ --+ pkgDependencies :: ComponentDeps [ConfiguredId],++ -- | Which optional stanzas (ie testsuites, benchmarks) can be built.+ -- This means the solver produced a plan that has them available.+ -- This doesn't necessary mean we build them by default.+ pkgStanzasAvailable :: Set OptionalStanza,++ -- | Which optional stanzas the user explicitly asked to enable or+ -- to disable. This tells us which ones we build by default, and+ -- helps with error messages when the user asks to build something+ -- they explicitly disabled.+ pkgStanzasRequested :: Map OptionalStanza Bool,++ -- | Which optional stanzas (ie testsuites, benchmarks) will actually+ -- be enabled during the package configure step.+ pkgStanzasEnabled :: Set OptionalStanza,++ -- | Where the package comes from, e.g. tarball, local dir etc. This+ -- is not the same as where it may be unpacked to for the build.+ pkgSourceLocation :: PackageLocation (Maybe FilePath),++ -- | The hash of the source, e.g. the tarball. We don't have this for+ -- local source dir packages.+ pkgSourceHash :: Maybe PackageSourceHash,++ --pkgSourceDir ? -- currently passed in later because they can use temp locations+ --pkgBuildDir ? -- but could in principle still have it here, with optional instr to use temp loc++ pkgBuildStyle :: BuildStyle,++ pkgSetupPackageDBStack :: PackageDBStack,+ pkgBuildPackageDBStack :: PackageDBStack,+ pkgRegisterPackageDBStack :: PackageDBStack,++ -- | The package contains a library and so must be registered+ pkgRequiresRegistration :: Bool,+ pkgDescriptionOverride :: Maybe CabalFileText,++ pkgVanillaLib :: Bool,+ pkgSharedLib :: Bool,+ pkgDynExe :: Bool,+ pkgGHCiLib :: Bool,+ pkgProfLib :: Bool,+ pkgProfExe :: Bool,+ pkgProfLibDetail :: ProfDetailLevel,+ pkgProfExeDetail :: ProfDetailLevel,+ pkgCoverage :: Bool,+ pkgOptimization :: OptimisationLevel,+ pkgSplitObjs :: Bool,+ pkgStripLibs :: Bool,+ pkgStripExes :: Bool,+ pkgDebugInfo :: DebugInfoLevel,++ pkgConfigureScriptArgs :: [String],+ pkgExtraLibDirs :: [FilePath],+ pkgExtraFrameworkDirs :: [FilePath],+ pkgExtraIncludeDirs :: [FilePath],+ pkgProgPrefix :: Maybe PathTemplate,+ pkgProgSuffix :: Maybe PathTemplate,++ pkgInstallDirs :: InstallDirs.InstallDirs FilePath,++ pkgHaddockHoogle :: Bool,+ pkgHaddockHtml :: Bool,+ pkgHaddockHtmlLocation :: Maybe String,+ pkgHaddockExecutables :: Bool,+ pkgHaddockTestSuites :: Bool,+ pkgHaddockBenchmarks :: Bool,+ pkgHaddockInternal :: Bool,+ pkgHaddockCss :: Maybe FilePath,+ pkgHaddockHscolour :: Bool,+ pkgHaddockHscolourCss :: Maybe FilePath,+ pkgHaddockContents :: Maybe PathTemplate,++ -- Setup.hs related things:++ -- | One of four modes for how we build and interact with the Setup.hs+ -- script, based on whether it's a build-type Custom, with or without+ -- explicit deps and the cabal spec version the .cabal file needs.+ pkgSetupScriptStyle :: SetupScriptStyle,++ -- | The version of the Cabal command line interface that we are using+ -- for this package. This is typically the version of the Cabal lib+ -- that the Setup.hs is built against.+ pkgSetupScriptCliVersion :: Version,++ -- Build time related:+ pkgBuildTargets :: [ComponentTarget],+ pkgReplTarget :: Maybe ComponentTarget,+ pkgBuildHaddocks :: Bool+ }+ deriving (Eq, Show, Generic)++instance Binary ElaboratedConfiguredPackage++instance Package ElaboratedConfiguredPackage where+ packageId = pkgSourceId++instance HasUnitId ElaboratedConfiguredPackage where+ installedUnitId = pkgInstalledId++instance PackageFixedDeps ElaboratedConfiguredPackage where+ depends = fmap (map installedPackageId) . pkgDependencies++-- | This is used in the install plan to indicate how the package will be+-- built.+--+data BuildStyle =+ -- | The classic approach where the package is built, then the files+ -- installed into some location and the result registered in a package db.+ --+ -- If the package came from a tarball then it's built in a temp dir and+ -- the results discarded.+ BuildAndInstall++ -- | The package is built, but the files are not installed anywhere,+ -- rather the build dir is kept and the package is registered inplace.+ --+ -- Such packages can still subsequently be installed.+ --+ -- Typically 'BuildAndInstall' packages will only depend on other+ -- 'BuildAndInstall' style packages and not on 'BuildInplaceOnly' ones.+ --+ | BuildInplaceOnly+ deriving (Eq, Show, Generic)++instance Binary BuildStyle++type CabalFileText = LBS.ByteString++type ElaboratedReadyPackage = GenericReadyPackage ElaboratedConfiguredPackage++--TODO: [code cleanup] this duplicates the InstalledPackageInfo quite a bit in an install plan+-- because the same ipkg is used by many packages. So the binary file will be big.+-- Could we keep just (ipkgid, deps) instead of the whole InstalledPackageInfo?+-- or transform to a shared form when serialising / deserialising++data GenericBuildResult ipkg iresult ifailure+ = BuildFailure ifailure+ | BuildSuccess (Maybe ipkg) iresult+ deriving (Eq, Show, Generic)++instance (Binary ipkg, Binary iresult, Binary ifailure) =>+ Binary (GenericBuildResult ipkg iresult ifailure)++type BuildResult = GenericBuildResult InstalledPackageInfo+ BuildSuccess BuildFailure++data BuildSuccess = BuildOk DocsResult TestsResult+ deriving (Eq, Show, Generic)++data DocsResult = DocsNotTried | DocsFailed | DocsOk+ deriving (Eq, Show, Generic)++data TestsResult = TestsNotTried | TestsOk+ deriving (Eq, Show, Generic)++data BuildFailure = PlanningFailed --TODO: [required eventually] not yet used+ | DependentFailed PackageId+ | DownloadFailed String --TODO: [required eventually] not yet used+ | UnpackFailed String --TODO: [required eventually] not yet used+ | ConfigureFailed String+ | BuildFailed String+ | TestsFailed String --TODO: [required eventually] not yet used+ | InstallFailed String+ deriving (Eq, Show, Typeable, Generic)++instance Exception BuildFailure++instance Binary BuildFailure+instance Binary BuildSuccess+instance Binary DocsResult+instance Binary TestsResult+++sanityCheckElaboratedConfiguredPackage :: ElaboratedSharedConfig+ -> ElaboratedConfiguredPackage+ -> Bool+sanityCheckElaboratedConfiguredPackage sharedConfig+ pkg@ElaboratedConfiguredPackage{..} =++ pkgStanzasEnabled `Set.isSubsetOf` pkgStanzasAvailable++ -- the stanzas explicitly enabled should be available and enabled+ && Map.keysSet (Map.filter id pkgStanzasRequested)+ `Set.isSubsetOf` pkgStanzasEnabled++ -- the stanzas explicitly disabled should not be available+ && Set.null (Map.keysSet (Map.filter not pkgStanzasRequested)+ `Set.intersection` pkgStanzasAvailable)++ && (pkgBuildStyle == BuildInplaceOnly ||+ installedPackageId pkg == hashedInstalledPackageId+ (packageHashInputs sharedConfig pkg))++ && (pkgBuildStyle == BuildInplaceOnly ||+ Set.null pkgStanzasAvailable)+++------------------------------------------------------------------------------+-- * Deciding what to do: making an 'ElaboratedInstallPlan'+------------------------------------------------------------------------------++rebuildInstallPlan :: Verbosity+ -> FilePath -> DistDirLayout -> CabalDirLayout+ -> ProjectConfig+ -> IO ( ElaboratedInstallPlan+ , ElaboratedSharedConfig+ , ProjectConfig )+rebuildInstallPlan verbosity+ projectRootDir+ distDirLayout@DistDirLayout {+ distDirectory,+ distProjectCacheFile,+ distProjectCacheDirectory+ }+ cabalDirLayout@CabalDirLayout {+ cabalPackageCacheDirectory,+ cabalStoreDirectory,+ cabalStorePackageDB+ }+ cliConfig =+ runRebuild $ do+ progsearchpath <- liftIO $ getSystemSearchPath+ let cliConfigPersistent = cliConfig { projectConfigBuildOnly = mempty }++ -- The overall improved plan is cached+ rerunIfChanged verbosity projectRootDir fileMonitorImprovedPlan+ -- react to changes in command line args and the path+ (cliConfigPersistent, progsearchpath) $ do++ -- And so is the elaborated plan that the improved plan based on+ (elaboratedPlan, elaboratedShared,+ projectConfig) <-+ rerunIfChanged verbosity projectRootDir fileMonitorElaboratedPlan+ (cliConfigPersistent, progsearchpath) $ do++ (projectConfig, projectConfigTransient) <- phaseReadProjectConfig+ localPackages <- phaseReadLocalPackages projectConfig+ compilerEtc <- phaseConfigureCompiler projectConfig+ solverPlan <- phaseRunSolver projectConfigTransient+ compilerEtc localPackages+ (elaboratedPlan,+ elaboratedShared) <- phaseElaboratePlan projectConfigTransient+ compilerEtc+ solverPlan localPackages++ return (elaboratedPlan, elaboratedShared,+ projectConfig)++ -- The improved plan changes each time we install something, whereas+ -- the underlying elaborated plan only changes when input config+ -- changes, so it's worth caching them separately.+ improvedPlan <- phaseImprovePlan elaboratedPlan elaboratedShared+ return (improvedPlan, elaboratedShared, projectConfig)++ where+ fileMonitorCompiler = newFileMonitorInCacheDir "compiler"+ fileMonitorSolverPlan = newFileMonitorInCacheDir "solver-plan"+ fileMonitorSourceHashes = newFileMonitorInCacheDir "source-hashes"+ fileMonitorElaboratedPlan = newFileMonitorInCacheDir "elaborated-plan"+ fileMonitorImprovedPlan = newFileMonitorInCacheDir "improved-plan"++ newFileMonitorInCacheDir :: Eq a => FilePath -> FileMonitor a b+ newFileMonitorInCacheDir = newFileMonitor . distProjectCacheFile++ -- Read the cabal.project (or implicit config) and combine it with+ -- arguments from the command line+ --+ phaseReadProjectConfig :: Rebuild (ProjectConfig, ProjectConfig)+ phaseReadProjectConfig = do+ liftIO $ do+ info verbosity "Project settings changed, reconfiguring..."+ createDirectoryIfMissingVerbose verbosity False distDirectory+ createDirectoryIfMissingVerbose verbosity False distProjectCacheDirectory++ projectConfig <- readProjectConfig verbosity projectRootDir++ -- The project config comming from the command line includes "build only"+ -- flags that we don't cache persistently (because like all "build only"+ -- flags they do not affect the value of the outcome) but that we do+ -- sometimes using during planning (in particular the http transport)+ let projectConfigTransient = projectConfig <> cliConfig+ projectConfigPersistent = projectConfig+ <> cliConfig {+ projectConfigBuildOnly = mempty+ }+ liftIO $ writeProjectConfigFile (distProjectCacheFile "config")+ projectConfigPersistent+ return (projectConfigPersistent, projectConfigTransient)++ -- Look for all the cabal packages in the project+ -- some of which may be local src dirs, tarballs etc+ --+ phaseReadLocalPackages :: ProjectConfig+ -> Rebuild [UnresolvedSourcePackage]+ phaseReadLocalPackages projectConfig = do++ localCabalFiles <- findProjectPackages projectRootDir projectConfig+ mapM (readSourcePackage verbosity) localCabalFiles+++ -- Configure the compiler we're using.+ --+ -- This is moderately expensive and doesn't change that often so we cache+ -- it independently.+ --+ phaseConfigureCompiler :: ProjectConfig+ -> Rebuild (Compiler, Platform, ProgramDb)+ phaseConfigureCompiler ProjectConfig{projectConfigShared} = do+ progsearchpath <- liftIO $ getSystemSearchPath+ rerunIfChanged verbosity projectRootDir fileMonitorCompiler+ (hcFlavor, hcPath, hcPkg, progsearchpath) $ do++ liftIO $ info verbosity "Compiler settings changed, reconfiguring..."+ result@(_, _, progdb) <- liftIO $+ Cabal.configCompilerEx+ hcFlavor hcPath hcPkg+ defaultProgramDb verbosity++ monitorFiles (programsMonitorFiles progdb)++ return result+ where+ hcFlavor = flagToMaybe projectConfigHcFlavor+ hcPath = flagToMaybe projectConfigHcPath+ hcPkg = flagToMaybe projectConfigHcPkg+ ProjectConfigShared {+ projectConfigHcFlavor,+ projectConfigHcPath,+ projectConfigHcPkg+ } = projectConfigShared+++ -- Run the solver to get the initial install plan.+ -- This is expensive so we cache it independently.+ --+ phaseRunSolver :: ProjectConfig+ -> (Compiler, Platform, ProgramDb)+ -> [UnresolvedSourcePackage]+ -> Rebuild (SolverInstallPlan, PackagesImplicitSetupDeps)+ phaseRunSolver projectConfig@ProjectConfig {+ projectConfigShared,+ projectConfigBuildOnly+ }+ (compiler, platform, progdb)+ localPackages =+ rerunIfChanged verbosity projectRootDir fileMonitorSolverPlan+ (solverSettings, cabalPackageCacheDirectory,+ localPackages, localPackagesEnabledStanzas,+ compiler, platform, programsDbSignature progdb) $ do++ installedPkgIndex <- getInstalledPackages verbosity+ compiler progdb platform+ corePackageDbs+ sourcePkgDb <- getSourcePackages verbosity withRepoCtx+ pkgConfigDB <- liftIO $+ readPkgConfigDb verbosity progdb++ liftIO $ do+ solver <- chooseSolver verbosity+ (solverSettingSolver solverSettings)+ (compilerInfo compiler)++ notice verbosity "Resolving dependencies..."+ foldProgress logMsg die return $+ planPackages compiler platform solver solverSettings+ installedPkgIndex sourcePkgDb pkgConfigDB+ localPackages localPackagesEnabledStanzas+ where+ corePackageDbs = [GlobalPackageDB]+ withRepoCtx = projectConfigWithSolverRepoContext verbosity+ cabalPackageCacheDirectory+ projectConfigShared+ projectConfigBuildOnly+ solverSettings = resolveSolverSettings projectConfigShared+ logMsg message rest = debugNoWrap verbosity message >> rest++ localPackagesEnabledStanzas =+ Map.fromList+ [ (pkgname, stanzas)+ | pkg <- localPackages+ , let pkgname = packageName pkg+ testsEnabled = lookupLocalPackageConfig+ packageConfigTests+ projectConfig pkgname+ benchmarksEnabled = lookupLocalPackageConfig+ packageConfigBenchmarks+ projectConfig pkgname+ stanzas =+ Map.fromList $+ [ (TestStanzas, enabled)+ | enabled <- flagToList testsEnabled ]+ ++ [ (BenchStanzas , enabled)+ | enabled <- flagToList benchmarksEnabled ]+ ]++ -- Elaborate the solver's install plan to get a fully detailed plan. This+ -- version of the plan has the final nix-style hashed ids.+ --+ phaseElaboratePlan :: ProjectConfig+ -> (Compiler, Platform, ProgramDb)+ -> (SolverInstallPlan, PackagesImplicitSetupDeps)+ -> [SourcePackage loc]+ -> Rebuild ( ElaboratedInstallPlan+ , ElaboratedSharedConfig )+ phaseElaboratePlan ProjectConfig {+ projectConfigShared,+ projectConfigLocalPackages,+ projectConfigSpecificPackage,+ projectConfigBuildOnly+ }+ (compiler, platform, progdb)+ (solverPlan, pkgsImplicitSetupDeps)+ localPackages = do++ liftIO $ debug verbosity "Elaborating the install plan..."++ sourcePackageHashes <-+ rerunIfChanged verbosity projectRootDir fileMonitorSourceHashes+ (map packageId $ InstallPlan.toList solverPlan) $+ getPackageSourceHashes verbosity withRepoCtx solverPlan++ defaultInstallDirs <- liftIO $ userInstallDirTemplates compiler+ return $+ elaborateInstallPlan+ platform compiler progdb+ distDirLayout+ cabalDirLayout+ solverPlan+ pkgsImplicitSetupDeps+ localPackages+ sourcePackageHashes+ defaultInstallDirs+ projectConfigShared+ projectConfigLocalPackages+ projectConfigSpecificPackage+ where+ withRepoCtx = projectConfigWithSolverRepoContext verbosity+ cabalPackageCacheDirectory+ projectConfigShared+ projectConfigBuildOnly+++ -- Improve the elaborated install plan. The elaborated plan consists+ -- mostly of source packages (with full nix-style hashed ids). Where+ -- corresponding installed packages already exist in the store, replace+ -- them in the plan.+ --+ -- Note that we do monitor the store's package db here, so we will redo+ -- this improvement phase when the db changes -- including as a result of+ -- executing a plan and installing things.+ --+ phaseImprovePlan :: ElaboratedInstallPlan+ -> ElaboratedSharedConfig+ -> Rebuild ElaboratedInstallPlan+ phaseImprovePlan elaboratedPlan elaboratedShared = do++ liftIO $ debug verbosity "Improving the install plan..."+ recreateDirectory verbosity True storeDirectory+ storePkgIndex <- getPackageDBContents verbosity+ compiler progdb platform+ storePackageDb+ let improvedPlan = improveInstallPlanWithPreExistingPackages+ storePkgIndex+ elaboratedPlan+ return improvedPlan++ where+ storeDirectory = cabalStoreDirectory (compilerId compiler)+ storePackageDb = cabalStorePackageDB (compilerId compiler)+ ElaboratedSharedConfig {+ pkgConfigCompiler = compiler,+ pkgConfigPlatform = platform,+ pkgConfigProgramDb = progdb+ } = elaboratedShared+++programsMonitorFiles :: ProgramDb -> [MonitorFilePath]+programsMonitorFiles progdb =+ [ monitor+ | prog <- configuredPrograms progdb+ , monitor <- monitorFileSearchPath (programMonitorFiles prog)+ (programPath prog)+ ]++-- | Select the bits of a 'ProgramDb' to monitor for value changes.+-- Use 'programsMonitorFiles' for the files to monitor.+--+programsDbSignature :: ProgramDb -> [ConfiguredProgram]+programsDbSignature progdb =+ [ prog { programMonitorFiles = []+ , programOverrideEnv = filter ((/="PATH") . fst)+ (programOverrideEnv prog) }+ | prog <- configuredPrograms progdb ]++getInstalledPackages :: Verbosity+ -> Compiler -> ProgramDb -> Platform+ -> PackageDBStack+ -> Rebuild InstalledPackageIndex+getInstalledPackages verbosity compiler progdb platform packagedbs = do+ monitorFiles . map monitorFileOrDirectory+ =<< liftIO (IndexUtils.getInstalledPackagesMonitorFiles+ verbosity compiler+ packagedbs progdb platform)+ liftIO $ IndexUtils.getInstalledPackages+ verbosity compiler+ packagedbs progdb++getPackageDBContents :: Verbosity+ -> Compiler -> ProgramDb -> Platform+ -> PackageDB+ -> Rebuild InstalledPackageIndex+getPackageDBContents verbosity compiler progdb platform packagedb = do+ monitorFiles . map monitorFileOrDirectory+ =<< liftIO (IndexUtils.getInstalledPackagesMonitorFiles+ verbosity compiler+ [packagedb] progdb platform)+ liftIO $ do+ createPackageDBIfMissing verbosity compiler+ progdb [packagedb]+ Cabal.getPackageDBContents verbosity compiler+ packagedb progdb++getSourcePackages :: Verbosity -> (forall a. (RepoContext -> IO a) -> IO a)+ -> Rebuild SourcePackageDb+getSourcePackages verbosity withRepoCtx = do+ (sourcePkgDb, repos) <-+ liftIO $+ withRepoCtx $ \repoctx -> do+ sourcePkgDb <- IndexUtils.getSourcePackages verbosity repoctx+ return (sourcePkgDb, repoContextRepos repoctx)++ monitorFiles . map monitorFile+ . IndexUtils.getSourcePackagesMonitorFiles+ $ repos+ return sourcePkgDb++createPackageDBIfMissing :: Verbosity -> Compiler -> ProgramDb+ -> PackageDBStack -> IO ()+createPackageDBIfMissing verbosity compiler progdb packageDbs =+ case reverse packageDbs of+ SpecificPackageDB dbPath : _ -> do+ exists <- liftIO $ Cabal.doesPackageDBExist dbPath+ unless exists $ do+ createDirectoryIfMissingVerbose verbosity False (takeDirectory dbPath)+ Cabal.createPackageDB verbosity compiler progdb False dbPath+ _ -> return ()+++recreateDirectory :: Verbosity -> Bool -> FilePath -> Rebuild ()+recreateDirectory verbosity createParents dir = do+ liftIO $ createDirectoryIfMissingVerbose verbosity createParents dir+ monitorFiles [monitorDirectoryExistence dir]+++-- | Get the 'HashValue' for all the source packages where we use hashes,+-- and download any packages required to do so.+--+-- Note that we don't get hashes for local unpacked packages.+--+getPackageSourceHashes :: Verbosity+ -> (forall a. (RepoContext -> IO a) -> IO a)+ -> SolverInstallPlan+ -> Rebuild (Map PackageId PackageSourceHash)+getPackageSourceHashes verbosity withRepoCtx installPlan = do++ -- Determine which packages need fetching, and which are present already+ --+ pkgslocs <- liftIO $ sequence+ [ do let locm = packageSource pkg+ mloc <- checkFetched locm+ return (pkg, locm, mloc)+ | InstallPlan.Configured+ (ConfiguredPackage pkg _ _ _) <- InstallPlan.toList installPlan ]++ let requireDownloading = [ (pkg, locm) | (pkg, locm, Nothing) <- pkgslocs ]+ alreadyDownloaded = [ (pkg, loc) | (pkg, _, Just loc) <- pkgslocs ]++ -- Download the ones we need+ --+ newlyDownloaded <-+ if null requireDownloading+ then return []+ else liftIO $+ withRepoCtx $ \repoctx ->+ sequence+ [ do loc <- fetchPackage verbosity repoctx locm+ return (pkg, loc)+ | (pkg, locm) <- requireDownloading ]++ -- Get the hashes of all the tarball packages (i.e. not local dir pkgs)+ --+ let pkgsTarballs =+ [ (packageId pkg, tarball)+ | (pkg, srcloc) <- newlyDownloaded ++ alreadyDownloaded+ , tarball <- maybeToList (tarballFileLocation srcloc) ]++ monitorFiles [ monitorFile tarball | (_pkgid, tarball) <- pkgsTarballs ]++ liftM Map.fromList $ liftIO $+ sequence+ [ do srchash <- readFileHashValue tarball+ return (pkgid, srchash)+ | (pkgid, tarball) <- pkgsTarballs ]+ where+ tarballFileLocation (LocalUnpackedPackage _dir) = Nothing+ tarballFileLocation (LocalTarballPackage tarball) = Just tarball+ tarballFileLocation (RemoteTarballPackage _ tarball) = Just tarball+ tarballFileLocation (RepoTarballPackage _ _ tarball) = Just tarball+++-- ------------------------------------------------------------+-- * Installation planning+-- ------------------------------------------------------------++planPackages :: Compiler+ -> Platform+ -> Solver -> SolverSettings+ -> InstalledPackageIndex+ -> SourcePackageDb+ -> PkgConfigDb+ -> [UnresolvedSourcePackage]+ -> Map PackageName (Map OptionalStanza Bool)+ -> Progress String String+ (SolverInstallPlan, PackagesImplicitSetupDeps)+planPackages comp platform solver SolverSettings{..}+ installedPkgIndex sourcePkgDb pkgConfigDB+ localPackages pkgStanzasEnable =++ rememberImplicitSetupDeps (depResolverSourcePkgIndex stdResolverParams) <$>++ resolveDependencies+ platform (compilerInfo comp)+ pkgConfigDB solver+ resolverParams++ where++ --TODO: [nice to have] disable multiple instances restriction in the solver, but then+ -- make sure we can cope with that in the output.+ resolverParams =++ setMaxBackjumps solverSettingMaxBackjumps++ --TODO: [required eventually] should only be configurable for custom installs+ -- . setIndependentGoals solverSettingIndependentGoals++ . setReorderGoals solverSettingReorderGoals++ --TODO: [required eventually] should only be configurable for custom installs+ -- . setAvoidReinstalls solverSettingAvoidReinstalls++ --TODO: [required eventually] should only be configurable for custom installs+ -- . setShadowPkgs solverSettingShadowPkgs++ . setStrongFlags solverSettingStrongFlags++ --TODO: [required eventually] decide if we need to prefer installed for+ -- global packages, or prefer latest even for global packages. Perhaps+ -- should be configurable but with a different name than "upgrade-dependencies".+ . setPreferenceDefault PreferLatestForSelected+ {-(if solverSettingUpgradeDeps+ then PreferAllLatest+ else PreferLatestForSelected)-}++ . removeUpperBounds solverSettingAllowNewer++ . addDefaultSetupDependencies (defaultSetupDeps platform+ . PD.packageDescription+ . packageDescription)++ . addPreferences+ -- preferences from the config file or command line+ [ PackageVersionPreference name ver+ | Dependency name ver <- solverSettingPreferences ]++ . addConstraints+ -- version constraints from the config file or command line+ [ LabeledPackageConstraint (userToPackageConstraint pc) src+ | (pc, src) <- solverSettingConstraints ]++ . addPreferences+ -- enable stanza preference where the user did not specify+ [ PackageStanzasPreference pkgname stanzas+ | pkg <- localPackages+ , let pkgname = packageName pkg+ stanzaM = Map.findWithDefault Map.empty pkgname pkgStanzasEnable+ stanzas = [ stanza | stanza <- [minBound..maxBound]+ , Map.lookup stanza stanzaM == Nothing ]+ , not (null stanzas)+ ]++ . addConstraints+ -- enable stanza constraints where the user asked to enable+ [ LabeledPackageConstraint+ (PackageConstraintStanzas pkgname stanzas)+ ConstraintSourceConfigFlagOrTarget+ | pkg <- localPackages+ , let pkgname = packageName pkg+ stanzaM = Map.findWithDefault Map.empty pkgname pkgStanzasEnable+ stanzas = [ stanza | stanza <- [minBound..maxBound]+ , Map.lookup stanza stanzaM == Just True ]+ , not (null stanzas)+ ]++ . addConstraints+ --TODO: [nice to have] this just applies all flags to all targets which+ -- is silly. We should check if the flags are appropriate+ [ LabeledPackageConstraint+ (PackageConstraintFlags pkgname flags)+ ConstraintSourceConfigFlagOrTarget+ | let flags = solverSettingFlagAssignment+ , not (null flags)+ , pkg <- localPackages+ , let pkgname = packageName pkg ]++ $ stdResolverParams++ stdResolverParams =+ standardInstallPolicy+ installedPkgIndex sourcePkgDb+ (map SpecificSourcePackage localPackages)+++------------------------------------------------------------------------------+-- * Install plan post-processing+------------------------------------------------------------------------------++-- This phase goes from the InstallPlan we get from the solver and has to+-- make an elaborated install plan.+--+-- We go in two steps:+--+-- 1. elaborate all the source packages that the solver has chosen.+-- 2. swap source packages for pre-existing installed packages wherever+-- possible.+--+-- We do it in this order, elaborating and then replacing, because the easiest+-- way to calculate the installed package ids used for the replacement step is+-- from the elaborated configuration for each package.+++++------------------------------------------------------------------------------+-- * Install plan elaboration+------------------------------------------------------------------------------++-- | Produce an elaborated install plan using the policy for local builds with+-- a nix-style shared store.+--+-- In theory should be able to make an elaborated install plan with a policy+-- matching that of the classic @cabal install --user@ or @--global@+--+elaborateInstallPlan+ :: Platform -> Compiler -> ProgramDb+ -> DistDirLayout+ -> CabalDirLayout+ -> SolverInstallPlan+ -> PackagesImplicitSetupDeps+ -> [SourcePackage loc]+ -> Map PackageId PackageSourceHash+ -> InstallDirs.InstallDirTemplates+ -> ProjectConfigShared+ -> PackageConfig+ -> Map PackageName PackageConfig+ -> (ElaboratedInstallPlan, ElaboratedSharedConfig)+elaborateInstallPlan platform compiler progdb+ DistDirLayout{..}+ cabalDirLayout@CabalDirLayout{cabalStorePackageDB}+ solverPlan pkgsImplicitSetupDeps localPackages+ sourcePackageHashes+ defaultInstallDirs+ _sharedPackageConfig+ localPackagesConfig+ perPackageConfig =+ (elaboratedInstallPlan, elaboratedSharedConfig)+ where+ elaboratedSharedConfig =+ ElaboratedSharedConfig {+ pkgConfigPlatform = platform,+ pkgConfigCompiler = compiler,+ pkgConfigProgramDb = progdb+ }++ elaboratedInstallPlan =+ flip InstallPlan.mapPreservingGraph solverPlan $ \mapDep planpkg ->+ case planpkg of+ InstallPlan.PreExisting pkg ->+ InstallPlan.PreExisting pkg++ InstallPlan.Configured pkg ->+ InstallPlan.Configured+ (elaborateConfiguredPackage (fixupDependencies mapDep pkg))++ _ -> error "elaborateInstallPlan: unexpected package state"++ -- remap the installed package ids of the direct deps, since we're+ -- changing the installed package ids of all the packages to use the+ -- final nix-style hashed ids.+ fixupDependencies mapDep+ (ConfiguredPackage pkg flags stanzas deps) =+ ConfiguredPackage pkg flags stanzas deps'+ where+ deps' = fmap (map (\d -> d { confInstId = mapDep (confInstId d) })) deps++ elaborateConfiguredPackage :: ConfiguredPackage UnresolvedPkgLoc+ -> ElaboratedConfiguredPackage+ elaborateConfiguredPackage+ pkg@(ConfiguredPackage (SourcePackage pkgid gdesc srcloc descOverride)+ flags stanzas deps) =+ elaboratedPackage+ where+ -- Knot tying: the final elaboratedPackage includes the+ -- pkgInstalledId, which is calculated by hashing many+ -- of the other fields of the elaboratedPackage.+ --+ elaboratedPackage = ElaboratedConfiguredPackage {..}++ pkgInstalledId+ | shouldBuildInplaceOnly pkg+ = mkUnitId (display pkgid ++ "-inplace")++ | otherwise+ = assert (isJust pkgSourceHash) $+ hashedInstalledPackageId+ (packageHashInputs+ elaboratedSharedConfig+ elaboratedPackage) -- recursive use of elaboratedPackage++ | otherwise+ = error $ "elaborateInstallPlan: non-inplace package "+ ++ " is missing a source hash: " ++ display pkgid++ -- All the other fields of the ElaboratedConfiguredPackage+ --+ pkgSourceId = pkgid+ pkgDescription = let Right (desc, _) =+ PD.finalizePackageDescription+ flags (const True)+ platform (compilerInfo compiler)+ [] gdesc+ in desc+ pkgFlagAssignment = flags+ pkgFlagDefaults = [ (Cabal.flagName flag, Cabal.flagDefault flag)+ | flag <- PD.genPackageFlags gdesc ]+ pkgDependencies = deps+ pkgStanzasAvailable = Set.fromList stanzas+ pkgStanzasRequested =+ Map.fromList $ [ (TestStanzas, v) | v <- maybeToList tests ]+ ++ [ (BenchStanzas, v) | v <- maybeToList benchmarks ]+ where+ tests, benchmarks :: Maybe Bool+ tests = perPkgOptionMaybe pkgid packageConfigTests+ benchmarks = perPkgOptionMaybe pkgid packageConfigBenchmarks++ -- These sometimes get adjusted later+ pkgStanzasEnabled = Set.empty+ pkgBuildTargets = []+ pkgReplTarget = Nothing+ pkgBuildHaddocks = False++ pkgSourceLocation = srcloc+ pkgSourceHash = Map.lookup pkgid sourcePackageHashes+ pkgBuildStyle = if shouldBuildInplaceOnly pkg+ then BuildInplaceOnly else BuildAndInstall+ pkgBuildPackageDBStack = buildAndRegisterDbs+ pkgRegisterPackageDBStack = buildAndRegisterDbs+ pkgRequiresRegistration = PD.hasPublicLib (PD.packageDescription gdesc)++ pkgSetupScriptStyle = packageSetupScriptStylePostSolver+ pkgsImplicitSetupDeps pkg pkgDescription+ pkgSetupScriptCliVersion = packageSetupScriptSpecVersion+ pkgSetupScriptStyle pkgDescription deps+ pkgSetupPackageDBStack = buildAndRegisterDbs++ buildAndRegisterDbs+ | shouldBuildInplaceOnly pkg = inplacePackageDbs+ | otherwise = storePackageDbs++ pkgDescriptionOverride = descOverride++ pkgVanillaLib = perPkgOptionFlag pkgid True packageConfigVanillaLib --TODO: [required feature]: also needs to be handled recursively+ pkgSharedLib = pkgid `Set.member` pkgsUseSharedLibrary+ pkgDynExe = perPkgOptionFlag pkgid False packageConfigDynExe+ pkgGHCiLib = perPkgOptionFlag pkgid False packageConfigGHCiLib --TODO: [required feature] needs to default to enabled on windows still++ pkgProfExe = perPkgOptionFlag pkgid False packageConfigProf+ pkgProfLib = pkgid `Set.member` pkgsUseProfilingLibrary++ (pkgProfExeDetail,+ pkgProfLibDetail) = perPkgOptionLibExeFlag pkgid ProfDetailDefault+ packageConfigProfDetail+ packageConfigProfLibDetail+ pkgCoverage = perPkgOptionFlag pkgid False packageConfigCoverage++ pkgOptimization = perPkgOptionFlag pkgid NormalOptimisation packageConfigOptimization+ pkgSplitObjs = perPkgOptionFlag pkgid False packageConfigSplitObjs+ pkgStripLibs = perPkgOptionFlag pkgid False packageConfigStripLibs+ pkgStripExes = perPkgOptionFlag pkgid False packageConfigStripExes+ pkgDebugInfo = perPkgOptionFlag pkgid NoDebugInfo packageConfigDebugInfo++ pkgConfigureScriptArgs = perPkgOptionList pkgid packageConfigConfigureArgs+ pkgExtraLibDirs = perPkgOptionList pkgid packageConfigExtraLibDirs+ pkgExtraFrameworkDirs = perPkgOptionList pkgid packageConfigExtraFrameworkDirs+ pkgExtraIncludeDirs = perPkgOptionList pkgid packageConfigExtraIncludeDirs+ pkgProgPrefix = perPkgOptionMaybe pkgid packageConfigProgPrefix+ pkgProgSuffix = perPkgOptionMaybe pkgid packageConfigProgSuffix++ pkgInstallDirs+ | shouldBuildInplaceOnly pkg+ -- use the ordinary default install dirs+ = (InstallDirs.absoluteInstallDirs+ pkgid+ (installedUnitId pkg)+ (compilerInfo compiler)+ InstallDirs.NoCopyDest+ platform+ defaultInstallDirs) {++ InstallDirs.libsubdir = "", -- absoluteInstallDirs sets these as+ InstallDirs.datasubdir = "" -- 'undefined' but we have to use+ } -- them as "Setup.hs configure" args++ | otherwise+ -- use special simplified install dirs+ = storePackageInstallDirs+ cabalDirLayout+ (compilerId compiler)+ pkgInstalledId++ pkgHaddockHoogle = perPkgOptionFlag pkgid False packageConfigHaddockHoogle+ pkgHaddockHtml = perPkgOptionFlag pkgid False packageConfigHaddockHtml+ pkgHaddockHtmlLocation = perPkgOptionMaybe pkgid packageConfigHaddockHtmlLocation+ pkgHaddockExecutables = perPkgOptionFlag pkgid False packageConfigHaddockExecutables+ pkgHaddockTestSuites = perPkgOptionFlag pkgid False packageConfigHaddockTestSuites+ pkgHaddockBenchmarks = perPkgOptionFlag pkgid False packageConfigHaddockBenchmarks+ pkgHaddockInternal = perPkgOptionFlag pkgid False packageConfigHaddockInternal+ pkgHaddockCss = perPkgOptionMaybe pkgid packageConfigHaddockCss+ pkgHaddockHscolour = perPkgOptionFlag pkgid False packageConfigHaddockHscolour+ pkgHaddockHscolourCss = perPkgOptionMaybe pkgid packageConfigHaddockHscolourCss+ pkgHaddockContents = perPkgOptionMaybe pkgid packageConfigHaddockContents++ perPkgOptionFlag :: PackageId -> a -> (PackageConfig -> Flag a) -> a+ perPkgOptionMaybe :: PackageId -> (PackageConfig -> Flag a) -> Maybe a+ perPkgOptionList :: PackageId -> (PackageConfig -> [a]) -> [a]++ perPkgOptionFlag pkgid def f = fromFlagOrDefault def (lookupPerPkgOption pkgid f)+ perPkgOptionMaybe pkgid f = flagToMaybe (lookupPerPkgOption pkgid f)+ perPkgOptionList pkgid f = lookupPerPkgOption pkgid f++ perPkgOptionLibExeFlag pkgid def fboth flib = (exe, lib)+ where+ exe = fromFlagOrDefault def bothflag+ lib = fromFlagOrDefault def (bothflag <> libflag)++ bothflag = lookupPerPkgOption pkgid fboth+ libflag = lookupPerPkgOption pkgid flib++ lookupPerPkgOption :: (Package pkg, Monoid m)+ => pkg -> (PackageConfig -> m) -> m+ lookupPerPkgOption pkg f+ -- the project config specifies values that apply to packages local to+ -- but by default non-local packages get all default config values+ -- the project, and can specify per-package values for any package,+ | isLocalToProject pkg = local <> perpkg+ | otherwise = perpkg+ where+ local = f localPackagesConfig+ perpkg = maybe mempty f (Map.lookup (packageName pkg) perPackageConfig)++ inplacePackageDbs = storePackageDbs+ ++ [ distPackageDB (compilerId compiler) ]++ storePackageDbs = [ GlobalPackageDB+ , cabalStorePackageDB (compilerId compiler) ]++ -- For this local build policy, every package that lives in a local source+ -- dir (as opposed to a tarball), or depends on such a package, will be+ -- built inplace into a shared dist dir. Tarball packages that depend on+ -- source dir packages will also get unpacked locally.+ shouldBuildInplaceOnly :: HasUnitId pkg => pkg -> Bool+ shouldBuildInplaceOnly pkg = Set.member (installedPackageId pkg)+ pkgsToBuildInplaceOnly++ pkgsToBuildInplaceOnly :: Set InstalledPackageId+ pkgsToBuildInplaceOnly =+ Set.fromList+ $ map installedPackageId+ $ InstallPlan.reverseDependencyClosure+ solverPlan+ [ fakeUnitId (packageId pkg)+ | pkg <- localPackages ]++ isLocalToProject :: Package pkg => pkg -> Bool+ isLocalToProject pkg = Set.member (packageId pkg)+ pkgsLocalToProject++ pkgsLocalToProject :: Set PackageId+ pkgsLocalToProject = Set.fromList [ packageId pkg | pkg <- localPackages ]++ pkgsUseSharedLibrary :: Set PackageId+ pkgsUseSharedLibrary =+ packagesWithDownwardClosedProperty needsSharedLib+ where+ needsSharedLib pkg =+ fromMaybe compilerShouldUseSharedLibByDefault+ (liftM2 (||) pkgSharedLib pkgDynExe)+ where+ pkgid = packageId pkg+ pkgSharedLib = perPkgOptionMaybe pkgid packageConfigSharedLib+ pkgDynExe = perPkgOptionMaybe pkgid packageConfigDynExe++ --TODO: [code cleanup] move this into the Cabal lib. It's currently open+ -- coded in Distribution.Simple.Configure, but should be made a proper+ -- function of the Compiler or CompilerInfo.+ compilerShouldUseSharedLibByDefault =+ case compilerFlavor compiler of+ GHC -> GHC.isDynamic compiler+ GHCJS -> GHCJS.isDynamic compiler+ _ -> False++ pkgsUseProfilingLibrary :: Set PackageId+ pkgsUseProfilingLibrary =+ packagesWithDownwardClosedProperty needsProfilingLib+ where+ needsProfilingLib pkg =+ fromFlagOrDefault False (profBothFlag <> profLibFlag)+ where+ pkgid = packageId pkg+ profBothFlag = lookupPerPkgOption pkgid packageConfigProf+ profLibFlag = lookupPerPkgOption pkgid packageConfigProfLib+ --TODO: [code cleanup] unused: the old deprecated packageConfigProfExe++ packagesWithDownwardClosedProperty property =+ Set.fromList+ $ map packageId+ $ InstallPlan.dependencyClosure+ solverPlan+ [ installedPackageId pkg+ | pkg <- InstallPlan.toList solverPlan+ , property pkg ] -- just the packages that satisfy the propety+ --TODO: [nice to have] this does not check the config consistency,+ -- e.g. a package explicitly turning off profiling, but something+ -- depending on it that needs profiling. This really needs a separate+ -- package config validation/resolution pass.++ --TODO: [nice to have] config consistency checking:+ -- * profiling libs & exes, exe needs lib, recursive+ -- * shared libs & exes, exe needs lib, recursive+ -- * vanilla libs & exes, exe needs lib, recursive+ -- * ghci or shared lib needed by TH, recursive, ghc version dependent+++---------------------------+-- Build targets+--++-- | The various targets within a package. This is more of a high level+-- specification than a elaborated prescription.+--+data PackageTarget =+ -- | Build the default components in this package. This usually means+ -- just the lib and exes, but it can also mean the testsuites and+ -- benchmarks if the user explicitly requested them.+ BuildDefaultComponents+ -- | Build a specific component in this package.+ | BuildSpecificComponent ComponentTarget+ | ReplDefaultComponent+ | ReplSpecificComponent ComponentTarget+ | HaddockDefaultComponents+ deriving (Eq, Show, Generic)++data ComponentTarget = ComponentTarget ComponentName SubComponentTarget+ deriving (Eq, Show, Generic)++data SubComponentTarget = WholeComponent+ | ModuleTarget ModuleName+ | FileTarget FilePath+ deriving (Eq, Show, Generic)++instance Binary PackageTarget+instance Binary ComponentTarget+instance Binary SubComponentTarget+++--TODO: this needs to report some user target/config errors+elaboratePackageTargets :: ElaboratedConfiguredPackage -> [PackageTarget]+ -> ([ComponentTarget], Maybe ComponentTarget, Bool)+elaboratePackageTargets ElaboratedConfiguredPackage{..} targets =+ let buildTargets = nubComponentTargets+ . map compatSubComponentTargets+ . concatMap elaborateBuildTarget+ $ targets+ --TODO: instead of listToMaybe we should be reporting an error here+ replTargets = listToMaybe+ . nubComponentTargets+ . map compatSubComponentTargets+ . concatMap elaborateReplTarget+ $ targets+ buildHaddocks = HaddockDefaultComponents `elem` targets++ in (buildTargets, replTargets, buildHaddocks)+ where+ --TODO: need to report an error here if defaultComponents is empty+ elaborateBuildTarget BuildDefaultComponents = pkgDefaultComponents+ elaborateBuildTarget (BuildSpecificComponent t) = [t]+ elaborateBuildTarget _ = []++ --TODO: need to report an error here if defaultComponents is empty+ elaborateReplTarget ReplDefaultComponent = take 1 pkgDefaultComponents+ elaborateReplTarget (ReplSpecificComponent t) = [t]+ elaborateReplTarget _ = []++ pkgDefaultComponents =+ [ ComponentTarget cname WholeComponent+ | c <- Cabal.pkgComponents pkgDescription+ , PD.buildable (Cabal.componentBuildInfo c)+ , let cname = Cabal.componentName c+ , enabledOptionalStanza cname+ ]+ where+ enabledOptionalStanza cname =+ case componentOptionalStanza cname of+ Nothing -> True+ Just stanza -> Map.lookup stanza pkgStanzasRequested+ == Just True++ -- Not all Cabal Setup.hs versions support sub-component targets, so switch+ -- them over to the whole component+ compatSubComponentTargets :: ComponentTarget -> ComponentTarget+ compatSubComponentTargets target@(ComponentTarget cname _subtarget)+ | not setupHsSupportsSubComponentTargets+ = ComponentTarget cname WholeComponent+ | otherwise = target++ -- Actually the reality is that no current version of Cabal's Setup.hs+ -- build command actually support building specific files or modules.+ setupHsSupportsSubComponentTargets = False+ -- TODO: when that changes, adjust this test, e.g.+ -- | pkgSetupScriptCliVersion >= Version [x,y] []++ nubComponentTargets :: [ComponentTarget] -> [ComponentTarget]+ nubComponentTargets =+ concatMap (wholeComponentOverrides . map snd)+ . groupBy ((==) `on` fst)+ . sortBy (compare `on` fst)+ . map (\t@(ComponentTarget cname _) -> (cname, t))++ -- If we're building the whole component then that the only target all we+ -- need, otherwise we can have several targets within the component.+ wholeComponentOverrides :: [ComponentTarget] -> [ComponentTarget]+ wholeComponentOverrides ts =+ case [ t | t@(ComponentTarget _ WholeComponent) <- ts ] of+ (t:_) -> [t]+ [] -> ts+++pkgHasEphemeralBuildTargets :: ElaboratedConfiguredPackage -> Bool+pkgHasEphemeralBuildTargets pkg =+ isJust (pkgReplTarget pkg)+ || (not . null) [ () | ComponentTarget _ subtarget <- pkgBuildTargets pkg+ , subtarget /= WholeComponent ]++-- | The components that we'll build all of, meaning that after they're built+-- we can skip building them again (unlike with building just some modules or+-- other files within a component).+--+pkgBuildTargetWholeComponents :: ElaboratedConfiguredPackage+ -> Set ComponentName+pkgBuildTargetWholeComponents pkg =+ Set.fromList+ [ cname | ComponentTarget cname WholeComponent <- pkgBuildTargets pkg ]+++------------------------------------------------------------------------------+-- * Install plan pruning+------------------------------------------------------------------------------++-- | Given a set of package targets (and optionally component targets within+-- those packages), take the subset of the install plan needed to build those+-- targets. Also, update the package config to specify which optional stanzas+-- to enable, and which targets within each package to build.+--+pruneInstallPlanToTargets :: Map InstalledPackageId [PackageTarget]+ -> ElaboratedInstallPlan -> ElaboratedInstallPlan+pruneInstallPlanToTargets perPkgTargetsMap =+ either (\_ -> assert False undefined) id+ . InstallPlan.new False+ . PackageIndex.fromList+ -- We have to do this in two passes+ . pruneInstallPlanPass2+ . pruneInstallPlanPass1 perPkgTargetsMap+ . InstallPlan.toList++-- The first pass does three things:+--+-- * Set the build targets based on the user targets (but not rev deps yet).+-- * A first go at determining which optional stanzas (testsuites, benchmarks)+-- are needed. We have a second go in the next pass.+-- * Take the dependency closure using pruned dependencies. We prune deps that+-- are used only by unneeded optional stanzas. These pruned deps are only+-- used for the dependency closure and are not persisted in this pass.+--+pruneInstallPlanPass1 :: Map InstalledPackageId [PackageTarget]+ -> [ElaboratedPlanPackage]+ -> [ElaboratedPlanPackage]+pruneInstallPlanPass1 perPkgTargetsMap pkgs =+ map fst $+ dependencyClosure+ (installedPackageId . fst) -- the pkg id+ snd -- the pruned deps+ [ (pkg', pruneOptionalDependencies pkg')+ | pkg <- pkgs+ , let pkg' = mapConfiguredPackage+ (pruneOptionalStanzas . setBuildTargets) pkg+ ]+ (Map.keys perPkgTargetsMap)+ where+ -- Elaborate and set the targets we'll build for this package. This is just+ -- based on the targets from the user, not targets implied by reverse+ -- depencencies. Those comes in the second pass once we know the rev deps.+ --+ setBuildTargets pkg =+ pkg {+ pkgBuildTargets = buildTargets,+ pkgReplTarget = replTarget,+ pkgBuildHaddocks = buildHaddocks+ }+ where+ (buildTargets, replTarget, buildHaddocks)+ = elaboratePackageTargets pkg targets+ targets = fromMaybe []+ $ Map.lookup (installedPackageId pkg) perPkgTargetsMap++ -- Decide whether or not to enable testsuites and benchmarks+ --+ -- The testsuite and benchmark targets are somewhat special in that we need+ -- to configure the packages with them enabled, and we need to do that even+ -- if we only want to build one of several testsuites.+ --+ -- There are two cases in which we will enable the testsuites (or+ -- benchmarks): if one of the targets is a testsuite, or if all of the+ -- testsuite depencencies are already cached in the store. The rationale+ -- for the latter is to minimise how often we have to reconfigure due to+ -- the particular targets we choose to build. Otherwise choosing to build+ -- a testsuite target, and then later choosing to build an exe target+ -- would involve unnecessarily reconfiguring the package with testsuites+ -- disabled. Technically this introduces a little bit of stateful+ -- behaviour to make this "sticky", but it should be benign.+ --+ pruneOptionalStanzas pkg = pkg { pkgStanzasEnabled = stanzas }+ where+ stanzas :: Set OptionalStanza+ stanzas = optionalStanzasRequiredByTargets pkg+ <> optionalStanzasRequestedByDefault pkg+ <> optionalStanzasWithDepsAvailable availablePkgs pkg++ -- Calculate package depencencies but cut out those needed only by+ -- optional stanzas that we've determined we will not enable.+ -- These pruned deps are not persisted in this pass since they're based on+ -- the optional stanzas and we'll make further tweaks to the optional+ -- stanzas in the next pass.+ --+ pruneOptionalDependencies :: ElaboratedPlanPackage -> [InstalledPackageId]+ pruneOptionalDependencies (InstallPlan.Configured pkg) =+ (CD.flatDeps . CD.filterDeps keepNeeded) (depends pkg)+ where+ keepNeeded (CD.ComponentTest _) _ = TestStanzas `Set.member` stanzas+ keepNeeded (CD.ComponentBench _) _ = BenchStanzas `Set.member` stanzas+ keepNeeded _ _ = True+ stanzas = pkgStanzasEnabled pkg+ pruneOptionalDependencies pkg =+ CD.flatDeps (depends pkg)++ optionalStanzasRequiredByTargets :: ElaboratedConfiguredPackage+ -> Set OptionalStanza+ optionalStanzasRequiredByTargets pkg =+ Set.fromList+ [ stanza+ | ComponentTarget cname _ <- pkgBuildTargets pkg+ ++ maybeToList (pkgReplTarget pkg)+ , stanza <- maybeToList (componentOptionalStanza cname)+ ]++ optionalStanzasRequestedByDefault :: ElaboratedConfiguredPackage+ -> Set OptionalStanza+ optionalStanzasRequestedByDefault =+ Map.keysSet+ . Map.filter (id :: Bool -> Bool)+ . pkgStanzasRequested++ availablePkgs =+ Set.fromList+ [ installedPackageId pkg+ | InstallPlan.PreExisting pkg <- pkgs ]++optionalStanzasWithDepsAvailable :: Set InstalledPackageId+ -> ElaboratedConfiguredPackage+ -> Set OptionalStanza+optionalStanzasWithDepsAvailable availablePkgs pkg =+ Set.fromList+ [ stanza+ | stanza <- Set.toList (pkgStanzasAvailable pkg)+ , let deps :: [InstalledPackageId]+ deps = map installedPackageId+ $ CD.select (optionalStanzaDeps stanza)+ (pkgDependencies pkg)+ , all (`Set.member` availablePkgs) deps+ ]+ where+ optionalStanzaDeps TestStanzas (CD.ComponentTest _) = True+ optionalStanzaDeps BenchStanzas (CD.ComponentBench _) = True+ optionalStanzaDeps _ _ = False+++-- The second pass does three things:+--+-- * A second go at deciding which optional stanzas to enable.+-- * Prune the depencencies based on the final choice of optional stanzas.+-- * Extend the targets within each package to build, now we know the reverse+-- depencencies, ie we know which libs are needed as deps by other packages.+--+-- Achieving sticky behaviour with enabling\/disabling optional stanzas is+-- tricky. The first approximation was handled by the first pass above, but+-- it's not quite enough. That pass will enable stanzas if all of the deps+-- of the optional stanza are already instaled /in the store/. That's important+-- but it does not account for depencencies that get built inplace as part of+-- the project. We cannot take those inplace build deps into account in the+-- pruning pass however because we don't yet know which ones we're going to+-- build. Once we do know, we can have another go and enable stanzas that have+-- all their deps available. Now we can consider all packages in the pruned+-- plan to be available, including ones we already decided to build from+-- source.+--+-- Deciding which targets to build depends on knowing which packages have+-- reverse dependencies (ie are needed). This requires the result of first+-- pass, which is another reason we have to split it into two passes.+--+-- Note that just because we might enable testsuites or benchmarks (in the+-- first or second pass) doesn't mean that we build all (or even any) of them.+-- That depends on which targets we picked in the first pass.+--+pruneInstallPlanPass2 :: [ElaboratedPlanPackage]+ -> [ElaboratedPlanPackage]+pruneInstallPlanPass2 pkgs =+ map (mapConfiguredPackage setStanzasDepsAndTargets) pkgs+ where+ setStanzasDepsAndTargets pkg =+ pkg {+ pkgStanzasEnabled = stanzas,+ pkgDependencies = CD.filterDeps keepNeeded (pkgDependencies pkg),+ pkgBuildTargets = pkgBuildTargets pkg ++ targetsRequiredForRevDeps+ }+ where+ stanzas :: Set OptionalStanza+ stanzas = pkgStanzasEnabled pkg+ <> optionalStanzasWithDepsAvailable availablePkgs pkg++ keepNeeded (CD.ComponentTest _) _ = TestStanzas `Set.member` stanzas+ keepNeeded (CD.ComponentBench _) _ = BenchStanzas `Set.member` stanzas+ keepNeeded _ _ = True++ targetsRequiredForRevDeps =+ [ ComponentTarget (Cabal.defaultLibName (pkgSourceId pkg)) WholeComponent+ -- if anything needs this pkg, build the library component+ | installedPackageId pkg `Set.member` hasReverseLibDeps+ ]+ --TODO: also need to track build-tool rev-deps for exes++ availablePkgs :: Set InstalledPackageId+ availablePkgs = Set.fromList (map installedPackageId pkgs)++ hasReverseLibDeps :: Set InstalledPackageId+ hasReverseLibDeps =+ Set.fromList [ depid | pkg <- pkgs+ , depid <- CD.flatDeps (depends pkg) ]+++mapConfiguredPackage :: (ElaboratedConfiguredPackage -> ElaboratedConfiguredPackage)+ -> ElaboratedPlanPackage+ -> ElaboratedPlanPackage+mapConfiguredPackage f (InstallPlan.Configured pkg) =+ InstallPlan.Configured (f pkg)+mapConfiguredPackage _ pkg = pkg++componentOptionalStanza :: Cabal.ComponentName -> Maybe OptionalStanza+componentOptionalStanza (Cabal.CTestName _) = Just TestStanzas+componentOptionalStanza (Cabal.CBenchName _) = Just BenchStanzas+componentOptionalStanza _ = Nothing+++dependencyClosure :: (pkg -> InstalledPackageId)+ -> (pkg -> [InstalledPackageId])+ -> [pkg]+ -> [InstalledPackageId]+ -> [pkg]+dependencyClosure pkgid deps allpkgs =+ map vertexToPkg+ . concatMap Tree.flatten+ . Graph.dfs graph+ . map pkgidToVertex+ where+ (graph, vertexToPkg, pkgidToVertex) = dependencyGraph pkgid deps allpkgs++dependencyGraph :: (pkg -> InstalledPackageId)+ -> (pkg -> [InstalledPackageId])+ -> [pkg]+ -> (Graph.Graph,+ Graph.Vertex -> pkg,+ InstalledPackageId -> Graph.Vertex)+dependencyGraph pkgid deps pkgs =+ (graph, vertexToPkg', pkgidToVertex')+ where+ (graph, vertexToPkg, pkgidToVertex) =+ Graph.graphFromEdges [ ( pkg, pkgid pkg, deps pkg )+ | pkg <- pkgs ]+ vertexToPkg' = (\(pkg,_,_) -> pkg)+ . vertexToPkg+ pkgidToVertex' = fromMaybe (error "dependencyGraph: lookup failure")+ . pkgidToVertex+++---------------------------+-- Setup.hs script policy+--++-- Handling for Setup.hs scripts is a bit tricky, part of it lives in the+-- solver phase, and part in the elaboration phase. We keep the helper+-- functions for both phases together here so at least you can see all of it+-- in one place.++-- | There are four major cases for Setup.hs handling:+--+-- 1. @build-type@ Custom with a @custom-setup@ section+-- 2. @build-type@ Custom without a @custom-setup@ section+-- 3. @build-type@ not Custom with @cabal-version > $our-cabal-version@+-- 4. @build-type@ not Custom with @cabal-version <= $our-cabal-version@+--+-- It's also worth noting that packages specifying @cabal-version: >= 1.23@+-- or later that have @build-type@ Custom will always have a @custom-setup@+-- section. Therefore in case 2, the specified @cabal-version@ will always be+-- less than 1.23.+--+-- In cases 1 and 2 we obviously have to build an external Setup.hs script,+-- while in case 4 we can use the internal library API. In case 3 we also have+-- to build an external Setup.hs script because the package needs a later+-- Cabal lib version than we can support internally.+--+data SetupScriptStyle = SetupCustomExplicitDeps+ | SetupCustomImplicitDeps+ | SetupNonCustomExternalLib+ | SetupNonCustomInternalLib+ deriving (Eq, Show, Generic)++instance Binary SetupScriptStyle+++-- | Work out the 'SetupScriptStyle' given the package description.+--+-- This only works on original packages before we give them to the solver,+-- since after the solver some implicit setup deps are made explicit.+--+-- See 'rememberImplicitSetupDeps' and 'packageSetupScriptStylePostSolver'.+--+packageSetupScriptStylePreSolver :: PD.PackageDescription -> SetupScriptStyle+packageSetupScriptStylePreSolver pkg+ | buildType == PD.Custom+ , isJust (PD.setupBuildInfo pkg)+ = SetupCustomExplicitDeps++ | buildType == PD.Custom+ = SetupCustomImplicitDeps++ | PD.specVersion pkg > cabalVersion -- one cabal-install is built against+ = SetupNonCustomExternalLib++ | otherwise+ = SetupNonCustomInternalLib+ where+ buildType = fromMaybe PD.Custom (PD.buildType pkg)+++-- | Part of our Setup.hs handling policy is implemented by getting the solver+-- to work out setup dependencies for packages. The solver already handles+-- packages that explicitly specify setup dependencies, but we can also tell+-- the solver to treat other packages as if they had setup dependencies.+-- That's what this function does, it gets called by the solver for all+-- packages that don't already have setup dependencies.+--+-- The dependencies we want to add is different for each 'SetupScriptStyle'.+--+-- Note that adding default deps means these deps are actually /added/ to the+-- packages that we get out of the solver in the 'SolverInstallPlan'. Making+-- implicit setup deps explicit is a problem in the post-solver stages because+-- we still need to distinguish the case of explicit and implict setup deps.+-- See 'rememberImplicitSetupDeps'.+--+defaultSetupDeps :: Platform -> PD.PackageDescription -> [Dependency]+defaultSetupDeps platform pkg =+ case packageSetupScriptStylePreSolver pkg of++ -- For packages with build type custom that do not specify explicit+ -- setup dependencies, we add a dependency on Cabal and a number+ -- of other packages.+ SetupCustomImplicitDeps ->+ [ Dependency depPkgname anyVersion+ | depPkgname <- legacyCustomSetupPkgs platform ] +++ -- The Cabal dep is slightly special:+ -- * we omit the dep for the Cabal lib itself (since it bootstraps),+ -- * we constrain it to be less than 1.23 since all packages+ -- relying on later Cabal spec versions are supposed to use+ -- explit setup deps. Having this constraint also allows later+ -- Cabal lib versions to make breaking API changes without breaking+ -- all old Setup.hs scripts.+ [ Dependency cabalPkgname cabalConstraint+ | packageName pkg /= cabalPkgname ]+ where+ cabalConstraint = orLaterVersion (PD.specVersion pkg)+ `intersectVersionRanges`+ earlierVersion cabalCompatMaxVer+ -- TODO/FIXME: turns out that constraining to less than 1.23 causes+ -- problems with GHC8 as there's too many important packages+ -- with Custom build-type, for which there wouldn't be any+ -- install-plan (as GHC8 requires Cabal-1.24+). So let's+ -- set an implicit upper bound `Cabal < 2` instead.+ cabalCompatMaxVer = Version [2] []++ -- For other build types (like Simple) if we still need to compile an+ -- external Setup.hs, it'll be one of the simple ones that only depends+ -- on Cabal and base.+ SetupNonCustomExternalLib ->+ [ Dependency cabalPkgname cabalConstraint+ , Dependency basePkgname anyVersion ]+ where+ cabalConstraint = orLaterVersion (PD.specVersion pkg)++ -- The internal setup wrapper method has no deps at all.+ SetupNonCustomInternalLib -> []++ SetupCustomExplicitDeps ->+ error $ "defaultSetupDeps: called for a package with explicit "+ ++ "setup deps: " ++ display (packageId pkg)+++-- | See 'rememberImplicitSetupDeps' for details.+type PackagesImplicitSetupDeps = Set InstalledPackageId++-- | A consequence of using 'defaultSetupDeps' in 'planPackages' is that by+-- making implicit setup deps explicit we loose track of which packages+-- originally had implicit setup deps. That's important because we do still+-- have different behaviour based on the setup style (in particular whether to+-- compile a Setup.hs script with version macros).+--+-- So we remember the necessary information in an auxilliary set and use it+-- in 'packageSetupScriptStylePreSolver' to recover the full info.+--+rememberImplicitSetupDeps :: SourcePackageIndex.PackageIndex (SourcePackage loc)+ -> SolverInstallPlan+ -> (SolverInstallPlan, PackagesImplicitSetupDeps)+rememberImplicitSetupDeps sourcePkgIndex plan =+ (plan, pkgsImplicitSetupDeps)+ where+ pkgsImplicitSetupDeps =+ Set.fromList+ [ installedPackageId pkg+ | InstallPlan.Configured+ pkg@(ConfiguredPackage newpkg _ _ _) <- InstallPlan.toList plan+ -- has explicit setup deps now+ , hasExplicitSetupDeps newpkg+ -- but originally had no setup deps+ , let Just origpkg = SourcePackageIndex.lookupPackageId+ sourcePkgIndex (packageId pkg)+ , not (hasExplicitSetupDeps origpkg)+ ]++ hasExplicitSetupDeps =+ (SetupCustomExplicitDeps==)+ . packageSetupScriptStylePreSolver+ . PD.packageDescription . packageDescription+++-- | Use the extra info saved by 'rememberImplicitSetupDeps' to let us work+-- out the correct 'SetupScriptStyle'. This should give the same result as+-- 'packageSetupScriptStylePreSolver' gave prior to munging the package info+-- through the solver.+--+packageSetupScriptStylePostSolver :: Set InstalledPackageId+ -> ConfiguredPackage loc+ -> PD.PackageDescription+ -> SetupScriptStyle+packageSetupScriptStylePostSolver pkgsImplicitSetupDeps pkg pkgDescription =+ case packageSetupScriptStylePreSolver pkgDescription of+ SetupCustomExplicitDeps+ | Set.member (installedPackageId pkg) pkgsImplicitSetupDeps+ -> SetupCustomImplicitDeps+ other -> other+++-- | Work out which version of the Cabal spec we will be using to talk to the+-- Setup.hs interface for this package.+--+-- This depends somewhat on the 'SetupScriptStyle' but most cases are a result+-- of what the solver picked for us, based on the explicit setup deps or the+-- ones added implicitly by 'defaultSetupDeps'.+--+packageSetupScriptSpecVersion :: Package pkg+ => SetupScriptStyle+ -> PD.PackageDescription+ -> ComponentDeps [pkg]+ -> Version++-- We're going to be using the internal Cabal library, so the spec version of+-- that is simply the version of the Cabal library that cabal-install has been+-- built with.+packageSetupScriptSpecVersion SetupNonCustomInternalLib _ _ =+ cabalVersion++-- If we happen to be building the Cabal lib itself then because that+-- bootstraps itself then we use the version of the lib we're building.+packageSetupScriptSpecVersion SetupCustomImplicitDeps pkg _+ | packageName pkg == cabalPkgname+ = packageVersion pkg++-- In all other cases we have a look at what version of the Cabal lib the+-- solver picked. Or if it didn't depend on Cabal at all (which is very rare)+-- then we look at the .cabal file to see what spec version it declares.+packageSetupScriptSpecVersion _ pkg deps =+ case find ((cabalPkgname ==) . packageName) (CD.setupDeps deps) of+ Just dep -> packageVersion dep+ Nothing -> PD.specVersion pkg+++cabalPkgname, basePkgname :: PackageName+cabalPkgname = PackageName "Cabal"+basePkgname = PackageName "base"+++legacyCustomSetupPkgs :: Platform -> [PackageName]+legacyCustomSetupPkgs (Platform _ os) =+ map PackageName $+ [ "array", "base", "binary", "bytestring", "containers"+ , "deepseq", "directory", "filepath", "pretty"+ , "process", "time" ]+ ++ [ "Win32" | os == Windows ]+ ++ [ "unix" | os /= Windows ]++-- The other aspects of our Setup.hs policy lives here where we decide on+-- the 'SetupScriptOptions'.+--+-- Our current policy for the 'SetupCustomImplicitDeps' case is that we+-- try to make the implicit deps cover everything, and we don't allow the+-- compiler to pick up other deps. This may or may not be sustainable, and+-- we might have to allow the deps to be non-exclusive, but that itself would+-- be tricky since we would have to allow the Setup access to all the packages+-- in the store and local dbs.++setupHsScriptOptions :: ElaboratedReadyPackage+ -> ElaboratedSharedConfig+ -> FilePath+ -> FilePath+ -> Bool+ -> Lock+ -> SetupScriptOptions+setupHsScriptOptions (ReadyPackage ElaboratedConfiguredPackage{..})+ ElaboratedSharedConfig{..} srcdir builddir+ isParallelBuild cacheLock =+ SetupScriptOptions {+ useCabalVersion = thisVersion pkgSetupScriptCliVersion,+ useCabalSpecVersion = Just pkgSetupScriptCliVersion,+ useCompiler = Just pkgConfigCompiler,+ usePlatform = Just pkgConfigPlatform,+ usePackageDB = pkgSetupPackageDBStack,+ usePackageIndex = Nothing,+ useDependencies = [ (uid, srcid)+ | ConfiguredId srcid uid <- CD.setupDeps pkgDependencies ],+ useDependenciesExclusive = True,+ useVersionMacros = pkgSetupScriptStyle == SetupCustomExplicitDeps,+ useProgramConfig = pkgConfigProgramDb,+ useDistPref = builddir,+ useLoggingHandle = Nothing, -- this gets set later+ useWorkingDir = Just srcdir,+ useWin32CleanHack = False, --TODO: [required eventually]+ forceExternalSetupMethod = isParallelBuild,+ setupCacheLock = Just cacheLock+ }+++-- | To be used for the input for elaborateInstallPlan.+--+-- TODO: [code cleanup] make InstallDirs.defaultInstallDirs pure.+--+userInstallDirTemplates :: Compiler+ -> IO InstallDirs.InstallDirTemplates+userInstallDirTemplates compiler = do+ InstallDirs.defaultInstallDirs+ (compilerFlavor compiler)+ True -- user install+ False -- unused++storePackageInstallDirs :: CabalDirLayout+ -> CompilerId+ -> InstalledPackageId+ -> InstallDirs.InstallDirs FilePath+storePackageInstallDirs CabalDirLayout{cabalStorePackageDirectory}+ compid ipkgid =+ InstallDirs.InstallDirs {..}+ where+ prefix = cabalStorePackageDirectory compid ipkgid+ bindir = prefix </> "bin"+ libdir = prefix </> "lib"+ libsubdir = ""+ dynlibdir = libdir+ libexecdir = prefix </> "libexec"+ includedir = libdir </> "include"+ datadir = prefix </> "share"+ datasubdir = ""+ docdir = datadir </> "doc"+ mandir = datadir </> "man"+ htmldir = docdir </> "html"+ haddockdir = htmldir+ sysconfdir = prefix </> "etc"+++--TODO: [code cleanup] perhaps reorder this code+-- based on the ElaboratedInstallPlan + ElaboratedSharedConfig,+-- make the various Setup.hs {configure,build,copy} flags+++setupHsConfigureFlags :: ElaboratedReadyPackage+ -> ElaboratedSharedConfig+ -> Verbosity+ -> FilePath+ -> Cabal.ConfigFlags+setupHsConfigureFlags (ReadyPackage+ pkg@ElaboratedConfiguredPackage{..})+ sharedConfig@ElaboratedSharedConfig{..}+ verbosity builddir =+ assert (sanityCheckElaboratedConfiguredPackage sharedConfig pkg)+ Cabal.ConfigFlags {..}+ where+ configDistPref = toFlag builddir+ configVerbosity = toFlag verbosity++ configIPID = toFlag (display (installedUnitId pkg))++ configProgramPaths = programDbProgramPaths pkgConfigProgramDb+ configProgramArgs = programDbProgramArgs pkgConfigProgramDb+ configProgramPathExtra = programDbPathExtra pkgConfigProgramDb+ configHcFlavor = toFlag (compilerFlavor pkgConfigCompiler)+ configHcPath = mempty -- use configProgramPaths instead+ configHcPkg = mempty -- use configProgramPaths instead++ configVanillaLib = toFlag pkgVanillaLib+ configSharedLib = toFlag pkgSharedLib+ configDynExe = toFlag pkgDynExe+ configGHCiLib = toFlag pkgGHCiLib+ configProfExe = mempty+ configProfLib = toFlag pkgProfLib+ configProf = toFlag pkgProfExe++ -- configProfDetail is for exe+lib, but overridden by configProfLibDetail+ -- so we specify both so we can specify independently+ configProfDetail = toFlag pkgProfExeDetail+ configProfLibDetail = toFlag pkgProfLibDetail++ configCoverage = toFlag pkgCoverage+ configLibCoverage = mempty++ configOptimization = toFlag pkgOptimization+ configSplitObjs = toFlag pkgSplitObjs+ configStripExes = toFlag pkgStripExes+ configStripLibs = toFlag pkgStripLibs+ configDebugInfo = toFlag pkgDebugInfo+ configAllowNewer = mempty -- we use configExactConfiguration True++ configConfigurationsFlags = pkgFlagAssignment+ configConfigureArgs = pkgConfigureScriptArgs+ configExtraLibDirs = pkgExtraLibDirs+ configExtraFrameworkDirs = pkgExtraFrameworkDirs+ configExtraIncludeDirs = pkgExtraIncludeDirs+ configProgPrefix = maybe mempty toFlag pkgProgPrefix+ configProgSuffix = maybe mempty toFlag pkgProgSuffix++ configInstallDirs = fmap (toFlag . InstallDirs.toPathTemplate)+ pkgInstallDirs++ -- we only use configDependencies, unless we're talking to an old Cabal+ -- in which case we use configConstraints+ configDependencies = [ (packageName srcid, uid)+ | ConfiguredId srcid uid <- CD.nonSetupDeps pkgDependencies ]+ configConstraints = [ thisPackageVersion srcid+ | ConfiguredId srcid _uid <- CD.nonSetupDeps pkgDependencies ]++ -- explicitly clear, then our package db stack+ -- TODO: [required eventually] have to do this differently for older Cabal versions+ configPackageDBs = Nothing : map Just pkgBuildPackageDBStack++ configTests = toFlag (TestStanzas `Set.member` pkgStanzasEnabled)+ configBenchmarks = toFlag (BenchStanzas `Set.member` pkgStanzasEnabled)++ configExactConfiguration = toFlag True+ configFlagError = mempty --TODO: [research required] appears not to be implemented+ configRelocatable = mempty --TODO: [research required] ???+ configScratchDir = mempty -- never use+ configUserInstall = mempty -- don't rely on defaults+ configPrograms_ = mempty -- never use, shouldn't exist++ programDbProgramPaths db =+ [ (programId prog, programPath prog)+ | prog <- configuredPrograms db ]++ programDbProgramArgs db =+ [ (programId prog, programOverrideArgs prog)+ | prog <- configuredPrograms db ]++ programDbPathExtra db =+ case getProgramSearchPath db of+ ProgramSearchPathDefault : extra ->+ toNubList [ dir | ProgramSearchPathDir dir <- extra ]+ _ -> error $ "setupHsConfigureFlags: we cannot currently cope with a "+ ++ "search path that does not start with the system path"+ -- the Setup.hs interface only has --extra-prog-path+ -- so we cannot put things before the $PATH, only after+++setupHsBuildFlags :: ElaboratedConfiguredPackage+ -> ElaboratedSharedConfig+ -> Verbosity+ -> FilePath+ -> Cabal.BuildFlags+setupHsBuildFlags ElaboratedConfiguredPackage{..} _ verbosity builddir =+ Cabal.BuildFlags {+ buildProgramPaths = mempty, --unused, set at configure time+ buildProgramArgs = mempty, --unused, set at configure time+ buildVerbosity = toFlag verbosity,+ buildDistPref = toFlag builddir,+ buildNumJobs = mempty, --TODO: [nice to have] sometimes want to use toFlag (Just numBuildJobs),+ buildArgs = mempty -- unused, passed via args not flags+ }+++setupHsBuildArgs :: ElaboratedConfiguredPackage -> [String]+setupHsBuildArgs pkg =+ map (showComponentTarget pkg) (pkgBuildTargets pkg)+++showComponentTarget :: ElaboratedConfiguredPackage -> ComponentTarget -> String+showComponentTarget _pkg =+ showBuildTarget . toBuildTarget+ where+ showBuildTarget t =+ Cabal.showBuildTarget (qlBuildTarget t) t++ qlBuildTarget Cabal.BuildTargetComponent{} = Cabal.QL2+ qlBuildTarget _ = Cabal.QL3++ toBuildTarget :: ComponentTarget -> Cabal.BuildTarget+ toBuildTarget (ComponentTarget cname subtarget) =+ case subtarget of+ WholeComponent -> Cabal.BuildTargetComponent cname+ ModuleTarget mname -> Cabal.BuildTargetModule cname mname+ FileTarget fname -> Cabal.BuildTargetFile cname fname+++setupHsReplFlags :: ElaboratedConfiguredPackage+ -> ElaboratedSharedConfig+ -> Verbosity+ -> FilePath+ -> Cabal.ReplFlags+setupHsReplFlags ElaboratedConfiguredPackage{..} _ verbosity builddir =+ Cabal.ReplFlags {+ replProgramPaths = mempty, --unused, set at configure time+ replProgramArgs = mempty, --unused, set at configure time+ replVerbosity = toFlag verbosity,+ replDistPref = toFlag builddir,+ replReload = mempty --only used as callback from repl+ }+++setupHsReplArgs :: ElaboratedConfiguredPackage -> [String]+setupHsReplArgs pkg =+ maybe [] (\t -> [showComponentTarget pkg t]) (pkgReplTarget pkg)+ --TODO: should be able to give multiple modules in one component+++setupHsCopyFlags :: ElaboratedConfiguredPackage+ -> ElaboratedSharedConfig+ -> Verbosity+ -> FilePath+ -> Cabal.CopyFlags+setupHsCopyFlags _ _ verbosity builddir =+ Cabal.CopyFlags {+ --TODO: [nice to have] we currently just rely on Setup.hs copy to always do the right+ -- thing, but perhaps we ought really to copy into an image dir and do+ -- some sanity checks and move into the final location ourselves+ copyArgs = [], -- TODO: could use this to only copy what we enabled+ copyDest = toFlag InstallDirs.NoCopyDest,+ copyDistPref = toFlag builddir,+ copyVerbosity = toFlag verbosity+ }++setupHsRegisterFlags :: ElaboratedConfiguredPackage+ -> ElaboratedSharedConfig+ -> Verbosity+ -> FilePath+ -> FilePath+ -> Cabal.RegisterFlags+setupHsRegisterFlags ElaboratedConfiguredPackage {pkgBuildStyle} _+ verbosity builddir pkgConfFile =+ Cabal.RegisterFlags {+ regPackageDB = mempty, -- misfeature+ regGenScript = mempty, -- never use+ regGenPkgConf = toFlag (Just pkgConfFile),+ regInPlace = case pkgBuildStyle of+ BuildInplaceOnly -> toFlag True+ _ -> toFlag False,+ regPrintId = mempty, -- never use+ regDistPref = toFlag builddir,+ regVerbosity = toFlag verbosity+ }++setupHsHaddockFlags :: ElaboratedConfiguredPackage+ -> ElaboratedSharedConfig+ -> Verbosity+ -> FilePath+ -> Cabal.HaddockFlags+setupHsHaddockFlags ElaboratedConfiguredPackage{..} _ verbosity builddir =+ Cabal.HaddockFlags {+ haddockProgramPaths = mempty, --unused, set at configure time+ haddockProgramArgs = mempty, --unused, set at configure time+ haddockHoogle = toFlag pkgHaddockHoogle,+ haddockHtml = toFlag pkgHaddockHtml,+ haddockHtmlLocation = maybe mempty toFlag pkgHaddockHtmlLocation,+ haddockForHackage = mempty, --TODO: new flag+ haddockExecutables = toFlag pkgHaddockExecutables,+ haddockTestSuites = toFlag pkgHaddockTestSuites,+ haddockBenchmarks = toFlag pkgHaddockBenchmarks,+ haddockInternal = toFlag pkgHaddockInternal,+ haddockCss = maybe mempty toFlag pkgHaddockCss,+ haddockHscolour = toFlag pkgHaddockHscolour,+ haddockHscolourCss = maybe mempty toFlag pkgHaddockHscolourCss,+ haddockContents = maybe mempty toFlag pkgHaddockContents,+ haddockDistPref = toFlag builddir,+ haddockKeepTempFiles = mempty, --TODO: from build settings+ haddockVerbosity = toFlag verbosity+ }++{-+setupHsTestFlags :: ElaboratedConfiguredPackage+ -> ElaboratedSharedConfig+ -> Verbosity+ -> FilePath+ -> Cabal.TestFlags+setupHsTestFlags _ _ verbosity builddir =+ Cabal.TestFlags {+ }+-}++------------------------------------------------------------------------------+-- * Sharing installed packages+------------------------------------------------------------------------------++--+-- Nix style store management for tarball packages+--+-- So here's our strategy:+--+-- We use a per-user nix-style hashed store, but /only/ for tarball packages.+-- So that includes packages from hackage repos (and other http and local+-- tarballs). For packages in local directories we do not register them into+-- the shared store by default, we just build them locally inplace.+--+-- The reason we do it like this is that it's easy to make stable hashes for+-- tarball packages, and these packages benefit most from sharing. By contrast+-- unpacked dir packages are harder to hash and they tend to change more+-- frequently so there's less benefit to sharing them.+--+-- When using the nix store approach we have to run the solver *without*+-- looking at the packages installed in the store, just at the source packages+-- (plus core\/global installed packages). Then we do a post-processing pass+-- to replace configured packages in the plan with pre-existing ones, where+-- possible. Where possible of course means where the nix-style package hash+-- equals one that's already in the store.+--+-- One extra wrinkle is that unless we know package tarball hashes upfront, we+-- will have to download the tarballs to find their hashes. So we have two+-- options: delay replacing source with pre-existing installed packages until+-- the point during the execution of the install plan where we have the+-- tarball, or try to do as much up-front as possible and then check again+-- during plan execution. The former isn't great because we would end up+-- telling users we're going to re-install loads of packages when in fact we+-- would just share them. It'd be better to give as accurate a prediction as+-- we can. The latter is better for users, but we do still have to check+-- during plan execution because it's important that we don't replace existing+-- installed packages even if they have the same package hash, because we+-- don't guarantee ABI stability.++-- TODO: [required eventually] for safety of concurrent installs, we must make sure we register but+-- not replace installed packages with ghc-pkg.++packageHashInputs :: ElaboratedSharedConfig+ -> ElaboratedConfiguredPackage+ -> PackageHashInputs+packageHashInputs+ pkgshared+ pkg@ElaboratedConfiguredPackage{+ pkgSourceId,+ pkgSourceHash = Just srchash,+ pkgDependencies+ } =+ PackageHashInputs {+ pkgHashPkgId = pkgSourceId,+ pkgHashSourceHash = srchash,+ pkgHashDirectDeps = Set.fromList+ [ installedPackageId dep+ | dep <- CD.select relevantDeps pkgDependencies ],+ pkgHashOtherConfig = packageHashConfigInputs pkgshared pkg+ }+ where+ -- Obviously the main deps are relevant+ relevantDeps (CD.ComponentLib _) = True+ relevantDeps (CD.ComponentExe _) = True+ -- Setup deps can affect the Setup.hs behaviour and thus what is built+ relevantDeps CD.ComponentSetup = True+ -- However testsuites and benchmarks do not get installed and should not+ -- affect the result, so we do not include them.+ relevantDeps (CD.ComponentTest _) = False+ relevantDeps (CD.ComponentBench _) = False++packageHashInputs _ pkg =+ error $ "packageHashInputs: only for packages with source hashes. "+ ++ display (packageId pkg)++packageHashConfigInputs :: ElaboratedSharedConfig+ -> ElaboratedConfiguredPackage+ -> PackageHashConfigInputs+packageHashConfigInputs+ ElaboratedSharedConfig{..}+ ElaboratedConfiguredPackage{..} =++ PackageHashConfigInputs {+ pkgHashCompilerId = compilerId pkgConfigCompiler,+ pkgHashPlatform = pkgConfigPlatform,+ pkgHashFlagAssignment = pkgFlagAssignment,+ pkgHashConfigureScriptArgs = pkgConfigureScriptArgs,+ pkgHashVanillaLib = pkgVanillaLib,+ pkgHashSharedLib = pkgSharedLib,+ pkgHashDynExe = pkgDynExe,+ pkgHashGHCiLib = pkgGHCiLib,+ pkgHashProfLib = pkgProfLib,+ pkgHashProfExe = pkgProfExe,+ pkgHashProfLibDetail = pkgProfLibDetail,+ pkgHashProfExeDetail = pkgProfExeDetail,+ pkgHashCoverage = pkgCoverage,+ pkgHashOptimization = pkgOptimization,+ pkgHashSplitObjs = pkgSplitObjs,+ pkgHashStripLibs = pkgStripLibs,+ pkgHashStripExes = pkgStripExes,+ pkgHashDebugInfo = pkgDebugInfo,+ pkgHashExtraLibDirs = pkgExtraLibDirs,+ pkgHashExtraFrameworkDirs = pkgExtraFrameworkDirs,+ pkgHashExtraIncludeDirs = pkgExtraIncludeDirs,+ pkgHashProgPrefix = pkgProgPrefix,+ pkgHashProgSuffix = pkgProgSuffix+ }+++-- | Given the 'InstalledPackageIndex' for a nix-style package store, and an+-- 'ElaboratedInstallPlan', replace configured source packages by pre-existing+-- installed packages whenever they exist.+--+improveInstallPlanWithPreExistingPackages :: InstalledPackageIndex+ -> ElaboratedInstallPlan+ -> ElaboratedInstallPlan+improveInstallPlanWithPreExistingPackages installedPkgIndex installPlan =+ replaceWithPreExisting installPlan+ [ ipkg+ | InstallPlan.Configured pkg+ <- InstallPlan.reverseTopologicalOrder installPlan+ , ipkg <- maybeToList (canPackageBeImproved pkg) ]+ where+ --TODO: sanity checks:+ -- * the installed package must have the expected deps etc+ -- * the installed package must not be broken, valid dep closure++ --TODO: decide what to do if we encounter broken installed packages,+ -- since overwriting is never safe.++ canPackageBeImproved pkg =+ PackageIndex.lookupUnitId+ installedPkgIndex (installedPackageId pkg)++ replaceWithPreExisting =+ foldl' (\plan ipkg -> InstallPlan.preexisting+ (installedPackageId ipkg) ipkg plan)
+ cabal/cabal-install/Distribution/Client/RebuildMonad.hs view
@@ -0,0 +1,135 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++-- | An abstraction for re-running actions if values or files have changed.+--+-- This is not a full-blown make-style incremental build system, it's a bit+-- more ad-hoc than that, but it's easier to integrate with existing code.+--+-- It's a convenient interface to the "Distribution.Client.FileMonitor"+-- functions.+--+module Distribution.Client.RebuildMonad (+ -- * Rebuild monad+ Rebuild,+ runRebuild,++ -- * Setting up file monitoring+ monitorFiles,+ MonitorFilePath,+ monitorFile,+ monitorFileHashed,+ monitorNonExistentFile,+ monitorDirectory,+ monitorDirectoryExistence,+ monitorFileOrDirectory,+ monitorFileSearchPath,+ monitorFileHashedSearchPath,+ -- ** Monitoring file globs+ monitorFileGlob,+ FilePathGlob(..),+ FilePathRoot(..),+ FilePathGlobRel(..),+ GlobPiece(..),++ -- * Using a file monitor+ FileMonitor(..),+ newFileMonitor,+ rerunIfChanged,++ -- * Utils+ matchFileGlob,+ ) where++import Distribution.Client.FileMonitor+import Distribution.Client.Glob hiding (matchFileGlob)+import qualified Distribution.Client.Glob as Glob (matchFileGlob)++import Distribution.Simple.Utils (debug)+import Distribution.Verbosity (Verbosity)++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative+#endif+import Control.Monad.State as State+import Distribution.Compat.Binary (Binary)+import System.FilePath (takeFileName)+++-- | A monad layered on top of 'IO' to help with re-running actions when the+-- input files and values they depend on change. The crucial operations are+-- 'rerunIfChanged' and 'monitorFiles'.+--+newtype Rebuild a = Rebuild (StateT [MonitorFilePath] IO a)+ deriving (Functor, Applicative, Monad, MonadIO)++-- | Use this wihin the body action of 'rerunIfChanged' to declare that the+-- action depends on the given files. This can be based on what the action+-- actually did. It is these files that will be checked for changes next+-- time 'rerunIfChanged' is called for that 'FileMonitor'.+--+monitorFiles :: [MonitorFilePath] -> Rebuild ()+monitorFiles filespecs = Rebuild (State.modify (filespecs++))++-- | Run a 'Rebuild' IO action.+unRebuild :: Rebuild a -> IO (a, [MonitorFilePath])+unRebuild (Rebuild action) = runStateT action []++-- | Run a 'Rebuild' IO action.+runRebuild :: Rebuild a -> IO a+runRebuild (Rebuild action) = evalStateT action []++-- | This captures the standard use pattern for a 'FileMonitor': given a+-- monitor, an action and the input value the action depends on, either+-- re-run the action to get its output, or if the value and files the action+-- depends on have not changed then return a previously cached action result.+--+-- The result is still in the 'Rebuild' monad, so these can be nested.+--+-- Do not share 'FileMonitor's between different uses of 'rerunIfChanged'.+--+rerunIfChanged :: (Binary a, Binary b)+ => Verbosity+ -> FilePath+ -> FileMonitor a b+ -> a+ -> Rebuild b+ -> Rebuild b+rerunIfChanged verbosity rootDir monitor key action = do+ changed <- liftIO $ checkFileMonitorChanged monitor rootDir key+ case changed of+ MonitorUnchanged result files -> do+ liftIO $ debug verbosity $ "File monitor '" ++ monitorName+ ++ "' unchanged."+ monitorFiles files+ return result++ MonitorChanged reason -> do+ liftIO $ debug verbosity $ "File monitor '" ++ monitorName+ ++ "' changed: " ++ showReason reason+ startTime <- liftIO $ beginUpdateFileMonitor+ (result, files) <- liftIO $ unRebuild action+ liftIO $ updateFileMonitor monitor rootDir+ (Just startTime) files key result+ monitorFiles files+ return result+ where+ monitorName = takeFileName (fileMonitorCacheFile monitor)++ showReason (MonitoredFileChanged file) = "file " ++ file+ showReason (MonitoredValueChanged _) = "monitor value changed"+ showReason MonitorFirstRun = "first run"+ showReason MonitorCorruptCache = "invalid cache file"+++-- | Utility to match a file glob against the file system, starting from a+-- given root directory. The results are all relative to the given root.+--+-- Since this operates in the 'Rebuild' monad, it also monitrs the given glob+-- for changes.+--+matchFileGlob :: FilePath -> FilePathGlob -> Rebuild [FilePath]+matchFileGlob root glob = do+ monitorFiles [monitorFileGlob glob]+ liftIO $ Glob.matchFileGlob root glob+
cabal/cabal-install/Distribution/Client/Sandbox.hs view
@@ -38,18 +38,17 @@ updateSandboxConfigFileFlag, updateInstallDirs, - -- FIXME: move somewhere else- configPackageDB', configCompilerAux'+ configPackageDB', configCompilerAux', getPersistOrConfigCompiler ) where import Distribution.Client.Setup ( SandboxFlags(..), ConfigFlags(..), ConfigExFlags(..), InstallFlags(..) , GlobalFlags(..), defaultConfigExFlags, defaultInstallFlags- , defaultSandboxLocation, globalRepos )+ , defaultSandboxLocation, withRepoContext ) import Distribution.Client.Sandbox.Timestamp ( listModifiedDeps , maybeAddCompilerTimestampRecord , withAddTimestamps- , withRemoveTimestamps )+ , removeTimestamps ) import Distribution.Client.Config ( SavedConfig(..), defaultUserInstall, loadConfig ) import Distribution.Client.Dependency ( foldProgress )@@ -65,7 +64,8 @@ , createPackageEnvironmentFile, classifyPackageEnvironment , tryLoadSandboxPackageEnvironmentFile, loadUserConfig , commentPackageEnvironment, showPackageEnvironmentWithComments- , sandboxPackageEnvironmentFile, userPackageEnvironmentFile )+ , sandboxPackageEnvironmentFile, userPackageEnvironmentFile+ , sandboxPackageDBPath ) import Distribution.Client.Sandbox.Types ( SandboxPackageInfo(..) , UseSandbox(..) ) import Distribution.Client.SetupWrapper@@ -73,7 +73,7 @@ import Distribution.Client.Types ( PackageLocation(..) , SourcePackage(..) ) import Distribution.Client.Utils ( inDir, tryCanonicalizePath- , tryFindAddSourcePackageDesc )+ , tryFindAddSourcePackageDesc) import Distribution.PackageDescription.Configuration ( flattenPackageDescription ) import Distribution.PackageDescription.Parse ( readPackageDescription )@@ -82,11 +82,14 @@ import Distribution.Simple.Configure ( configCompilerAuxEx , interpretPackageDbFlags , getPackageDBContents+ , maybeGetPersistBuildConfig+ , findDistPrefOrDefault , findDistPref )+import qualified Distribution.Simple.LocalBuildInfo as LocalBuildInfo import Distribution.Simple.PreProcess ( knownSuffixHandlers ) import Distribution.Simple.Program ( ProgramConfiguration ) import Distribution.Simple.Setup ( Flag(..), HaddockFlags(..)- , fromFlagOrDefault )+ , fromFlagOrDefault, flagToMaybe ) import Distribution.Simple.SrcDist ( prepareTree ) import Distribution.Simple.Utils ( die, debug, notice, info, warn , debugNoWrap, defaultPackageDesc@@ -104,19 +107,25 @@ import qualified Distribution.Simple.Register as Register import qualified Data.Map as M import qualified Data.Set as S+import Data.Either (partitionEithers) import Control.Exception ( assert, bracket_ )-import Control.Monad ( forM, liftM2, unless, when )+import Control.Monad ( forM, liftM, liftM2, unless, when ) import Data.Bits ( shiftL, shiftR, xor ) import Data.Char ( ord ) import Data.IORef ( newIORef, writeIORef, readIORef )-import Data.List ( delete, foldl' )+import Data.List ( delete+ , foldl'+ , intersperse+ , isPrefixOf+ , groupBy ) import Data.Maybe ( fromJust ) #if !MIN_VERSION_base(4,8,0) import Data.Monoid ( mempty, mappend ) #endif import Data.Word ( Word32 ) import Numeric ( showHex )-import System.Directory ( createDirectory+import System.Directory ( canonicalizePath+ , createDirectory , doesDirectoryExist , doesFileExist , getCurrentDirectory@@ -126,9 +135,9 @@ import System.FilePath ( (</>), equalFilePath , getSearchPath , searchPathSeparator+ , splitSearchPath , takeDirectory ) - -- -- * Constants --@@ -362,9 +371,28 @@ ++ "'.\nAssuming a shared sandbox. Please delete '" ++ sandboxDir ++ "' manually." - notice verbosity $ "Deleting the sandbox located at " ++ sandboxDir- removeDirectoryRecursive sandboxDir+ absSandboxDir <- canonicalizePath sandboxDir+ notice verbosity $ "Deleting the sandbox located at " ++ absSandboxDir+ removeDirectoryRecursive absSandboxDir + let+ pathInsideSandbox = isPrefixOf absSandboxDir++ -- Warn the user if deleting the sandbox deleted a package database+ -- referenced in the current environment.+ checkPackagePaths var = do+ let+ checkPath path = do+ absPath <- canonicalizePath path+ (when (pathInsideSandbox absPath) . warn verbosity)+ (var ++ " refers to package database " ++ path+ ++ " inside the deleted sandbox.")+ liftM (maybe [] splitSearchPath) (lookupEnv var) >>= mapM_ checkPath++ checkPackagePaths "CABAL_SANDBOX_PACKAGE_PATH"+ checkPackagePaths "GHC_PACKAGE_PATH"+ checkPackagePaths "GHCJS_PACKAGE_PATH"+ -- Common implementation of 'sandboxAddSource' and 'sandboxAddSourceSnapshot'. doAddSource :: Verbosity -> [FilePath] -> FilePath -> PackageEnvironment -> BuildTreeRefType@@ -380,7 +408,7 @@ (compilerId comp) platform withAddTimestamps sandboxDir $ do- -- FIXME: path canonicalisation is done in addBuildTreeRefs, but we do it+ -- Path canonicalisation is done in addBuildTreeRefs, but we do it -- twice because of the timestamps file. buildTreeRefs' <- mapM tryCanonicalizePath buildTreeRefs Index.addBuildTreeRefs verbosity indexFile buildTreeRefs' refType@@ -448,14 +476,54 @@ (sandboxDir, pkgEnv) <- tryLoadSandboxConfig verbosity globalFlags indexFile <- tryGetIndexFilePath (pkgEnvSavedConfig pkgEnv) - withRemoveTimestamps sandboxDir $ do+ (results, convDict) <- Index.removeBuildTreeRefs verbosity indexFile buildTreeRefs + let (failedPaths, removedPaths) = partitionEithers results+ removedRefs = fmap convDict removedPaths++ unless (null removedPaths) $ do+ removeTimestamps sandboxDir removedPaths++ notice verbosity $ "Success deleting sources: " +++ showL removedRefs ++ "\n\n"++ unless (null failedPaths) $ do+ let groupedFailures = groupBy errorType failedPaths+ mapM_ handleErrors groupedFailures+ die $ "The sources with the above errors were skipped. (" +++ showL (fmap getPath failedPaths) ++ ")"+ notice verbosity $ "Note: 'sandbox delete-source' only unregisters the " ++ "source dependency, but does not remove the package " ++ "from the sandbox package DB.\n\n" ++ "Use 'sandbox hc-pkg -- unregister' to do that."+ where+ getPath (Index.ErrNonregisteredSource p) = p+ getPath (Index.ErrNonexistentSource p) = p + showPaths f = concat . intersperse " " . fmap (show . f)++ showL = showPaths id++ showE [] = return ' '+ showE errs = showPaths getPath errs++ errorType Index.ErrNonregisteredSource{} Index.ErrNonregisteredSource{} =+ True+ errorType Index.ErrNonexistentSource{} Index.ErrNonexistentSource{} = True+ errorType _ _ = False++ handleErrors [] = return ()+ handleErrors errs@(Index.ErrNonregisteredSource{}:_) =+ warn verbosity ("Sources not registered: " ++ showE errs ++ "\n\n")+ handleErrors errs@(Index.ErrNonexistentSource{}:_) =+ warn verbosity+ ("Source directory not found for paths: " ++ showE errs ++ "\n"+ ++ "If you are trying to delete a reference to a removed directory, "+ ++ "please provide the full absolute path "+ ++ "(as given by `sandbox list-sources`).\n\n")+ -- | Entry point for the 'cabal sandbox list-sources' command. sandboxListSources :: Verbosity -> SandboxFlags -> GlobalFlags -> IO ()@@ -479,11 +547,13 @@ -- tool with provided arguments, restricted to the sandbox. sandboxHcPkg :: Verbosity -> SandboxFlags -> GlobalFlags -> [String] -> IO () sandboxHcPkg verbosity _sandboxFlags globalFlags extraArgs = do- (_sandboxDir, pkgEnv) <- tryLoadSandboxConfig verbosity globalFlags+ (sandboxDir, pkgEnv) <- tryLoadSandboxConfig verbosity globalFlags let configFlags = savedConfigureFlags . pkgEnvSavedConfig $ pkgEnv- dbStack = configPackageDB' configFlags- (comp, _platform, conf) <- configCompilerAux' configFlags-+ -- Invoke hc-pkg for the most recently configured compiler (if any),+ -- using the right package-db for the compiler (see #1935).+ (comp, platform, conf) <- getPersistOrConfigCompiler configFlags+ let dir = sandboxPackageDBPath sandboxDir comp platform+ dbStack = [GlobalPackageDB, SpecificPackageDB dir] Register.invokeHcPkg verbosity comp conf dbStack extraArgs updateInstallDirs :: Flag Bool@@ -517,7 +587,7 @@ loadConfigOrSandboxConfig verbosity globalFlags = do let configFileFlag = globalConfigFile globalFlags sandboxConfigFileFlag = globalSandboxConfigFile globalFlags- ignoreSandboxFlag = globalIgnoreSandbox globalFlags+ ignoreSandboxFlag = globalIgnoreSandbox globalFlags pkgEnvDir <- getPkgEnvDir sandboxConfigFileFlag pkgEnvType <- classifyPackageEnvironment pkgEnvDir sandboxConfigFileFlag@@ -533,7 +603,7 @@ -- Only @cabal.config@ is present. UserPackageEnvironment -> do config <- loadConfig verbosity configFileFlag- userConfig <- loadUserConfig verbosity pkgEnvDir+ userConfig <- loadUserConfig verbosity pkgEnvDir Nothing let config' = config `mappend` userConfig dieIfSandboxRequired config' return (NoSandbox, config')@@ -541,8 +611,13 @@ -- Neither @cabal.sandbox.config@ nor @cabal.config@ are present. AmbientPackageEnvironment -> do config <- loadConfig verbosity configFileFlag+ let globalConstraintsOpt =+ flagToMaybe . globalConstraintsFile . savedGlobalFlags $ config+ globalConstraintConfig <-+ loadUserConfig verbosity pkgEnvDir globalConstraintsOpt+ let config' = config `mappend` globalConstraintConfig dieIfSandboxRequired config- return (NoSandbox, config)+ return (NoSandbox, config') where -- Return the path to the package environment directory - either the@@ -605,9 +680,10 @@ comp platform conf sandboxDir $ \sandboxPkgInfo -> unless (null $ modifiedAddSourceDependencies sandboxPkgInfo) $ do + withRepoContext verbosity globalFlags $ \repoContext -> do let args :: InstallArgs args = ((configPackageDB' configFlags)- ,(globalRepos globalFlags)+ ,repoContext ,comp, platform, conf ,UseSandbox sandboxDir, Just sandboxPkgInfo ,globalFlags, configFlags, configExFlags, installFlags@@ -630,7 +706,9 @@ die' message = die (message ++ installFailedInSandbox) -- TODO: use a better error message, remove duplication. installFailedInSandbox =- "Note: when using a sandbox, all packages are required to have consistent dependencies. Try reinstalling/unregistering the offending packages or recreating the sandbox."+ "Note: when using a sandbox, all packages are required to have "+ ++ "consistent dependencies. Try reinstalling/unregistering the "+ ++ "offending packages or recreating the sandbox." logMsg message rest = debugNoWrap verbosity message >> rest topHandler' = topHandlerWith $ \_ -> do@@ -694,8 +772,8 @@ toSourcePackage (path, pkgDesc) = SourcePackage (packageId pkgDesc) pkgDesc (LocalUnpackedPackage path) Nothing --- | Same as 'withSandboxPackageInfo' if we're inside a sandbox and a no-op--- otherwise.+-- | Same as 'withSandboxPackageInfo' if we're inside a sandbox and the+-- identity otherwise. maybeWithSandboxPackageInfo :: Verbosity -> ConfigFlags -> GlobalFlags -> Compiler -> Platform -> ProgramConfiguration -> UseSandbox@@ -791,3 +869,18 @@ configCompilerAuxEx configFlags --FIXME: make configCompilerAux use a sensible verbosity { configVerbosity = fmap lessVerbose (configVerbosity configFlags) }++-- | Try to read the most recently configured compiler from the+-- 'localBuildInfoFile', falling back on 'configCompilerAuxEx' if it+-- cannot be read.+getPersistOrConfigCompiler :: ConfigFlags+ -> IO (Compiler, Platform, ProgramConfiguration)+getPersistOrConfigCompiler configFlags = do+ distPref <- findDistPrefOrDefault (configDistPref configFlags)+ mlbi <- maybeGetPersistBuildConfig distPref+ case mlbi of+ Nothing -> do configCompilerAux' configFlags+ Just lbi -> return ( LocalBuildInfo.compiler lbi+ , LocalBuildInfo.hostPlatform lbi+ , LocalBuildInfo.withPrograms lbi+ )
cabal/cabal-install/Distribution/Client/Sandbox/Index.hs view
@@ -12,42 +12,44 @@ addBuildTreeRefs, removeBuildTreeRefs, ListIgnoredBuildTreeRefs(..), RefTypesToList(..),+ DeleteSourceError(..), listBuildTreeRefs, validateIndexPath, defaultIndexFileName ) where +import qualified Codec.Archive.Tar as Tar+import qualified Codec.Archive.Tar.Entry as Tar+import qualified Codec.Archive.Tar.Index as Tar import qualified Distribution.Client.Tar as Tar import Distribution.Client.IndexUtils ( BuildTreeRefType(..) , refTypeFromTypeCode , typeCodeFromRefType , updatePackageIndexCacheFile- , getSourcePackagesStrict )-import Distribution.Client.PackageIndex ( allPackages )-import Distribution.Client.Types ( Repo(..), LocalRepo(..)- , SourcePackageDb(..)- , SourcePackage(..), PackageLocation(..) )+ , readCacheStrict+ , Index(..) )+import qualified Distribution.Client.IndexUtils as IndexUtils import Distribution.Client.Utils ( byteStringToFilePath, filePathToByteString , makeAbsoluteToCwd, tryCanonicalizePath- , canonicalizePathNoThrow , tryFindAddSourcePackageDesc ) import Distribution.Simple.Utils ( die, debug )+import Distribution.Compat.Exception ( tryIO ) import Distribution.Verbosity ( Verbosity ) import qualified Data.ByteString.Lazy as BS-import Control.Exception ( evaluate )+import Control.Exception ( evaluate, throw, Exception ) import Control.Monad ( liftM, unless )-import Data.List ( (\\), intersect, nub )-import Data.Maybe ( catMaybes )+import Control.Monad.Writer.Lazy (WriterT(..), runWriterT, tell)+import Data.List ( (\\), intersect, nub, find )+import Data.Maybe ( catMaybes, fromMaybe )+import Data.Either (partitionEithers) import System.Directory ( createDirectoryIfMissing, doesDirectoryExist, doesFileExist,- renameFile )-import System.FilePath ( (</>), (<.>), takeDirectory, takeExtension- , replaceExtension )-import System.IO ( IOMode(..), SeekMode(..)- , hSeek, withBinaryFile )+ renameFile, canonicalizePath)+import System.FilePath ( (</>), (<.>), takeDirectory, takeExtension )+import System.IO ( IOMode(..), withBinaryFile ) -- | A reference to a local build tree. data BuildTreeRef = BuildTreeRef {@@ -80,16 +82,27 @@ -- | Given a sequence of tar archive entries, extract all references to local -- build trees.-readBuildTreeRefs :: Tar.Entries -> [BuildTreeRef]+readBuildTreeRefs :: Exception e => Tar.Entries e -> [BuildTreeRef] readBuildTreeRefs = catMaybes- . Tar.foldrEntries (\e r -> readBuildTreeRef e : r)- [] error+ . Tar.foldEntries (\e r -> readBuildTreeRef e : r)+ [] throw -- | Given a path to a tar archive, extract all references to local build trees. readBuildTreeRefsFromFile :: FilePath -> IO [BuildTreeRef] readBuildTreeRefsFromFile = liftM (readBuildTreeRefs . Tar.read) . BS.readFile +-- | Read build tree references from an index cache+readBuildTreeRefsFromCache :: Verbosity -> FilePath -> IO [BuildTreeRef]+readBuildTreeRefsFromCache verbosity indexPath = do+ (mRefs, _prefs) <- readCacheStrict verbosity (SandboxIndex indexPath) buildTreeRef+ return (catMaybes mRefs)+ where+ buildTreeRef pkgEntry =+ case pkgEntry of+ IndexUtils.NormalPackage _ _ _ _ -> Nothing+ IndexUtils.BuildTreeRef typ _ _ path _ -> Just $ BuildTreeRef typ path+ -- | Given a local build tree ref, serialise it to a tar archive entry. writeBuildTreeRef :: BuildTreeRef -> Tar.Entry writeBuildTreeRef (BuildTreeRef refType path) = Tar.simpleEntry tarPath content@@ -143,45 +156,76 @@ treesToAdd <- mapM (buildTreeRefFromPath refType) (l \\ treesInIndex) let entries = map writeBuildTreeRef (catMaybes treesToAdd) unless (null entries) $ do- offset <-- fmap (Tar.foldrEntries (\e acc -> Tar.entrySizeInBytes e + acc) 0 error- . Tar.read) $ BS.readFile path- _ <- evaluate offset- debug verbosity $ "Writing at offset: " ++ show offset withBinaryFile path ReadWriteMode $ \h -> do- hSeek h AbsoluteSeek (fromIntegral offset)+ block <- Tar.hSeekEndEntryOffset h Nothing+ debug verbosity $ "Writing at tar block: " ++ show block BS.hPut h (Tar.write entries) debug verbosity $ "Successfully appended to '" ++ path ++ "'"- updatePackageIndexCacheFile verbosity path- (path `replaceExtension` "cache")+ updatePackageIndexCacheFile verbosity $ SandboxIndex path +data DeleteSourceError = ErrNonregisteredSource { nrPath :: FilePath }+ | ErrNonexistentSource { nePath :: FilePath } deriving Show+ -- | Remove given local build tree references from the index.-removeBuildTreeRefs :: Verbosity -> FilePath -> [FilePath] -> IO [FilePath]+--+-- Returns a tuple with either removed build tree refs or errors and a function+-- that converts from a provided build tree ref to corresponding full directory path.+removeBuildTreeRefs :: Verbosity -> FilePath -> [FilePath]+ -> IO ([Either DeleteSourceError FilePath],+ (FilePath -> FilePath)) removeBuildTreeRefs _ _ [] = error "Distribution.Client.Sandbox.Index.removeBuildTreeRefs: unexpected"-removeBuildTreeRefs verbosity path l' = do- checkIndexExists path- l <- mapM canonicalizePathNoThrow l'- let tmpFile = path <.> "tmp"+removeBuildTreeRefs verbosity indexPath l = do+ checkIndexExists indexPath+ let tmpFile = indexPath <.> "tmp"++ canonRes <- mapM (\btr -> do res <- tryIO $ canonicalizePath btr+ return $ case res of+ Right pth -> Right (btr, pth)+ Left _ -> Left $ ErrNonexistentSource btr) l+ let (failures, convDict) = partitionEithers canonRes+ allRefs = fmap snd convDict+ -- Performance note: on my system, it takes 'index --remove-source' -- approx. 3,5s to filter a 65M file. Real-life indices are expected to be -- much smaller.- BS.writeFile tmpFile . Tar.writeEntries . Tar.filterEntries (p l) . Tar.read- =<< BS.readFile path- renameFile tmpFile path+ removedRefs <- doRemove convDict tmpFile++ renameFile tmpFile indexPath debug verbosity $ "Successfully renamed '" ++ tmpFile- ++ "' to '" ++ path ++ "'"- updatePackageIndexCacheFile verbosity path (path `replaceExtension` "cache")- -- FIXME: return only the refs that vere actually removed.- return l+ ++ "' to '" ++ indexPath ++ "'"++ unless (null removedRefs) $+ updatePackageIndexCacheFile verbosity $ SandboxIndex indexPath++ let results = fmap Right removedRefs+ ++ fmap Left failures+ ++ fmap (Left . ErrNonregisteredSource)+ (fmap (convertWith convDict) (allRefs \\ removedRefs))++ return (results, convertWith convDict)+ where- p l entry = case readBuildTreeRef entry of- Nothing -> True+ doRemove :: [(FilePath, FilePath)] -> FilePath -> IO [FilePath]+ doRemove srcRefs tmpFile = do+ (newIdx, changedPaths) <-+ Tar.read `fmap` BS.readFile indexPath+ >>= runWriterT . Tar.filterEntriesM (p $ fmap snd srcRefs)+ BS.writeFile tmpFile . Tar.write . Tar.entriesToList $ newIdx+ return changedPaths++ p :: [FilePath] -> Tar.Entry -> WriterT [FilePath] IO Bool+ p refs entry = case readBuildTreeRef entry of+ Nothing -> return True -- FIXME: removing snapshot deps is done with `delete-source -- .cabal-sandbox/snapshots/$SNAPSHOT_NAME`. Perhaps we also want to -- support removing snapshots by providing the original path.- (Just (BuildTreeRef _ pth)) -> pth `notElem` l+ (Just (BuildTreeRef _ pth)) -> if pth `elem` refs+ then tell [pth] >> return False+ else return True + convertWith dict pth = fromMaybe pth $ fmap fst $ find ((==pth) . snd) dict+ -- | A build tree ref can become ignored if the user later adds a build tree ref -- with the same package ID. We display ignored build tree refs when the user -- runs 'cabal sandbox list-sources', but do not look at their timestamps in@@ -222,16 +266,11 @@ LinksAndSnapshots -> const True listWithIgnored :: IO [BuildTreeRef]- listWithIgnored = readBuildTreeRefsFromFile $ path+ listWithIgnored = readBuildTreeRefsFromFile path listWithoutIgnored :: IO [FilePath]- listWithoutIgnored = do- let repo = Repo { repoKind = Right LocalRepo- , repoLocalDir = takeDirectory path }- pkgIndex <- fmap packageIndex- . getSourcePackagesStrict verbosity $ [repo]- return [ pkgPath | (LocalUnpackedPackage pkgPath) <-- map packageSource . allPackages $ pkgIndex ]+ listWithoutIgnored = fmap (map buildTreePath)+ $ readBuildTreeRefsFromCache verbosity path -- | Check that the package index file exists and exit with error if it does not.
cabal/cabal-install/Distribution/Client/Sandbox/PackageEnvironment.hs view
@@ -1,4 +1,5 @@-{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveGeneric #-}+ ----------------------------------------------------------------------------- -- | -- Module : Distribution.Client.Sandbox.PackageEnvironment@@ -61,10 +62,8 @@ import Control.Monad ( foldM, liftM2, when, unless ) import Data.List ( partition ) import Data.Maybe ( isJust )-#if !MIN_VERSION_base(4,8,0)-import Data.Monoid ( Monoid(..) )-#endif import Distribution.Compat.Exception ( catchIO )+import Distribution.Compat.Semigroup import System.Directory ( doesDirectoryExist, doesFileExist , renameFile ) import System.FilePath ( (<.>), (</>), takeDirectory )@@ -75,6 +74,7 @@ import qualified Distribution.Compat.ReadP as Parse import qualified Distribution.ParseUtils as ParseUtils ( Field(..) ) import qualified Distribution.Text as Text+import GHC.Generics ( Generic ) --@@ -88,20 +88,14 @@ -- for constructing nested sandboxes (see discussion in #1196). pkgEnvInherit :: Flag FilePath, pkgEnvSavedConfig :: SavedConfig-}+} deriving Generic instance Monoid PackageEnvironment where- mempty = PackageEnvironment {- pkgEnvInherit = mempty,- pkgEnvSavedConfig = mempty- }+ mempty = gmempty+ mappend = (<>) - mappend a b = PackageEnvironment {- pkgEnvInherit = combine pkgEnvInherit,- pkgEnvSavedConfig = combine pkgEnvSavedConfig- }- where- combine f = f a `mappend` f b+instance Semigroup PackageEnvironment where+ (<>) = gmappend -- | The automatically-created package environment file that should not be -- touched by the user.@@ -280,27 +274,30 @@ return $ mempty { pkgEnvSavedConfig = conf } -- | Load the user package environment if it exists (the optional "cabal.config"--- file).-userPackageEnvironment :: Verbosity -> FilePath -> IO PackageEnvironment-userPackageEnvironment verbosity pkgEnvDir = do- let path = pkgEnvDir </> userPackageEnvironmentFile- minp <- readPackageEnvironmentFile ConstraintSourceUserConfig mempty path- case minp of- Nothing -> return mempty- Just (ParseOk warns parseResult) -> do+-- file). If it does not exist locally, attempt to load an optional global one.+userPackageEnvironment :: Verbosity -> FilePath -> Maybe FilePath -> IO PackageEnvironment+userPackageEnvironment verbosity pkgEnvDir globalConfigLocation = do+ let path = pkgEnvDir </> userPackageEnvironmentFile+ minp <- readPackageEnvironmentFile (ConstraintSourceUserConfig path) mempty path+ case (minp, globalConfigLocation) of+ (Just parseRes, _) -> processConfigParse path parseRes+ (_, Just globalLoc) -> maybe (warn verbosity ("no constraints file found at " ++ globalLoc) >> return mempty) (processConfigParse globalLoc) =<< readPackageEnvironmentFile (ConstraintSourceUserConfig globalLoc) mempty globalLoc+ _ -> return mempty+ where+ processConfigParse path (ParseOk warns parseResult) = do when (not $ null warns) $ warn verbosity $ unlines (map (showPWarning path) warns) return parseResult- Just (ParseFailed err) -> do+ processConfigParse path (ParseFailed err) = do let (line, msg) = locatedErrorMsg err- warn verbosity $ "Error parsing user package environment file " ++ path+ warn verbosity $ "Error parsing package environment file " ++ path ++ maybe "" (\n -> ":" ++ show n) line ++ ":\n" ++ msg return mempty -- | Same as @userPackageEnvironmentFile@, but returns a SavedConfig.-loadUserConfig :: Verbosity -> FilePath -> IO SavedConfig-loadUserConfig verbosity pkgEnvDir = fmap pkgEnvSavedConfig- $ userPackageEnvironment verbosity pkgEnvDir+loadUserConfig :: Verbosity -> FilePath -> Maybe FilePath -> IO SavedConfig+loadUserConfig verbosity pkgEnvDir globalConfigLocation =+ fmap pkgEnvSavedConfig $ userPackageEnvironment verbosity pkgEnvDir globalConfigLocation -- | Common error handling code used by 'tryLoadSandboxPackageEnvironment' and -- 'updatePackageEnvironment'.@@ -347,7 +344,7 @@ let base = basePackageEnvironment let common = commonPackageEnvironment sandboxDir- user <- userPackageEnvironment verbosity pkgEnvDir+ user <- userPackageEnvironment verbosity pkgEnvDir Nothing --TODO inherited <- inheritedPackageEnvironment verbosity user -- Layer the package environment settings over settings from ~/.cabal/config.@@ -400,7 +397,6 @@ (fromFlagOrDefault Disp.empty . fmap Disp.text) (optional parseFilePathQ) pkgEnvInherit (\v pkgEnv -> pkgEnv { pkgEnvInherit = v }) - -- FIXME: Should we make these fields part of ~/.cabal/config ? , commaNewLineListField "constraints" (Text.disp . fst) ((\pc -> (pc, src)) `fmap` Text.parse) (configExConstraints . savedConfigureExFlags . pkgEnvSavedConfig)
cabal/cabal-install/Distribution/Client/Sandbox/Timestamp.hs view
@@ -10,10 +10,15 @@ module Distribution.Client.Sandbox.Timestamp ( AddSourceTimestamp, withAddTimestamps,- withRemoveTimestamps, withUpdateTimestamps, maybeAddCompilerTimestampRecord, listModifiedDeps,+ removeTimestamps,++ -- * For testing+ TimestampFileRecord,+ readTimestampFile,+ writeTimestampFile ) where import Control.Exception (IOException)@@ -50,12 +55,13 @@ (inDir, removeExistingFile, tryCanonicalizePath, tryFindAddSourcePackageDesc) import Distribution.Compat.Exception (catchIO)-import Distribution.Client.Compat.Time (EpochTime, getCurTime,- getModTime)+import Distribution.Client.Compat.Time (ModTime, getCurTime,+ getModTime,+ posixSecondsToModTime) -- | Timestamp of an add-source dependency.-type AddSourceTimestamp = (FilePath, EpochTime)+type AddSourceTimestamp = (FilePath, ModTime) -- | Timestamp file record - a string identifying the compiler & platform plus a -- list of add-source timestamps. type TimestampFileRecord = (String, [AddSourceTimestamp])@@ -79,15 +85,37 @@ readTimestampFile timestampFile = do timestampString <- readFile timestampFile `catchIO` \_ -> return "[]" case reads timestampString of- [(timestamps, s)] | all isSpace s -> return timestamps- _ ->- die $ "The timestamps file is corrupted. "- ++ "Please delete & recreate the sandbox."+ [(version, s)]+ | version == (2::Int) ->+ case reads s of+ [(timestamps, s')] | all isSpace s' -> return timestamps+ _ -> dieCorrupted+ | otherwise -> dieWrongFormat + -- Old format (timestamps are POSIX seconds). Convert to new format.+ [] ->+ case reads timestampString of+ [(timestamps, s)] | all isSpace s -> do+ let timestamps' = map (\(i, ts) ->+ (i, map (\(p, t) ->+ (p, posixSecondsToModTime t)) ts))+ timestamps+ writeTimestampFile timestampFile timestamps'+ return timestamps'+ _ -> dieCorrupted+ _ -> dieCorrupted+ where+ dieWrongFormat = die $ wrongFormat ++ deleteAndRecreate+ dieCorrupted = die $ corrupted ++ deleteAndRecreate+ wrongFormat = "The timestamps file is in the wrong format."+ corrupted = "The timestamps file is corrupted."+ deleteAndRecreate = " Please delete and recreate the sandbox."+ -- | Write the timestamp file, atomically. writeTimestampFile :: FilePath -> [TimestampFileRecord] -> IO () writeTimestampFile timestampFile timestamps = do- writeFile timestampTmpFile (show timestamps)+ writeFile timestampTmpFile "2\n" -- version+ appendFile timestampTmpFile (show timestamps ++ "\n") renameFile timestampTmpFile timestampFile where timestampTmpFile = timestampFile <.> "tmp"@@ -105,7 +133,7 @@ -- we've added and an initial timestamp, add an 'AddSourceTimestamp' to the list -- for each path. If a timestamp for a given path already exists in the list, -- update it.-addTimestamps :: EpochTime -> [AddSourceTimestamp] -> [FilePath]+addTimestamps :: ModTime -> [AddSourceTimestamp] -> [FilePath] -> [AddSourceTimestamp] addTimestamps initial timestamps newPaths = [ (p, initial) | p <- newPaths ] ++ oldTimestamps@@ -116,7 +144,7 @@ -- | Given a list of 'AddSourceTimestamp's, a list of paths to add-source deps -- we've reinstalled and a new timestamp value, update the timestamp value for -- the deps in the list. If there are new paths in the list, ignore them.-updateTimestamps :: [AddSourceTimestamp] -> [FilePath] -> EpochTime+updateTimestamps :: [AddSourceTimestamp] -> [FilePath] -> ModTime -> [AddSourceTimestamp] updateTimestamps timestamps pathsToUpdate newTimestamp = foldr updateTimestamp [] timestamps@@ -127,8 +155,8 @@ -- | Given a list of 'TimestampFileRecord's and a list of paths to add-source -- deps we've removed, remove those deps from the list.-removeTimestamps :: [AddSourceTimestamp] -> [FilePath] -> [AddSourceTimestamp]-removeTimestamps l pathsToRemove = foldr removeTimestamp [] l+removeTimestamps' :: [AddSourceTimestamp] -> [FilePath] -> [AddSourceTimestamp]+removeTimestamps' l pathsToRemove = foldr removeTimestamp [] l where removeTimestamp t@(path, _oldTimestamp) rest = if path `elem` pathsToRemove@@ -156,13 +184,14 @@ -- build tree refs to the timestamps file (for all compilers). withAddTimestamps :: FilePath -> IO [FilePath] -> IO () withAddTimestamps sandboxDir act = do- let initialTimestamp = 0+ let initialTimestamp = minBound withActionOnAllTimestamps (addTimestamps initialTimestamp) sandboxDir act --- | Given an IO action that returns a list of build tree refs, remove those+-- | Given a list of build tree refs, remove those -- build tree refs from the timestamps file (for all compilers).-withRemoveTimestamps :: FilePath -> IO [FilePath] -> IO ()-withRemoveTimestamps = withActionOnAllTimestamps removeTimestamps+removeTimestamps :: FilePath -> [FilePath] -> IO ()+removeTimestamps idxFile =+ withActionOnAllTimestamps removeTimestamps' idxFile . return -- | Given an IO action that returns a list of build tree refs, update the -- timestamps of the returned build tree refs to the current time (only for the@@ -191,7 +220,7 @@ -- list of 'AddSourceTimestamp's for this compiler, applies 'f' to the result -- and then updates the timestamp file record. The IO action is run only once. withActionOnCompilerTimestamps :: ([AddSourceTimestamp]- -> [FilePath] -> EpochTime+ -> [FilePath] -> ModTime -> [AddSourceTimestamp]) -> FilePath -> CompilerId@@ -249,7 +278,7 @@ return ret -- | Has this dependency been modified since we have last looked at it?-isDepModified :: Verbosity -> EpochTime -> AddSourceTimestamp -> IO Bool+isDepModified :: Verbosity -> ModTime -> AddSourceTimestamp -> IO Bool isDepModified verbosity now (packageDir, timestamp) = do debug verbosity ("Checking whether the dependency is modified: " ++ packageDir) depSources <- allPackageSourceFiles verbosity packageDir
cabal/cabal-install/Distribution/Client/Sandbox/Types.hs view
@@ -14,10 +14,11 @@ ) where import qualified Distribution.Simple.PackageIndex as InstalledPackageIndex-import Distribution.Client.Types (SourcePackage)+import Distribution.Client.Types (UnresolvedSourcePackage)+import Distribution.Compat.Semigroup (Semigroup((<>))) #if !MIN_VERSION_base(4,8,0)-import Data.Monoid+import Data.Monoid (Monoid(..)) #endif import qualified Data.Set as S @@ -26,10 +27,12 @@ instance Monoid UseSandbox where mempty = NoSandbox+ mappend = (<>) - NoSandbox `mappend` s = s- u0@(UseSandbox _) `mappend` NoSandbox = u0- (UseSandbox _) `mappend` u1@(UseSandbox _) = u1+instance Semigroup UseSandbox where+ NoSandbox <> s = s+ u0@(UseSandbox _) <> NoSandbox = u0+ (UseSandbox _) <> u1@(UseSandbox _) = u1 -- | Convert a @UseSandbox@ value to a boolean. Useful in conjunction with -- @when@.@@ -46,11 +49,11 @@ -- | Data about the packages installed in the sandbox that is passed from -- 'reinstallAddSourceDeps' to the solver. data SandboxPackageInfo = SandboxPackageInfo {- modifiedAddSourceDependencies :: ![SourcePackage],+ modifiedAddSourceDependencies :: ![UnresolvedSourcePackage], -- ^ Modified add-source deps that we want to reinstall. These are guaranteed -- to be already installed in the sandbox. - otherAddSourceDependencies :: ![SourcePackage],+ otherAddSourceDependencies :: ![UnresolvedSourcePackage], -- ^ Remaining add-source deps. Some of these may be not installed in the -- sandbox.
+ cabal/cabal-install/Distribution/Client/Security/HTTP.hs view
@@ -0,0 +1,174 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+-- | Implementation of 'HttpLib' using cabal-install's own 'HttpTransport'+module Distribution.Client.Security.HTTP (HttpLib, transportAdapter) where++-- stdlibs+import Control.Exception+ ( Exception(..), IOException )+import Data.List+ ( intercalate )+import Data.Typeable+ ( Typeable )+import System.Directory+ ( getTemporaryDirectory )+import Network.URI+ ( URI )+import qualified Data.ByteString.Lazy as BS.L+import qualified Network.HTTP as HTTP++-- Cabal/cabal-install+import Distribution.Verbosity+ ( Verbosity )+import Distribution.Client.HttpUtils+ ( HttpTransport(..), HttpCode )+import Distribution.Client.Utils+ ( withTempFileName )++-- hackage-security+import Hackage.Security.Client+import Hackage.Security.Client.Repository.HttpLib+import Hackage.Security.Util.Checked+import Hackage.Security.Util.Pretty+import qualified Hackage.Security.Util.Lens as Lens++{-------------------------------------------------------------------------------+ 'HttpLib' implementation+-------------------------------------------------------------------------------}++-- | Translate from hackage-security's 'HttpLib' to cabal-install's 'HttpTransport'+--+-- NOTE: The match between these two APIs is currently not perfect:+--+-- * We don't get any response headers back from the 'HttpTransport', so we+-- don't know if the server supports range requests. For now we optimistically+-- assume that it does.+-- * The 'HttpTransport' wants to know where to place the resulting file,+-- whereas the 'HttpLib' expects an 'IO' action which streams the download;+-- the security library then makes sure that the file gets written to a+-- location which is suitable (in particular, to a temporary file in the+-- directory where the file needs to end up, so that it can "finalize" the+-- file simply by doing 'renameFile'). Right now we write the file to a+-- temporary file in the system temp directory here and then read it again+-- to pass it to the security library; this is a problem for two reasons: it+-- is a source of inefficiency; and it means that the security library cannot+-- insist on a minimum download rate (potential security attack).+-- Fixing it however would require changing the 'HttpTransport'.+transportAdapter :: Verbosity -> IO HttpTransport -> HttpLib+transportAdapter verbosity getTransport = HttpLib{+ httpGet = \headers uri callback -> do+ transport <- getTransport+ get verbosity transport headers uri callback+ , httpGetRange = \headers uri range callback -> do+ transport <- getTransport+ getRange verbosity transport headers uri range callback+ }++get :: Throws SomeRemoteError+ => Verbosity+ -> HttpTransport+ -> [HttpRequestHeader] -> URI+ -> ([HttpResponseHeader] -> BodyReader -> IO a)+ -> IO a+get verbosity transport reqHeaders uri callback = wrapCustomEx $ do+ get' verbosity transport reqHeaders uri Nothing $ \code respHeaders br ->+ case code of+ 200 -> callback respHeaders br+ _ -> throwChecked $ UnexpectedResponse uri code++getRange :: Throws SomeRemoteError+ => Verbosity+ -> HttpTransport+ -> [HttpRequestHeader] -> URI -> (Int, Int)+ -> (HttpStatus -> [HttpResponseHeader] -> BodyReader -> IO a)+ -> IO a+getRange verbosity transport reqHeaders uri range callback = wrapCustomEx $ do+ get' verbosity transport reqHeaders uri (Just range) $ \code respHeaders br ->+ case code of+ 200 -> callback HttpStatus200OK respHeaders br+ 206 -> callback HttpStatus206PartialContent respHeaders br+ _ -> throwChecked $ UnexpectedResponse uri code++-- | Internal generalization of 'get' and 'getRange'+get' :: Verbosity+ -> HttpTransport+ -> [HttpRequestHeader] -> URI -> Maybe (Int, Int)+ -> (HttpCode -> [HttpResponseHeader] -> BodyReader -> IO a)+ -> IO a+get' verbosity transport reqHeaders uri mRange callback = do+ tempDir <- getTemporaryDirectory+ withTempFileName tempDir "transportAdapterGet" $ \temp -> do+ (code, _etag) <- getHttp transport verbosity uri Nothing temp reqHeaders'+ br <- bodyReaderFromBS =<< BS.L.readFile temp+ callback code [HttpResponseAcceptRangesBytes] br+ where+ reqHeaders' = mkReqHeaders reqHeaders mRange++{-------------------------------------------------------------------------------+ Request headers+-------------------------------------------------------------------------------}++mkRangeHeader :: Int -> Int -> HTTP.Header+mkRangeHeader from to = HTTP.Header HTTP.HdrRange rangeHeader+ where+ -- Content-Range header uses inclusive rather than exclusive bounds+ -- See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html>+ rangeHeader = "bytes=" ++ show from ++ "-" ++ show (to - 1)++mkReqHeaders :: [HttpRequestHeader] -> Maybe (Int, Int) -> [HTTP.Header]+mkReqHeaders reqHeaders mRange = concat [+ tr [] reqHeaders+ , [mkRangeHeader fr to | Just (fr, to) <- [mRange]]+ ]+ where+ tr :: [(HTTP.HeaderName, [String])] -> [HttpRequestHeader] -> [HTTP.Header]+ tr acc [] =+ concatMap finalize acc+ tr acc (HttpRequestMaxAge0:os) =+ tr (insert HTTP.HdrCacheControl ["max-age=0"] acc) os+ tr acc (HttpRequestNoTransform:os) =+ tr (insert HTTP.HdrCacheControl ["no-transform"] acc) os++ -- Some headers are comma-separated, others need multiple headers for+ -- multiple options.+ --+ -- TODO: Right we we just comma-separate all of them.+ finalize :: (HTTP.HeaderName, [String]) -> [HTTP.Header]+ finalize (name, strs) = [HTTP.Header name (intercalate ", " (reverse strs))]++ insert :: Eq a => a -> [b] -> [(a, [b])] -> [(a, [b])]+ insert x y = Lens.modify (Lens.lookupM x) (++ y)++{-------------------------------------------------------------------------------+ Custom exceptions+-------------------------------------------------------------------------------}++data UnexpectedResponse = UnexpectedResponse URI Int+ deriving (Typeable)++instance Pretty UnexpectedResponse where+ pretty (UnexpectedResponse uri code) = "Unexpected response " ++ show code+ ++ "for " ++ show uri++#if MIN_VERSION_base(4,8,0)+deriving instance Show UnexpectedResponse+instance Exception UnexpectedResponse where displayException = pretty+#else+instance Show UnexpectedResponse where show = pretty+instance Exception UnexpectedResponse+#endif++wrapCustomEx :: ( ( Throws UnexpectedResponse+ , Throws IOException+ ) => IO a)+ -> (Throws SomeRemoteError => IO a)+wrapCustomEx act = handleChecked (\(ex :: UnexpectedResponse) -> go ex)+ $ handleChecked (\(ex :: IOException) -> go ex)+ $ act+ where+ go ex = throwChecked (SomeRemoteError ex)
cabal/cabal-install/Distribution/Client/Setup.hs view
@@ -1,4 +1,7 @@-{-# LANGUAGE CPP #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE DeriveGeneric #-} ----------------------------------------------------------------------------- -- | -- Module : Distribution.Client.Setup@@ -12,13 +15,15 @@ -- ----------------------------------------------------------------------------- module Distribution.Client.Setup- ( globalCommand, GlobalFlags(..), defaultGlobalFlags, globalRepos+ ( globalCommand, GlobalFlags(..), defaultGlobalFlags+ , RepoContext(..), withRepoContext , configureCommand, ConfigFlags(..), filterConfigureFlags , configureExCommand, ConfigExFlags(..), defaultConfigExFlags , configureExOptions , buildCommand, BuildFlags(..), BuildExFlags(..), SkipAddSourceDepsCheck(..) , replCommand, testCommand, benchmarkCommand , installCommand, InstallFlags(..), installOptions, defaultInstallFlags+ , defaultSolver, defaultMaxBackjumps , listCommand, ListFlags(..) , updateCommand , upgradeCommand@@ -26,6 +31,7 @@ , infoCommand, InfoFlags(..) , fetchCommand, FetchFlags(..) , freezeCommand, FreezeFlags(..)+ , genBoundsCommand , getCommand, unpackCommand, GetFlags(..) , checkCommand , formatCommand@@ -39,6 +45,7 @@ , sandboxCommand, defaultSandboxLocation, SandboxFlags(..) , execCommand, ExecFlags(..) , userConfigCommand, UserConfigFlags(..)+ , manpageCommand , parsePackageArgs --TODO: stop exporting these:@@ -48,11 +55,11 @@ ) where import Distribution.Client.Types- ( Username(..), Password(..), Repo(..), RemoteRepo(..), LocalRepo(..) )+ ( Username(..), Password(..), RemoteRepo(..) ) import Distribution.Client.BuildReports.Types ( ReportLevel(..) ) import Distribution.Client.Dependency.Types- ( AllowNewer(..), PreSolver(..), ConstraintSource(..) )+ ( PreSolver(..), ConstraintSource(..) ) import qualified Distribution.Client.Init.Types as IT ( InitFlags(..), PackageType(..) ) import Distribution.Client.Targets@@ -60,6 +67,7 @@ import Distribution.Utils.NubList ( NubList, toNubList, fromNubList) + import Distribution.Simple.Compiler (PackageDB) import Distribution.Simple.Program ( defaultProgramConfiguration )@@ -71,8 +79,9 @@ , TestFlags(..), BenchmarkFlags(..) , SDistFlags(..), HaddockFlags(..) , readPackageDbList, showPackageDbList- , Flag(..), toFlag, fromFlag, flagToMaybe, flagToList- , optionVerbosity, boolOpt, boolOpt', trueArg, falseArg, optionNumJobs )+ , Flag(..), toFlag, flagToMaybe, flagToList+ , optionVerbosity, boolOpt, boolOpt', trueArg, falseArg+ , readPToMaybe, optionNumJobs ) import Distribution.Simple.InstallDirs ( PathTemplate, InstallDirs(sysconfdir) , toPathTemplate, fromPathTemplate )@@ -87,22 +96,25 @@ import Distribution.ReadE ( ReadE(..), readP_to_E, succeedReadE ) import qualified Distribution.Compat.ReadP as Parse- ( ReadP, readP_to_S, readS_to_P, char, munch1, pfail, sepBy1, (+++) )+ ( ReadP, char, munch1, pfail, (+++) )+import Distribution.Compat.Semigroup import Distribution.Verbosity ( Verbosity, normal ) import Distribution.Simple.Utils ( wrapText, wrapLine )+import Distribution.Client.GlobalFlags+ ( GlobalFlags(..), defaultGlobalFlags+ , RepoContext(..), withRepoContext+ ) import Data.Char- ( isSpace, isAlphaNum )+ ( isAlphaNum ) import Data.List ( intercalate, deleteFirstsBy ) import Data.Maybe- ( listToMaybe, maybeToList, fromMaybe )-#if !MIN_VERSION_base(4,8,0)-import Data.Monoid- ( Monoid(..) )-#endif+ ( maybeToList, fromMaybe )+import GHC.Generics (Generic)+import Distribution.Compat.Binary (Binary) import Control.Monad ( liftM ) import System.FilePath@@ -110,42 +122,6 @@ import Network.URI ( parseAbsoluteURI, uriToString ) --- --------------------------------------------------------------- * Global flags--- ---------------------------------------------------------------- | Flags that apply at the top level, not to any sub-command.-data GlobalFlags = GlobalFlags {- globalVersion :: Flag Bool,- globalNumericVersion :: Flag Bool,- globalConfigFile :: Flag FilePath,- globalSandboxConfigFile :: Flag FilePath,- globalRemoteRepos :: NubList RemoteRepo, -- ^ Available Hackage servers.- globalCacheDir :: Flag FilePath,- globalLocalRepos :: NubList FilePath,- globalLogsDir :: Flag FilePath,- globalWorldFile :: Flag FilePath,- globalRequireSandbox :: Flag Bool,- globalIgnoreSandbox :: Flag Bool,- globalHttpTransport :: Flag String- }--defaultGlobalFlags :: GlobalFlags-defaultGlobalFlags = GlobalFlags {- globalVersion = Flag False,- globalNumericVersion = Flag False,- globalConfigFile = mempty,- globalSandboxConfigFile = mempty,- globalRemoteRepos = mempty,- globalCacheDir = mempty,- globalLocalRepos = mempty,- globalLogsDir = mempty,- globalWorldFile = mempty,- globalRequireSandbox = Flag False,- globalIgnoreSandbox = Flag False,- globalHttpTransport = mempty- }- globalCommand :: [Command action] -> CommandUI GlobalFlags globalCommand commands = CommandUI { commandName = "",@@ -187,6 +163,7 @@ , "upload" , "report" , "freeze"+ , "gen-bounds" , "haddock" , "hscolour" , "copy"@@ -237,6 +214,7 @@ , addCmd "report" , par , addCmd "freeze"+ , addCmd "gen-bounds" , addCmd "haddock" , addCmd "hscolour" , addCmd "copy"@@ -261,9 +239,17 @@ ++ " " ++ pname ++ " update\n", commandNotes = Nothing, commandDefaultFlags = mempty,- commandOptions = \showOrParseArgs ->- (case showOrParseArgs of ShowArgs -> take 7; ParseArgs -> id)- [option ['V'] ["version"]+ commandOptions = args+ }+ where+ args :: ShowOrParseArgs -> [OptionField GlobalFlags]+ args ShowArgs = argsShown+ args ParseArgs = argsShown ++ argsNotShown++ -- arguments we want to show in the help+ argsShown :: [OptionField GlobalFlags]+ argsShown = [+ option ['V'] ["version"] "Print version information" globalVersion (\v flags -> flags { globalVersion = v }) trueArg@@ -280,9 +266,14 @@ ,option [] ["sandbox-config-file"] "Set an alternate location for the sandbox config file (default: './cabal.sandbox.config')"- globalConfigFile (\v flags -> flags { globalSandboxConfigFile = v })+ globalSandboxConfigFile (\v flags -> flags { globalSandboxConfigFile = v }) (reqArgFlag "FILE") + ,option [] ["default-user-config"]+ "Set a location for a cabal.config file for projects without their own cabal.config freeze file."+ globalConstraintsFile (\v flags -> flags {globalConstraintsFile = v})+ (reqArgFlag "FILE")+ ,option [] ["require-sandbox"] "requiring the presence of a sandbox for sandbox-aware commands" globalRequireSandbox (\v flags -> flags { globalRequireSandbox = v })@@ -293,12 +284,21 @@ globalIgnoreSandbox (\v flags -> flags { globalIgnoreSandbox = v }) trueArg + ,option [] ["ignore-expiry"]+ "Ignore expiry dates on signed metadata (use only in exceptional circumstances)"+ globalIgnoreExpiry (\v flags -> flags { globalIgnoreExpiry = v })+ trueArg+ ,option [] ["http-transport"] "Set a transport for http(s) requests. Accepts 'curl', 'wget', 'powershell', and 'plain-http'. (default: 'curl')"- globalConfigFile (\v flags -> flags { globalHttpTransport = v })+ globalHttpTransport (\v flags -> flags { globalHttpTransport = v }) (reqArgFlag "HttpTransport")+ ] - ,option [] ["remote-repo"]+ -- arguments we don't want shown in the help+ argsNotShown :: [OptionField GlobalFlags]+ argsNotShown = [+ option [] ["remote-repo"] "The name and url for a remote repository" globalRemoteRepos (\v flags -> flags { globalRemoteRepos = v }) (reqArg' "NAME:URL" (toNubList . maybeToList . readRepo) (map showRepo . fromNubList))@@ -323,51 +323,7 @@ globalWorldFile (\v flags -> flags { globalWorldFile = v }) (reqArgFlag "FILE") ]- } -instance Monoid GlobalFlags where- mempty = GlobalFlags {- globalVersion = mempty,- globalNumericVersion = mempty,- globalConfigFile = mempty,- globalSandboxConfigFile = mempty,- globalRemoteRepos = mempty,- globalCacheDir = mempty,- globalLocalRepos = mempty,- globalLogsDir = mempty,- globalWorldFile = mempty,- globalRequireSandbox = mempty,- globalIgnoreSandbox = mempty,- globalHttpTransport = mempty- }- mappend a b = GlobalFlags {- globalVersion = combine globalVersion,- globalNumericVersion = combine globalNumericVersion,- globalConfigFile = combine globalConfigFile,- globalSandboxConfigFile = combine globalConfigFile,- globalRemoteRepos = combine globalRemoteRepos,- globalCacheDir = combine globalCacheDir,- globalLocalRepos = combine globalLocalRepos,- globalLogsDir = combine globalLogsDir,- globalWorldFile = combine globalWorldFile,- globalRequireSandbox = combine globalRequireSandbox,- globalIgnoreSandbox = combine globalIgnoreSandbox,- globalHttpTransport = combine globalHttpTransport- }- where combine field = field a `mappend` field b--globalRepos :: GlobalFlags -> [Repo]-globalRepos globalFlags = remoteRepos ++ localRepos- where- remoteRepos =- [ Repo (Left remote) cacheDir- | remote <- fromNubList $ globalRemoteRepos globalFlags- , let cacheDir = fromFlag (globalCacheDir globalFlags)- </> remoteRepoName remote ]- localRepos =- [ Repo (Right LocalRepo) local- | local <- fromNubList $ globalLocalRepos globalFlags ]- -- ------------------------------------------------------------ -- * Config flags -- ------------------------------------------------------------@@ -397,6 +353,7 @@ -- ^ NB: we expect the latest version to be the most common case. | cabalLibVersion < Version [1,3,10] [] = flags_1_3_10 | cabalLibVersion < Version [1,10,0] [] = flags_1_10_0+ | cabalLibVersion < Version [1,12,0] [] = flags_1_12_0 | cabalLibVersion < Version [1,14,0] [] = flags_1_14_0 | cabalLibVersion < Version [1,18,0] [] = flags_1_18_0 | cabalLibVersion < Version [1,19,1] [] = flags_1_19_0@@ -406,12 +363,18 @@ | cabalLibVersion < Version [1,23,0] [] = flags_1_22_0 | otherwise = flags_latest where- -- Cabal >= 1.19.1 uses '--dependency' and does not need '--constraint'.- flags_latest = flags { configConstraints = [] }+ flags_latest = flags {+ -- Cabal >= 1.19.1 uses '--dependency' and does not need '--constraint'.+ configConstraints = [],+ -- Passing '--allow-newer' to Setup.hs is unnecessary, we use+ -- '--exact-configuration' instead.+ configAllowNewer = Just Cabal.AllowNewerNone+ } -- Cabal < 1.23 doesn't know about '--profiling-detail'. flags_1_22_0 = flags_latest { configProfDetail = NoFlag- , configProfLibDetail = NoFlag }+ , configProfLibDetail = NoFlag+ , configIPID = NoFlag } -- Cabal < 1.22 doesn't know about '--disable-debug-info'. flags_1_21_0 = flags_1_22_0 { configDebugInfo = NoFlag }@@ -440,8 +403,12 @@ configInstallDirs_1_18_0 = (configInstallDirs flags) { sysconfdir = NoFlag } -- Cabal < 1.14.0 doesn't know about '--disable-benchmarks'. flags_1_14_0 = flags_1_18_0 { configBenchmarks = NoFlag }+ -- Cabal < 1.12.0 doesn't know about '--enable/disable-executable-dynamic'+ -- and '--enable/disable-library-coverage'.+ flags_1_12_0 = flags_1_14_0 { configLibCoverage = NoFlag+ , configDynExe = NoFlag } -- Cabal < 1.10.0 doesn't know about '--disable-tests'.- flags_1_10_0 = flags_1_14_0 { configTests = NoFlag }+ flags_1_10_0 = flags_1_12_0 { configTests = NoFlag } -- Cabal < 1.3.10 does not grok the '--constraints' flag. flags_1_3_10 = flags_1_10_0 { configConstraints = [] } @@ -455,13 +422,12 @@ configCabalVersion :: Flag Version, configExConstraints:: [(UserConstraint, ConstraintSource)], configPreferences :: [Dependency],- configSolver :: Flag PreSolver,- configAllowNewer :: Flag AllowNewer+ configSolver :: Flag PreSolver }+ deriving (Eq, Generic) defaultConfigExFlags :: ConfigExFlags-defaultConfigExFlags = mempty { configSolver = Flag defaultSolver- , configAllowNewer = Flag AllowNewerNone }+defaultConfigExFlags = mempty { configSolver = Flag defaultSolver } configureExCommand :: CommandUI (ConfigFlags, ConfigExFlags) configureExCommand = configureCommand {@@ -505,33 +471,15 @@ , optionSolver configSolver (\v flags -> flags { configSolver = v }) - , option [] ["allow-newer"]- ("Ignore upper bounds in all dependencies or " ++ allowNewerArgument)- configAllowNewer (\v flags -> flags { configAllowNewer = v})- (optArg allowNewerArgument- (fmap Flag allowNewerParser) (Flag AllowNewerAll)- allowNewerPrinter)- ]- where allowNewerArgument = "DEPS" instance Monoid ConfigExFlags where- mempty = ConfigExFlags {- configCabalVersion = mempty,- configExConstraints= mempty,- configPreferences = mempty,- configSolver = mempty,- configAllowNewer = mempty- }- mappend a b = ConfigExFlags {- configCabalVersion = combine configCabalVersion,- configExConstraints= combine configExConstraints,- configPreferences = combine configPreferences,- configSolver = combine configSolver,- configAllowNewer = combine configAllowNewer- }- where combine field = field a `mappend` field b+ mempty = gmempty+ mappend = (<>) +instance Semigroup ConfigExFlags where+ (<>) = gmappend+ -- ------------------------------------------------------------ -- * Build flags -- ------------------------------------------------------------@@ -542,7 +490,7 @@ data BuildExFlags = BuildExFlags { buildOnly :: Flag SkipAddSourceDepsCheck-}+} deriving Generic buildExOptions :: ShowOrParseArgs -> [OptionField BuildExFlags] buildExOptions _showOrParseArgs =@@ -569,14 +517,12 @@ parent = Cabal.buildCommand defaultProgramConfiguration instance Monoid BuildExFlags where- mempty = BuildExFlags {- buildOnly = mempty- }- mappend a b = BuildExFlags {- buildOnly = combine buildOnly- }- where combine field = field a `mappend` field b+ mempty = gmempty+ mappend = (<>) +instance Semigroup BuildExFlags where+ (<>) = gmappend+ -- ------------------------------------------------------------ -- * Repl command -- ------------------------------------------------------------@@ -799,6 +745,22 @@ } +genBoundsCommand :: CommandUI FreezeFlags+genBoundsCommand = CommandUI {+ commandName = "gen-bounds",+ commandSynopsis = "Generate dependency bounds.",+ commandDescription = Just $ \_ -> wrapText $+ "Generates bounds for all dependencies that do not currently have them. "+ ++ "Generated bounds are printed to stdout. You can then paste them into your .cabal file.\n"+ ++ "\n",+ commandNotes = Nothing,+ commandUsage = usageFlags "gen-bounds",+ commandDefaultFlags = defaultFreezeFlags,+ commandOptions = \ _ -> [+ optionVerbosity freezeVerbosity (\v flags -> flags { freezeVerbosity = v })+ ]+ }+ -- ------------------------------------------------------------ -- * Other commands -- ------------------------------------------------------------@@ -877,6 +839,18 @@ commandOptions = \_ -> [] } +manpageCommand :: CommandUI (Flag Verbosity)+manpageCommand = CommandUI {+ commandName = "manpage",+ commandSynopsis = "Outputs manpage source.",+ commandDescription = Just $ \_ ->+ "Output manpage source to STDOUT.\n",+ commandNotes = Nothing,+ commandUsage = usageFlags "manpage",+ commandDefaultFlags = toFlag normal,+ commandOptions = \_ -> [optionVerbosity id const]+ }+ runCommand :: CommandUI (BuildFlags, BuildExFlags) runCommand = CommandUI { commandName = "run",@@ -918,7 +892,7 @@ reportUsername :: Flag Username, reportPassword :: Flag Password, reportVerbosity :: Flag Verbosity- }+ } deriving Generic defaultReportFlags :: ReportFlags defaultReportFlags = ReportFlags {@@ -954,18 +928,12 @@ } instance Monoid ReportFlags where- mempty = ReportFlags {- reportUsername = mempty,- reportPassword = mempty,- reportVerbosity = mempty- }- mappend a b = ReportFlags {- reportUsername = combine reportUsername,- reportPassword = combine reportPassword,- reportVerbosity = combine reportVerbosity- }- where combine field = field a `mappend` field b+ mempty = gmempty+ mappend = (<>) +instance Semigroup ReportFlags where+ (<>) = gmappend+ -- ------------------------------------------------------------ -- * Get flags -- ------------------------------------------------------------@@ -975,7 +943,7 @@ getPristine :: Flag Bool, getSourceRepository :: Flag (Maybe RepoKind), getVerbosity :: Flag Verbosity- }+ } deriving Generic defaultGetFlags :: GetFlags defaultGetFlags = GetFlags {@@ -1034,20 +1002,12 @@ } instance Monoid GetFlags where- mempty = GetFlags {- getDestDir = mempty,- getPristine = mempty,- getSourceRepository = mempty,- getVerbosity = mempty- }- mappend a b = GetFlags {- getDestDir = combine getDestDir,- getPristine = combine getPristine,- getSourceRepository = combine getSourceRepository,- getVerbosity = combine getVerbosity- }- where combine field = field a `mappend` field b+ mempty = gmempty+ mappend = (<>) +instance Semigroup GetFlags where+ (<>) = gmappend+ -- ------------------------------------------------------------ -- * List flags -- ------------------------------------------------------------@@ -1057,7 +1017,7 @@ listSimpleOutput :: Flag Bool, listVerbosity :: Flag Verbosity, listPackageDBs :: [Maybe PackageDB]- }+ } deriving Generic defaultListFlags :: ListFlags defaultListFlags = ListFlags {@@ -1113,20 +1073,12 @@ } instance Monoid ListFlags where- mempty = ListFlags {- listInstalled = mempty,- listSimpleOutput = mempty,- listVerbosity = mempty,- listPackageDBs = mempty- }- mappend a b = ListFlags {- listInstalled = combine listInstalled,- listSimpleOutput = combine listSimpleOutput,- listVerbosity = combine listVerbosity,- listPackageDBs = combine listPackageDBs- }- where combine field = field a `mappend` field b+ mempty = gmempty+ mappend = (<>) +instance Semigroup ListFlags where+ (<>) = gmappend+ -- ------------------------------------------------------------ -- * Info flags -- ------------------------------------------------------------@@ -1134,7 +1086,7 @@ data InfoFlags = InfoFlags { infoVerbosity :: Flag Verbosity, infoPackageDBs :: [Maybe PackageDB]- }+ } deriving Generic defaultInfoFlags :: InfoFlags defaultInfoFlags = InfoFlags {@@ -1171,16 +1123,12 @@ } instance Monoid InfoFlags where- mempty = InfoFlags {- infoVerbosity = mempty,- infoPackageDBs = mempty- }- mappend a b = InfoFlags {- infoVerbosity = combine infoVerbosity,- infoPackageDBs = combine infoPackageDBs- }- where combine field = field a `mappend` field b+ mempty = gmempty+ mappend = (<>) +instance Semigroup InfoFlags where+ (<>) = gmappend+ -- ------------------------------------------------------------ -- * Install flags -- ------------------------------------------------------------@@ -1213,7 +1161,10 @@ installRunTests :: Flag Bool, installOfflineMode :: Flag Bool }+ deriving (Eq, Generic) +instance Binary InstallFlags+ defaultInstallFlags :: InstallFlags defaultInstallFlags = InstallFlags { installDocumentation = Flag False,@@ -1245,27 +1196,6 @@ docIndexFile = toPathTemplate ("$datadir" </> "doc" </> "$arch-$os-$compiler" </> "index.html") -allowNewerParser :: ReadE AllowNewer-allowNewerParser = ReadE $ \s ->- case s of- "" -> Right AllowNewerNone- "False" -> Right AllowNewerNone- "True" -> Right AllowNewerAll- _ ->- case readPToMaybe pkgsParser s of- Just pkgs -> Right . AllowNewerSome $ pkgs- Nothing -> Left ("Cannot parse the list of packages: " ++ s)- where- pkgsParser = Parse.sepBy1 parse (Parse.char ',')--allowNewerPrinter :: Flag AllowNewer -> [Maybe String]-allowNewerPrinter (Flag AllowNewerNone) = [Just "False"]-allowNewerPrinter (Flag AllowNewerAll) = [Just "True"]-allowNewerPrinter (Flag (AllowNewerSome pkgs)) =- [Just . intercalate "," . map display $ pkgs]-allowNewerPrinter NoFlag = []-- defaultMaxBackjumps :: Int defaultMaxBackjumps = 2000 @@ -1482,75 +1412,29 @@ instance Monoid InstallFlags where- mempty = InstallFlags {- installDocumentation = mempty,- installHaddockIndex = mempty,- installDryRun = mempty,- installReinstall = mempty,- installAvoidReinstalls = mempty,- installOverrideReinstall = mempty,- installMaxBackjumps = mempty,- installUpgradeDeps = mempty,- installReorderGoals = mempty,- installIndependentGoals= mempty,- installShadowPkgs = mempty,- installStrongFlags = mempty,- installOnly = mempty,- installOnlyDeps = mempty,- installRootCmd = mempty,- installSummaryFile = mempty,- installLogFile = mempty,- installBuildReports = mempty,- installReportPlanningFailure = mempty,- installSymlinkBinDir = mempty,- installOneShot = mempty,- installNumJobs = mempty,- installRunTests = mempty,- installOfflineMode = mempty- }- mappend a b = InstallFlags {- installDocumentation = combine installDocumentation,- installHaddockIndex = combine installHaddockIndex,- installDryRun = combine installDryRun,- installReinstall = combine installReinstall,- installAvoidReinstalls = combine installAvoidReinstalls,- installOverrideReinstall = combine installOverrideReinstall,- installMaxBackjumps = combine installMaxBackjumps,- installUpgradeDeps = combine installUpgradeDeps,- installReorderGoals = combine installReorderGoals,- installIndependentGoals= combine installIndependentGoals,- installShadowPkgs = combine installShadowPkgs,- installStrongFlags = combine installStrongFlags,- installOnly = combine installOnly,- installOnlyDeps = combine installOnlyDeps,- installRootCmd = combine installRootCmd,- installSummaryFile = combine installSummaryFile,- installLogFile = combine installLogFile,- installBuildReports = combine installBuildReports,- installReportPlanningFailure = combine installReportPlanningFailure,- installSymlinkBinDir = combine installSymlinkBinDir,- installOneShot = combine installOneShot,- installNumJobs = combine installNumJobs,- installRunTests = combine installRunTests,- installOfflineMode = combine installOfflineMode- }- where combine field = field a `mappend` field b+ mempty = gmempty+ mappend = (<>) +instance Semigroup InstallFlags where+ (<>) = gmappend+ -- ------------------------------------------------------------ -- * Upload flags -- ------------------------------------------------------------ data UploadFlags = UploadFlags { uploadCheck :: Flag Bool,+ uploadDoc :: Flag Bool, uploadUsername :: Flag Username, uploadPassword :: Flag Password, uploadPasswordCmd :: Flag [String], uploadVerbosity :: Flag Verbosity- }+ } deriving Generic defaultUploadFlags :: UploadFlags defaultUploadFlags = UploadFlags { uploadCheck = toFlag False,+ uploadDoc = toFlag False, uploadUsername = mempty, uploadPassword = mempty, uploadPasswordCmd = mempty,@@ -1560,7 +1444,7 @@ uploadCommand :: CommandUI UploadFlags uploadCommand = CommandUI { commandName = "upload",- commandSynopsis = "Uploads source packages to Hackage.",+ commandSynopsis = "Uploads source packages or documentation to Hackage.", commandDescription = Nothing, commandNotes = Just $ \_ -> "You can store your Hackage login in the ~/.cabal/config file\n"@@ -1576,6 +1460,11 @@ uploadCheck (\v flags -> flags { uploadCheck = v }) trueArg + ,option ['d'] ["documentation"]+ "Upload documentation instead of a source package. Cannot be used together with --check."+ uploadDoc (\v flags -> flags { uploadDoc = v })+ trueArg+ ,option ['u'] ["username"] "Hackage username." uploadUsername (\v flags -> flags { uploadUsername = v })@@ -1596,22 +1485,12 @@ } instance Monoid UploadFlags where- mempty = UploadFlags {- uploadCheck = mempty,- uploadUsername = mempty,- uploadPassword = mempty,- uploadPasswordCmd = mempty,- uploadVerbosity = mempty- }- mappend a b = UploadFlags {- uploadCheck = combine uploadCheck,- uploadUsername = combine uploadUsername,- uploadPassword = combine uploadPassword,- uploadPasswordCmd = combine uploadPasswordCmd,- uploadVerbosity = combine uploadVerbosity- }- where combine field = field a `mappend` field b+ mempty = gmempty+ mappend = (<>) +instance Semigroup UploadFlags where+ (<>) = gmappend+ -- ------------------------------------------------------------ -- * Init flags -- ------------------------------------------------------------@@ -1807,7 +1686,7 @@ data SDistExFlags = SDistExFlags { sDistFormat :: Flag ArchiveFormat }- deriving Show+ deriving (Show, Generic) data ArchiveFormat = TargzFormat | ZipFormat -- | ... deriving (Show, Eq)@@ -1840,22 +1719,19 @@ ] instance Monoid SDistExFlags where- mempty = SDistExFlags {- sDistFormat = mempty- }- mappend a b = SDistExFlags {- sDistFormat = combine sDistFormat- }- where- combine field = field a `mappend` field b+ mempty = gmempty+ mappend = (<>) +instance Semigroup SDistExFlags where+ (<>) = gmappend+ -- ------------------------------------------------------------ -- * Win32SelfUpgrade flags -- ------------------------------------------------------------ data Win32SelfUpgradeFlags = Win32SelfUpgradeFlags { win32SelfUpgradeVerbosity :: Flag Verbosity-}+} deriving Generic defaultWin32SelfUpgradeFlags :: Win32SelfUpgradeFlags defaultWin32SelfUpgradeFlags = Win32SelfUpgradeFlags {@@ -1878,21 +1754,19 @@ } instance Monoid Win32SelfUpgradeFlags where- mempty = Win32SelfUpgradeFlags {- win32SelfUpgradeVerbosity = mempty- }- mappend a b = Win32SelfUpgradeFlags {- win32SelfUpgradeVerbosity = combine win32SelfUpgradeVerbosity- }- where combine field = field a `mappend` field b+ mempty = gmempty+ mappend = (<>) +instance Semigroup Win32SelfUpgradeFlags where+ (<>) = gmappend+ -- ------------------------------------------------------------ -- * ActAsSetup flags -- ------------------------------------------------------------ data ActAsSetupFlags = ActAsSetupFlags { actAsSetupBuildType :: Flag BuildType-}+} deriving Generic defaultActAsSetupFlags :: ActAsSetupFlags defaultActAsSetupFlags = ActAsSetupFlags {@@ -1919,14 +1793,12 @@ } instance Monoid ActAsSetupFlags where- mempty = ActAsSetupFlags {- actAsSetupBuildType = mempty- }- mappend a b = ActAsSetupFlags {- actAsSetupBuildType = combine actAsSetupBuildType- }- where combine field = field a `mappend` field b+ mempty = gmempty+ mappend = (<>) +instance Semigroup ActAsSetupFlags where+ (<>) = gmappend+ -- ------------------------------------------------------------ -- * Sandbox-related flags -- ------------------------------------------------------------@@ -1936,7 +1808,7 @@ sandboxSnapshot :: Flag Bool, -- FIXME: this should be an 'add-source'-only -- flag. sandboxLocation :: Flag FilePath-}+} deriving Generic defaultSandboxLocation :: FilePath defaultSandboxLocation = ".cabal-sandbox"@@ -2043,25 +1915,19 @@ } instance Monoid SandboxFlags where- mempty = SandboxFlags {- sandboxVerbosity = mempty,- sandboxSnapshot = mempty,- sandboxLocation = mempty- }- mappend a b = SandboxFlags {- sandboxVerbosity = combine sandboxVerbosity,- sandboxSnapshot = combine sandboxSnapshot,- sandboxLocation = combine sandboxLocation- }- where combine field = field a `mappend` field b+ mempty = gmempty+ mappend = (<>) +instance Semigroup SandboxFlags where+ (<>) = gmappend+ -- ------------------------------------------------------------ -- * Exec Flags -- ------------------------------------------------------------ data ExecFlags = ExecFlags { execVerbosity :: Flag Verbosity-}+} deriving Generic defaultExecFlags :: ExecFlags defaultExecFlags = ExecFlags {@@ -2113,31 +1979,31 @@ } instance Monoid ExecFlags where- mempty = ExecFlags {- execVerbosity = mempty- }- mappend a b = ExecFlags {- execVerbosity = combine execVerbosity- }- where combine field = field a `mappend` field b+ mempty = gmempty+ mappend = (<>) +instance Semigroup ExecFlags where+ (<>) = gmappend+ -- ------------------------------------------------------------ -- * UserConfig flags -- ------------------------------------------------------------ data UserConfigFlags = UserConfigFlags {- userConfigVerbosity :: Flag Verbosity-}+ userConfigVerbosity :: Flag Verbosity,+ userConfigForce :: Flag Bool+} deriving Generic instance Monoid UserConfigFlags where mempty = UserConfigFlags {- userConfigVerbosity = toFlag normal- }- mappend a b = UserConfigFlags {- userConfigVerbosity = combine userConfigVerbosity+ userConfigVerbosity = toFlag normal,+ userConfigForce = toFlag False }- where combine field = field a `mappend` field b+ mappend = (<>) +instance Semigroup UserConfigFlags where+ (<>) = gmappend+ userConfigCommand :: CommandUI UserConfigFlags userConfigCommand = CommandUI { commandName = "user-config",@@ -2149,6 +2015,9 @@ ++ " (i.e. all bindings that are actually defined and not commented out)" ++ " and the default config of the new version.\n" ++ "\n"+ ++ "init: Creates a new config file at either ~/.cabal/config or as"+ ++ " specified by --config-file, if given. An existing file won't be "+ ++ " overwritten unless -f or --force is given.\n" ++ "diff: Shows a pseudo-diff of the user's ~/.cabal/config file and" ++ " the default configuration that would be created by cabal if the" ++ " config file did not exist.\n"@@ -2156,10 +2025,14 @@ ++ " created by default, and write the result back to ~/.cabal/config.", commandNotes = Nothing,- commandUsage = usageAlternatives "user-config" ["diff", "update"],+ commandUsage = usageAlternatives "user-config" ["init", "diff", "update"], commandDefaultFlags = mempty, commandOptions = \ _ -> [ optionVerbosity userConfigVerbosity (\v flags -> flags { userConfigVerbosity = v })+ , option ['f'] ["force"]+ "Overwrite the config file if it already exists."+ userConfigForce (\v flags -> flags { userConfigForce = v })+ trueArg ] } @@ -2201,8 +2074,7 @@ [ option [] ["max-backjumps"] ("Maximum number of backjumps allowed while solving (default: " ++ show defaultMaxBackjumps ++ "). Use a negative number to enable unlimited backtracking. Use 0 to disable backtracking completely.") getmbj setmbj- (reqArg "NUM" (readP_to_E ("Cannot parse number: "++)- (fmap toFlag (Parse.readS_to_P reads)))+ (reqArg "NUM" (readP_to_E ("Cannot parse number: "++) (fmap toFlag parse)) (map show . flagToList)) , option [] ["reorder-goals"] "Try to reorder goals according to certain heuristics. Slows things down on average, but may make backtracking faster for some packages."@@ -2250,10 +2122,6 @@ show arg ++ " is not valid syntax for a package name or" ++ " package dependency." -readPToMaybe :: Parse.ReadP a a -> String -> Maybe a-readPToMaybe p str = listToMaybe [ r | (r,s) <- Parse.readP_to_S p str- , all isSpace s ]- parseDependencyOrPackageId :: Parse.ReadP r Dependency parseDependencyOrPackageId = parse Parse.+++ liftM pkgidToDependency parse where@@ -2278,7 +2146,9 @@ return RemoteRepo { remoteRepoName = name, remoteRepoURI = uri,- remoteRepoRootKeys = (),+ remoteRepoSecure = Nothing,+ remoteRepoRootKeys = [],+ remoteRepoKeyThreshold = 0, remoteRepoShouldTryHttps = False } @@ -2301,6 +2171,7 @@ indentParagraph :: String -> String indentParagraph = unlines+ . (flip (++)) [""] . map ((" "++).unwords) . wrapLine 77 . words
cabal/cabal-install/Distribution/Client/SetupWrapper.hs view
@@ -27,9 +27,9 @@ ( Version(..), VersionRange, anyVersion , intersectVersionRanges, orLaterVersion , withinRange )-import Distribution.InstalledPackageInfo (installedPackageId)+import Distribution.InstalledPackageInfo (installedUnitId) import Distribution.Package- ( InstalledPackageId(..), PackageIdentifier(..), PackageId,+ ( UnitId(..), PackageIdentifier(..), PackageId, PackageName(..), Package(..), packageName , packageVersion, Dependency(..) ) import Distribution.PackageDescription@@ -120,8 +120,37 @@ import qualified System.Win32 as Win32 #endif +--TODO: The 'setupWrapper' and 'SetupScriptOptions' should be split into two+-- parts: one that has no policy and just does as it's told with all the+-- explicit options, and an optional initial part that applies certain+-- policies (like if we should add the Cabal lib as a dep, and if so which+-- version). This could be structured as an action that returns a fully+-- elaborated 'SetupScriptOptions' containing no remaining policy choices.+--+-- See also the discussion at https://github.com/haskell/cabal/pull/3094+ data SetupScriptOptions = SetupScriptOptions {+ -- | The version of the Cabal library to use (if 'useDependenciesExclusive'+ -- is not set). A suitable version of the Cabal library must be installed+ -- (or for some build-types be the one cabal-install was built with).+ --+ -- The version found also determines the version of the Cabal specification+ -- that we us for talking to the Setup.hs, unless overridden by+ -- 'useCabalSpecVersion'.+ -- useCabalVersion :: VersionRange,++ -- | This is the version of the Cabal specification that we believe that+ -- this package uses. This affects the semantics and in particular the+ -- Setup command line interface.+ --+ -- This is similar to 'useCabalVersion' but instead of probing the system+ -- for a version of the /Cabal library/ you just say exactly which version+ -- of the /spec/ we will use. Using this also avoid adding the Cabal+ -- library as an additional dependency, so add it to 'useDependencies'+ -- if needed.+ --+ useCabalSpecVersion :: Maybe Version, useCompiler :: Maybe Compiler, usePlatform :: Maybe Platform, usePackageDB :: PackageDBStack,@@ -133,18 +162,29 @@ forceExternalSetupMethod :: Bool, -- | List of dependencies to use when building Setup.hs- useDependencies :: [(InstalledPackageId, PackageId)],+ useDependencies :: [(UnitId, PackageId)], -- | Is the list of setup dependencies exclusive? --- -- This is here for legacy reasons. Before the introduction of the explicit- -- setup stanza in .cabal files we compiled Setup.hs scripts with all- -- packages in the environment visible, but we will needed to restrict- -- _some_ packages; in particular, we need to restrict the version of Cabal- -- that the setup script gets linked against (this was the only "dependency- -- constraint" that we had previously for Setup scripts).+ -- When this is @False@, if we compile the Setup.hs script we do so with+ -- the list in 'useDependencies' but all other packages in the environment+ -- are also visible. Additionally, a suitable version of @Cabal@ library+ -- is added to the list of dependencies (see 'useCabalVersion').+ --+ -- When @True@, only the 'useDependencies' packages are used, with other+ -- packages in the environment hidden.+ --+ -- This feature is here to support the setup stanza in .cabal files that+ -- specifies explicit (and exclusive) dependencies, as well as the old+ -- style with no dependencies. useDependenciesExclusive :: Bool, + -- | Should we build the Setup.hs with CPP version macros available?+ -- We turn this on when we have a setup stanza in .cabal that declares+ -- explicit setup dependencies.+ --+ useVersionMacros :: Bool,+ -- Used only by 'cabal clean' on Windows. -- -- Note: win32 clean hack@@ -174,12 +214,14 @@ defaultSetupScriptOptions :: SetupScriptOptions defaultSetupScriptOptions = SetupScriptOptions { useCabalVersion = anyVersion,+ useCabalSpecVersion = Nothing, useCompiler = Nothing, usePlatform = Nothing, usePackageDB = [GlobalPackageDB, UserPackageDB], usePackageIndex = Nothing, useDependencies = [], useDependenciesExclusive = False,+ useVersionMacros = False, useProgramConfig = emptyProgramConfiguration, useDistPref = defaultDistPref, useLoggingHandle = Nothing,@@ -235,7 +277,9 @@ -- between the self and internal setup methods, which are -- consistent with each other. | buildType' == Custom = externalSetupMethod- | not (cabalVersion `withinRange`+ | maybe False (cabalVersion /=)+ (useCabalSpecVersion options)+ || not (cabalVersion `withinRange` useCabalVersion options) = externalSetupMethod | isJust (useLoggingHandle options) -- Forcing is done to use an external process e.g. due to parallel@@ -304,7 +348,8 @@ externalSetupMethod :: SetupMethod externalSetupMethod verbosity options pkg bt mkargs = do debug verbosity $ "Using external setup method with build-type " ++ show bt- debug verbosity $ "Using explicit dependencies: " ++ show (useDependenciesExclusive options)+ debug verbosity $ "Using explicit dependencies: "+ ++ show (useDependenciesExclusive options) createDirectoryIfMissingVerbose verbosity True setupDir (cabalLibVersion, mCabalLibInstalledPkgId, options') <- cabalLibVersionToUse debug verbosity $ "Using Cabal library version " ++ display cabalLibVersion@@ -335,20 +380,26 @@ Nothing -> getInstalledPackages verbosity comp (usePackageDB options') conf - cabalLibVersionToUse :: IO (Version, (Maybe InstalledPackageId)+ cabalLibVersionToUse :: IO (Version, (Maybe UnitId) ,SetupScriptOptions)- cabalLibVersionToUse = do- savedVer <- savedVersion- case savedVer of- Just version | version `withinRange` useCabalVersion options- -> do updateSetupScript version bt- -- Does the previously compiled setup executable still exist and- -- is it up-to date?- useExisting <- canUseExistingSetup version- if useExisting- then return (version, Nothing, options)- else installedVersion- _ -> installedVersion+ cabalLibVersionToUse =+ case useCabalSpecVersion options of+ Just version -> do+ updateSetupScript version bt+ writeFile setupVersionFile (show version ++ "\n")+ return (version, Nothing, options)+ Nothing -> do+ savedVer <- savedVersion+ case savedVer of+ Just version | version `withinRange` useCabalVersion options+ -> do updateSetupScript version bt+ -- Does the previously compiled setup executable still exist+ -- and is it up-to date?+ useExisting <- canUseExistingSetup version+ if useExisting+ then return (version, Nothing, options)+ else installedVersion+ _ -> installedVersion where -- This check duplicates the checks in 'getCachedSetupExecutable' / -- 'compileSetupExecutable'. Unfortunately, we have to perform it twice@@ -364,7 +415,7 @@ (&&) <$> setupProgFile `existsAndIsMoreRecentThan` setupHs <*> setupProgFile `existsAndIsMoreRecentThan` setupVersionFile - installedVersion :: IO (Version, Maybe InstalledPackageId+ installedVersion :: IO (Version, Maybe UnitId ,SetupScriptOptions) installedVersion = do (comp, conf, options') <- configureCompiler options@@ -411,10 +462,8 @@ UnknownBuildType _ -> error "buildTypeScript UnknownBuildType" installedCabalVersion :: SetupScriptOptions -> Compiler -> ProgramConfiguration- -> IO (Version, Maybe InstalledPackageId+ -> IO (Version, Maybe UnitId ,SetupScriptOptions)- installedCabalVersion options' _ _ | packageName pkg == PackageName "Cabal" =- return (packageVersion pkg, Nothing, options') installedCabalVersion options' compiler conf = do index <- maybeGetInstalledPackages options' compiler conf let cabalDep = Dependency (PackageName "Cabal") (useCabalVersion options')@@ -426,7 +475,7 @@ ++ " but no suitable version is installed." pkgs -> let ipkginfo = head . snd . bestVersion fst $ pkgs in return (packageVersion ipkginfo- ,Just . installedPackageId $ ipkginfo, options'')+ ,Just . installedUnitId $ ipkginfo, options'') bestVersion :: (a -> Version) -> [a] -> a bestVersion f = firstMaximumBy (comparing (preference . f))@@ -499,7 +548,7 @@ -- | Look up the setup executable in the cache; update the cache if the setup -- executable is not found. getCachedSetupExecutable :: SetupScriptOptions- -> Version -> Maybe InstalledPackageId+ -> Version -> Maybe UnitId -> IO FilePath getCachedSetupExecutable options' cabalLibVersion maybeCabalLibInstalledPkgId = do@@ -534,7 +583,7 @@ -- Currently this is GHC/GHCJS only. It should really be generalised. -- compileSetupExecutable :: SetupScriptOptions- -> Version -> Maybe InstalledPackageId -> Bool+ -> Version -> Maybe UnitId -> Bool -> IO FilePath compileSetupExecutable options' cabalLibVersion maybeCabalLibInstalledPkgId forceCompile = do@@ -552,14 +601,20 @@ cabalDep = maybe [] (\ipkgid -> [(ipkgid, cabalPkgid)]) maybeCabalLibInstalledPkgId - -- We do a few things differently once packages opt-in and declare- -- a custom-settup stanza. In particular we then enforce the deps- -- specified, but also let the Setup.hs use the version macros.- newPedanticDeps = useDependenciesExclusive options'- selectedDeps- | newPedanticDeps = useDependencies options'- | otherwise = useDependencies options' ++ cabalDep- addRenaming (ipid, pid) = (ipid, pid, defaultRenaming)+ -- With 'useDependenciesExclusive' we enforce the deps specified,+ -- so only the given ones can be used. Otherwise we allow the use+ -- of packages in the ambient environment, and add on a dep on the+ -- Cabal library.+ --+ -- With 'useVersionMacros' we use a version CPP macros .h file.+ --+ -- Both of these options should be enabled for packages that have+ -- opted-in and declared a custom-settup stanza.+ --+ selectedDeps | useDependenciesExclusive options'+ = useDependencies options'+ | otherwise = useDependencies options' ++ cabalDep+ addRenaming (ipid, _) = (ipid, defaultRenaming) cppMacrosFile = setupDir </> "setup_macros.h" ghcOptions = mempty { ghcOptVerbosity = Flag verbosity@@ -569,17 +624,19 @@ , ghcOptObjDir = Flag setupDir , ghcOptHiDir = Flag setupDir , ghcOptSourcePathClear = Flag True- , ghcOptSourcePath = toNubListR [workingDir]+ , ghcOptSourcePath = case bt of+ Custom -> toNubListR [workingDir]+ _ -> mempty , ghcOptPackageDBs = usePackageDB options''- , ghcOptHideAllPackages = Flag newPedanticDeps- , ghcOptCabal = Flag newPedanticDeps+ , ghcOptHideAllPackages = Flag (useDependenciesExclusive options')+ , ghcOptCabal = Flag (useDependenciesExclusive options') , ghcOptPackages = toNubListR $ map addRenaming selectedDeps , ghcOptCppIncludes = toNubListR [ cppMacrosFile- | newPedanticDeps ]+ | useVersionMacros options' ] , ghcOptExtra = toNubListR extraOpts }- let ghcCmdLine = renderGhcOptions compiler ghcOptions- when newPedanticDeps $+ let ghcCmdLine = renderGhcOptions compiler platform ghcOptions+ when (useVersionMacros options') $ rewriteFile cppMacrosFile (generatePackageVersionMacros [ pid | (_ipid, pid) <- selectedDeps ]) case useLoggingHandle options of
cabal/cabal-install/Distribution/Client/SrcDist.hs view
@@ -33,7 +33,7 @@ import Distribution.Version (Version(..), orLaterVersion) import System.FilePath ((</>), (<.>))-import Control.Monad (when, unless)+import Control.Monad (when, unless, liftM) import System.Directory (doesFileExist, removeFile, canonicalizePath) import System.Process (runProcess, waitForProcess) import System.Exit (ExitCode(..))@@ -41,9 +41,8 @@ -- |Create a source distribution. sdist :: SDistFlags -> SDistExFlags -> IO () sdist flags exflags = do- pkg <- return . flattenPackageDescription- =<< readPackageDescription verbosity- =<< defaultPackageDesc verbosity+ pkg <- liftM flattenPackageDescription+ (readPackageDescription verbosity =<< defaultPackageDesc verbosity) let withDir = if not needMakeArchive then (\f -> f tmpTargetDir) else withTempDirectory verbosity tmpTargetDir "sdist." -- 'withTempDir' fails if we don't create 'tmpTargetDir'...
cabal/cabal-install/Distribution/Client/Tar.hs view
@@ -1,5 +1,5 @@ {-# LANGUAGE DeriveFunctor #-}-{-# OPTIONS_GHC -fno-warn-unused-imports #-}+{-# OPTIONS_GHC -fno-warn-orphans #-} ----------------------------------------------------------------------------- -- | -- Module : Distribution.Client.Tar@@ -15,94 +15,27 @@ -- ----------------------------------------------------------------------------- module Distribution.Client.Tar (- -- * High level \"all in one\" operations+ -- * @tar.gz@ operations createTarGzFile, extractTarGzFile, - -- * Converting between internal and external representation- read,- write,- writeEntries,-- -- * Packing and unpacking files to\/from internal representation- pack,- unpack,-- -- * Tar entry and associated types- Entry(..),- entryPath,- EntryContent(..),- Ownership(..),- FileSize,- Permissions,- EpochTime,- DevMajor,- DevMinor,- TypeCode,- Format(..),+ -- * Other local utils buildTreeRefTypeCode, buildTreeSnapshotTypeCode, isBuildTreeRefTypeCode,- entrySizeInBlocks,- entrySizeInBytes,-- -- * Constructing simple entry values- simpleEntry,- fileEntry,- directoryEntry,-- -- * TarPath type- TarPath,- toTarPath,- fromTarPath,-- -- ** Sequences of tar entries- Entries(..),- foldrEntries,- foldlEntries,- unfoldrEntries,- mapEntries, filterEntries,- entriesIndex,-+ filterEntriesM,+ entriesToList, ) where -import Data.Char (ord)-import Data.Int (Int64)-import Data.Bits (Bits, shiftL, testBit)-import Data.List (foldl')-import Numeric (readOct, showOct)-import Control.Applicative (Applicative(..))-import Control.Monad (MonadPlus(mplus), when, ap, liftM)-import qualified Data.Map as Map-import qualified Data.ByteString.Lazy as BS-import qualified Data.ByteString.Lazy.Char8 as BS.Char8-import Data.ByteString.Lazy (ByteString)-import qualified Codec.Compression.GZip as GZip+import qualified Data.ByteString.Lazy as BS+import qualified Codec.Archive.Tar as Tar+import qualified Codec.Archive.Tar.Entry as Tar+import qualified Codec.Archive.Tar.Check as Tar+import qualified Codec.Compression.GZip as GZip import qualified Distribution.Client.GZipUtils as GZipUtils -import System.FilePath- ( (</>) )-import qualified System.FilePath as FilePath.Native-import qualified System.FilePath.Windows as FilePath.Windows-import qualified System.FilePath.Posix as FilePath.Posix-import System.Directory- ( getDirectoryContents, doesDirectoryExist- , getPermissions, createDirectoryIfMissing, copyFile )-import qualified System.Directory as Permissions- ( Permissions(executable) )-import Distribution.Client.Compat.FilePerms- ( setFileExecutable )-import System.Posix.Types- ( FileMode )-import Distribution.Client.Compat.Time- ( EpochTime, getModTime )-import System.IO- ( IOMode(ReadMode), openBinaryFile, hFileSize )-import System.IO.Unsafe (unsafeInterleaveIO)--import Prelude hiding (read)-+import Control.Exception (Exception(..), throw) -- -- * High level operations@@ -113,839 +46,65 @@ -> FilePath -- ^ Directory to archive, relative to base dir -> IO () createTarGzFile tar base dir =- BS.writeFile tar . GZip.compress . write =<< pack base [dir]+ BS.writeFile tar . GZip.compress . Tar.write =<< Tar.pack base [dir] extractTarGzFile :: FilePath -- ^ Destination directory -> FilePath -- ^ Expected subdir (to check for tarbombs) -> FilePath -- ^ Tarball -> IO () extractTarGzFile dir expected tar =- unpack dir . checkTarbomb expected . read+ Tar.unpack dir . Tar.checkTarbomb expected . Tar.read . GZipUtils.maybeDecompress =<< BS.readFile tar ------ * Entry type---+instance (Exception a, Exception b) => Exception (Either a b) where+ toException (Left e) = toException e+ toException (Right e) = toException e -type FileSize = Int64-type DevMajor = Int-type DevMinor = Int-type TypeCode = Char-type Permissions = FileMode+ fromException e =+ case fromException e of+ Just e' -> Just (Left e')+ Nothing -> case fromException e of+ Just e' -> Just (Right e')+ Nothing -> Nothing --- | Tar archive entry.----data Entry = Entry { - -- | The path of the file or directory within the archive. This is in a- -- tar-specific form. Use 'entryPath' to get a native 'FilePath'.- entryTarPath :: !TarPath,-- -- | The real content of the entry. For 'NormalFile' this includes the- -- file data. An entry usually contains a 'NormalFile' or a 'Directory'.- entryContent :: !EntryContent,-- -- | File permissions (Unix style file mode).- entryPermissions :: !Permissions,-- -- | The user and group to which this file belongs.- entryOwnership :: !Ownership,-- -- | The time the file was last modified.- entryTime :: !EpochTime,-- -- | The tar format the archive is using.- entryFormat :: !Format- }- -- | Type code for the local build tree reference entry type. We don't use the -- symbolic link entry type because it allows only 100 ASCII characters for the -- path.-buildTreeRefTypeCode :: TypeCode+buildTreeRefTypeCode :: Tar.TypeCode buildTreeRefTypeCode = 'C' -- | Type code for the local build tree snapshot entry type.-buildTreeSnapshotTypeCode :: TypeCode+buildTreeSnapshotTypeCode :: Tar.TypeCode buildTreeSnapshotTypeCode = 'S' -- | Is this a type code for a build tree reference?-isBuildTreeRefTypeCode :: TypeCode -> Bool+isBuildTreeRefTypeCode :: Tar.TypeCode -> Bool isBuildTreeRefTypeCode typeCode | (typeCode == buildTreeRefTypeCode || typeCode == buildTreeSnapshotTypeCode) = True | otherwise = False --- | Native 'FilePath' of the file or directory within the archive.----entryPath :: Entry -> FilePath-entryPath = fromTarPath . entryTarPath---- | Return the size of an entry in bytes.-entrySizeInBytes :: Entry -> FileSize-entrySizeInBytes = (*512) . fromIntegral . entrySizeInBlocks---- | Return the number of blocks in an entry.-entrySizeInBlocks :: Entry -> Int-entrySizeInBlocks entry = 1 + case entryContent entry of- NormalFile _ size -> bytesToBlocks size- OtherEntryType _ _ size -> bytesToBlocks size- _ -> 0- where- bytesToBlocks s = 1 + ((fromIntegral s - 1) `div` 512)---- | The content of a tar archive entry, which depends on the type of entry.------ Portable archives should contain only 'NormalFile' and 'Directory'.----data EntryContent = NormalFile ByteString !FileSize- | Directory- | SymbolicLink !LinkTarget- | HardLink !LinkTarget- | CharacterDevice !DevMajor !DevMinor- | BlockDevice !DevMajor !DevMinor- | NamedPipe- | OtherEntryType !TypeCode ByteString !FileSize--data Ownership = Ownership {- -- | The owner user name. Should be set to @\"\"@ if unknown.- ownerName :: String,-- -- | The owner group name. Should be set to @\"\"@ if unknown.- groupName :: String,-- -- | Numeric owner user id. Should be set to @0@ if unknown.- ownerId :: !Int,-- -- | Numeric owner group id. Should be set to @0@ if unknown.- groupId :: !Int- }---- | There have been a number of extensions to the tar file format over the--- years. They all share the basic entry fields and put more meta-data in--- different extended headers.----data Format =-- -- | This is the classic Unix V7 tar format. It does not support owner and- -- group names, just numeric Ids. It also does not support device numbers.- V7Format-- -- | The \"USTAR\" format is an extension of the classic V7 format. It was- -- later standardised by POSIX. It has some restrictions but is the most- -- portable format.- --- | UstarFormat-- -- | The GNU tar implementation also extends the classic V7 format, though- -- in a slightly different way from the USTAR format. In general for new- -- archives the standard USTAR/POSIX should be used.- --- | GnuFormat- deriving Eq---- | @rw-r--r--@ for normal files-ordinaryFilePermissions :: Permissions-ordinaryFilePermissions = 0o0644---- | @rwxr-xr-x@ for executable files-executableFilePermissions :: Permissions-executableFilePermissions = 0o0755---- | @rwxr-xr-x@ for directories-directoryPermissions :: Permissions-directoryPermissions = 0o0755--isExecutable :: Permissions -> Bool-isExecutable p = testBit p 0 || testBit p 6 -- user or other executable---- | An 'Entry' with all default values except for the file name and type. It--- uses the portable USTAR/POSIX format (see 'UstarHeader').------ You can use this as a basis and override specific fields, eg:------ > (emptyEntry name HardLink) { linkTarget = target }----simpleEntry :: TarPath -> EntryContent -> Entry-simpleEntry tarpath content = Entry {- entryTarPath = tarpath,- entryContent = content,- entryPermissions = case content of- Directory -> directoryPermissions- _ -> ordinaryFilePermissions,- entryOwnership = Ownership "" "" 0 0,- entryTime = 0,- entryFormat = UstarFormat- }---- | A tar 'Entry' for a file.------ Entry fields such as file permissions and ownership have default values.------ You can use this as a basis and override specific fields. For example if you--- need an executable file you could use:------ > (fileEntry name content) { fileMode = executableFileMode }----fileEntry :: TarPath -> ByteString -> Entry-fileEntry name fileContent =- simpleEntry name (NormalFile fileContent (BS.length fileContent))---- | A tar 'Entry' for a directory.------ Entry fields such as file permissions and ownership have default values.----directoryEntry :: TarPath -> Entry-directoryEntry name = simpleEntry name Directory------- * Tar paths------- | The classic tar format allowed just 100 characters for the file name. The--- USTAR format extended this with an extra 155 characters, however it uses a--- complex method of splitting the name between the two sections.------ Instead of just putting any overflow into the extended area, it uses the--- extended area as a prefix. The aggravating insane bit however is that the--- prefix (if any) must only contain a directory prefix. That is the split--- between the two areas must be on a directory separator boundary. So there is--- no simple calculation to work out if a file name is too long. Instead we--- have to try to find a valid split that makes the name fit in the two areas.------ The rationale presumably was to make it a bit more compatible with old tar--- programs that only understand the classic format. A classic tar would be--- able to extract the file name and possibly some dir prefix, but not the--- full dir prefix. So the files would end up in the wrong place, but that's--- probably better than ending up with the wrong names too.------ So it's understandable but rather annoying.------ * Tar paths use POSIX format (ie @\'/\'@ directory separators), irrespective--- of the local path conventions.------ * The directory separator between the prefix and name is /not/ stored.----data TarPath = TarPath FilePath -- path name, 100 characters max.- FilePath -- path prefix, 155 characters max.- deriving (Eq, Ord)---- | Convert a 'TarPath' to a native 'FilePath'.------ The native 'FilePath' will use the native directory separator but it is not--- otherwise checked for validity or sanity. In particular:------ * The tar path may be invalid as a native path, eg the filename @\"nul\"@ is--- not valid on Windows.------ * The tar path may be an absolute path or may contain @\"..\"@ components.--- For security reasons this should not usually be allowed, but it is your--- responsibility to check for these conditions (eg using 'checkSecurity').----fromTarPath :: TarPath -> FilePath-fromTarPath (TarPath name prefix) = adjustDirectory $- FilePath.Native.joinPath $ FilePath.Posix.splitDirectories prefix- ++ FilePath.Posix.splitDirectories name- where- adjustDirectory | FilePath.Posix.hasTrailingPathSeparator name- = FilePath.Native.addTrailingPathSeparator- | otherwise = id---- | Convert a native 'FilePath' to a 'TarPath'.------ The conversion may fail if the 'FilePath' is too long. See 'TarPath' for a--- description of the problem with splitting long 'FilePath's.----toTarPath :: Bool -- ^ Is the path for a directory? This is needed because for- -- directories a 'TarPath' must always use a trailing @\/@.- -> FilePath -> Either String TarPath-toTarPath isDir = splitLongPath- . addTrailingSep- . FilePath.Posix.joinPath- . FilePath.Native.splitDirectories- where- addTrailingSep | isDir = FilePath.Posix.addTrailingPathSeparator- | otherwise = id---- | Take a sanitized path, split on directory separators and try to pack it--- into the 155 + 100 tar file name format.------ The strategy is this: take the name-directory components in reverse order--- and try to fit as many components into the 100 long name area as possible.--- If all the remaining components fit in the 155 name area then we win.----splitLongPath :: FilePath -> Either String TarPath-splitLongPath path =- case packName nameMax (reverse (FilePath.Posix.splitPath path)) of- Left err -> Left err- Right (name, []) -> Right (TarPath name "")- Right (name, first:rest) -> case packName prefixMax remainder of- Left err -> Left err- Right (_ , _ : _) -> Left "File name too long (cannot split)"- Right (prefix, []) -> Right (TarPath name prefix)- where- -- drop the '/' between the name and prefix:- remainder = init first : rest-- where- nameMax, prefixMax :: Int- nameMax = 100- prefixMax = 155-- packName _ [] = Left "File name empty"- packName maxLen (c:cs)- | n > maxLen = Left "File name too long"- | otherwise = Right (packName' maxLen n [c] cs)- where n = length c-- packName' maxLen n ok (c:cs)- | n' <= maxLen = packName' maxLen n' (c:ok) cs- where n' = n + length c- packName' _ _ ok cs = (FilePath.Posix.joinPath ok, cs)---- | The tar format allows just 100 ASCII characters for the 'SymbolicLink' and--- 'HardLink' entry types.----newtype LinkTarget = LinkTarget FilePath- deriving (Eq, Ord)---- | Convert a tar 'LinkTarget' to a native 'FilePath'.----fromLinkTarget :: LinkTarget -> FilePath-fromLinkTarget (LinkTarget path) = adjustDirectory $- FilePath.Native.joinPath $ FilePath.Posix.splitDirectories path- where- adjustDirectory | FilePath.Posix.hasTrailingPathSeparator path- = FilePath.Native.addTrailingPathSeparator- | otherwise = id------- * Entries type------- | A tar archive is a sequence of entries.-data Entries = Next Entry Entries- | Done- | Fail String--unfoldrEntries :: (a -> Either String (Maybe (Entry, a))) -> a -> Entries-unfoldrEntries f = unfold- where- unfold x = case f x of- Left err -> Fail err- Right Nothing -> Done- Right (Just (e, x')) -> Next e (unfold x')--foldrEntries :: (Entry -> a -> a) -> a -> (String -> a) -> Entries -> a-foldrEntries next done fail' = fold- where- fold (Next e es) = next e (fold es)- fold Done = done- fold (Fail err) = fail' err--foldlEntries :: (a -> Entry -> a) -> a -> Entries -> Either String a-foldlEntries f = fold- where- fold a (Next e es) = (fold $! f a e) es- fold a Done = Right a- fold _ (Fail err) = Left err--mapEntries :: (Entry -> Entry) -> Entries -> Entries-mapEntries f = foldrEntries (Next . f) Done Fail--filterEntries :: (Entry -> Bool) -> Entries -> Entries+filterEntries :: (Tar.Entry -> Bool) -> Tar.Entries e -> Tar.Entries e filterEntries p =- foldrEntries- (\entry rest -> if p entry- then Next entry rest- else rest)- Done Fail--checkEntries :: (Entry -> Maybe String) -> Entries -> Entries-checkEntries checkEntry =- foldrEntries- (\entry rest -> case checkEntry entry of- Nothing -> Next entry rest- Just err -> Fail err)- Done Fail--entriesIndex :: Entries -> Either String (Map.Map TarPath Entry)-entriesIndex = foldlEntries (\m e -> Map.insert (entryTarPath e) e m) Map.empty------- * Checking------- | This function checks a sequence of tar entries for file name security--- problems. It checks that:------ * file paths are not absolute------ * file paths do not contain any path components that are \"@..@\"------ * file names are valid------ These checks are from the perspective of the current OS. That means we check--- for \"@C:\blah@\" files on Windows and \"\/blah\" files on Unix. For archive--- entry types 'HardLink' and 'SymbolicLink' the same checks are done for the--- link target. A failure in any entry terminates the sequence of entries with--- an error.----checkSecurity :: Entries -> Entries-checkSecurity = checkEntries checkEntrySecurity--checkTarbomb :: FilePath -> Entries -> Entries-checkTarbomb expectedTopDir = checkEntries (checkEntryTarbomb expectedTopDir)--checkEntrySecurity :: Entry -> Maybe String-checkEntrySecurity entry = case entryContent entry of- HardLink link -> check (entryPath entry)- `mplus` check (fromLinkTarget link)- SymbolicLink link -> check (entryPath entry)- `mplus` check (fromLinkTarget link)- _ -> check (entryPath entry)-- where- check name- | not (FilePath.Native.isRelative name)- = Just $ "Absolute file name in tar archive: " ++ show name-- | not (FilePath.Native.isValid name)- = Just $ "Invalid file name in tar archive: " ++ show name-- | ".." `elem` FilePath.Native.splitDirectories name- = Just $ "Invalid file name in tar archive: " ++ show name-- | otherwise = Nothing--checkEntryTarbomb :: FilePath -> Entry -> Maybe String-checkEntryTarbomb _ entry | nonFilesystemEntry = Nothing- where- -- Ignore some special entries we will not unpack anyway- nonFilesystemEntry =- case entryContent entry of- OtherEntryType 'g' _ _ -> True --PAX global header- OtherEntryType 'x' _ _ -> True --PAX individual header- _ -> False--checkEntryTarbomb expectedTopDir entry =- case FilePath.Native.splitDirectories (entryPath entry) of- (topDir:_) | topDir == expectedTopDir -> Nothing- s -> Just $ "File in tar archive is not in the expected directory. "- ++ "Expected: " ++ show expectedTopDir- ++ " but got the following hierarchy: "- ++ show s-------- * Reading-----read :: ByteString -> Entries-read = unfoldrEntries getEntry--getEntry :: ByteString -> Either String (Maybe (Entry, ByteString))-getEntry bs- | BS.length header < 512 = Left "truncated tar archive"-- -- Tar files end with at least two blocks of all '0'. Checking this serves- -- two purposes. It checks the format but also forces the tail of the data- -- which is necessary to close the file if it came from a lazily read file.- | BS.head bs == 0 = case BS.splitAt 1024 bs of- (end, trailing)- | BS.length end /= 1024 -> Left "short tar trailer"- | not (BS.all (== 0) end) -> Left "bad tar trailer"- | not (BS.all (== 0) trailing) -> Left "tar file has trailing junk"- | otherwise -> Right Nothing-- | otherwise = partial $ do-- case (chksum_, format_) of- (Ok chksum, _ ) | correctChecksum header chksum -> return ()- (Ok _, Ok _) -> fail "tar checksum error"- _ -> fail "data is not in tar format"-- -- These fields are partial, have to check them- format <- format_; mode <- mode_;- uid <- uid_; gid <- gid_;- size <- size_; mtime <- mtime_;- devmajor <- devmajor_; devminor <- devminor_;-- let content = BS.take size (BS.drop 512 bs)- padding = (512 - size) `mod` 512- bs' = BS.drop (512 + size + padding) bs-- entry = Entry {- entryTarPath = TarPath name prefix,- entryContent = case typecode of- '\0' -> NormalFile content size- '0' -> NormalFile content size- '1' -> HardLink (LinkTarget linkname)- '2' -> SymbolicLink (LinkTarget linkname)- '3' -> CharacterDevice devmajor devminor- '4' -> BlockDevice devmajor devminor- '5' -> Directory- '6' -> NamedPipe- '7' -> NormalFile content size- _ -> OtherEntryType typecode content size,- entryPermissions = mode,- entryOwnership = Ownership uname gname uid gid,- entryTime = mtime,- entryFormat = format- }-- return (Just (entry, bs'))-- where- header = BS.take 512 bs-- name = getString 0 100 header- mode_ = getOct 100 8 header- uid_ = getOct 108 8 header- gid_ = getOct 116 8 header- size_ = getOct 124 12 header- mtime_ = getOct 136 12 header- chksum_ = getOct 148 8 header- typecode = getByte 156 header- linkname = getString 157 100 header- magic = getChars 257 8 header- uname = getString 265 32 header- gname = getString 297 32 header- devmajor_ = getOct 329 8 header- devminor_ = getOct 337 8 header- prefix = getString 345 155 header--- trailing = getBytes 500 12 header-- format_ = case magic of- "\0\0\0\0\0\0\0\0" -> return V7Format- "ustar\NUL00" -> return UstarFormat- "ustar \NUL" -> return GnuFormat- _ -> fail "tar entry not in a recognised format"--correctChecksum :: ByteString -> Int -> Bool-correctChecksum header checksum = checksum == checksum'- where- -- sum of all 512 bytes in the header block,- -- treating each byte as an 8-bit unsigned value- checksum' = BS.Char8.foldl' (\x y -> x + ord y) 0 header'- -- treating the 8 bytes of chksum as blank characters.- header' = BS.concat [BS.take 148 header,- BS.Char8.replicate 8 ' ',- BS.drop 156 header]---- * TAR format primitive input--getOct :: (Integral a, Bits a) => Int64 -> Int64 -> ByteString -> Partial a-getOct off len header- | BS.head bytes == 128 = parseBinInt (BS.unpack (BS.tail bytes))- | null octstr = return 0- | otherwise = case readOct octstr of- [(x,[])] -> return x- _ -> fail "tar header is malformed (bad numeric encoding)"- where- bytes = getBytes off len header- octstr = BS.Char8.unpack- . BS.Char8.takeWhile (\c -> c /= '\NUL' && c /= ' ')- . BS.Char8.dropWhile (== ' ')- $ bytes-- -- Some tar programs switch into a binary format when they try to represent- -- field values that will not fit in the required width when using the text- -- octal format. In particular, the UID/GID fields can only hold up to 2^21- -- while in the binary format can hold up to 2^32. The binary format uses- -- '\128' as the header which leaves 7 bytes. Only the last 4 are used.- parseBinInt [0, 0, 0, byte3, byte2, byte1, byte0] =- return $! shiftL (fromIntegral byte3) 24- + shiftL (fromIntegral byte2) 16- + shiftL (fromIntegral byte1) 8- + shiftL (fromIntegral byte0) 0- parseBinInt _ = fail "tar header uses non-standard number encoding"--getBytes :: Int64 -> Int64 -> ByteString -> ByteString-getBytes off len = BS.take len . BS.drop off--getByte :: Int64 -> ByteString -> Char-getByte off bs = BS.Char8.index bs off--getChars :: Int64 -> Int64 -> ByteString -> String-getChars off len = BS.Char8.unpack . getBytes off len--getString :: Int64 -> Int64 -> ByteString -> String-getString off len = BS.Char8.unpack . BS.Char8.takeWhile (/='\0')- . getBytes off len--data Partial a = Error String | Ok a- deriving Functor--partial :: Partial a -> Either String a-partial (Error msg) = Left msg-partial (Ok x) = Right x--instance Applicative Partial where- pure = return- (<*>) = ap--instance Monad Partial where- return = Ok- Error m >>= _ = Error m- Ok x >>= k = k x- fail = Error------- * Writing------- | Create the external representation of a tar archive by serialising a list--- of tar entries.------ * The conversion is done lazily.----write :: [Entry] -> ByteString-write es = BS.concat $ map putEntry es ++ [BS.replicate (512*2) 0]---- | Same as 'write', but for 'Entries'.-writeEntries :: Entries -> ByteString-writeEntries entries = BS.concat $ foldrEntries (\e res -> putEntry e : res)- [BS.replicate (512*2) 0] error entries--putEntry :: Entry -> ByteString-putEntry entry = case entryContent entry of- NormalFile content size -> BS.concat [ header, content, padding size ]- OtherEntryType _ content size -> BS.concat [ header, content, padding size ]- _ -> header- where- header = putHeader entry- padding size = BS.replicate paddingSize 0- where paddingSize = fromIntegral (negate size `mod` 512)--putHeader :: Entry -> ByteString-putHeader entry =- BS.concat [ BS.take 148 block- , BS.Char8.pack $ putOct 7 checksum- , BS.Char8.singleton ' '- , BS.drop 156 block ]- where- -- putHeaderNoChkSum returns a String, so we convert it to the final- -- representation before calculating the checksum.- block = BS.Char8.pack . putHeaderNoChkSum $ entry- checksum = BS.Char8.foldl' (\x y -> x + ord y) 0 block--putHeaderNoChkSum :: Entry -> String-putHeaderNoChkSum Entry {- entryTarPath = TarPath name prefix,- entryContent = content,- entryPermissions = permissions,- entryOwnership = ownership,- entryTime = modTime,- entryFormat = format- } =-- concat- [ putString 100 $ name- , putOct 8 $ permissions- , putOct 8 $ ownerId ownership- , putOct 8 $ groupId ownership- , putOct 12 $ contentSize- , putOct 12 $ modTime- , fill 8 $ ' ' -- dummy checksum- , putChar8 $ typeCode- , putString 100 $ linkTarget- ] ++- case format of- V7Format ->- fill 255 '\NUL'- UstarFormat -> concat- [ putString 8 $ "ustar\NUL00"- , putString 32 $ ownerName ownership- , putString 32 $ groupName ownership- , putOct 8 $ deviceMajor- , putOct 8 $ deviceMinor- , putString 155 $ prefix- , fill 12 $ '\NUL'- ]- GnuFormat -> concat- [ putString 8 $ "ustar \NUL"- , putString 32 $ ownerName ownership- , putString 32 $ groupName ownership- , putGnuDev 8 $ deviceMajor- , putGnuDev 8 $ deviceMinor- , putString 155 $ prefix- , fill 12 $ '\NUL'- ]- where- (typeCode, contentSize, linkTarget,- deviceMajor, deviceMinor) = case content of- NormalFile _ size -> ('0' , size, [], 0, 0)- Directory -> ('5' , 0, [], 0, 0)- SymbolicLink (LinkTarget link) -> ('2' , 0, link, 0, 0)- HardLink (LinkTarget link) -> ('1' , 0, link, 0, 0)- CharacterDevice major minor -> ('3' , 0, [], major, minor)- BlockDevice major minor -> ('4' , 0, [], major, minor)- NamedPipe -> ('6' , 0, [], 0, 0)- OtherEntryType code _ size -> (code, size, [], 0, 0)-- putGnuDev w n = case content of- CharacterDevice _ _ -> putOct w n- BlockDevice _ _ -> putOct w n- _ -> replicate w '\NUL'---- * TAR format primitive output--type FieldWidth = Int--putString :: FieldWidth -> String -> String-putString n s = take n s ++ fill (n - length s) '\NUL'----TODO: check integer widths, eg for large file sizes-putOct :: (Show a, Integral a) => FieldWidth -> a -> String-putOct n x =- let octStr = take (n-1) $ showOct x ""- in fill (n - length octStr - 1) '0'- ++ octStr- ++ putChar8 '\NUL'--putChar8 :: Char -> String-putChar8 c = [c]--fill :: FieldWidth -> Char -> String-fill n c = replicate n c------- * Unpacking-----unpack :: FilePath -> Entries -> IO ()-unpack baseDir entries = unpackEntries [] (checkSecurity entries)- >>= emulateLinks-- where- -- We're relying here on 'checkSecurity' to make sure we're not scribbling- -- files all over the place.-- unpackEntries _ (Fail err) = fail err- unpackEntries links Done = return links- unpackEntries links (Next entry es) = case entryContent entry of- NormalFile file _ -> extractFile entry path file- >> unpackEntries links es- Directory -> extractDir path- >> unpackEntries links es- HardLink link -> (unpackEntries $! saveLink path link links) es- SymbolicLink link -> (unpackEntries $! saveLink path link links) es- _ -> unpackEntries links es --ignore other file types- where- path = entryPath entry-- extractFile entry path content = do- -- Note that tar archives do not make sure each directory is created- -- before files they contain, indeed we may have to create several- -- levels of directory.- createDirectoryIfMissing True absDir- BS.writeFile absPath content- when (isExecutable (entryPermissions entry))- (setFileExecutable absPath)- where- absDir = baseDir </> FilePath.Native.takeDirectory path- absPath = baseDir </> path-- extractDir path = createDirectoryIfMissing True (baseDir </> path)-- saveLink path link links = seq (length path)- $ seq (length link')- $ (path, link'):links- where link' = fromLinkTarget link-- emulateLinks = mapM_ $ \(relPath, relLinkTarget) ->- let absPath = baseDir </> relPath- absTarget = FilePath.Native.takeDirectory absPath </> relLinkTarget- in copyFile absTarget absPath------- * Packing-----pack :: FilePath -- ^ Base directory- -> [FilePath] -- ^ Files and directories to pack, relative to the base dir- -> IO [Entry]-pack baseDir paths0 = preparePaths baseDir paths0 >>= packPaths baseDir--preparePaths :: FilePath -> [FilePath] -> IO [FilePath]-preparePaths baseDir paths =- fmap concat $ interleave- [ do isDir <- doesDirectoryExist (baseDir </> path)- if isDir- then do entries <- getDirectoryContentsRecursive (baseDir </> path)- return (FilePath.Native.addTrailingPathSeparator path- : map (path </>) entries)- else return [path]- | path <- paths ]--packPaths :: FilePath -> [FilePath] -> IO [Entry]-packPaths baseDir paths =- interleave- [ do tarpath <- either fail return (toTarPath isDir relpath)- if isDir then packDirectoryEntry filepath tarpath- else packFileEntry filepath tarpath- | relpath <- paths- , let isDir = FilePath.Native.hasTrailingPathSeparator filepath- filepath = baseDir </> relpath ]--interleave :: [IO a] -> IO [a]-interleave = unsafeInterleaveIO . go- where- go [] = return []- go (x:xs) = do- x' <- x- xs' <- interleave xs- return (x':xs')--packFileEntry :: FilePath -- ^ Full path to find the file on the local disk- -> TarPath -- ^ Path to use for the tar Entry in the archive- -> IO Entry-packFileEntry filepath tarpath = do- mtime <- getModTime filepath- perms <- getPermissions filepath- file <- openBinaryFile filepath ReadMode- size <- hFileSize file- content <- BS.hGetContents file- return (simpleEntry tarpath (NormalFile content (fromIntegral size))) {- entryPermissions = if Permissions.executable perms- then executableFilePermissions- else ordinaryFilePermissions,- entryTime = mtime- }--packDirectoryEntry :: FilePath -- ^ Full path to find the file on the local disk- -> TarPath -- ^ Path to use for the tar Entry in the archive- -> IO Entry-packDirectoryEntry filepath tarpath = do- mtime <- getModTime filepath- return (directoryEntry tarpath) {- entryTime = mtime- }--getDirectoryContentsRecursive :: FilePath -> IO [FilePath]-getDirectoryContentsRecursive dir0 =- fmap tail (recurseDirectories dir0 [""])--recurseDirectories :: FilePath -> [FilePath] -> IO [FilePath]-recurseDirectories _ [] = return []-recurseDirectories base (dir:dirs) = unsafeInterleaveIO $ do- (files, dirs') <- collect [] [] =<< getDirectoryContents (base </> dir)+ Tar.foldEntries+ (\e es -> if p e then Tar.Next e es else es)+ Tar.Done+ Tar.Fail - files' <- recurseDirectories base (dirs' ++ dirs)- return (dir : files ++ files')+filterEntriesM :: Monad m => (Tar.Entry -> m Bool)+ -> Tar.Entries e -> m (Tar.Entries e)+filterEntriesM p =+ Tar.foldEntries+ (\entry rest -> do+ keep <- p entry+ xs <- rest+ if keep+ then return (Tar.Next entry xs)+ else return xs)+ (return Tar.Done)+ (return . Tar.Fail) - where- collect files dirs' [] = return (reverse files, reverse dirs')- collect files dirs' (entry:entries) | ignore entry- = collect files dirs' entries- collect files dirs' (entry:entries) = do- let dirEntry = dir </> entry- dirEntry' = FilePath.Native.addTrailingPathSeparator dirEntry- isDirectory <- doesDirectoryExist (base </> dirEntry)- if isDirectory- then collect files (dirEntry':dirs') entries- else collect (dirEntry:files) dirs' entries+entriesToList :: Exception e => Tar.Entries e -> [Tar.Entry]+entriesToList = Tar.foldEntries (:) [] throw - ignore ['.'] = True- ignore ['.', '.'] = True- ignore _ = False
cabal/cabal-install/Distribution/Client/Targets.hs view
@@ -1,4 +1,6 @@ {-# LANGUAGE CPP #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DeriveGeneric #-} ----------------------------------------------------------------------------- -- | -- Module : Distribution.Client.Targets@@ -42,7 +44,9 @@ UserConstraint(..), userConstraintPackageName, readUserConstraint,- userToPackageConstraint+ userToPackageConstraint,+ dispFlagAssignment,+ parseFlagAssignment, ) where @@ -51,7 +55,8 @@ , PackageIdentifier(..), packageName, packageVersion , Dependency(Dependency) ) import Distribution.Client.Types- ( SourcePackage(..), PackageLocation(..), OptionalStanza(..) )+ ( SourcePackage(..), PackageLocation(..), OptionalStanza(..)+ , ResolvedPkgLoc, UnresolvedSourcePackage ) import Distribution.Client.Dependency.Types ( PackageConstraint(..), ConstraintSource(..) , LabeledPackageConstraint(..) )@@ -59,10 +64,13 @@ import qualified Distribution.Client.World as World import Distribution.Client.PackageIndex (PackageIndex) import qualified Distribution.Client.PackageIndex as PackageIndex+import qualified Codec.Archive.Tar as Tar+import qualified Codec.Archive.Tar.Entry as Tar import qualified Distribution.Client.Tar as Tar import Distribution.Client.FetchUtils-import Distribution.Client.HttpUtils ( HttpTransport(..) ) import Distribution.Client.Utils ( tryFindPackageDesc )+import Distribution.Client.GlobalFlags+ ( RepoContext(..) ) import Distribution.PackageDescription ( GenericPackageDescription, FlagName(..), FlagAssignment )@@ -95,6 +103,8 @@ import qualified Distribution.Compat.ReadP as Parse import Distribution.Compat.ReadP ( (+++), (<++) )+import qualified Distribution.Compat.Semigroup as Semi+ ( Semigroup((<>)) ) import qualified Text.PrettyPrint as Disp import Text.PrettyPrint ( (<>), (<+>) )@@ -106,6 +116,8 @@ ( doesFileExist, doesDirectoryExist ) import Network.URI ( URI(..), URIAuth(..), parseAbsoluteURI )+import GHC.Generics (Generic)+import Distribution.Compat.Binary (Binary) -- ------------------------------------------------------------ -- * User targets@@ -182,8 +194,10 @@ -- | A fully specified source package. -- | SpecificSourcePackage pkg- deriving Show+ deriving (Eq, Show, Generic) +instance Binary pkg => Binary (PackageSpecifier pkg)+ pkgSpecifierTarget :: Package pkg => PackageSpecifier pkg -> PackageName pkgSpecifierTarget (NamedPackage name _) = name pkgSpecifierTarget (SpecificSourcePackage pkg) = packageName pkg@@ -357,17 +371,17 @@ -- resolveUserTargets :: Package pkg => Verbosity- -> HttpTransport+ -> RepoContext -> FilePath -> PackageIndex pkg -> [UserTarget]- -> IO [PackageSpecifier SourcePackage]-resolveUserTargets verbosity transport worldFile available userTargets = do+ -> IO [PackageSpecifier UnresolvedSourcePackage]+resolveUserTargets verbosity repoCtxt worldFile available userTargets = do -- given the user targets, get a list of fully or partially resolved -- package references packageTargets <- mapM (readPackageTarget verbosity)- =<< mapM (fetchPackageTarget transport verbosity) . concat+ =<< mapM (fetchPackageTarget verbosity repoCtxt) . concat =<< mapM (expandUserTarget worldFile) userTargets -- users are allowed to give package names case-insensitively, so we must@@ -453,15 +467,15 @@ -- | Fetch any remote targets so that they can be read. ---fetchPackageTarget :: HttpTransport- -> Verbosity+fetchPackageTarget :: Verbosity+ -> RepoContext -> PackageTarget (PackageLocation ())- -> IO (PackageTarget (PackageLocation FilePath))-fetchPackageTarget transport verbosity target = case target of+ -> IO (PackageTarget ResolvedPkgLoc)+fetchPackageTarget verbosity repoCtxt target = case target of PackageTargetNamed n cs ut -> return (PackageTargetNamed n cs ut) PackageTargetNamedFuzzy n cs ut -> return (PackageTargetNamedFuzzy n cs ut) PackageTargetLocation location -> do- location' <- fetchPackage transport verbosity (fmap (const Nothing) location)+ location' <- fetchPackage verbosity repoCtxt (fmap (const Nothing) location) return (PackageTargetLocation location') @@ -470,8 +484,8 @@ -- This only affects targets given by location, named targets are unaffected. -- readPackageTarget :: Verbosity- -> PackageTarget (PackageLocation FilePath)- -> IO (PackageTarget SourcePackage)+ -> PackageTarget ResolvedPkgLoc+ -> IO (PackageTarget UnresolvedSourcePackage) readPackageTarget verbosity target = case target of PackageTargetNamed pkgname constraints userTarget ->@@ -524,7 +538,7 @@ extractTarballPackageCabalFile tarballFile tarballOriginalLoc = either (die . formatErr) return . check- . Tar.entriesIndex+ . accumEntryMap . Tar.filterEntries isCabalFile . Tar.read . GZipUtils.maybeDecompress@@ -532,7 +546,11 @@ where formatErr msg = "Error reading " ++ tarballOriginalLoc ++ ": " ++ msg - check (Left e) = Left e+ accumEntryMap = Tar.foldlEntries+ (\m e -> Map.insert (Tar.entryTarPath e) e m)+ Map.empty++ check (Left e) = Left (show e) check (Right m) = case Map.elems m of [] -> Left noCabalFile [file] -> case Tar.entryContent file of@@ -662,7 +680,10 @@ instance Monoid PackageNameEnv where mempty = PackageNameEnv (const [])- mappend (PackageNameEnv lookupA) (PackageNameEnv lookupB) =+ mappend = (Semi.<>)++instance Semi.Semigroup PackageNameEnv where+ PackageNameEnv lookupA <> PackageNameEnv lookupB = PackageNameEnv (\name -> lookupA name ++ lookupB name) indexPackageNameEnv :: PackageIndex pkg -> PackageNameEnv@@ -691,8 +712,10 @@ | UserConstraintSource PackageName | UserConstraintFlags PackageName FlagAssignment | UserConstraintStanzas PackageName [OptionalStanza]- deriving (Show,Eq)+ deriving (Eq, Show, Generic) +instance Binary UserConstraint+ userConstraintPackageName :: UserConstraint -> PackageName userConstraintPackageName uc = case uc of UserConstraintVersion name _ -> name@@ -728,7 +751,6 @@ "expected a package name followed by a constraint, which is " ++ "either a version range, 'installed', 'source' or flags" ---FIXME: use Text instance for FlagName and FlagAssignment instance Text UserConstraint where disp (UserConstraintVersion pkgname verrange) = disp pkgname <+> disp verrange@@ -738,12 +760,6 @@ <+> Disp.text "source" disp (UserConstraintFlags pkgname flags) = disp pkgname <+> dispFlagAssignment flags- where- dispFlagAssignment = Disp.hsep . map dispFlagValue- dispFlagValue (f, True) = Disp.char '+' <> dispFlagName f- dispFlagValue (f, False) = Disp.char '-' <> dispFlagName f- dispFlagName (FlagName f) = Disp.text f- disp (UserConstraintStanzas pkgname stanzas) = disp pkgname <+> dispStanzas stanzas where@@ -753,37 +769,51 @@ parse = parse >>= parseConstraint where- spaces = Parse.satisfy isSpace >> Parse.skipSpaces- parseConstraint pkgname = ((parse >>= return . UserConstraintVersion pkgname)- +++ (do spaces+ +++ (do skipSpaces1 _ <- Parse.string "installed" return (UserConstraintInstalled pkgname))- +++ (do spaces+ +++ (do skipSpaces1 _ <- Parse.string "source" return (UserConstraintSource pkgname))- +++ (do spaces+ +++ (do skipSpaces1 _ <- Parse.string "test" return (UserConstraintStanzas pkgname [TestStanzas]))- +++ (do spaces+ +++ (do skipSpaces1 _ <- Parse.string "bench" return (UserConstraintStanzas pkgname [BenchStanzas])))- <++ (parseFlagAssignment >>= (return . UserConstraintFlags pkgname))+ <++ (do skipSpaces1+ flags <- parseFlagAssignment+ return (UserConstraintFlags pkgname flags)) - parseFlagAssignment = Parse.many1 (spaces >> parseFlagValue)- parseFlagValue =- (do Parse.optional (Parse.char '+')- f <- parseFlagName- return (f, True))- +++ (do _ <- Parse.char '-'- f <- parseFlagName- return (f, False))- parseFlagName = liftM FlagName ident+--TODO: [code cleanup] move these somewhere else+dispFlagAssignment :: FlagAssignment -> Disp.Doc+dispFlagAssignment = Disp.hsep . map dispFlagValue+ where+ dispFlagValue (f, True) = Disp.char '+' <> dispFlagName f+ dispFlagValue (f, False) = Disp.char '-' <> dispFlagName f+ dispFlagName (FlagName f) = Disp.text f - ident :: Parse.ReadP r String- ident = Parse.munch1 identChar >>= \s -> check s >> return s- where- identChar c = isAlphaNum c || c == '_' || c == '-'- check ('-':_) = Parse.pfail- check _ = return ()+parseFlagAssignment :: Parse.ReadP r FlagAssignment+parseFlagAssignment = Parse.sepBy1 parseFlagValue skipSpaces1+ where+ parseFlagValue =+ (do Parse.optional (Parse.char '+')+ f <- parseFlagName+ return (f, True))+ +++ (do _ <- Parse.char '-'+ f <- parseFlagName+ return (f, False))+ parseFlagName = liftM (FlagName . lowercase) ident++ ident :: Parse.ReadP r String+ ident = Parse.munch1 identChar >>= \s -> check s >> return s+ where+ identChar c = isAlphaNum c || c == '_' || c == '-'+ check ('-':_) = Parse.pfail+ check _ = return ()++skipSpaces1 :: Parse.ReadP r ()+skipSpaces1 = Parse.satisfy isSpace >> Parse.skipSpaces+
cabal/cabal-install/Distribution/Client/Types.hs view
@@ -1,4 +1,9 @@ {-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# OPTIONS_GHC -fno-warn-orphans #-} ----------------------------------------------------------------------------- -- | -- Module : Distribution.Client.Types@@ -16,9 +21,8 @@ import Distribution.Package ( PackageName, PackageId, Package(..)- , mkPackageKey, PackageKey, InstalledPackageId(..)- , HasInstalledPackageId(..), PackageInstalled(..)- , LibraryName, packageKeyLibraryName )+ , UnitId(..), mkUnitId, pkgName+ , HasUnitId(..), PackageInstalled(..) ) import Distribution.InstalledPackageInfo ( InstalledPackageInfo ) import Distribution.PackageDescription@@ -33,31 +37,50 @@ import qualified Distribution.Client.ComponentDeps as CD import Distribution.Version ( VersionRange )-import Distribution.Simple.Compiler- ( Compiler, packageKeySupported ) import Distribution.Text (display)-import qualified Distribution.InstalledPackageInfo as Info import Data.Map (Map)-import Network.URI (URI, nullURI)+import Network.URI (URI(..), URIAuth(..), nullURI) import Data.ByteString.Lazy (ByteString) import Control.Exception ( SomeException )+import GHC.Generics (Generic)+import Distribution.Compat.Binary (Binary(..)) + newtype Username = Username { unUsername :: String } newtype Password = Password { unPassword :: String } -- | This is the information we get from a @00-index.tar.gz@ hackage index. -- data SourcePackageDb = SourcePackageDb {- packageIndex :: PackageIndex SourcePackage,+ packageIndex :: PackageIndex UnresolvedSourcePackage, packagePreferences :: Map PackageName VersionRange }+ deriving (Eq, Generic) +instance Binary SourcePackageDb+ -- ------------------------------------------------------------ -- * Various kinds of information about packages -- ------------------------------------------------------------ +-- | Within Cabal the library we no longer have a @InstalledPackageId@ type.+-- That's because it deals with the compilers' notion of a registered library,+-- and those really are libraries not packages. Those are now named units.+--+-- The package management layer does however deal with installed packages, as+-- whole packages not just as libraries. So we do still need a type for+-- installed package ids. At the moment however we track instaled packages via+-- their primary library, which is a unit id. In future this may change+-- slightly and we may distinguish these two types and have an explicit+-- conversion when we register units with the compiler.+--+type InstalledPackageId = UnitId++installedPackageId :: HasUnitId pkg => pkg -> InstalledPackageId+installedPackageId = installedUnitId+ -- | Subclass of packages that have specific versioned dependencies. -- -- So for example a not-yet-configured package has dependencies on version@@ -66,44 +89,47 @@ -- dependency graphs) only make sense on this subclass of package types. -- class Package pkg => PackageFixedDeps pkg where- depends :: pkg -> ComponentDeps [InstalledPackageId]+ depends :: pkg -> ComponentDeps [UnitId] instance PackageFixedDeps InstalledPackageInfo where- depends = CD.fromInstalled . installedDepends+ depends pkg = CD.fromInstalled (display (pkgName (packageId pkg)))+ (installedDepends pkg) -- | In order to reuse the implementation of PackageIndex which relies on--- 'InstalledPackageId', we need to be able to synthesize these IDs prior+-- 'UnitId', we need to be able to synthesize these IDs prior -- to installation. Eventually, we'll move to a representation of--- 'InstalledPackageId' which can be properly computed before compilation+-- 'UnitId' which can be properly computed before compilation -- (of course, it's a bit of a misnomer since the packages are not actually -- installed yet.) In any case, we'll synthesize temporary installed package -- IDs to use as keys during install planning. These should never be written -- out! Additionally, they need to be guaranteed unique within the install -- plan.-fakeInstalledPackageId :: PackageId -> InstalledPackageId-fakeInstalledPackageId = InstalledPackageId . (".fake."++) . display+fakeUnitId :: PackageId -> UnitId+fakeUnitId = mkUnitId . (".fake."++) . display -- | A 'ConfiguredPackage' is a not-yet-installed package along with the -- total configuration information. The configuration information is total in -- the sense that it provides all the configuration information and so the -- final configure process will be independent of the environment. ---data ConfiguredPackage = ConfiguredPackage- SourcePackage -- package info, including repo- FlagAssignment -- complete flag assignment for the package- [OptionalStanza] -- list of enabled optional stanzas for the package- (ComponentDeps [ConfiguredId])- -- set of exact dependencies (installed or source).- -- These must be consistent with the 'buildDepends'- -- in the 'PackageDescription' that you'd get by- -- applying the flag assignment and optional stanzas.- deriving Show+data ConfiguredPackage loc = ConfiguredPackage {+ confPkgSource :: SourcePackage loc, -- package info, including repo+ confPkgFlags :: FlagAssignment, -- complete flag assignment for the package+ confPkgStanzas :: [OptionalStanza], -- list of enabled optional stanzas for the package+ confPkgDeps :: ComponentDeps [ConfiguredId]+ -- set of exact dependencies (installed or source).+ -- These must be consistent with the 'buildDepends'+ -- in the 'PackageDescription' that you'd get by+ -- applying the flag assignment and optional stanzas.+ }+ deriving (Eq, Show, Generic) +instance (Binary loc) => Binary (ConfiguredPackage loc)+ -- | A ConfiguredId is a package ID for a configured package. ----- Once we configure a source package we know it's InstalledPackageId--- (at least, in principle, even if we have to fake it currently). It is still+-- Once we configure a source package we know it's UnitId. It is still -- however useful in lots of places to also know the source ID for the package. -- We therefore bundle the two. --@@ -111,81 +137,69 @@ -- configuration parameters and dependencies have been specified). -- -- TODO: I wonder if it would make sense to promote this datatype to Cabal--- and use it consistently instead of InstalledPackageIds?+-- and use it consistently instead of UnitIds? data ConfiguredId = ConfiguredId { confSrcId :: PackageId- , confInstId :: InstalledPackageId+ , confInstId :: UnitId }+ deriving (Eq, Generic) +instance Binary ConfiguredId+ instance Show ConfiguredId where show = show . confSrcId -instance Package ConfiguredPackage where- packageId (ConfiguredPackage pkg _ _ _) = packageId pkg--instance PackageFixedDeps ConfiguredPackage where- depends (ConfiguredPackage _ _ _ deps) = fmap (map confInstId) deps--instance HasInstalledPackageId ConfiguredPackage where- installedPackageId = fakeInstalledPackageId . packageId---- | Like 'ConfiguredPackage', but with all dependencies guaranteed to be--- installed already, hence itself ready to be installed.-data GenericReadyPackage srcpkg ipkg- = ReadyPackage- srcpkg -- see 'ConfiguredPackage'.- (ComponentDeps [ipkg]) -- Installed dependencies.- deriving (Eq, Show)--type ReadyPackage = GenericReadyPackage ConfiguredPackage InstalledPackageInfo+instance Package ConfiguredId where+ packageId = confSrcId -instance Package srcpkg => Package (GenericReadyPackage srcpkg ipkg) where- packageId (ReadyPackage srcpkg _deps) = packageId srcpkg+instance HasUnitId ConfiguredId where+ installedUnitId = confInstId -instance (Package srcpkg, HasInstalledPackageId ipkg) =>- PackageFixedDeps (GenericReadyPackage srcpkg ipkg) where- depends (ReadyPackage _ deps) = fmap (map installedPackageId) deps+instance Package (ConfiguredPackage loc) where+ packageId cpkg = packageId (confPkgSource cpkg) -instance HasInstalledPackageId srcpkg =>- HasInstalledPackageId (GenericReadyPackage srcpkg ipkg) where- installedPackageId (ReadyPackage pkg _) = installedPackageId pkg+instance PackageFixedDeps (ConfiguredPackage loc) where+ depends cpkg = fmap (map installedUnitId) (confPkgDeps cpkg) +instance HasUnitId (ConfiguredPackage loc) where+ installedUnitId cpkg = fakeUnitId (packageId cpkg) --- | Extracts a package key from ReadyPackage, a common operation needed--- to calculate build paths.-readyPackageKey :: Compiler -> ReadyPackage -> PackageKey-readyPackageKey comp (ReadyPackage pkg deps) =- mkPackageKey (packageKeySupported comp) (packageId pkg)- (map Info.libraryName (CD.nonSetupDeps deps))+-- | Like 'ConfiguredPackage', but with all dependencies guaranteed to be+-- installed already, hence itself ready to be installed.+newtype GenericReadyPackage srcpkg = ReadyPackage srcpkg -- see 'ConfiguredPackage'.+ deriving (Eq, Show, Generic, Package, PackageFixedDeps, HasUnitId, Binary) --- | Extracts a library name from ReadyPackage, a common operation needed--- to calculate build paths.-readyLibraryName :: Compiler -> ReadyPackage -> LibraryName-readyLibraryName comp ready@(ReadyPackage pkg _) =- packageKeyLibraryName (packageId pkg) (readyPackageKey comp ready)+type ReadyPackage = GenericReadyPackage (ConfiguredPackage UnresolvedPkgLoc) -- | A package description along with the location of the package sources. ---data SourcePackage = SourcePackage {+data SourcePackage loc = SourcePackage { packageInfoId :: PackageId, packageDescription :: GenericPackageDescription,- packageSource :: PackageLocation (Maybe FilePath),+ packageSource :: loc, packageDescrOverride :: PackageDescriptionOverride }- deriving Show+ deriving (Eq, Show, Generic) +instance (Binary loc) => Binary (SourcePackage loc)++-- | Convenience alias for 'SourcePackage UnresolvedPkgLoc'.+type UnresolvedSourcePackage = SourcePackage UnresolvedPkgLoc+ -- | We sometimes need to override the .cabal file in the tarball with -- the newer one from the package index. type PackageDescriptionOverride = Maybe ByteString -instance Package SourcePackage where packageId = packageInfoId+instance Package (SourcePackage a) where packageId = packageInfoId data OptionalStanza = TestStanzas | BenchStanzas- deriving (Eq, Ord, Show)+ deriving (Eq, Ord, Enum, Bounded, Show, Generic) +instance Binary OptionalStanza+ enableStanzas :: [OptionalStanza] -> GenericPackageDescription@@ -204,6 +218,10 @@ -- * Package locations and repositories -- ------------------------------------------------------------ +type UnresolvedPkgLoc = PackageLocation (Maybe FilePath)++type ResolvedPkgLoc = PackageLocation FilePath+ data PackageLocation local = -- | An unpacked package in the given dir, or current dir@@ -224,17 +242,40 @@ --TODO: -- * add support for darcs and other SCM style remote repos with a local cache -- | ScmPackage- deriving (Show, Functor)+ deriving (Show, Functor, Eq, Ord, Generic) -data LocalRepo = LocalRepo- deriving (Show,Eq)+instance Binary local => Binary (PackageLocation local) +-- note, network-uri-2.6.0.3+ provide a Generic instance but earlier+-- versions do not, so we use manual Binary instances here+instance Binary URI where+ put (URI a b c d e) = do put a; put b; put c; put d; put e+ get = do !a <- get; !b <- get; !c <- get; !d <- get; !e <- get+ return (URI a b c d e)++instance Binary URIAuth where+ put (URIAuth a b c) = do put a; put b; put c+ get = do !a <- get; !b <- get; !c <- get; return (URIAuth a b c)+ data RemoteRepo = RemoteRepo { remoteRepoName :: String, remoteRepoURI :: URI,- remoteRepoRootKeys :: (), + -- | Enable secure access?+ --+ -- 'Nothing' here represents "whatever the default is"; this is important+ -- to allow for a smooth transition from opt-in to opt-out security+ -- (once we switch to opt-out, all access to the central Hackage+ -- repository should be secure by default)+ remoteRepoSecure :: Maybe Bool,++ -- | Root key IDs (for bootstrapping)+ remoteRepoRootKeys :: [String],++ -- | Threshold for verification during bootstrapping+ remoteRepoKeyThreshold :: Int,+ -- | Normally a repo just specifies an HTTP or HTTPS URI, but as a -- special case we may know a repo supports both and want to try HTTPS -- if we can, but still allow falling back to HTTP.@@ -244,20 +285,51 @@ remoteRepoShouldTryHttps :: Bool } - -- FIXME: discuss this type some more.+ deriving (Show, Eq, Ord, Generic) - deriving (Show,Eq,Ord)+instance Binary RemoteRepo -- | Construct a partial 'RemoteRepo' value to fold the field parser list over. emptyRemoteRepo :: String -> RemoteRepo-emptyRemoteRepo name = RemoteRepo name nullURI () False+emptyRemoteRepo name = RemoteRepo name nullURI Nothing [] 0 False -data Repo = Repo {- repoKind :: Either RemoteRepo LocalRepo,- repoLocalDir :: FilePath- }- deriving (Show,Eq)+-- | Different kinds of repositories+--+-- NOTE: It is important that this type remains serializable.+data Repo =+ -- | Local repositories+ RepoLocal {+ repoLocalDir :: FilePath+ } + -- | Standard (unsecured) remote repositores+ | RepoRemote {+ repoRemote :: RemoteRepo+ , repoLocalDir :: FilePath+ }++ -- | Secure repositories+ --+ -- Although this contains the same fields as 'RepoRemote', we use a separate+ -- constructor to avoid confusing the two.+ --+ -- Not all access to a secure repo goes through the hackage-security+ -- library currently; code paths that do not still make use of the+ -- 'repoRemote' and 'repoLocalDir' fields directly.+ | RepoSecure {+ repoRemote :: RemoteRepo+ , repoLocalDir :: FilePath+ }+ deriving (Show, Eq, Ord, Generic)++instance Binary Repo++-- | Check if this is a remote repo+maybeRepoRemote :: Repo -> Maybe RemoteRepo+maybeRepoRemote (RepoLocal _localDir) = Nothing+maybeRepoRemote (RepoRemote r _localDir) = Just r+maybeRepoRemote (RepoSecure r _localDir) = Just r+ -- ------------------------------------------------------------ -- * Build results -- ------------------------------------------------------------@@ -271,8 +343,22 @@ | BuildFailed SomeException | TestsFailed SomeException | InstallFailed SomeException+ deriving (Show, Generic) data BuildSuccess = BuildOk DocsResult TestsResult (Maybe InstalledPackageInfo)+ deriving (Show, Generic) data DocsResult = DocsNotTried | DocsFailed | DocsOk+ deriving (Show, Generic) data TestsResult = TestsNotTried | TestsOk+ deriving (Show, Generic)++instance Binary BuildFailure+instance Binary BuildSuccess+instance Binary DocsResult+instance Binary TestsResult++--FIXME: this is a total cheat+instance Binary SomeException where+ put _ = return ()+ get = fail "cannot serialise exceptions"
cabal/cabal-install/Distribution/Client/Update.hs view
@@ -10,39 +10,46 @@ -- -- -----------------------------------------------------------------------------+{-# LANGUAGE RecordWildCards #-} module Distribution.Client.Update ( update ) where import Distribution.Client.Types- ( Repo(..), RemoteRepo(..), LocalRepo(..) )+ ( Repo(..), RemoteRepo(..), maybeRepoRemote ) import Distribution.Client.HttpUtils- ( DownloadResult(..), HttpTransport(..) )+ ( DownloadResult(..) ) import Distribution.Client.FetchUtils ( downloadIndex ) import Distribution.Client.IndexUtils- ( updateRepoIndexCache )+ ( updateRepoIndexCache, Index(..) ) import Distribution.Client.JobControl ( newParallelJobControl, spawnJob, collectJob )+import Distribution.Client.Setup+ ( RepoContext(..) )+import Distribution.Verbosity+ ( Verbosity ) import Distribution.Simple.Utils ( writeFileAtomic, warn, notice )-import Distribution.Verbosity- ( Verbosity ) import qualified Data.ByteString.Lazy as BS import Distribution.Client.GZipUtils (maybeDecompress) import System.FilePath (dropExtension)-import Data.Either (lefts)+import Data.Maybe (catMaybes)+import Data.Time (getCurrentTime) +import qualified Hackage.Security.Client as Sec+ -- | 'update' downloads the package list from all known servers-update :: HttpTransport -> Verbosity -> [Repo] -> IO ()-update _ verbosity [] =+update :: Verbosity -> RepoContext -> IO ()+update verbosity repoCtxt | null (repoContextRepos repoCtxt) = do warn verbosity $ "No remote package servers have been specified. Usually " ++ "you would have one specified in the config file."-update transport verbosity repos = do+update verbosity repoCtxt = do jobCtrl <- newParallelJobControl- let remoteRepos = lefts (map repoKind repos)+ let repos = repoContextRepos repoCtxt+ remoteRepos = catMaybes (map maybeRepoRemote repos) case remoteRepos of [] -> return () [remoteRepo] ->@@ -51,17 +58,31 @@ _ -> notice verbosity . unlines $ "Downloading the latest package lists from: " : map (("- " ++) . remoteRepoName) remoteRepos- mapM_ (spawnJob jobCtrl . updateRepo transport verbosity) repos+ mapM_ (spawnJob jobCtrl . updateRepo verbosity repoCtxt) repos mapM_ (\_ -> collectJob jobCtrl) repos -updateRepo :: HttpTransport -> Verbosity -> Repo -> IO ()-updateRepo transport verbosity repo = case repoKind repo of- Right LocalRepo -> return ()- Left remoteRepo -> do- downloadResult <- downloadIndex transport verbosity remoteRepo (repoLocalDir repo)- case downloadResult of- FileAlreadyInCache -> return ()- FileDownloaded indexPath -> do- writeFileAtomic (dropExtension indexPath) . maybeDecompress- =<< BS.readFile indexPath- updateRepoIndexCache verbosity repo+updateRepo :: Verbosity -> RepoContext -> Repo -> IO ()+updateRepo verbosity repoCtxt repo = do+ transport <- repoContextGetTransport repoCtxt+ case repo of+ RepoLocal{..} -> return ()+ RepoRemote{..} -> do+ downloadResult <- downloadIndex transport verbosity repoRemote repoLocalDir+ case downloadResult of+ FileAlreadyInCache -> return ()+ FileDownloaded indexPath -> do+ writeFileAtomic (dropExtension indexPath) . maybeDecompress+ =<< BS.readFile indexPath+ updateRepoIndexCache verbosity (RepoIndex repoCtxt repo)+ RepoSecure{} -> repoContextWithSecureRepo repoCtxt repo $ \repoSecure -> do+ ce <- if repoContextIgnoreExpiry repoCtxt+ then Just `fmap` getCurrentTime+ else return Nothing+ updated <- Sec.uncheckClientErrors $ Sec.checkForUpdates repoSecure ce+ -- Update cabal's internal index as well so that it's not out of sync+ -- (If all access to the cache goes through hackage-security this can go)+ case updated of+ Sec.NoUpdates ->+ return ()+ Sec.HasUpdates ->+ updateRepoIndexCache verbosity (RepoIndex repoCtxt repo)
cabal/cabal-install/Distribution/Client/Upload.hs view
@@ -1,11 +1,11 @@--- This is a quick hack for uploading packages to Hackage.--- See http://hackage.haskell.org/trac/hackage/wiki/CabalUpload--module Distribution.Client.Upload (check, upload, report) where+module Distribution.Client.Upload (check, upload, uploadDoc, report) where -import Distribution.Client.Types (Username(..), Password(..),Repo(..),RemoteRepo(..))+import Distribution.Client.Types ( Username(..), Password(..)+ , RemoteRepo(..), maybeRepoRemote ) import Distribution.Client.HttpUtils- ( isOldHackageURI, HttpTransport(..), remoteRepoTryUpgradeToHttps )+ ( HttpTransport(..), remoteRepoTryUpgradeToHttps )+import Distribution.Client.Setup+ ( RepoContext(..) ) import Distribution.Simple.Utils (notice, warn, info, die) import Distribution.Verbosity (Verbosity)@@ -16,45 +16,88 @@ import qualified Distribution.Client.BuildReports.Upload as BuildReport import Network.URI (URI(uriPath), parseURI)+import Network.HTTP (Header(..), HeaderName(..)) import System.IO (hFlush, stdin, stdout, hGetEcho, hSetEcho)+import System.Exit (exitFailure) import Control.Exception (bracket)-import System.FilePath ((</>), takeExtension)+import System.FilePath ((</>), takeExtension, takeFileName) import qualified System.FilePath.Posix as FilePath.Posix ((</>)) import System.Directory import Control.Monad (forM_, when)+import Data.Maybe (catMaybes) type Auth = Maybe (String, String) ---FIXME: how do we find this path for an arbitrary hackage server?--- is it always at some fixed location relative to the server root?-legacyUploadURI :: URI-Just legacyUploadURI = parseURI "http://hackage.haskell.org/cgi-bin/hackage-scripts/protected/upload-pkg"- checkURI :: URI-Just checkURI = parseURI "http://hackage.haskell.org/cgi-bin/hackage-scripts/check-pkg"+Just checkURI = parseURI $ "http://hackage.haskell.org/cgi-bin/"+ ++ "hackage-scripts/check-pkg" -upload :: HttpTransport -> Verbosity -> [Repo] -> Maybe Username -> Maybe Password -> [FilePath] -> IO ()-upload transport verbosity repos mUsername mPassword paths = do+upload :: Verbosity -> RepoContext+ -> Maybe Username -> Maybe Password -> [FilePath]+ -> IO ()+upload verbosity repoCtxt mUsername mPassword paths = do+ let repos = repoContextRepos repoCtxt+ transport <- repoContextGetTransport repoCtxt targetRepo <-- case [ remoteRepo | Left remoteRepo <- map repoKind repos ] of- [] -> die $ "Cannot upload. No remote repositories are configured."+ case [ remoteRepo | Just remoteRepo <- map maybeRepoRemote repos ] of+ [] -> die "Cannot upload. No remote repositories are configured." rs -> remoteRepoTryUpgradeToHttps transport (last rs) let targetRepoURI = remoteRepoURI targetRepo- uploadURI- | isOldHackageURI targetRepoURI- = legacyUploadURI- | otherwise- = targetRepoURI {- uriPath = uriPath targetRepoURI FilePath.Posix.</> "upload"- }+ rootIfEmpty x = if null x then "/" else x+ uploadURI = targetRepoURI {+ uriPath = rootIfEmpty (uriPath targetRepoURI)+ FilePath.Posix.</> "upload"+ } Username username <- maybe promptUsername return mUsername Password password <- maybe promptPassword return mPassword let auth = Just (username,password)- flip mapM_ paths $ \path -> do+ forM_ paths $ \path -> do notice verbosity $ "Uploading " ++ path ++ "... " handlePackage transport verbosity uploadURI auth path +uploadDoc :: Verbosity -> RepoContext+ -> Maybe Username -> Maybe Password -> FilePath+ -> IO ()+uploadDoc verbosity repoCtxt mUsername mPassword path = do+ let repos = repoContextRepos repoCtxt+ transport <- repoContextGetTransport repoCtxt+ targetRepo <-+ case [ remoteRepo | Just remoteRepo <- map maybeRepoRemote repos ] of+ [] -> die $ "Cannot upload. No remote repositories are configured."+ rs -> remoteRepoTryUpgradeToHttps transport (last rs)+ let targetRepoURI = remoteRepoURI targetRepo+ rootIfEmpty x = if null x then "/" else x+ uploadURI = targetRepoURI {+ uriPath = rootIfEmpty (uriPath targetRepoURI)+ FilePath.Posix.</> "package/" ++ pkgid ++ "/docs"+ }+ (reverseSuffix, reversePkgid) = break (== '-')+ (reverse (takeFileName path))+ pkgid = reverse $ tail reversePkgid+ when (reverse reverseSuffix /= "docs.tar.gz"+ || null reversePkgid || head reversePkgid /= '-') $+ die "Expected a file name matching the pattern <pkgid>-docs.tar.gz"+ Username username <- maybe promptUsername return mUsername+ Password password <- maybe promptPassword return mPassword++ let auth = Just (username,password)+ headers =+ [ Header HdrContentType "application/x-tar"+ , Header HdrContentEncoding "gzip"+ ]+ notice verbosity $ "Uploading documentation " ++ path ++ "... "+ resp <- putHttpFile transport verbosity uploadURI path auth headers+ case resp of+ (200,_) ->+ notice verbosity "Ok"+ (code,err) -> do+ notice verbosity $ "Error uploading documentation "+ ++ path ++ ": "+ ++ "http code " ++ show code ++ "\n"+ ++ err+ exitFailure+ promptUsername :: IO Username promptUsername = do putStr "Hackage username: "@@ -72,43 +115,49 @@ putStrLn "" return passwd -report :: Verbosity -> [Repo] -> Maybe Username -> Maybe Password -> IO ()-report verbosity repos mUsername mPassword = do- Username username <- maybe promptUsername return mUsername- Password password <- maybe promptPassword return mPassword- let auth = (username,password)- forM_ repos $ \repo -> case repoKind repo of- Left remoteRepo- -> do dotCabal <- defaultCabalDir- let srcDir = dotCabal </> "reports" </> remoteRepoName remoteRepo- -- We don't want to bomb out just because we haven't built any packages from this repo yet- srcExists <- doesDirectoryExist srcDir- when srcExists $ do- contents <- getDirectoryContents srcDir- forM_ (filter (\c -> takeExtension c == ".log") contents) $ \logFile ->- do inp <- readFile (srcDir </> logFile)- let (reportStr, buildLog) = read inp :: (String,String)- case BuildReport.parse reportStr of- Left errs -> do warn verbosity $ "Errors: " ++ errs -- FIXME- Right report' ->- do info verbosity $ "Uploading report for " ++ display (BuildReport.package report')- BuildReport.uploadReports verbosity auth (remoteRepoURI remoteRepo) [(report', Just buildLog)]- return ()- Right{} -> return ()+report :: Verbosity -> RepoContext -> Maybe Username -> Maybe Password -> IO ()+report verbosity repoCtxt mUsername mPassword = do+ Username username <- maybe promptUsername return mUsername+ Password password <- maybe promptPassword return mPassword+ let auth = (username, password)+ repos = repoContextRepos repoCtxt+ remoteRepos = catMaybes (map maybeRepoRemote repos)+ forM_ remoteRepos $ \remoteRepo ->+ do dotCabal <- defaultCabalDir+ let srcDir = dotCabal </> "reports" </> remoteRepoName remoteRepo+ -- We don't want to bomb out just because we haven't built any packages+ -- from this repo yet.+ srcExists <- doesDirectoryExist srcDir+ when srcExists $ do+ contents <- getDirectoryContents srcDir+ forM_ (filter (\c -> takeExtension c ==".log") contents) $ \logFile ->+ do inp <- readFile (srcDir </> logFile)+ let (reportStr, buildLog) = read inp :: (String,String)+ case BuildReport.parse reportStr of+ Left errs -> warn verbosity $ "Errors: " ++ errs -- FIXME+ Right report' ->+ do info verbosity $ "Uploading report for "+ ++ display (BuildReport.package report')+ BuildReport.uploadReports verbosity repoCtxt auth+ (remoteRepoURI remoteRepo) [(report', Just buildLog)]+ return () -check :: HttpTransport -> Verbosity -> [FilePath] -> IO ()-check transport verbosity paths = do- flip mapM_ paths $ \path -> do- notice verbosity $ "Checking " ++ path ++ "... "- handlePackage transport verbosity checkURI Nothing path+check :: Verbosity -> RepoContext -> [FilePath] -> IO ()+check verbosity repoCtxt paths = do+ transport <- repoContextGetTransport repoCtxt+ forM_ paths $ \path -> do+ notice verbosity $ "Checking " ++ path ++ "... "+ handlePackage transport verbosity checkURI Nothing path handlePackage :: HttpTransport -> Verbosity -> URI -> Auth -> FilePath -> IO () handlePackage transport verbosity uri auth path = do resp <- postHttpFile transport verbosity uri path auth case resp of- (200,_) -> do notice verbosity "Ok"- (code,err) -> do notice verbosity $ "Error uploading " ++ path ++ ": "- ++ "http code " ++ show code ++ "\n"- ++ err-+ (200,_) ->+ notice verbosity "Ok"+ (code,err) -> do+ notice verbosity $ "Error uploading " ++ path ++ ": "+ ++ "http code " ++ show code ++ "\n"+ ++ err+ exitFailure
cabal/cabal-install/Distribution/Client/Utils.hs view
@@ -3,10 +3,13 @@ module Distribution.Client.Utils ( MergeResult(..) , mergeBy, duplicates, duplicatesBy , readMaybe- , inDir, determineNumJobs, numberOfProcessors+ , inDir, logDirChange+ , determineNumJobs, numberOfProcessors , removeExistingFile , withTempFileName- , makeAbsoluteToCwd, filePathToByteString+ , makeAbsoluteToCwd+ , makeRelativeToCwd, makeRelativeToDir+ , filePathToByteString , byteStringToFilePath, tryCanonicalizePath , canonicalizePathNoThrow , moreRecentFile, existsAndIsMoreRecentThan@@ -20,6 +23,9 @@ import Distribution.Simple.Setup ( Flag(..) ) import Distribution.Simple.Utils ( die, findPackageDesc ) import qualified Data.ByteString.Lazy as BS+#if !MIN_VERSION_base(4,8,0)+import Control.Applicative+#endif import Control.Monad ( when ) import Data.Bits@@ -41,7 +47,7 @@ ( canonicalizePath, doesFileExist, getCurrentDirectory , removeFile, setCurrentDirectory ) import System.FilePath- ( (</>), isAbsolute )+ ( (</>), isAbsolute, takeDrive, splitPath, joinPath ) import System.IO ( Handle, hClose, openTempFile #if MIN_VERSION_base(4,4,0)@@ -57,7 +63,7 @@ ( recoverEncode, CodingFailureMode(TransliterateCodingFailure) ) #endif -#if defined(mingw32_HOST_OS)+#if defined(mingw32_HOST_OS) || MIN_VERSION_directory(1,2,3) import Prelude hiding (ioError) import Control.Monad (liftM2, unless) import System.Directory (doesDirectoryExist)@@ -123,6 +129,9 @@ (\(name, h) -> hClose h >> action name) -- | Executes the action in the specified directory.+--+-- Warning: This operation is NOT thread-safe, because current+-- working directory is a process-global concept. inDir :: Maybe FilePath -> IO a -> IO a inDir Nothing m = m inDir (Just d) m = do@@ -130,6 +139,14 @@ setCurrentDirectory d m `Exception.finally` setCurrentDirectory old +-- | Log directory change in 'make' compatible syntax+logDirChange :: (String -> IO ()) -> Maybe FilePath -> IO a -> IO a+logDirChange _ Nothing m = m+logDirChange l (Just d) m = do+ l $ "cabal: Entering directory '" ++ d ++ "'\n"+ m `Exception.finally`+ (l $ "cabal: Leaving directory '" ++ d ++ "'\n")+ foreign import ccall "getNumberOfProcessors" c_getNumberOfProcessors :: IO CInt -- The number of processors is not going to change during the duration of the@@ -152,6 +169,30 @@ | otherwise = do cwd <- getCurrentDirectory return $! cwd </> path +-- | Given a path (relative or absolute), make it relative to the current+-- directory, including using @../..@ if necessary.+makeRelativeToCwd :: FilePath -> IO FilePath+makeRelativeToCwd path =+ makeRelativeCanonical <$> canonicalizePath path <*> getCurrentDirectory++-- | Given a path (relative or absolute), make it relative to the given+-- directory, including using @../..@ if necessary.+makeRelativeToDir :: FilePath -> FilePath -> IO FilePath+makeRelativeToDir path dir =+ makeRelativeCanonical <$> canonicalizePath path <*> canonicalizePath dir++-- | Given a canonical absolute path and canonical absolute dir, make the path+-- relative to the directory, including using @../..@ if necessary. Returns+-- the original absolute path if it is not on the same drive as the given dir.+makeRelativeCanonical :: FilePath -> FilePath -> FilePath+makeRelativeCanonical path dir+ | takeDrive path /= takeDrive dir = path+ | otherwise = go (splitPath path) (splitPath dir)+ where+ go (p:ps) (d:ds) | p == d = go ps ds+ go [] [] = "./"+ go ps ds = joinPath (replicate (length ds) ".." ++ ps)+ -- | Convert a 'FilePath' to a lazy 'ByteString'. Each 'Char' is -- encoded as a little-endian 'Word32'. filePathToByteString :: FilePath -> BS.ByteString@@ -187,13 +228,12 @@ b2 = fromIntegral $ BS.index bs (i + 2) b3 = fromIntegral $ BS.index bs (i + 3) --- | Workaround for the inconsistent behaviour of 'canonicalizePath'. It throws--- an error if the path refers to a non-existent file on *nix, but not on--- Windows.+-- | Workaround for the inconsistent behaviour of 'canonicalizePath'. Always+-- throws an error if the path refers to a non-existent file. tryCanonicalizePath :: FilePath -> IO FilePath tryCanonicalizePath path = do ret <- canonicalizePath path-#if defined(mingw32_HOST_OS)+#if defined(mingw32_HOST_OS) || MIN_VERSION_directory(1,2,3) exists <- liftM2 (||) (doesFileExist ret) (doesDirectoryExist ret) unless exists $ ioError $ mkIOError doesNotExistErrorType "canonicalizePath"
cabal/cabal-install/Main.hs view
@@ -16,7 +16,7 @@ module Main (main) where import Distribution.Client.Setup- ( GlobalFlags(..), globalCommand, globalRepos+ ( GlobalFlags(..), globalCommand, withRepoContext , ConfigFlags(..) , ConfigExFlags(..), defaultConfigExFlags, configureExCommand , BuildFlags(..), BuildExFlags(..), SkipAddSourceDepsCheck(..)@@ -25,6 +25,7 @@ , installCommand, upgradeCommand, uninstallCommand , FetchFlags(..), fetchCommand , FreezeFlags(..), freezeCommand+ , genBoundsCommand , GetFlags(..), getCommand, unpackCommand , checkCommand , formatCommand@@ -42,6 +43,7 @@ , ExecFlags(..), execCommand , UserConfigFlags(..), userConfigCommand , reportCommand+ , manpageCommand ) import Distribution.Simple.Setup ( HaddockFlags(..), haddockCommand, defaultHaddockFlags@@ -59,23 +61,28 @@ ( setupWrapper, SetupScriptOptions(..), defaultSetupScriptOptions ) import Distribution.Client.Config ( SavedConfig(..), loadConfig, defaultConfigFile, userConfigDiff- , userConfigUpdate )+ , userConfigUpdate, createDefaultConfigFile, getConfigFilePath ) import Distribution.Client.Targets ( readUserTargets ) import qualified Distribution.Client.List as List ( list, info ) +--TODO: temporary import, just to force these modules to be built.+-- It will be replaced by import of new build command once merged.+import Distribution.Client.ProjectPlanning ()+import Distribution.Client.ProjectBuilding ()+ import Distribution.Client.Install (install) import Distribution.Client.Configure (configure) import Distribution.Client.Update (update) import Distribution.Client.Exec (exec) import Distribution.Client.Fetch (fetch) import Distribution.Client.Freeze (freeze)+import Distribution.Client.GenBounds (genBounds) import Distribution.Client.Check as Check (check) --import Distribution.Client.Clean (clean)-import Distribution.Client.Upload as Upload (upload, check, report)+import qualified Distribution.Client.Upload as Upload import Distribution.Client.Run (run, splitRunArgs)-import Distribution.Client.HttpUtils (configureTransport) import Distribution.Client.SrcDist (sdist) import Distribution.Client.Get (get) import Distribution.Client.Sandbox (sandboxInit@@ -100,14 +107,17 @@ ,updateInstallDirs ,configCompilerAux'+ ,getPersistOrConfigCompiler ,configPackageDB') import Distribution.Client.Sandbox.PackageEnvironment (setPackageDB ,userPackageEnvironmentFile) import Distribution.Client.Sandbox.Timestamp (maybeAddCompilerTimestampRecord) import Distribution.Client.Sandbox.Types (UseSandbox(..), whenUsingSandbox)+import Distribution.Client.Tar (createTarGzFile) import Distribution.Client.Types (Password (..)) import Distribution.Client.Init (initCabal)+import Distribution.Client.Manpage (manpage) import qualified Distribution.Client.Win32SelfUpgrade as Win32SelfUpgrade import Distribution.Client.Utils (determineNumJobs #if defined(mingw32_HOST_OS)@@ -115,9 +125,9 @@ #endif ,existsAndIsMoreRecentThan) +import Distribution.Package (packageId) import Distribution.PackageDescription- ( BuildType(..), Executable(..), benchmarkName, benchmarkBuildInfo- , testName, testBuildInfo, buildable )+ ( BuildType(..), Executable(..), buildable ) import Distribution.PackageDescription.Parse ( readPackageDescription ) import Distribution.PackageDescription.PrettyPrint@@ -127,8 +137,9 @@ import Distribution.Simple.Build ( startInterpreter ) import Distribution.Simple.Command- ( CommandParse(..), CommandUI(..), Command- , commandsRun, commandAddAction, hiddenCommand )+ ( CommandParse(..), CommandUI(..), Command, CommandSpec(..)+ , CommandType(..), commandsRun, commandAddAction, hiddenCommand+ , commandFromSpec) import Distribution.Simple.Compiler ( Compiler(..) ) import Distribution.Simple.Configure@@ -155,7 +166,8 @@ import System.Environment (getArgs, getProgName) import System.Exit (exitFailure)-import System.FilePath (splitExtension, takeExtension)+import System.FilePath ( dropExtension, splitExtension+ , takeExtension, (</>), (<.>)) import System.IO ( BufferMode(LineBuffering), hSetBuffering #ifdef mingw32_HOST_OS , stderr@@ -163,7 +175,7 @@ , stdout ) import System.Directory (doesFileExist, getCurrentDirectory) import Data.List (intercalate)-import Data.Maybe (mapMaybe)+import Data.Maybe (listToMaybe) #if !MIN_VERSION_base(4,8,0) import Data.Monoid (Monoid(..)) import Control.Applicative (pure, (<$>))@@ -224,59 +236,71 @@ printNumericVersion = putStrLn $ display Paths_cabal_install.version printVersion = putStrLn $ "cabal-install version " ++ display Paths_cabal_install.version- ++ "\nusing version "+ ++ "\ncompiled using version " ++ display cabalVersion ++ " of the Cabal library " - commands =- [installCommand `commandAddAction` installAction- ,updateCommand `commandAddAction` updateAction- ,listCommand `commandAddAction` listAction- ,infoCommand `commandAddAction` infoAction- ,fetchCommand `commandAddAction` fetchAction- ,freezeCommand `commandAddAction` freezeAction- ,getCommand `commandAddAction` getAction- ,hiddenCommand $- unpackCommand `commandAddAction` unpackAction- ,checkCommand `commandAddAction` checkAction- ,sdistCommand `commandAddAction` sdistAction- ,uploadCommand `commandAddAction` uploadAction- ,reportCommand `commandAddAction` reportAction- ,runCommand `commandAddAction` runAction- ,initCommand `commandAddAction` initAction- ,configureExCommand `commandAddAction` configureAction- ,buildCommand `commandAddAction` buildAction- ,replCommand `commandAddAction` replAction- ,sandboxCommand `commandAddAction` sandboxAction- ,haddockCommand `commandAddAction` haddockAction- ,execCommand `commandAddAction` execAction- ,userConfigCommand `commandAddAction` userConfigAction- ,cleanCommand `commandAddAction` cleanAction- ,wrapperAction copyCommand- copyVerbosity copyDistPref- ,wrapperAction hscolourCommand- hscolourVerbosity hscolourDistPref- ,wrapperAction registerCommand- regVerbosity regDistPref- ,testCommand `commandAddAction` testAction- ,benchmarkCommand `commandAddAction` benchmarkAction- ,hiddenCommand $- uninstallCommand `commandAddAction` uninstallAction- ,hiddenCommand $- formatCommand `commandAddAction` formatAction- ,hiddenCommand $- upgradeCommand `commandAddAction` upgradeAction- ,hiddenCommand $- win32SelfUpgradeCommand`commandAddAction` win32SelfUpgradeAction- ,hiddenCommand $- actAsSetupCommand`commandAddAction` actAsSetupAction+ commands = map commandFromSpec commandSpecs+ commandSpecs =+ [ regularCmd installCommand installAction+ , regularCmd updateCommand updateAction+ , regularCmd listCommand listAction+ , regularCmd infoCommand infoAction+ , regularCmd fetchCommand fetchAction+ , regularCmd freezeCommand freezeAction+ , regularCmd getCommand getAction+ , hiddenCmd unpackCommand unpackAction+ , regularCmd checkCommand checkAction+ , regularCmd sdistCommand sdistAction+ , regularCmd uploadCommand uploadAction+ , regularCmd reportCommand reportAction+ , regularCmd runCommand runAction+ , regularCmd initCommand initAction+ , regularCmd configureExCommand configureAction+ , regularCmd buildCommand buildAction+ , regularCmd replCommand replAction+ , regularCmd sandboxCommand sandboxAction+ , regularCmd haddockCommand haddockAction+ , regularCmd execCommand execAction+ , regularCmd userConfigCommand userConfigAction+ , regularCmd cleanCommand cleanAction+ , regularCmd genBoundsCommand genBoundsAction+ , wrapperCmd copyCommand copyVerbosity copyDistPref+ , wrapperCmd hscolourCommand hscolourVerbosity hscolourDistPref+ , wrapperCmd registerCommand regVerbosity regDistPref+ , regularCmd testCommand testAction+ , regularCmd benchmarkCommand benchmarkAction+ , hiddenCmd uninstallCommand uninstallAction+ , hiddenCmd formatCommand formatAction+ , hiddenCmd upgradeCommand upgradeAction+ , hiddenCmd win32SelfUpgradeCommand win32SelfUpgradeAction+ , hiddenCmd actAsSetupCommand actAsSetupAction+ , hiddenCmd manpageCommand (manpageAction commandSpecs) ] +type Action = GlobalFlags -> IO ()++regularCmd :: CommandUI flags -> (flags -> [String] -> action)+ -> CommandSpec action+regularCmd ui action =+ CommandSpec ui ((flip commandAddAction) action) NormalCommand++hiddenCmd :: CommandUI flags -> (flags -> [String] -> action)+ -> CommandSpec action+hiddenCmd ui action =+ CommandSpec ui (\ui' -> hiddenCommand (commandAddAction ui' action))+ HiddenCommand++wrapperCmd :: Monoid flags => CommandUI flags -> (flags -> Flag Verbosity)+ -> (flags -> Flag String) -> CommandSpec Action+wrapperCmd ui verbosity distPref =+ CommandSpec ui (\ui' -> wrapperAction ui' verbosity distPref) NormalCommand+ wrapperAction :: Monoid flags => CommandUI flags -> (flags -> Flag Verbosity) -> (flags -> Flag String)- -> Command (GlobalFlags -> IO ())+ -> Command Action wrapperAction command verbosityFlag distPrefFlag = commandAddAction command { commandDefaultFlags = mempty } $ \flags extraArgs globalFlags -> do@@ -288,7 +312,7 @@ command (const flags) extraArgs configureAction :: (ConfigFlags, ConfigExFlags)- -> [String] -> GlobalFlags -> IO ()+ -> [String] -> Action configureAction (configFlags, configExFlags) extraArgs globalFlags = do let verbosity = fromFlagOrDefault normal (configVerbosity configFlags) @@ -319,12 +343,13 @@ (compilerId comp) platform maybeWithSandboxDirOnSearchPath useSandbox $+ withRepoContext verbosity globalFlags' $ \repoContext -> configure verbosity (configPackageDB' configFlags'')- (globalRepos globalFlags')+ repoContext comp platform conf configFlags'' configExFlags' extraArgs -buildAction :: (BuildFlags, BuildExFlags) -> [String] -> GlobalFlags -> IO ()+buildAction :: (BuildFlags, BuildExFlags) -> [String] -> Action buildAction (buildFlags, buildExFlags) extraArgs globalFlags = do let verbosity = fromFlagOrDefault normal (buildVerbosity buildFlags) noAddSource = fromFlagOrDefault DontSkipAddSourceDepsCheck@@ -378,7 +403,7 @@ numJobsCmdLineFlag = buildNumJobs buildFlags -replAction :: (ReplFlags, BuildExFlags) -> [String] -> GlobalFlags -> IO ()+replAction :: (ReplFlags, BuildExFlags) -> [String] -> Action replAction (replFlags, buildExFlags) extraArgs globalFlags = do cwd <- getCurrentDirectory pkgDesc <- findPackageDesc cwd@@ -418,12 +443,13 @@ onNoPkgDesc = do (_useSandbox, config) <- loadConfigOrSandboxConfig verbosity globalFlags let configFlags = savedConfigureFlags config- (comp, _platform, programDb) <- configCompilerAux' configFlags+ (comp, platform, programDb) <- configCompilerAux' configFlags programDb' <- reconfigurePrograms verbosity (replProgramPaths replFlags) (replProgramArgs replFlags) programDb- startInterpreter verbosity programDb' comp (configPackageDB' configFlags)+ startInterpreter verbosity programDb' comp platform+ (configPackageDB' configFlags) -- | Re-configure the package in the current directory if needed. Deciding -- when to reconfigure and with which options is convoluted:@@ -508,6 +534,9 @@ ++ "to be corrupt." _ -> show err case err of+ -- Note: the build config could have been generated by a custom setup+ -- script built against a different Cabal version, so it's crucial that+ -- we ignore the bad version error here. ConfigStateFileBadVersion _ _ _ -> info verbosity msg _ -> do let distVerbFlags = mempty@@ -656,7 +685,7 @@ ++ configureManually installAction :: (ConfigFlags, ConfigExFlags, InstallFlags, HaddockFlags)- -> [String] -> GlobalFlags -> IO ()+ -> [String] -> Action installAction (configFlags, _, installFlags, _) _ globalFlags | fromFlagOrDefault False (installOnly installFlags) = do let verbosity = fromFlagOrDefault normal (configVerbosity configFlags)@@ -701,7 +730,8 @@ haddockFlags { haddockDistPref = toFlag distPref } globalFlags' = savedGlobalFlags config `mappend` globalFlags (comp, platform, conf) <- configCompilerAux' configFlags'- -- TODO: Redesign ProgramDB API to prevent such problems as #2241 in the future.+ -- TODO: Redesign ProgramDB API to prevent such problems as #2241 in the+ -- future. conf' <- configureAllKnownPrograms verbosity conf -- If we're working inside a sandbox and the user has set the -w option, we@@ -709,7 +739,8 @@ -- timestamp record for this compiler to the timestamp file. configFlags'' <- case useSandbox of NoSandbox -> configAbsolutePaths $ configFlags'- (UseSandbox sandboxDir) -> return $ setPackageDB sandboxDir comp platform configFlags'+ (UseSandbox sandboxDir) -> return $ setPackageDB sandboxDir comp platform+ configFlags' whenUsingSandbox useSandbox $ \sandboxDir -> do initPackageDBIfNeeded verbosity configFlags'' comp conf'@@ -718,7 +749,7 @@ maybeAddCompilerTimestampRecord verbosity sandboxDir indexFile (compilerId comp) platform - -- FIXME: Passing 'SandboxPackageInfo' to install unconditionally here means+ -- TODO: Passing 'SandboxPackageInfo' to install unconditionally here means -- that 'cabal install some-package' inside a sandbox will sometimes reinstall -- modified add-source deps, even if they are not among the dependencies of -- 'some-package'. This can also prevent packages that depend on older@@ -726,9 +757,10 @@ maybeWithSandboxPackageInfo verbosity configFlags'' globalFlags' comp platform conf useSandbox $ \mSandboxPkgInfo -> maybeWithSandboxDirOnSearchPath useSandbox $+ withRepoContext verbosity globalFlags' $ \repoContext -> install verbosity (configPackageDB' configFlags'')- (globalRepos globalFlags')+ repoContext comp platform conf' useSandbox mSandboxPkgInfo globalFlags' configFlags'' configExFlags'@@ -764,33 +796,47 @@ let setupOptions = defaultSetupScriptOptions { useDistPref = distPref } testFlags' = testFlags { testDistPref = toFlag distPref } - -- the package was just configured, so the LBI must be available- lbi <- getPersistBuildConfig distPref- let pkgDescr = LBI.localPkgDescr lbi- nameTestsOnly =- LBI.foldComponent- (const Nothing)- (const Nothing)- (\t ->- if buildable (testBuildInfo t)- then Just (testName t)- else Nothing)- (const Nothing)- tests = mapMaybe nameTestsOnly $ LBI.pkgComponents pkgDescr- extraArgs'- | null extraArgs = tests- | otherwise = extraArgs+ -- The package was just configured, so the LBI must be available.+ names <- componentNamesFromLBI distPref "test suites"+ (\c -> case c of { LBI.CTest{} -> True; _ -> False })+ let extraArgs'+ | null extraArgs = case names of+ ComponentNamesUnknown -> []+ ComponentNames names' -> [ name | LBI.CTestName name <- names' ]+ | otherwise = extraArgs - if null tests- then notice verbosity "Package has no buildable test suites."- else do- maybeWithSandboxDirOnSearchPath useSandbox $- build verbosity config distPref buildFlags' extraArgs'+ maybeWithSandboxDirOnSearchPath useSandbox $+ build verbosity config distPref buildFlags' extraArgs' - maybeWithSandboxDirOnSearchPath useSandbox $- setupWrapper verbosity setupOptions Nothing- Cabal.testCommand (const testFlags') extraArgs'+ maybeWithSandboxDirOnSearchPath useSandbox $+ setupWrapper verbosity setupOptions Nothing+ Cabal.testCommand (const testFlags') extraArgs' +data ComponentNames = ComponentNamesUnknown+ | ComponentNames [LBI.ComponentName]++-- | Return the names of all buildable components matching a given predicate.+componentNamesFromLBI :: FilePath -> String -> (LBI.Component -> Bool)+ -> IO ComponentNames+componentNamesFromLBI distPref targetsDescr compPred = do+ eLBI <- tryGetPersistBuildConfig distPref+ case eLBI of+ Left err -> case err of+ -- Note: the build config could have been generated by a custom setup+ -- script built against a different Cabal version, so it's crucial that+ -- we ignore the bad version error here.+ ConfigStateFileBadVersion _ _ _ -> return ComponentNamesUnknown+ _ -> die (show err)+ Right lbi -> do+ let pkgDescr = LBI.localPkgDescr lbi+ names = map LBI.componentName+ . filter (buildable . LBI.componentBuildInfo)+ . filter compPred $+ LBI.pkgComponents pkgDescr+ if null names+ then die $ "Package has no buildable " ++ targetsDescr ++ "."+ else return $! (ComponentNames names)+ benchmarkAction :: (BenchmarkFlags, BuildFlags, BuildExFlags) -> [String] -> GlobalFlags -> IO ()@@ -816,34 +862,23 @@ let setupOptions = defaultSetupScriptOptions { useDistPref = distPref } benchmarkFlags'= benchmarkFlags { benchmarkDistPref = toFlag distPref } - -- the package was just configured, so the LBI must be available- lbi <- getPersistBuildConfig distPref- let pkgDescr = LBI.localPkgDescr lbi- nameBenchsOnly =- LBI.foldComponent- (const Nothing)- (const Nothing)- (const Nothing)- (\b ->- if buildable (benchmarkBuildInfo b)- then Just (benchmarkName b)- else Nothing)- benchs = mapMaybe nameBenchsOnly $ LBI.pkgComponents pkgDescr- extraArgs'- | null extraArgs = benchs- | otherwise = extraArgs+ -- The package was just configured, so the LBI must be available.+ names <- componentNamesFromLBI distPref "benchmarks"+ (\c -> case c of { LBI.CBench{} -> True; _ -> False; })+ let extraArgs'+ | null extraArgs = case names of+ ComponentNamesUnknown -> []+ ComponentNames names' -> [name | LBI.CBenchName name <- names']+ | otherwise = extraArgs - if null benchs- then notice verbosity "Package has no buildable benchmarks."- else do- maybeWithSandboxDirOnSearchPath useSandbox $- build verbosity config distPref buildFlags' extraArgs'+ maybeWithSandboxDirOnSearchPath useSandbox $+ build verbosity config distPref buildFlags' extraArgs' - maybeWithSandboxDirOnSearchPath useSandbox $- setupWrapper verbosity setupOptions Nothing- Cabal.benchmarkCommand (const benchmarkFlags') extraArgs'+ maybeWithSandboxDirOnSearchPath useSandbox $+ setupWrapper verbosity setupOptions Nothing+ Cabal.benchmarkCommand (const benchmarkFlags') extraArgs' -haddockAction :: HaddockFlags -> [String] -> GlobalFlags -> IO ()+haddockAction :: HaddockFlags -> [String] -> Action haddockAction haddockFlags extraArgs globalFlags = do let verbosity = fromFlag (haddockVerbosity haddockFlags) (_useSandbox, config, distPref) <-@@ -856,8 +891,15 @@ setupScriptOptions = defaultSetupScriptOptions { useDistPref = distPref } setupWrapper verbosity setupScriptOptions Nothing haddockCommand (const haddockFlags') extraArgs+ when (fromFlagOrDefault False $ haddockForHackage haddockFlags) $ do+ pkg <- fmap LBI.localPkgDescr (getPersistBuildConfig distPref)+ let dest = distPref </> name <.> "tar.gz"+ name = display (packageId pkg) ++ "-docs"+ docDir = distPref </> "doc" </> "html"+ createTarGzFile dest docDir name+ notice verbosity $ "Documentation tarball created: " ++ dest -cleanAction :: CleanFlags -> [String] -> GlobalFlags -> IO ()+cleanAction :: CleanFlags -> [String] -> Action cleanAction cleanFlags extraArgs globalFlags = do (_, config) <- loadConfigOrSandboxConfig verbosity globalFlags distPref <- findSavedDistPref config (cleanDistPref cleanFlags)@@ -871,7 +913,7 @@ where verbosity = fromFlagOrDefault normal (cleanVerbosity cleanFlags) -listAction :: ListFlags -> [String] -> GlobalFlags -> IO ()+listAction :: ListFlags -> [String] -> Action listAction listFlags extraArgs globalFlags = do let verbosity = fromFlag (listVerbosity listFlags) (_useSandbox, config) <- loadConfigOrSandboxConfig verbosity@@ -883,15 +925,16 @@ } globalFlags' = savedGlobalFlags config `mappend` globalFlags (comp, _, conf) <- configCompilerAux' configFlags- List.list verbosity+ withRepoContext verbosity globalFlags' $ \repoContext ->+ List.list verbosity (configPackageDB' configFlags)- (globalRepos globalFlags')+ repoContext comp conf listFlags extraArgs -infoAction :: InfoFlags -> [String] -> GlobalFlags -> IO ()+infoAction :: InfoFlags -> [String] -> Action infoAction infoFlags extraArgs globalFlags = do let verbosity = fromFlag (infoVerbosity infoFlags) targets <- readUserTargets verbosity extraArgs@@ -904,16 +947,17 @@ } globalFlags' = savedGlobalFlags config `mappend` globalFlags (comp, _, conf) <- configCompilerAuxEx configFlags- List.info verbosity+ withRepoContext verbosity globalFlags' $ \repoContext ->+ List.info verbosity (configPackageDB' configFlags)- (globalRepos globalFlags')+ repoContext comp conf globalFlags' infoFlags targets -updateAction :: Flag Verbosity -> [String] -> GlobalFlags -> IO ()+updateAction :: Flag Verbosity -> [String] -> Action updateAction verbosityFlag extraArgs globalFlags = do unless (null extraArgs) $ die $ "'update' doesn't take any extra arguments: " ++ unwords extraArgs@@ -921,11 +965,11 @@ (_useSandbox, config) <- loadConfigOrSandboxConfig verbosity (globalFlags { globalRequireSandbox = Flag False }) let globalFlags' = savedGlobalFlags config `mappend` globalFlags- transport <- configureTransport verbosity (flagToMaybe (globalHttpTransport globalFlags'))- update transport verbosity (globalRepos globalFlags')+ withRepoContext verbosity globalFlags' $ \repoContext ->+ update verbosity repoContext upgradeAction :: (ConfigFlags, ConfigExFlags, InstallFlags, HaddockFlags)- -> [String] -> GlobalFlags -> IO ()+ -> [String] -> Action upgradeAction _ _ _ = die $ "Use the 'cabal install' command instead of 'cabal upgrade'.\n" ++ "You can install the latest version of a package using 'cabal install'. "@@ -939,7 +983,7 @@ ++ "--upgrade-dependencies, it is recommended that you do not upgrade core " ++ "packages (e.g. by using appropriate --constraint= flags)." -fetchAction :: FetchFlags -> [String] -> GlobalFlags -> IO ()+fetchAction :: FetchFlags -> [String] -> Action fetchAction fetchFlags extraArgs globalFlags = do let verbosity = fromFlag (fetchVerbosity fetchFlags) targets <- readUserTargets verbosity extraArgs@@ -947,13 +991,14 @@ let configFlags = savedConfigureFlags config globalFlags' = savedGlobalFlags config `mappend` globalFlags (comp, platform, conf) <- configCompilerAux' configFlags- fetch verbosity+ withRepoContext verbosity globalFlags' $ \repoContext ->+ fetch verbosity (configPackageDB' configFlags)- (globalRepos globalFlags')+ repoContext comp platform conf globalFlags' fetchFlags targets -freezeAction :: FreezeFlags -> [String] -> GlobalFlags -> IO ()+freezeAction :: FreezeFlags -> [String] -> Action freezeAction freezeFlags _extraArgs globalFlags = do let verbosity = fromFlag (freezeVerbosity freezeFlags) (useSandbox, config) <- loadConfigOrSandboxConfig verbosity globalFlags@@ -964,20 +1009,44 @@ maybeWithSandboxPackageInfo verbosity configFlags globalFlags' comp platform conf useSandbox $ \mSandboxPkgInfo -> maybeWithSandboxDirOnSearchPath useSandbox $+ withRepoContext verbosity globalFlags' $ \repoContext -> freeze verbosity (configPackageDB' configFlags)- (globalRepos globalFlags')+ repoContext comp platform conf mSandboxPkgInfo globalFlags' freezeFlags -uploadAction :: UploadFlags -> [String] -> GlobalFlags -> IO ()+genBoundsAction :: FreezeFlags -> [String] -> GlobalFlags -> IO ()+genBoundsAction freezeFlags _extraArgs globalFlags = do+ let verbosity = fromFlag (freezeVerbosity freezeFlags)+ (useSandbox, config) <- loadConfigOrSandboxConfig verbosity globalFlags+ let configFlags = savedConfigureFlags config+ globalFlags' = savedGlobalFlags config `mappend` globalFlags+ (comp, platform, conf) <- configCompilerAux' configFlags++ maybeWithSandboxPackageInfo verbosity configFlags globalFlags'+ comp platform conf useSandbox $ \mSandboxPkgInfo ->+ maybeWithSandboxDirOnSearchPath useSandbox $+ withRepoContext verbosity globalFlags' $ \repoContext ->+ genBounds verbosity+ (configPackageDB' configFlags)+ repoContext+ comp platform conf+ mSandboxPkgInfo+ globalFlags' freezeFlags++uploadAction :: UploadFlags -> [String] -> Action uploadAction uploadFlags extraArgs globalFlags = do- let verbosity = fromFlag (uploadVerbosity uploadFlags) config <- loadConfig verbosity (globalConfigFile globalFlags) let uploadFlags' = savedUploadFlags config `mappend` uploadFlags globalFlags' = savedGlobalFlags config `mappend` globalFlags tarfiles = extraArgs+ when (null tarfiles && not (fromFlag (uploadDoc uploadFlags'))) $+ die "the 'upload' command expects at least one .tar.gz archive."+ when (fromFlag (uploadCheck uploadFlags')+ && fromFlag (uploadDoc uploadFlags')) $+ die "--check and --doc cannot be used together." checkTarFiles extraArgs maybe_password <- case uploadPasswordCmd uploadFlags'@@ -985,21 +1054,32 @@ getProgramInvocationOutput verbosity (simpleProgramInvocation xs xss) _ -> pure $ flagToMaybe $ uploadPassword uploadFlags'- transport <- configureTransport verbosity (flagToMaybe (globalHttpTransport globalFlags'))- if fromFlag (uploadCheck uploadFlags')- then Upload.check transport verbosity tarfiles- else upload transport- verbosity- (globalRepos globalFlags')- (flagToMaybe $ uploadUsername uploadFlags')- maybe_password- tarfiles- where+ withRepoContext verbosity globalFlags' $ \repoContext -> do+ if fromFlag (uploadCheck uploadFlags')+ then do+ Upload.check verbosity repoContext tarfiles+ else if fromFlag (uploadDoc uploadFlags')+ then do+ when (length tarfiles > 1) $+ die $ "the 'upload' command can only upload documentation "+ ++ "for one package at a time."+ tarfile <- maybe (generateDocTarball config) return $ listToMaybe tarfiles+ Upload.uploadDoc verbosity+ repoContext+ (flagToMaybe $ uploadUsername uploadFlags')+ maybe_password+ tarfile+ else do+ Upload.upload verbosity+ repoContext+ (flagToMaybe $ uploadUsername uploadFlags')+ maybe_password+ tarfiles+ where+ verbosity = fromFlag (uploadVerbosity uploadFlags) checkTarFiles tarfiles- | null tarfiles- = die "the 'upload' command expects one or more .tar.gz packages." | not (null otherFiles)- = die $ "the 'upload' command expects only .tar.gz packages: "+ = die $ "the 'upload' command expects only .tar.gz archives: " ++ intercalate ", " otherFiles | otherwise = sequence_ [ do exists <- doesFileExist tarfile@@ -1010,15 +1090,23 @@ isTarGzFile file = case splitExtension file of (file', ".gz") -> takeExtension file' == ".tar" _ -> False+ generateDocTarball config = do+ notice verbosity+ "No documentation tarball specified. Building documentation tarball..."+ haddockAction (defaultHaddockFlags { haddockForHackage = Flag True })+ [] globalFlags+ distPref <- findSavedDistPref config NoFlag+ pkg <- fmap LBI.localPkgDescr (getPersistBuildConfig distPref)+ return $ distPref </> display (packageId pkg) ++ "-docs" <.> "tar.gz" -checkAction :: Flag Verbosity -> [String] -> GlobalFlags -> IO ()+checkAction :: Flag Verbosity -> [String] -> Action checkAction verbosityFlag extraArgs _globalFlags = do unless (null extraArgs) $ die $ "'check' doesn't take any extra arguments: " ++ unwords extraArgs allOk <- Check.check (fromFlag verbosityFlag) unless allOk exitFailure -formatAction :: Flag Verbosity -> [String] -> GlobalFlags -> IO ()+formatAction :: Flag Verbosity -> [String] -> Action formatAction verbosityFlag extraArgs _globalFlags = do let verbosity = fromFlag verbosityFlag path <- case extraArgs of@@ -1029,18 +1117,19 @@ -- Uses 'writeFileAtomic' under the hood. writeGenericPackageDescription path pkgDesc -uninstallAction :: Flag Verbosity -> [String] -> GlobalFlags -> IO ()+uninstallAction :: Flag Verbosity -> [String] -> Action uninstallAction _verbosityFlag extraArgs _globalFlags = do let package = case extraArgs of p:_ -> p _ -> "PACKAGE_NAME"- die $ "This version of 'cabal-install' does not support the 'uninstall' operation. "- ++ "It will likely be implemented at some point in the future; in the meantime "- ++ "you're advised to use either 'ghc-pkg unregister " ++ package ++ "' or "- ++ "'cabal sandbox hc-pkg -- unregister " ++ package ++ "'."+ die $ "This version of 'cabal-install' does not support the 'uninstall' "+ ++ "operation. "+ ++ "It will likely be implemented at some point in the future; "+ ++ "in the meantime you're advised to use either 'ghc-pkg unregister "+ ++ package ++ "' or 'cabal sandbox hc-pkg -- unregister " ++ package ++ "'." -sdistAction :: (SDistFlags, SDistExFlags) -> [String] -> GlobalFlags -> IO ()+sdistAction :: (SDistFlags, SDistExFlags) -> [String] -> Action sdistAction (sdistFlags, sdistExFlags) extraArgs globalFlags = do unless (null extraArgs) $ die $ "'sdist' doesn't take any extra arguments: " ++ unwords extraArgs@@ -1050,7 +1139,7 @@ let sdistFlags' = sdistFlags { sDistDistPref = toFlag distPref } sdist sdistFlags' sdistExFlags -reportAction :: ReportFlags -> [String] -> GlobalFlags -> IO ()+reportAction :: ReportFlags -> [String] -> Action reportAction reportFlags extraArgs globalFlags = do unless (null extraArgs) $ die $ "'report' doesn't take any extra arguments: " ++ unwords extraArgs@@ -1060,11 +1149,12 @@ let globalFlags' = savedGlobalFlags config `mappend` globalFlags reportFlags' = savedReportFlags config `mappend` reportFlags - Upload.report verbosity (globalRepos globalFlags')+ withRepoContext verbosity globalFlags' $ \repoContext ->+ Upload.report verbosity repoContext (flagToMaybe $ reportUsername reportFlags') (flagToMaybe $ reportPassword reportFlags') -runAction :: (BuildFlags, BuildExFlags) -> [String] -> GlobalFlags -> IO ()+runAction :: (BuildFlags, BuildExFlags) -> [String] -> Action runAction (buildFlags, buildExFlags) extraArgs globalFlags = do let verbosity = fromFlagOrDefault normal (buildVerbosity buildFlags) let noAddSource = fromFlagOrDefault DontSkipAddSourceDepsCheck@@ -1086,39 +1176,43 @@ maybeWithSandboxDirOnSearchPath useSandbox $ run verbosity lbi exe exeArgs -getAction :: GetFlags -> [String] -> GlobalFlags -> IO ()+getAction :: GetFlags -> [String] -> Action getAction getFlags extraArgs globalFlags = do let verbosity = fromFlag (getVerbosity getFlags) targets <- readUserTargets verbosity extraArgs (_useSandbox, config) <- loadConfigOrSandboxConfig verbosity (globalFlags { globalRequireSandbox = Flag False }) let globalFlags' = savedGlobalFlags config `mappend` globalFlags- get verbosity- (globalRepos (savedGlobalFlags config))+ withRepoContext verbosity (savedGlobalFlags config) $ \repoContext ->+ get verbosity+ repoContext globalFlags' getFlags targets -unpackAction :: GetFlags -> [String] -> GlobalFlags -> IO ()+unpackAction :: GetFlags -> [String] -> Action unpackAction getFlags extraArgs globalFlags = do getAction getFlags extraArgs globalFlags -initAction :: InitFlags -> [String] -> GlobalFlags -> IO ()-initAction initFlags _extraArgs globalFlags = do+initAction :: InitFlags -> [String] -> Action+initAction initFlags extraArgs globalFlags = do+ when (extraArgs /= []) $+ die $ "'init' doesn't take any extra arguments: " ++ unwords extraArgs let verbosity = fromFlag (initVerbosity initFlags) (_useSandbox, config) <- loadConfigOrSandboxConfig verbosity (globalFlags { globalRequireSandbox = Flag False }) let configFlags = savedConfigureFlags config let globalFlags' = savedGlobalFlags config `mappend` globalFlags (comp, _, conf) <- configCompilerAux' configFlags- initCabal verbosity+ withRepoContext verbosity globalFlags' $ \repoContext ->+ initCabal verbosity (configPackageDB' configFlags)- (globalRepos globalFlags')+ repoContext comp conf initFlags -sandboxAction :: SandboxFlags -> [String] -> GlobalFlags -> IO ()+sandboxAction :: SandboxFlags -> [String] -> Action sandboxAction sandboxFlags extraArgs globalFlags = do let verbosity = fromFlag (sandboxVerbosity sandboxFlags) case extraArgs of@@ -1153,29 +1247,35 @@ where noExtraArgs = (<1) . length -execAction :: ExecFlags -> [String] -> GlobalFlags -> IO ()+execAction :: ExecFlags -> [String] -> Action execAction execFlags extraArgs globalFlags = do let verbosity = fromFlag (execVerbosity execFlags) (useSandbox, config) <- loadConfigOrSandboxConfig verbosity globalFlags let configFlags = savedConfigureFlags config- (comp, platform, conf) <- configCompilerAux' configFlags+ (comp, platform, conf) <- getPersistOrConfigCompiler configFlags exec verbosity useSandbox comp platform conf extraArgs -userConfigAction :: UserConfigFlags -> [String] -> GlobalFlags -> IO ()+userConfigAction :: UserConfigFlags -> [String] -> Action userConfigAction ucflags extraArgs globalFlags = do let verbosity = fromFlag (userConfigVerbosity ucflags)+ force = fromFlag (userConfigForce ucflags) case extraArgs of+ ("init":_) -> do+ path <- configFile+ fileExists <- doesFileExist path+ if (not fileExists || (fileExists && force))+ then createDefaultConfigFile verbosity path+ else die $ path ++ " already exists." ("diff":_) -> mapM_ putStrLn =<< userConfigDiff globalFlags ("update":_) -> userConfigUpdate verbosity globalFlags -- Error handling. [] -> die $ "Please specify a subcommand (see 'help user-config')" _ -> die $ "Unknown 'user-config' subcommand: " ++ unwords extraArgs-+ where configFile = getConfigFilePath (globalConfigFile globalFlags) -- | See 'Distribution.Client.Install.withWin32SelfUpgrade' for details. ---win32SelfUpgradeAction :: Win32SelfUpgradeFlags -> [String] -> GlobalFlags- -> IO ()+win32SelfUpgradeAction :: Win32SelfUpgradeFlags -> [String] -> Action win32SelfUpgradeAction selfUpgradeFlags (pid:path:_extraArgs) _globalFlags = do let verbosity = fromFlag (win32SelfUpgradeVerbosity selfUpgradeFlags) Win32SelfUpgrade.deleteOldExeFile verbosity (read pid) path@@ -1184,7 +1284,7 @@ -- | Used as an entry point when cabal-install needs to invoke itself -- as a setup script. This can happen e.g. when doing parallel builds. ---actAsSetupAction :: ActAsSetupFlags -> [String] -> GlobalFlags -> IO ()+actAsSetupAction :: ActAsSetupFlags -> [String] -> Action actAsSetupAction actAsSetupFlags args _globalFlags = let bt = fromFlag (actAsSetupBuildType actAsSetupFlags) in case bt of@@ -1194,3 +1294,13 @@ Make -> Make.defaultMainArgs args Custom -> error "actAsSetupAction Custom" (UnknownBuildType _) -> error "actAsSetupAction UnknownBuildType"++manpageAction :: [CommandSpec action] -> Flag Verbosity -> [String] -> Action+manpageAction commands _ extraArgs _ = do+ unless (null extraArgs) $+ die $ "'manpage' doesn't take any extra arguments: " ++ unwords extraArgs+ pname <- getProgName+ let cabalCmd = if takeExtension pname == ".exe"+ then dropExtension pname+ else pname+ putStrLn $ manpage cabalCmd commands
cabal/cabal-install/Setup.hs view
@@ -1,2 +1,63 @@-import Distribution.Simple-main = defaultMain+import Distribution.PackageDescription ( PackageDescription )+import Distribution.Simple ( defaultMainWithHooks+ , simpleUserHooks+ , postBuild+ , postCopy+ , postInst+ )+import Distribution.Simple.InstallDirs ( mandir+ , CopyDest (NoCopyDest)+ )+import Distribution.Simple.LocalBuildInfo ( LocalBuildInfo(..)+ , absoluteInstallDirs+ )+import Distribution.Simple.Utils ( copyFiles+ , notice )+import Distribution.Simple.Setup ( buildVerbosity+ , copyDest+ , copyVerbosity+ , fromFlag+ , installVerbosity+ )+import Distribution.Verbosity ( Verbosity )++import System.IO ( openFile+ , IOMode (WriteMode)+ )+import System.Process ( runProcess )+import System.FilePath ( (</>) )++-- WARNING to editors of this file:+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+-- At this moment (Cabal 1.23), whatever you write here must be+-- compatible with ALL Cabal libraries which we support bootstrapping+-- with. This is because pre-setup-depends versions of cabal-install will+-- build Setup.hs against the version of Cabal which MATCHES the library+-- that cabal-install was built against. There is no way of overriding+-- this behavior without bumping the required 'cabal-version' in our+-- Cabal file. Travis will let you know if we fail to install from+-- tarball!++main :: IO ()+main = defaultMainWithHooks $ simpleUserHooks+ { postBuild = \ _ flags _ lbi ->+ buildManpage lbi (fromFlag $ buildVerbosity flags)+ , postCopy = \ _ flags pkg lbi ->+ installManpage pkg lbi (fromFlag $ copyVerbosity flags) (fromFlag $ copyDest flags)+ , postInst = \ _ flags pkg lbi ->+ installManpage pkg lbi (fromFlag $ installVerbosity flags) NoCopyDest+ }++buildManpage :: LocalBuildInfo -> Verbosity -> IO ()+buildManpage lbi verbosity = do+ let cabal = buildDir lbi </> "cabal/cabal"+ manpage = buildDir lbi </> "cabal/cabal.1"+ manpageHandle <- openFile manpage WriteMode+ notice verbosity ("Generating manual page " ++ manpage ++ " ...")+ _ <- runProcess cabal ["manpage"] Nothing Nothing Nothing (Just manpageHandle) Nothing+ return ()++installManpage :: PackageDescription -> LocalBuildInfo -> Verbosity -> CopyDest -> IO ()+installManpage pkg lbi verbosity copy = do+ let destDir = mandir (absoluteInstallDirs pkg lbi copy) </> "man1"+ copyFiles verbosity destDir [(buildDir lbi </> "cabal", "cabal.1")]
cabal/cabal-install/bash-completion/cabal view
@@ -53,8 +53,27 @@ done } +__cabal_has_doubledash ()+{+ local c=1+ # Ignore the last word, because it is replaced anyways.+ # This allows expansion for flags on "cabal foo --",+ # but does not try to complete after "cabal foo -- ".+ local n=$((${#COMP_WORDS[@]} - 1))+ while [ $c -lt $n ]; do+ if [ "--" = "${COMP_WORDS[c]}" ]; then+ return 0+ fi+ ((c++))+ done+ return 1+}+ _cabal() {+ # no completion past cabal arguments.+ __cabal_has_doubledash && return+ # get the word currently being completed local cur cur=${COMP_WORDS[$COMP_CWORD]}
cabal/cabal-install/bootstrap.sh view
@@ -179,36 +179,52 @@ # The version regex says what existing installed versions are ok. PARSEC_VER="3.1.9"; PARSEC_VER_REGEXP="[3]\.[01]\." # >= 3.0 && < 3.2-DEEPSEQ_VER="1.4.1.1"; DEEPSEQ_VER_REGEXP="1\.[1-9]\."+DEEPSEQ_VER="1.4.1.2"; DEEPSEQ_VER_REGEXP="1\.[1-9]\." # >= 1.1 && < 2-BINARY_VER="0.7.2.3"; BINARY_VER_REGEXP="[0]\.[7]\."- # == 0.7.*-TEXT_VER="1.2.1.1"; TEXT_VER_REGEXP="((1\.[012]\.)|(0\.([2-9]|(1[0-1]))\.))"+BINARY_VER="0.8.2.1"; BINARY_VER_REGEXP="[0]\.[78]\."+ # >= 0.7 && < 0.9+TEXT_VER="1.2.2.1"; TEXT_VER_REGEXP="((1\.[012]\.)|(0\.([2-9]|(1[0-1]))\.))" # >= 0.2 && < 1.3-NETWORK_VER="2.6.2.0"; NETWORK_VER_REGEXP="2\.[0-6]\."+NETWORK_VER="2.6.2.1"; NETWORK_VER_REGEXP="2\.[0-6]\." # >= 2.0 && < 2.7-NETWORK_URI_VER="2.6.0.3"; NETWORK_URI_VER_REGEXP="2\.6\."+NETWORK_URI_VER="2.6.1.0"; NETWORK_URI_VER_REGEXP="2\.6\." # >= 2.6 && < 2.7-CABAL_VER="1.23.0.0"; CABAL_VER_REGEXP="1\.23\."- # >= 1.23 && < 1.24-TRANS_VER="0.4.2.0"; TRANS_VER_REGEXP="0\.[4]\."- # >= 0.2.* && < 0.5+CABAL_VER="1.25.0.0"; CABAL_VER_REGEXP="1\.25\.[0-9]"+ # >= 1.25 && < 1.26+TRANS_VER="0.5.2.0"; TRANS_VER_REGEXP="0\.[45]\."+ # >= 0.2.* && < 0.6 MTL_VER="2.2.1"; MTL_VER_REGEXP="[2]\." # >= 2.0 && < 3-HTTP_VER="4000.2.19"; HTTP_VER_REGEXP="4000\.2\.([5-9]|1[0-9]|2[0-9])"- # >= 4000.2.5 < 4000.3-ZLIB_VER="0.5.4.2"; ZLIB_VER_REGEXP="0\.[45]\."- # == 0.4.* || == 0.5.*-TIME_VER="1.5.0.1" TIME_VER_REGEXP="1\.[12345]\.?"- # >= 1.1 && < 1.6+HTTP_VER="4000.3.3"; HTTP_VER_REGEXP="4000\.(2\.([5-9]|1[0-9]|2[0-9])|3\.?)"+ # >= 4000.2.5 < 4000.4+ZLIB_VER="0.6.1.1"; ZLIB_VER_REGEXP="(0\.5\.([3-9]|1[0-9])|0\.6)"+ # >= 0.5.3 && <= 0.7+TIME_VER="1.6" TIME_VER_REGEXP="1\.[1-6]\.?"+ # >= 1.1 && < 1.7 RANDOM_VER="1.1" RANDOM_VER_REGEXP="1\.[01]\.?" # >= 1 && < 1.2-STM_VER="2.4.4"; STM_VER_REGEXP="2\."+STM_VER="2.4.4.1"; STM_VER_REGEXP="2\." # == 2.*+ASYNC_VER="2.1.0"; ASYNC_VER_REGEXP="2\."+ # 2.* OLD_TIME_VER="1.1.0.3"; OLD_TIME_VER_REGEXP="1\.[01]\.?" # >=1.0.0.0 && <1.2 OLD_LOCALE_VER="1.0.0.7"; OLD_LOCALE_VER_REGEXP="1\.0\.?" # >=1.0.0.0 && <1.1+BYTEABLE_VER="0.1.1"; BYTEABLE_VER_REGEXP="0\.?"+ # 0.1.1+CRYPTOHASH_VER="0.11.7"; CRYPTOHASH_VER_REGEXP="0\.11\.?"+ # 0.11.*+ED25519_VER="0.0.5.0"; ED25519_VER_REGEXP="0\.0\.?"+ # 0.0.*+HACKAGE_SECURITY_VER="0.5.0.2"; HACKAGE_SECURITY_VER_REGEXP="0\.5\.?"+ # >= 0.5 && < 0.6+TAR_VER="0.5.0.1"; TAR_VER_REGEXP="0\.5\.([1-9]|1[0-9]|0\.1)\.?"+ # >= 0.5.0.1 && < 0.6+BASE64_BYTESTRING_VER="1.0.0.1"; BASE64_BYTESTRING_REGEXP="1\."+ # >=1.0+HASHABLE_VER="1.2.4.0"; HASHABLE_VER_REGEXP="1\."+ # 1.* HACKAGE_URL="https://hackage.haskell.org/package" @@ -255,24 +271,32 @@ PKG=$1 VER=$2 - URL=${HACKAGE_URL}/${PKG}-${VER}/${PKG}-${VER}.tar.gz+ URL_PKG=${HACKAGE_URL}/${PKG}-${VER}/${PKG}-${VER}.tar.gz+ URL_PKGDESC=${HACKAGE_URL}/${PKG}-${VER}/${PKG}.cabal if which ${CURL} > /dev/null then # TODO: switch back to resuming curl command once # https://github.com/haskell/hackage-server/issues/111 is resolved- #${CURL} -L --fail -C - -O ${URL} || die "Failed to download ${PKG}."- ${CURL} -L --fail -O ${URL} || die "Failed to download ${PKG}."+ #${CURL} -L --fail -C - -O ${URL_PKG} || die "Failed to download ${PKG}."+ ${CURL} -L --fail -O ${URL_PKG} || die "Failed to download ${PKG}."+ ${CURL} -L --fail -O ${URL_PKGDESC} \+ || die "Failed to download '${PKG}.cabal'." elif which ${WGET} > /dev/null then- ${WGET} -c ${URL} || die "Failed to download ${PKG}."+ ${WGET} -c ${URL_PKG} || die "Failed to download ${PKG}."+ ${WGET} -c ${URL_PKGDESC} || die "Failed to download '${PKG}.cabal'." elif which ${FETCH} > /dev/null then- ${FETCH} ${URL} || die "Failed to download ${PKG}."+ ${FETCH} ${URL_PKG} || die "Failed to download ${PKG}."+ ${FETCH} ${URL_PKGDESC} || die "Failed to download '${PKG}.cabal'." else die "Failed to find a downloader. 'curl', 'wget' or 'fetch' is required." fi [ -f "${PKG}-${VER}.tar.gz" ] ||- die "Downloading ${URL} did not create ${PKG}-${VER}.tar.gz"+ die "Downloading ${URL_PKG} did not create ${PKG}-${VER}.tar.gz"+ [ -f "${PKG}.cabal" ] ||+ die "Downloading ${URL_PKGDESC} did not create ${PKG}.cabal"+ mv "${PKG}.cabal" "${PKG}.cabal.hackage" } unpack_pkg () {@@ -282,6 +306,7 @@ rm -rf "${PKG}-${VER}.tar" "${PKG}-${VER}" ${GZIP_PROGRAM} -d < "${PKG}-${VER}.tar.gz" | ${TAR} -xf - [ -d "${PKG}-${VER}" ] || die "Failed to unpack ${PKG}-${VER}.tar.gz"+ cp "${PKG}.cabal.hackage" "${PKG}-${VER}/${PKG}.cabal" } install_pkg () {@@ -307,7 +332,8 @@ if [ ! ${NO_DOCUMENTATION} ] then- if echo "${PKG}-${VER}" | egrep ${NO_DOCS_PACKAGES_VER_REGEXP} > /dev/null 2>&1+ if echo "${PKG}-${VER}" | egrep ${NO_DOCS_PACKAGES_VER_REGEXP} \+ > /dev/null 2>&1 then echo "Skipping documentation for the ${PKG} package." else@@ -356,8 +382,10 @@ do_pkg "network-uri" ${NETWORK_URI_VER} ${NETWORK_URI_VER_REGEXP} else # Use network < 2.6 && network-uri < 2.6- info_pkg "network-uri" ${NETWORK_URI_DUMMY_VER} ${NETWORK_URI_DUMMY_VER_REGEXP}- do_pkg "network-uri" ${NETWORK_URI_DUMMY_VER} ${NETWORK_URI_DUMMY_VER_REGEXP}+ info_pkg "network-uri" ${NETWORK_URI_DUMMY_VER} \+ ${NETWORK_URI_DUMMY_VER_REGEXP}+ do_pkg "network-uri" ${NETWORK_URI_DUMMY_VER} \+ ${NETWORK_URI_DUMMY_VER_REGEXP} fi } @@ -373,11 +401,21 @@ info_pkg "parsec" ${PARSEC_VER} ${PARSEC_VER_REGEXP} info_pkg "network" ${NETWORK_VER} ${NETWORK_VER_REGEXP} info_pkg "old-locale" ${OLD_LOCALE_VER} ${OLD_LOCALE_VER_REGEXP}-info_pkg "old-time" ${OLD_TIME_VER} ${OLD_TIME_VER_REGEXP}+info_pkg "old-time" ${OLD_TIME_VER} ${OLD_TIME_VER_REGEXP} info_pkg "HTTP" ${HTTP_VER} ${HTTP_VER_REGEXP} info_pkg "zlib" ${ZLIB_VER} ${ZLIB_VER_REGEXP} info_pkg "random" ${RANDOM_VER} ${RANDOM_VER_REGEXP} info_pkg "stm" ${STM_VER} ${STM_VER_REGEXP}+info_pkg "async" ${ASYNC_VER} ${ASYNC_VER_REGEXP}+info_pkg "byteable" ${BYTEABLE_VER} ${BYTEABLE_VER_REGEXP}+info_pkg "cryptohash" ${CRYPTOHASH_VER} ${CRYPTOHASH_VER_REGEXP}+info_pkg "ed25519" ${ED25519_VER} ${ED25519_VER_REGEXP}+info_pkg "tar" ${TAR_VER} ${TAR_VER_REGEXP}+info_pkg "base64-bytestring" ${BASE64_BYTESTRING_VER} \+ ${BASE64_BYTESTRING_VER_REGEXP}+info_pkg "hashable" ${HASHABLE_VER} ${HASHABLE_VER_REGEXP}+info_pkg "hackage-security" ${HACKAGE_SECURITY_VER} \+ ${HACKAGE_SECURITY_VER_REGEXP} do_pkg "deepseq" ${DEEPSEQ_VER} ${DEEPSEQ_VER_REGEXP} do_pkg "binary" ${BINARY_VER} ${BINARY_VER_REGEXP}@@ -393,11 +431,22 @@ do_network_uri_pkg do_pkg "old-locale" ${OLD_LOCALE_VER} ${OLD_LOCALE_VER_REGEXP}-do_pkg "old-time" ${OLD_TIME_VER} ${OLD_TIME_VER_REGEXP}-do_pkg "HTTP" ${HTTP_VER} ${HTTP_VER_REGEXP}-do_pkg "zlib" ${ZLIB_VER} ${ZLIB_VER_REGEXP}-do_pkg "random" ${RANDOM_VER} ${RANDOM_VER_REGEXP}-do_pkg "stm" ${STM_VER} ${STM_VER_REGEXP}+do_pkg "old-time" ${OLD_TIME_VER} ${OLD_TIME_VER_REGEXP}+do_pkg "HTTP" ${HTTP_VER} ${HTTP_VER_REGEXP}+do_pkg "zlib" ${ZLIB_VER} ${ZLIB_VER_REGEXP}+do_pkg "random" ${RANDOM_VER} ${RANDOM_VER_REGEXP}+do_pkg "stm" ${STM_VER} ${STM_VER_REGEXP}+do_pkg "async" ${ASYNC_VER} ${ASYNC_VER_REGEXP}+do_pkg "byteable" ${BYTEABLE_VER} ${BYTEABLE_VER_REGEXP}+do_pkg "cryptohash" ${CRYPTOHASH_VER} ${CRYPTOHASH_VER_REGEXP}+do_pkg "ed25519" ${ED25519_VER} ${ED25519_VER_REGEXP}+do_pkg "tar" ${TAR_VER} ${TAR_VER_REGEXP}+do_pkg "base64-bytestring" ${BASE64_BYTESTRING_VER} \+ ${BASE64_BYTESTRING_VER_REGEXP}+do_pkg "hashable" ${HASHABLE_VER} ${HASHABLE_VER_REGEXP}+do_pkg "hackage-security" ${HACKAGE_SECURITY_VER} \+ ${HACKAGE_SECURITY_VER_REGEXP}+ install_pkg "cabal-install"
cabal/cabal-install/cabal-install.cabal view
@@ -1,5 +1,5 @@ Name: cabal-install-Version: 1.23.0.0+Version: 1.25.0.0 Synopsis: The command-line interface for Cabal and Hackage. Description: The \'cabal\' command-line program simplifies the process of managing@@ -21,13 +21,80 @@ 2007 Isaac Potoczny-Jones <ijones@syntaxpolice.org> 2007-2012 Duncan Coutts <duncan@community.haskell.org> Category: Distribution-Build-type: Simple+Build-type: Custom Cabal-Version: >= 1.10 Extra-Source-Files: README.md bash-completion/cabal bootstrap.sh changelog+ tests/README.md - -- Generated with '../Cabal/misc/gen-extra-source-files.sh | sort'- tests/PackageTests/Freeze/my.cabal+ -- Generated with '../Cabal/misc/gen-extra-source-files.sh'+ -- Do NOT edit this section manually; instead, run the script.+ -- BEGIN gen-extra-source-files+ tests/IntegrationTests/common.sh+ tests/IntegrationTests/custom/plain.err+ tests/IntegrationTests/custom/plain.sh+ tests/IntegrationTests/custom/plain/A.hs+ tests/IntegrationTests/custom/plain/Setup.hs+ tests/IntegrationTests/custom/plain/plain.cabal+ tests/IntegrationTests/exec/Foo.hs+ tests/IntegrationTests/exec/My.hs+ tests/IntegrationTests/exec/adds_sandbox_bin_directory_to_path.out+ tests/IntegrationTests/exec/adds_sandbox_bin_directory_to_path.sh+ tests/IntegrationTests/exec/auto_configures_on_exec.out+ tests/IntegrationTests/exec/auto_configures_on_exec.sh+ tests/IntegrationTests/exec/can_run_executables_installed_in_sandbox.out+ tests/IntegrationTests/exec/can_run_executables_installed_in_sandbox.sh+ tests/IntegrationTests/exec/configures_cabal_to_use_sandbox.sh+ tests/IntegrationTests/exec/configures_ghc_to_use_sandbox.sh+ tests/IntegrationTests/exec/exit_with_failure_without_args.err+ tests/IntegrationTests/exec/exit_with_failure_without_args.sh+ tests/IntegrationTests/exec/my.cabal+ tests/IntegrationTests/exec/runs_given_command.out+ tests/IntegrationTests/exec/runs_given_command.sh+ tests/IntegrationTests/freeze/disable_benchmarks_freezes_bench_deps.sh+ tests/IntegrationTests/freeze/disable_tests_freezes_test_deps.sh+ tests/IntegrationTests/freeze/does_not_freeze_nondeps.sh+ tests/IntegrationTests/freeze/does_not_freeze_self.sh+ tests/IntegrationTests/freeze/dry_run_does_not_create_config.sh+ tests/IntegrationTests/freeze/enable_benchmarks_freezes_bench_deps.sh+ tests/IntegrationTests/freeze/enable_tests_freezes_test_deps.sh+ tests/IntegrationTests/freeze/freezes_direct_dependencies.sh+ tests/IntegrationTests/freeze/freezes_transitive_dependencies.sh+ tests/IntegrationTests/freeze/my.cabal+ tests/IntegrationTests/freeze/runs_without_error.sh+ tests/IntegrationTests/internal-libs/internal_lib_basic.sh+ tests/IntegrationTests/internal-libs/internal_lib_shadow.sh+ tests/IntegrationTests/internal-libs/p/Foo.hs+ tests/IntegrationTests/internal-libs/p/p.cabal+ tests/IntegrationTests/internal-libs/p/p/P.hs+ tests/IntegrationTests/internal-libs/p/q/Q.hs+ tests/IntegrationTests/internal-libs/q/Q.hs+ tests/IntegrationTests/internal-libs/q/q.cabal+ tests/IntegrationTests/manpage/outputs_manpage.sh+ tests/IntegrationTests/multiple-source/finds_second_source_of_multiple_source.sh+ tests/IntegrationTests/multiple-source/p/Setup.hs+ tests/IntegrationTests/multiple-source/p/p.cabal+ tests/IntegrationTests/multiple-source/q/Setup.hs+ tests/IntegrationTests/multiple-source/q/q.cabal+ tests/IntegrationTests/sandbox-sources/fail_removing_source_thats_not_registered.err+ tests/IntegrationTests/sandbox-sources/fail_removing_source_thats_not_registered.sh+ tests/IntegrationTests/sandbox-sources/p/Setup.hs+ tests/IntegrationTests/sandbox-sources/p/p.cabal+ tests/IntegrationTests/sandbox-sources/q/Setup.hs+ tests/IntegrationTests/sandbox-sources/q/q.cabal+ tests/IntegrationTests/sandbox-sources/remove_nonexistent_source.sh+ tests/IntegrationTests/sandbox-sources/report_success_removing_source.out+ tests/IntegrationTests/sandbox-sources/report_success_removing_source.sh+ tests/IntegrationTests/user-config/common.sh+ tests/IntegrationTests/user-config/doesnt_overwrite_without_f.err+ tests/IntegrationTests/user-config/doesnt_overwrite_without_f.sh+ tests/IntegrationTests/user-config/overwrites_with_f.out+ tests/IntegrationTests/user-config/overwrites_with_f.sh+ tests/IntegrationTests/user-config/runs_without_error.out+ tests/IntegrationTests/user-config/runs_without_error.sh+ tests/IntegrationTests/user-config/uses_CABAL_CONFIG.out+ tests/IntegrationTests/user-config/uses_CABAL_CONFIG.sh+ -- END gen-extra-source-files source-repository head type: git@@ -45,6 +112,11 @@ executable cabal main-is: Main.hs ghc-options: -Wall -fwarn-tabs+ if impl(ghc >= 8.0)+ ghc-options: -Wcompat+ -Wnoncanonical-monad-instances+ -Wnoncanonical-monadfail-instances+ other-modules: Distribution.Client.BuildReports.Anonymous Distribution.Client.BuildReports.Storage@@ -64,6 +136,7 @@ Distribution.Client.Dependency.Modular.Builder Distribution.Client.Dependency.Modular.Configured Distribution.Client.Dependency.Modular.ConfiguredConversion+ Distribution.Client.Dependency.Modular.Cycles Distribution.Client.Dependency.Modular.Dependency Distribution.Client.Dependency.Modular.Explore Distribution.Client.Dependency.Modular.Flag@@ -79,11 +152,16 @@ Distribution.Client.Dependency.Modular.Tree Distribution.Client.Dependency.Modular.Validate Distribution.Client.Dependency.Modular.Version+ Distribution.Client.DistDirLayout Distribution.Client.Exec Distribution.Client.Fetch Distribution.Client.FetchUtils+ Distribution.Client.FileMonitor Distribution.Client.Freeze+ Distribution.Client.GenBounds Distribution.Client.Get+ Distribution.Client.Glob+ Distribution.Client.GlobalFlags Distribution.Client.GZipUtils Distribution.Client.Haddock Distribution.Client.HttpUtils@@ -97,16 +175,26 @@ Distribution.Client.InstallSymlink Distribution.Client.JobControl Distribution.Client.List+ Distribution.Client.Manpage+ Distribution.Client.PackageHash Distribution.Client.PackageIndex Distribution.Client.PackageUtils Distribution.Client.ParseUtils+ Distribution.Client.PkgConfigDb Distribution.Client.PlanIndex+ Distribution.Client.ProjectBuilding+ Distribution.Client.ProjectConfig+ Distribution.Client.ProjectConfig.Types+ Distribution.Client.ProjectConfig.Legacy+ Distribution.Client.ProjectPlanning Distribution.Client.Run+ Distribution.Client.RebuildMonad Distribution.Client.Sandbox Distribution.Client.Sandbox.Index Distribution.Client.Sandbox.PackageEnvironment Distribution.Client.Sandbox.Timestamp Distribution.Client.Sandbox.Types+ Distribution.Client.Security.HTTP Distribution.Client.Setup Distribution.Client.SetupWrapper Distribution.Client.SrcDist@@ -129,48 +217,54 @@ -- NOTE: when updating build-depends, don't forget to update version regexps -- in bootstrap.sh. build-depends:- array >= 0.1 && < 0.6,- base >= 4.3 && < 5,+ async >= 2.0 && < 3,+ array >= 0.4 && < 0.6,+ base >= 4.5 && < 5,+ binary >= 0.5 && < 0.9,+ byteable >= 0.1 && < 0.2, bytestring >= 0.9 && < 1,- Cabal >= 1.23 && < 1.24,- containers >= 0.1 && < 0.6,- filepath >= 1.0 && < 1.5,- HTTP >= 4000.1.5 && < 4000.3,+ Cabal >= 1.25 && < 1.26,+ containers >= 0.4 && < 0.6,+ cryptohash >= 0.11 && < 0.12,+ filepath >= 1.3 && < 1.5,+ hashable >= 1.0 && < 2,+ HTTP >= 4000.1.5 && < 4000.4, mtl >= 2.0 && < 3,- pretty >= 1 && < 1.2,+ pretty >= 1.1 && < 1.2, random >= 1 && < 1.2, stm >= 2.0 && < 3,- time >= 1.1 && < 1.6,- zlib >= 0.5.3 && < 0.7+ tar >= 0.5.0.1 && < 0.6,+ time >= 1.4 && < 1.7,+ zlib >= 0.5.3 && < 0.7,+ hackage-security >= 0.5 && < 0.6 if flag(old-directory)- build-depends: directory >= 1 && < 1.2, old-time >= 1 && < 1.2,+ build-depends: directory >= 1.1 && < 1.2, old-time >= 1 && < 1.2, process >= 1.0.1.1 && < 1.1.0.2 else build-depends: directory >= 1.2 && < 1.3,- process >= 1.1.0.2 && < 1.4+ process >= 1.1.0.2 && < 1.5 -- NOTE: you MUST include the network dependency even when network-uri -- is pulled in, otherwise the constraint solver doesn't have enough -- information if flag(network-uri)- build-depends: network-uri >= 2.6, network >= 2.6+ build-depends: network-uri >= 2.6 && < 2.7, network >= 2.6 && < 2.7 else build-depends: network >= 2.4 && < 2.6 + -- Needed for GHC.Generics before GHC 7.6+ if impl(ghc < 7.6)+ build-depends: ghc-prim >= 0.2 && < 0.3+ if os(windows) build-depends: Win32 >= 2 && < 3- cpp-options: -DWIN32 else- build-depends: unix >= 2.0 && < 2.8+ build-depends: unix >= 2.5 && < 2.8 - if arch(arm) && impl(ghc < 7.6)- -- older ghc on arm does not support -threaded- cc-options: -DCABAL_NO_THREADED- else- ghc-options: -threaded+ if !(arch(arm) && impl(ghc < 7.6))+ ghc-options: -threaded - c-sources: cbits/getnumcores.c default-language: Haskell2010 -- Small, fast running tests.@@ -180,13 +274,21 @@ hs-source-dirs: tests, . ghc-options: -Wall -fwarn-tabs other-modules:+ UnitTests.Distribution.Client.ArbitraryInstances UnitTests.Distribution.Client.Targets+ UnitTests.Distribution.Client.Compat.Time UnitTests.Distribution.Client.Dependency.Modular.PSQ UnitTests.Distribution.Client.Dependency.Modular.Solver UnitTests.Distribution.Client.Dependency.Modular.DSL+ UnitTests.Distribution.Client.FileMonitor+ UnitTests.Distribution.Client.Glob UnitTests.Distribution.Client.GZipUtils UnitTests.Distribution.Client.Sandbox+ UnitTests.Distribution.Client.Sandbox.Timestamp+ UnitTests.Distribution.Client.Tar UnitTests.Distribution.Client.UserConfig+ UnitTests.Distribution.Client.ProjectConfig+ UnitTests.Options build-depends: base, array,@@ -198,16 +300,20 @@ process, directory, filepath,+ hashable, stm,+ tar, time, HTTP, zlib,+ binary, random,+ hackage-security, tasty, tasty-hunit, tasty-quickcheck, tagged,- QuickCheck >= 2.5+ QuickCheck >= 2.8.2 if flag(old-directory) build-depends: old-time@@ -217,52 +323,101 @@ else build-depends: network-uri < 2.6, network < 2.6 + if impl(ghc < 7.6)+ build-depends: ghc-prim >= 0.2 && < 0.3+ if os(windows) build-depends: Win32- cpp-options: -DWIN32 else build-depends: unix - if arch(arm)- cc-options: -DCABAL_NO_THREADED+ if !(arch(arm) && impl(ghc < 7.6))+ ghc-options: -threaded+ default-language: Haskell2010++-- Slow solver tests+Test-Suite solver-quickcheck+ type: exitcode-stdio-1.0+ main-is: SolverQuickCheck.hs+ hs-source-dirs: tests, .+ ghc-options: -Wall -fwarn-tabs+ other-modules:+ UnitTests.Distribution.Client.Dependency.Modular.DSL+ UnitTests.Distribution.Client.Dependency.Modular.QuickCheck+ build-depends:+ base,+ array,+ bytestring,+ Cabal,+ containers,+ mtl,+ pretty,+ process,+ directory,+ filepath,+ hashable,+ stm,+ tar,+ time,+ HTTP,+ zlib,+ binary,+ random,+ hackage-security,+ tasty,+ tasty-quickcheck,+ QuickCheck >= 2.8.2,+ pretty-show++ if flag(old-directory)+ build-depends: old-time++ if flag(network-uri)+ build-depends: network-uri >= 2.6, network >= 2.6 else+ build-depends: network-uri < 2.6, network < 2.6++ if impl(ghc < 7.6)+ build-depends: ghc-prim >= 0.2 && < 0.3++ if os(windows)+ build-depends: Win32+ else+ build-depends: unix++ if !(arch(arm) && impl(ghc < 7.6)) ghc-options: -threaded default-language: Haskell2010 --- Large, system tests that build packages.-test-suite package-tests+test-suite integration-tests type: exitcode-stdio-1.0 hs-source-dirs: tests- main-is: PackageTests.hs- other-modules:- PackageTests.Exec.Check- PackageTests.Freeze.Check- PackageTests.MultipleSource.Check- PackageTests.PackageTester+ main-is: IntegrationTests.hs build-depends: Cabal,- QuickCheck >= 2.1.0.1 && < 2.9,+ async, base, bytestring, directory,- extensible-exceptions, filepath, process, regex-posix, tasty,- tasty-hunit,- tasty-quickcheck+ tasty-hunit if os(windows) build-depends: Win32 >= 2 && < 3- cpp-options: -DWIN32 else- build-depends: unix >= 2.0 && < 2.8+ build-depends: unix >= 2.5 && < 2.8 - if arch(arm)- cc-options: -DCABAL_NO_THREADED- else+ if !(arch(arm) && impl(ghc < 7.6)) ghc-options: -threaded ghc-options: -Wall default-language: Haskell2010++custom-setup+ setup-depends: Cabal >= 1.25,+ base,+ process >= 1.1.0.1 && < 1.5,+ filepath >= 1.3 && < 1.5
− cabal/cabal-install/cbits/getnumcores.c
@@ -1,46 +0,0 @@-#if defined(__GLASGOW_HASKELL__) && (__GLASGOW_HASKELL__ >= 612) && !defined(CABAL_NO_THREADED)-/* Since version 6.12, GHC's threaded RTS includes a getNumberOfProcessors- function, so we try to use that if available. cabal-install is always built- with -threaded nowadays. */-#define HAS_GET_NUMBER_OF_PROCESSORS-#endif---#ifndef HAS_GET_NUMBER_OF_PROCESSORS--#ifdef _WIN32-#include <windows.h>-#elif MACOS-#include <sys/param.h>-#include <sys/sysctl.h>-#elif __linux__-#include <unistd.h>-#endif--int getNumberOfProcessors() {-#ifdef WIN32- SYSTEM_INFO sysinfo;- GetSystemInfo(&sysinfo);- return sysinfo.dwNumberOfProcessors;-#elif MACOS- int nm[2];- size_t len = 4;- uint32_t count;-- nm[0] = CTL_HW; nm[1] = HW_AVAILCPU;- sysctl(nm, 2, &count, &len, NULL, 0);-- if(count < 1) {- nm[1] = HW_NCPU;- sysctl(nm, 2, &count, &len, NULL, 0);- if(count < 1) { count = 1; }- }- return count;-#elif __linux__- return sysconf(_SC_NPROCESSORS_ONLN);-#else- return 1;-#endif-}--#endif /* HAS_GET_NUMBER_OF_PROCESSORS */
cabal/cabal-install/changelog view
@@ -1,6 +1,9 @@ -*-change-log-*- -1.23.x.x (current development version)+1.25.x.x (current development version)+ * Made the solver aware of pkg-config constraints (#3023).++1.24.0.0 Ryan Thomas <ryan@ryant.org> March 2016 * If there are multiple remote repos, 'cabal update' now updates them in parallel (#2503). * New 'cabal upload' option '-P'/'--password-command' for reading@@ -18,13 +21,44 @@ * Automatically use https for cabal upload for the main hackage.haskell.org (other repos will use whatever they are configured to use).- * Support for dependencies of custom Setup.hs scripts.+ * Support for dependencies of custom Setup.hs scripts+ (see http://www.well-typed.com/blog/2015/07/cabal-setup-deps/). * 'cabal' program itself now can be used as an external setup method. This fixes an issue when Cabal version mismatch caused unnecessary reconfigures (#2633). * Improved error message for unsatisfiable package constraints (#2727). * Fixed a space leak in 'cabal update' (#2826).+ * 'cabal exec' and 'sandbox hc-pkg' now use the configured+ compiler (#2859).+ * New 'cabal haddock' option: '--for-hackage' (#2852).+ * Added a warning when the solver cannot find a dependency (#2853).+ * New 'cabal upload' option: '--doc': upload documentation to+ hackage (#2890).+ * Improved error handling for 'sandbox delete-source' (#2943).+ * Solver support for extension and language flavours (#2873).+ * Support for secure repos using hackage-security (#2983).+ * Added a log file message similar to one printed by 'make' when+ building in another directory (#2642).+ * Added new subcommand 'init' to 'cabal user-config'. This+ subcommand creates a cabal configuration file in either the+ default location or as specified by --config-file (#2553).+ * The man page for 'cabal-install' is now automatically generated+ (#2877).+ * The '--allow-newer' option now works as expected when specified+ multiple times (#2588).+ * New config file field: 'extra-framework-dirs' (extra locations+ to find OS X frameworks in). Can be also specified as an argument+ for 'install' and 'configure' commands (#3158).+ * It's now possible to limit the scope of '--allow-newer' to+ single packages in the install plan (#2756).+ * Full '--allow-newer' syntax is now supported in the config file+ (that is, 'allow-newer: base, ghc-prim, some-package:vector')+ (#3171).+ * Improved performance of '--reorder-goals' (#3208).+ * Fixed space leaks in modular solver (#2916, #2914).+ * Added a new command: 'gen-bounds' (#3223). See+ http://softwaresimply.blogspot.se/2015/08/cabal-gen-bounds-easy-generation-of.html. 1.22.0.0 Johan Tibell <johan.tibell@gmail.com> January 2015 * New command: user-config (#2159).
+ cabal/cabal-install/tests/IntegrationTests.hs view
@@ -0,0 +1,319 @@+{-# LANGUAGE CPP #-}+-- | Groups black-box tests of cabal-install and configures them to test+-- the correct binary.+--+-- This file should do nothing but import tests from other modules and run+-- them with the path to the correct cabal-install binary.+module Main+ where++-- Modules from Cabal.+import Distribution.Compat.CreatePipe (createPipe)+import Distribution.Compat.Environment (setEnv)+import Distribution.Compat.Internal.TempFile (createTempDirectory)+import Distribution.Simple.Configure (findDistPrefOrDefault)+import Distribution.Simple.Program.Builtin (ghcPkgProgram)+import Distribution.Simple.Program.Db+ (defaultProgramDb, requireProgram, setProgramSearchPath)+import Distribution.Simple.Program.Find+ (ProgramSearchPathEntry(ProgramSearchPathDir), defaultProgramSearchPath)+import Distribution.Simple.Program.Types+ ( Program(..), simpleProgram, programPath)+import Distribution.Simple.Setup ( Flag(..) )+import Distribution.Simple.Utils ( findProgramVersion, copyDirectoryRecursive, installOrdinaryFile )+import Distribution.Verbosity (normal)++-- Third party modules.+import Control.Concurrent.Async (withAsync, wait)+import Control.Exception (bracket)+import Data.Maybe (fromMaybe)+import System.Directory+ ( canonicalizePath+ , findExecutable+ , getDirectoryContents+ , getTemporaryDirectory+ , doesDirectoryExist+ , removeDirectoryRecursive+ , doesFileExist )+import System.FilePath+import Test.Tasty (TestTree, defaultMain, testGroup)+import Test.Tasty.HUnit (testCase, Assertion, assertFailure)+import Control.Monad ( filterM, forM, unless, when )+import Data.List (isPrefixOf, isSuffixOf, sort)+import Data.IORef (newIORef, writeIORef, readIORef)+import System.Exit (ExitCode(..))+import System.IO (withBinaryFile, IOMode(ReadMode))+import System.Process (runProcess, waitForProcess)+import Text.Regex.Posix ((=~))+import qualified Data.ByteString as B+import qualified Data.ByteString.Char8 as C8+import Data.ByteString (ByteString)++#if MIN_VERSION_base(4,6,0)+import System.Environment ( getExecutablePath )+#endif++-- | Test case.+data TestCase = TestCase+ { tcName :: String -- ^ Name of the shell script+ , tcBaseDirectory :: FilePath -- ^ The base directory where tests are found+ -- e.g., cabal-install/tests/IntegrationTests+ , tcCategory :: String -- ^ Category of test; e.g., custom, exec, freeze, ...+ , tcStdOutPath :: Maybe FilePath -- ^ File path of expected standard output+ , tcStdErrPath :: Maybe FilePath -- ^ File path of expected standard error+ }++-- | Test result.+data TestResult = TestResult+ { trExitCode :: ExitCode -- ^ Exit code of test script+ , trStdOut :: ByteString -- ^ Actual standard output+ , trStdErr :: ByteString -- ^ Actual standard error+ , trWorkingDirectory :: FilePath -- ^ Temporary working directory test was+ -- executed in.+ }++-- | Cabal executable+cabalProgram :: Program+cabalProgram = (simpleProgram "cabal") {+ programFindVersion = findProgramVersion "--numeric-version" id+ }++-- | Convert test result to user-friendly string message.+testResultToString :: TestResult -> String+testResultToString testResult =+ exitStatus ++ "\n" ++ workingDirectory ++ "\n\n" ++ stdOut ++ "\n\n" ++ stdErr+ where+ exitStatus = "Exit status: " ++ show (trExitCode testResult)+ workingDirectory = "Working directory: " ++ (trWorkingDirectory testResult)+ stdOut = "<stdout> was:\n" ++ C8.unpack (trStdOut testResult)+ stdErr = "<stderr> was:\n" ++ C8.unpack (trStdErr testResult)++-- | Returns the command that was issued, the return code, and the output text+run :: FilePath -> String -> [String] -> IO TestResult+run cwd path args = do+ -- path is relative to the current directory; canonicalizePath makes it+ -- absolute, so that runProcess will find it even when changing directory.+ path' <- canonicalizePath path++ (pid, hReadStdOut, hReadStdErr) <- do+ -- Create pipes for StdOut and StdErr+ (hReadStdOut, hWriteStdOut) <- createPipe+ (hReadStdErr, hWriteStdErr) <- createPipe+ -- Run the process+ pid <- runProcess path' args (Just cwd) Nothing Nothing (Just hWriteStdOut) (Just hWriteStdErr)+ -- Return the pid and read ends of the pipes+ return (pid, hReadStdOut, hReadStdErr)+ -- Read subprocess output using asynchronous threads; we need to+ -- do this aynchronously to avoid deadlocks due to buffers filling+ -- up.+ withAsync (B.hGetContents hReadStdOut) $ \stdOutAsync -> do+ withAsync (B.hGetContents hReadStdErr) $ \stdErrAsync -> do+ -- Wait for the subprocess to terminate+ exitcode <- waitForProcess pid+ -- We can now be sure that no further output is going to arrive,+ -- so we wait for the results of the asynchronous reads.+ stdOut <- wait stdOutAsync+ stdErr <- wait stdErrAsync+ -- Done+ return $ TestResult exitcode stdOut stdErr cwd++-- | Get a list of all names in a directory, excluding all hidden or+-- system files/directories such as '.', '..' or any files/directories+-- starting with a '.'.+listDirectory :: FilePath -> IO [String]+listDirectory directory = do+ fmap (filter notHidden) $ getDirectoryContents directory+ where+ notHidden = not . isHidden+ isHidden name = "." `isPrefixOf` name++-- | List a directory as per 'listDirectory', but return an empty list+-- in case the directory does not exist.+listDirectoryLax :: FilePath -> IO [String]+listDirectoryLax directory = do+ d <- doesDirectoryExist directory+ if d then+ listDirectory directory+ else+ return [ ]++-- | @pathIfExists p == return (Just p)@ if @p@ exists, and+-- @return Nothing@ otherwise.+pathIfExists :: FilePath -> IO (Maybe FilePath)+pathIfExists p = do+ e <- doesFileExist p+ if e then+ return $ Just p+ else+ return Nothing++-- | Checks if file @p@ matches a 'ByteString' @s@, subject+-- to line-break normalization. Lines in the file which are+-- prefixed with @RE:@ are treated specially as a regular expression.+fileMatchesString :: FilePath -> ByteString -> IO Bool+fileMatchesString p s = do+ withBinaryFile p ReadMode $ \h -> do+ expected <- (C8.lines . normalizeLinebreaks) `fmap` B.hGetContents h -- Strict+ let actual = C8.lines $ normalizeLinebreaks s+ return $ length expected == length actual &&+ and (zipWith matches expected actual)+ where+ matches :: ByteString -> ByteString -> Bool+ matches pattern line+ | C8.pack "RE:" `B.isPrefixOf` pattern = line =~ C8.drop 3 pattern+ | otherwise = line == pattern++ -- This is a bit of a hack, but since we're comparing+ -- *text* output, we should be OK.+ normalizeLinebreaks = B.filter (not . ((==) 13)) -- carriage return++-- | Check if the @actual@ 'ByteString' matches the contents of+-- @expected@ (always succeeds if the expectation is 'Nothing').+-- Also takes the 'TestResult' and a label @handleName@ describing+-- what text the string values correspond to.+mustMatch :: TestResult -> String -> ByteString -> Maybe FilePath -> Assertion+mustMatch _ _ _ Nothing = return ()+mustMatch testResult handleName actual (Just expected) = do+ m <- fileMatchesString expected actual+ unless m $ assertFailure $+ "<" ++ handleName ++ "> did not match file '"+ ++ expected ++ "'.\n" ++ testResultToString testResult++-- | Given a @directory@, return its subdirectories. This is+-- run on @cabal-install/tests/IntegrationTests@ to get the+-- list of test categories which can be run.+discoverTestCategories :: FilePath -> IO [String]+discoverTestCategories directory = do+ names <- listDirectory directory+ fmap sort $ filterM (\name -> doesDirectoryExist $ directory </> name) names++-- | Find all shell scripts in @baseDirectory </> category@.+discoverTestCases :: FilePath -> String -> IO [TestCase]+discoverTestCases baseDirectory category = do+ -- Find the names of the shell scripts+ names <- fmap (filter isTestCase) $ listDirectoryLax directory+ -- Fill in TestCase for each script+ forM (sort names) $ \name -> do+ stdOutPath <- pathIfExists $ directory </> name `replaceExtension` ".out"+ stdErrPath <- pathIfExists $ directory </> name `replaceExtension` ".err"+ return $ TestCase { tcName = name+ , tcBaseDirectory = baseDirectory+ , tcCategory = category+ , tcStdOutPath = stdOutPath+ , tcStdErrPath = stdErrPath+ }+ where+ directory = baseDirectory </> category+ isTestCase name = ".sh" `isSuffixOf` name++-- | Given a list of 'TestCase's (describing a shell script for a+-- single test case), run @mk@ on each of them to form a runnable+-- 'TestTree'.+createTestCases :: [TestCase] -> (TestCase -> Assertion) -> IO [TestTree]+createTestCases testCases mk =+ return $ (flip map) testCases $ \tc -> testCase (tcName tc ++ suffix tc) $ mk tc+ where+ suffix tc = case (tcStdOutPath tc, tcStdErrPath tc) of+ (Nothing, Nothing) -> " (ignoring stdout+stderr)"+ (Just _ , Nothing) -> " (ignoring stderr)"+ (Nothing, Just _ ) -> " (ignoring stdout)"+ (Just _ , Just _ ) -> ""++-- | Given a 'TestCase', run it.+--+-- A test case of the form @category/testname.sh@ is+-- run in the following way+--+-- 1. We make a full copy all of @category@ into a temporary+-- directory.+-- 2. In the temporary directory, run @testname.sh@.+-- 3. Test that the exit code is zero, and that stdout/stderr+-- match the expected results.+--+runTestCase :: TestCase -> IO ()+runTestCase tc = do+ doRemove <- newIORef False+ bracket createWorkDirectory (removeWorkDirectory doRemove) $ \workDirectory -> do+ -- Run+ let scriptDirectory = workDirectory+ sh <- fmap (fromMaybe $ error "Cannot find 'sh' executable") $ findExecutable "sh"+ testResult <- run scriptDirectory sh [ "-e", tcName tc]+ -- Assert that we got what we expected+ case trExitCode testResult of+ ExitSuccess ->+ return () -- We're good+ ExitFailure _ ->+ assertFailure $ "Unexpected exit status.\n\n" ++ testResultToString testResult+ mustMatch testResult "stdout" (trStdOut testResult) (tcStdOutPath tc)+ mustMatch testResult "stderr" (trStdErr testResult) (tcStdErrPath tc)+ -- Only remove working directory if test succeeded+ writeIORef doRemove True+ where+ createWorkDirectory = do+ -- Create the temporary directory+ tempDirectory <- getTemporaryDirectory+ workDirectory <- createTempDirectory tempDirectory "cabal-install-test"+ -- Copy all the files from the category into the working directory.+ copyDirectoryRecursive normal+ (tcBaseDirectory tc </> tcCategory tc)+ workDirectory+ -- Copy in the common.sh stub+ let commonDest = workDirectory </> "common.sh"+ e <- doesFileExist commonDest+ unless e $+ installOrdinaryFile normal (tcBaseDirectory tc </> "common.sh") commonDest+ -- Done+ return workDirectory+ removeWorkDirectory doRemove workDirectory = do+ remove <- readIORef doRemove+ when remove $ removeDirectoryRecursive workDirectory++discoverCategoryTests :: FilePath -> String -> IO [TestTree]+discoverCategoryTests baseDirectory category = do+ testCases <- discoverTestCases baseDirectory category+ createTestCases testCases runTestCase++main :: IO ()+main = do+ -- Find executables and build directories, etc.+ distPref <- guessDistDir+ buildDir <- canonicalizePath (distPref </> "build/cabal")+ let programSearchPath = ProgramSearchPathDir buildDir : defaultProgramSearchPath+ (cabal, _) <- requireProgram normal cabalProgram (setProgramSearchPath programSearchPath defaultProgramDb)+ (ghcPkg, _) <- requireProgram normal ghcPkgProgram defaultProgramDb+ baseDirectory <- canonicalizePath $ "tests" </> "IntegrationTests"+ -- Set up environment variables for test scripts+ setEnv "GHC_PKG" $ programPath ghcPkg+ setEnv "CABAL" $ programPath cabal+ -- Define default arguments+ setEnv "CABAL_ARGS" $ "--config-file=config-file"+ setEnv "CABAL_ARGS_NO_CONFIG_FILE" " "+ -- Discover all the test categories+ categories <- discoverTestCategories baseDirectory+ -- Discover tests in each category+ tests <- forM categories $ \category -> do+ categoryTests <- discoverCategoryTests baseDirectory category+ return (category, categoryTests)+ -- Map into a test tree+ let testTree = map (\(category, categoryTests) -> testGroup category categoryTests) tests+ -- Run the tests+ defaultMain $ testGroup "Integration Tests" $ testTree++-- See this function in Cabal's PackageTests. If you update this,+-- update its copy in cabal-install. (Why a copy here? I wanted+-- to try moving this into the Cabal library, but to do this properly+-- I'd have to BC'ify getExecutablePath, and then it got hairy, so+-- I aborted and did something simple.)+guessDistDir :: IO FilePath+guessDistDir = do+#if MIN_VERSION_base(4,6,0)+ exe_path <- canonicalizePath =<< getExecutablePath+ let dist0 = dropFileName exe_path </> ".." </> ".."+ b <- doesFileExist (dist0 </> "setup-config")+#else+ let dist0 = error "no path"+ b = False+#endif+ -- Method (2)+ if b then canonicalizePath dist0+ else findDistPrefOrDefault NoFlag >>= canonicalizePath
+ cabal/cabal-install/tests/IntegrationTests/common.sh view
@@ -0,0 +1,12 @@+# A globally available set of useful utilities. Test+# scripts can include this by saying ". ./common.sh"++# Helper to run Cabal+cabal() {+ "$CABAL" $CABAL_ARGS "$@"+}++die() {+ echo "die: $@"+ exit 1+}
+ cabal/cabal-install/tests/IntegrationTests/custom/plain.err view
@@ -0,0 +1,2 @@+Custom+Custom
+ cabal/cabal-install/tests/IntegrationTests/custom/plain.sh view
@@ -0,0 +1,4 @@+. ./common.sh+cd plain+cabal configure+cabal build
+ cabal/cabal-install/tests/IntegrationTests/custom/plain/A.hs view
@@ -0,0 +1,1 @@+module A where
+ cabal/cabal-install/tests/IntegrationTests/custom/plain/Setup.hs view
@@ -0,0 +1,3 @@+import Distribution.Simple+import System.IO+main = hPutStrLn stderr "Custom" >> defaultMain
+ cabal/cabal-install/tests/IntegrationTests/custom/plain/plain.cabal view
@@ -0,0 +1,12 @@+name: plain+version: 0.1.0.0+license: BSD3+author: Edward Z. Yang+maintainer: ezyang@cs.stanford.edu+build-type: Custom+cabal-version: >=1.10++library+ exposed-modules: A+ build-depends: base+ default-language: Haskell2010
+ cabal/cabal-install/tests/IntegrationTests/exec/Foo.hs view
@@ -0,0 +1,4 @@+module Foo where++foo :: String+foo = "foo"
+ cabal/cabal-install/tests/IntegrationTests/exec/My.hs view
@@ -0,0 +1,5 @@+module Main where++main :: IO ()+main = do+ putStrLn "This is my-executable"
+ cabal/cabal-install/tests/IntegrationTests/exec/adds_sandbox_bin_directory_to_path.out view
@@ -0,0 +1,1 @@+This is my-executable
+ cabal/cabal-install/tests/IntegrationTests/exec/adds_sandbox_bin_directory_to_path.sh view
@@ -0,0 +1,10 @@+. ./common.sh++cabal sandbox delete > /dev/null+cabal exec my-executable && die "Unexpectedly found executable"++cabal sandbox init > /dev/null+cabal install > /dev/null++# Execute indirectly via bash to ensure that we go through $PATH+cabal exec sh -- -c my-executable || die "Did not find executable"
+ cabal/cabal-install/tests/IntegrationTests/exec/auto_configures_on_exec.out view
@@ -0,0 +1,4 @@+Config file path source is commandline option.+Config file config-file not found.+Writing default configuration to config-file+find_me_in_output
+ cabal/cabal-install/tests/IntegrationTests/exec/auto_configures_on_exec.sh view
@@ -0,0 +1,2 @@+. ./common.sh+cabal exec echo find_me_in_output
+ cabal/cabal-install/tests/IntegrationTests/exec/can_run_executables_installed_in_sandbox.out view
@@ -0,0 +1,1 @@+This is my-executable
+ cabal/cabal-install/tests/IntegrationTests/exec/can_run_executables_installed_in_sandbox.sh view
@@ -0,0 +1,9 @@+. ./common.sh++cabal sandbox delete > /dev/null+cabal exec my-executable && die "Unexpectedly found executable"++cabal sandbox init > /dev/null+cabal install > /dev/null++cabal exec my-executable || die "Did not find executable"
+ cabal/cabal-install/tests/IntegrationTests/exec/configures_cabal_to_use_sandbox.sh view
@@ -0,0 +1,14 @@+. ./common.sh++cabal sandbox delete > /dev/null+cabal exec my-executable && die "Unexpectedly found executable"++cabal sandbox init > /dev/null+cabal install > /dev/null++# The library should not be available outside the sandbox+"$GHC_PKG" list | grep -v "my-0.1"++# When run inside 'cabal-exec' the 'sandbox hc-pkg list' sub-command+# should find the library.+cabal exec sh -- -c 'cd subdir && "$CABAL" sandbox hc-pkg list' | grep "my-0.1"
+ cabal/cabal-install/tests/IntegrationTests/exec/configures_ghc_to_use_sandbox.sh view
@@ -0,0 +1,13 @@+. ./common.sh++cabal sandbox delete > /dev/null+cabal exec my-executable && die "Unexpectedly found executable"++cabal sandbox init > /dev/null+cabal install > /dev/null++# The library should not be available outside the sandbox+"$GHC_PKG" list | grep -v "my-0.1"++# Execute ghc-pkg inside the sandbox; it should find my-0.1+cabal exec ghc-pkg list | grep "my-0.1"
+ cabal/cabal-install/tests/IntegrationTests/exec/exit_with_failure_without_args.err view
@@ -0,0 +1,1 @@+RE:^cabal(\.exe)?: Please specify an executable to run$
+ cabal/cabal-install/tests/IntegrationTests/exec/exit_with_failure_without_args.sh view
@@ -0,0 +1,3 @@+. ./common.sh++! cabal exec
+ cabal/cabal-install/tests/IntegrationTests/exec/my.cabal view
@@ -0,0 +1,14 @@+name: my+version: 0.1+license: BSD3+cabal-version: >= 1.2+build-type: Simple++library+ exposed-modules: Foo+ build-depends: base+++executable my-executable+ main-is: My.hs+ build-depends: base
+ cabal/cabal-install/tests/IntegrationTests/exec/runs_given_command.out view
@@ -0,0 +1,1 @@+this string
+ cabal/cabal-install/tests/IntegrationTests/exec/runs_given_command.sh view
@@ -0,0 +1,3 @@+. ./common.sh+cabal configure > /dev/null+cabal exec echo this string
+ cabal/cabal-install/tests/IntegrationTests/exec/subdir/.gitkeep view
+ cabal/cabal-install/tests/IntegrationTests/freeze/disable_benchmarks_freezes_bench_deps.sh view
@@ -0,0 +1,3 @@+. ./common.sh+cabal freeze --disable-benchmarks+grep -v " criterion ==" cabal.config || die "should NOT have frozen criterion"
+ cabal/cabal-install/tests/IntegrationTests/freeze/disable_tests_freezes_test_deps.sh view
@@ -0,0 +1,3 @@+. ./common.sh+cabal freeze --disable-tests+grep -v " test-framework ==" cabal.config || die "should NOT have frozen test-framework"
+ cabal/cabal-install/tests/IntegrationTests/freeze/does_not_freeze_nondeps.sh view
@@ -0,0 +1,5 @@+. ./common.sh+# TODO: Test this against a package installed in the sandbox but not+# depended upon.+cabal freeze+grep -v "exceptions ==" cabal.config || die "should not have frozen exceptions"
+ cabal/cabal-install/tests/IntegrationTests/freeze/does_not_freeze_self.sh view
@@ -0,0 +1,3 @@+. ./common.sh+cabal freeze+grep -v " my ==" cabal.config || die "should not have frozen self"
+ cabal/cabal-install/tests/IntegrationTests/freeze/dry_run_does_not_create_config.sh view
@@ -0,0 +1,3 @@+. ./common.sh+cabal freeze --dry-run+[ ! -e cabal.config ] || die "cabal.config file should not have been created"
+ cabal/cabal-install/tests/IntegrationTests/freeze/enable_benchmarks_freezes_bench_deps.sh view
@@ -0,0 +1,4 @@+. ./common.sh+# TODO: solver should find solution without extra flags too+cabal freeze --enable-benchmarks --reorder-goals --max-backjumps=-1+grep " criterion ==" cabal.config || die "should have frozen criterion"
+ cabal/cabal-install/tests/IntegrationTests/freeze/enable_tests_freezes_test_deps.sh view
@@ -0,0 +1,3 @@+. ./common.sh+cabal freeze --enable-tests+grep " test-framework ==" cabal.config || die "should have frozen test-framework"
+ cabal/cabal-install/tests/IntegrationTests/freeze/freezes_direct_dependencies.sh view
@@ -0,0 +1,3 @@+. ./common.sh+cabal freeze+grep " base ==" cabal.config || die "'base' should have been frozen"
+ cabal/cabal-install/tests/IntegrationTests/freeze/freezes_transitive_dependencies.sh view
@@ -0,0 +1,3 @@+. ./common.sh+cabal freeze+grep " ghc-prim ==" cabal.config || die "'ghc-prim' should have been frozen"
+ cabal/cabal-install/tests/IntegrationTests/freeze/my.cabal view
@@ -0,0 +1,21 @@+name: my+version: 0.1+license: BSD3+cabal-version: >= 1.20.0+build-type: Simple++library+ exposed-modules: Foo+ build-depends: base++test-suite test-Foo+ type: exitcode-stdio-1.0+ hs-source-dirs: tests+ main-is: test-Foo.hs+ build-depends: base, my, test-framework++benchmark bench-Foo+ type: exitcode-stdio-1.0+ hs-source-dirs: benchmarks+ main-is: benchmark-Foo.hs+ build-depends: base, my, criterion
+ cabal/cabal-install/tests/IntegrationTests/freeze/runs_without_error.sh view
@@ -0,0 +1,2 @@+. ./common.sh+cabal freeze
+ cabal/cabal-install/tests/IntegrationTests/internal-libs/internal_lib_basic.sh view
@@ -0,0 +1,5 @@+. ./common.sh++cabal sandbox init+cabal sandbox add-source p+cabal install p
+ cabal/cabal-install/tests/IntegrationTests/internal-libs/internal_lib_shadow.sh view
@@ -0,0 +1,6 @@+. ./common.sh++cabal sandbox init+cabal sandbox add-source p+cabal sandbox add-source q+cabal install p
+ cabal/cabal-install/tests/IntegrationTests/internal-libs/p/Foo.hs view
@@ -0,0 +1,2 @@+import Q+main = putStrLn q
+ cabal/cabal-install/tests/IntegrationTests/internal-libs/p/p.cabal view
@@ -0,0 +1,23 @@+name: p+version: 0.1.0.0+license: BSD3+author: Edward Z. Yang+maintainer: ezyang@cs.stanford.edu+build-type: Simple+cabal-version: >=1.23++library q+ build-depends: base+ exposed-modules: Q+ hs-source-dirs: q+ default-language: Haskell2010++library+ build-depends: base, q+ exposed-modules: P+ hs-source-dirs: p+ default-language: Haskell2010++executable foo+ build-depends: base, q+ main-is: Foo.hs
+ cabal/cabal-install/tests/IntegrationTests/internal-libs/p/p/P.hs view
@@ -0,0 +1,2 @@+module P where+import Q
+ cabal/cabal-install/tests/IntegrationTests/internal-libs/p/q/Q.hs view
@@ -0,0 +1,2 @@+module Q where+q = "I AM THE ONE"
+ cabal/cabal-install/tests/IntegrationTests/internal-libs/q/Q.hs view
@@ -0,0 +1,2 @@+module Q where+q = "DO NOT SEE ME"
+ cabal/cabal-install/tests/IntegrationTests/internal-libs/q/q.cabal view
@@ -0,0 +1,12 @@+name: q+version: 0.1.0.0+license: BSD3+author: Edward Z. Yang+maintainer: ezyang@cs.stanford.edu+build-type: Simple+cabal-version: >=1.10++library+ exposed-modules: Q+ build-depends: base+ default-language: Haskell2010
+ cabal/cabal-install/tests/IntegrationTests/manpage/outputs_manpage.sh view
@@ -0,0 +1,11 @@+. ./common.sh++OUTPUT=`cabal manpage`++# contains visible command descriptions+echo $OUTPUT | grep -q '\.B cabal install' || die "visible command description line not found in:\n----$OUTPUT\n----"++# does not contain hidden command descriptions+echo $OUTPUT | grep -q '\.B cabal manpage' && die "hidden command description line found in:\n----$OUTPUT\n----"++exit 0
+ cabal/cabal-install/tests/IntegrationTests/multiple-source/finds_second_source_of_multiple_source.sh view
@@ -0,0 +1,11 @@+. ./common.sh++# Create the sandbox+cabal sandbox init++# Add the sources+cabal sandbox add-source p+cabal sandbox add-source q++# Install the second package+cabal install q
+ cabal/cabal-install/tests/IntegrationTests/multiple-source/p/LICENSE view
+ cabal/cabal-install/tests/IntegrationTests/multiple-source/p/Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ cabal/cabal-install/tests/IntegrationTests/multiple-source/p/p.cabal view
@@ -0,0 +1,11 @@+name: p+version: 0.1.0.0+license-file: LICENSE+author: Edward Z. Yang+maintainer: ezyang@cs.stanford.edu+build-type: Simple+cabal-version: >=1.10++library+ build-depends: base+ default-language: Haskell2010
+ cabal/cabal-install/tests/IntegrationTests/multiple-source/q/LICENSE view
+ cabal/cabal-install/tests/IntegrationTests/multiple-source/q/Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ cabal/cabal-install/tests/IntegrationTests/multiple-source/q/q.cabal view
@@ -0,0 +1,11 @@+name: q+version: 0.1.0.0+license-file: LICENSE+author: Edward Z. Yang+maintainer: ezyang@cs.stanford.edu+build-type: Simple+cabal-version: >=1.10++library+ build-depends: base+ default-language: Haskell2010
+ cabal/cabal-install/tests/IntegrationTests/sandbox-sources/fail_removing_source_thats_not_registered.err view
@@ -0,0 +1,3 @@+Warning: Sources not registered: "q"++RE:^cabal(\.exe)?: The sources with the above errors were skipped\. \("q"\)$
+ cabal/cabal-install/tests/IntegrationTests/sandbox-sources/fail_removing_source_thats_not_registered.sh view
@@ -0,0 +1,10 @@+. ./common.sh++# Create the sandbox+cabal sandbox init > /dev/null++# Add one source+cabal sandbox add-source p > /dev/null++# Remove a source that exists on disk, but is not registered+! cabal sandbox delete-source q
+ cabal/cabal-install/tests/IntegrationTests/sandbox-sources/p/LICENSE view
+ cabal/cabal-install/tests/IntegrationTests/sandbox-sources/p/Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ cabal/cabal-install/tests/IntegrationTests/sandbox-sources/p/p.cabal view
@@ -0,0 +1,11 @@+name: p+version: 0.1.0.0+license-file: LICENSE+author: Edward Z. Yang+maintainer: ezyang@cs.stanford.edu+build-type: Simple+cabal-version: >=1.10++library+ build-depends: base+ default-language: Haskell2010
+ cabal/cabal-install/tests/IntegrationTests/sandbox-sources/q/LICENSE view
+ cabal/cabal-install/tests/IntegrationTests/sandbox-sources/q/Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ cabal/cabal-install/tests/IntegrationTests/sandbox-sources/q/q.cabal view
@@ -0,0 +1,11 @@+name: q+version: 0.1.0.0+license-file: LICENSE+author: Edward Z. Yang+maintainer: ezyang@cs.stanford.edu+build-type: Simple+cabal-version: >=1.10++library+ build-depends: base+ default-language: Haskell2010
+ cabal/cabal-install/tests/IntegrationTests/sandbox-sources/remove_nonexistent_source.sh view
@@ -0,0 +1,22 @@+. ./common.sh++# Create the sandbox+cabal sandbox init++# Add the sources+cabal sandbox add-source p+cabal sandbox add-source q++# delete the directory on disk+rm -R p++# Remove the registered source which is no longer on disk. cabal's handling of+# non-existent sources depends on the behavior of the directory package.+if OUTPUT=`cabal sandbox delete-source p 2>&1`; then+ # 'canonicalizePath' should always succeed with directory >= 1.2.3.0+ echo $OUTPUT | grep 'Success deleting sources: "p"' \+ || die "Incorrect success message: $OUTPUT"+else+ echo $OUTPUT | grep 'Warning: Source directory not found for paths: "p"' \+ || die "Incorrect failure message: $OUTPUT"+fi
+ cabal/cabal-install/tests/IntegrationTests/sandbox-sources/report_success_removing_source.out view
@@ -0,0 +1,6 @@+Success deleting sources: "p" "q"++Note: 'sandbox delete-source' only unregisters the source dependency, but does+not remove the package from the sandbox package DB.++Use 'sandbox hc-pkg -- unregister' to do that.
+ cabal/cabal-install/tests/IntegrationTests/sandbox-sources/report_success_removing_source.sh view
@@ -0,0 +1,11 @@+. ./common.sh++# Create the sandbox+cabal sandbox init > /dev/null++# Add the sources+cabal sandbox add-source p > /dev/null+cabal sandbox add-source q > /dev/null++# Remove one of the sources+cabal sandbox delete-source p q
+ cabal/cabal-install/tests/IntegrationTests/user-config/cabal-config view
@@ -0,0 +1,182 @@+-- This is the configuration file for the 'cabal' command line tool.++-- The available configuration options are listed below.+-- Some of them have default values listed.++-- Lines (like this one) beginning with '--' are comments.+-- Be careful with spaces and indentation because they are+-- used to indicate layout for nested sections.+++repository hackage.haskell.org+ url: http://hackage.haskell.org/++-- default-user-config:+-- require-sandbox: False+-- ignore-sandbox: False+-- ignore-expiry: False+-- http-transport:+remote-repo-cache: /home/gman/.cabal/packages+-- local-repo:+-- logs-dir:+world-file: /home/gman/.cabal/world+-- verbose: 1+-- compiler: ghc+-- with-compiler:+-- with-hc-pkg:+-- program-prefix:+-- program-suffix:+-- library-vanilla: True+-- library-profiling:+-- shared:+-- executable-dynamic: False+-- profiling:+-- executable-profiling:+-- profiling-detail:+-- library-profiling-detail:+-- optimization: True+-- debug-info: False+-- library-for-ghci:+-- split-objs: False+-- executable-stripping: True+-- library-stripping: True+-- configure-option:+-- user-install: True+-- package-db:+-- flags:+-- extra-include-dirs:+-- extra-lib-dirs:+extra-prog-path: /home/gman/.cabal/bin+-- tests: False+-- coverage: False+-- library-coverage:+-- exact-configuration: False+-- benchmarks: False+-- relocatable: False+-- cabal-lib-version:+-- constraint:+-- preference:+-- solver: choose+-- allow-newer: False+-- documentation: False+-- doc-index-file: $datadir/doc/$arch-$os-$compiler/index.html+-- max-backjumps: 2000+-- reorder-goals: False+-- shadow-installed-packages: False+-- strong-flags: False+-- reinstall: False+-- avoid-reinstalls: False+-- force-reinstalls: False+-- upgrade-dependencies: False+-- root-cmd:+-- symlink-bindir:+build-summary: /home/gman/.cabal/logs/build.log+-- build-log:+remote-build-reporting: anonymous+-- report-planning-failure: False+-- one-shot: False+-- run-tests:+jobs: $ncpus+-- offline: False+-- username:+-- password:+-- password-command:+-- builddir:++haddock+ -- keep-temp-files: False+ -- hoogle: False+ -- html: False+ -- html-location:+ -- for-hackage: False+ -- executables: False+ -- tests: False+ -- benchmarks: False+ -- all:+ -- internal: False+ -- css:+ -- hyperlink-source: False+ -- hscolour-css:+ -- contents-location:++install-dirs user+ -- prefix: /home/gman/.cabal+ -- bindir: $prefix/bin+ -- libdir: $prefix/lib+ -- libsubdir: $abi/$libname+ -- libexecdir: $prefix/libexec+ -- datadir: $prefix/share+ -- datasubdir: $abi/$pkgid+ -- docdir: $datadir/doc/$abi/$pkgid+ -- htmldir: $docdir/html+ -- haddockdir: $htmldir+ -- sysconfdir: $prefix/etc++install-dirs global+ -- prefix: /usr/local+ -- bindir: $prefix/bin+ -- libdir: $prefix/lib+ -- libsubdir: $abi/$libname+ -- libexecdir: $prefix/libexec+ -- datadir: $prefix/share+ -- datasubdir: $abi/$pkgid+ -- docdir: $datadir/doc/$abi/$pkgid+ -- htmldir: $docdir/html+ -- haddockdir: $htmldir+ -- sysconfdir: $prefix/etc++program-locations+ -- alex-location:+ -- ar-location:+ -- c2hs-location:+ -- cpphs-location:+ -- gcc-location:+ -- ghc-location:+ -- ghc-pkg-location:+ -- ghcjs-location:+ -- ghcjs-pkg-location:+ -- greencard-location:+ -- haddock-location:+ -- happy-location:+ -- haskell-suite-location:+ -- haskell-suite-pkg-location:+ -- hmake-location:+ -- hpc-location:+ -- hsc2hs-location:+ -- hscolour-location:+ -- jhc-location:+ -- ld-location:+ -- lhc-location:+ -- lhc-pkg-location:+ -- pkg-config-location:+ -- strip-location:+ -- tar-location:+ -- uhc-location:++program-default-options+ -- alex-options:+ -- ar-options:+ -- c2hs-options:+ -- cpphs-options:+ -- gcc-options:+ -- ghc-options:+ -- ghc-pkg-options:+ -- ghcjs-options:+ -- ghcjs-pkg-options:+ -- greencard-options:+ -- haddock-options:+ -- happy-options:+ -- haskell-suite-options:+ -- haskell-suite-pkg-options:+ -- hmake-options:+ -- hpc-options:+ -- hsc2hs-options:+ -- hscolour-options:+ -- jhc-options:+ -- ld-options:+ -- lhc-options:+ -- lhc-pkg-options:+ -- pkg-config-options:+ -- strip-options:+ -- tar-options:+ -- uhc-options:
+ cabal/cabal-install/tests/IntegrationTests/user-config/common.sh view
@@ -0,0 +1,9 @@+# Helper to run Cabal+cabal() {+ "$CABAL" $CABAL_ARGS_NO_CONFIG_FILE "$@"+}++die() {+ echo "die: $@"+ exit 1+}
+ cabal/cabal-install/tests/IntegrationTests/user-config/doesnt_overwrite_without_f.err view
@@ -0,0 +1,1 @@+RE:^cabal(\.exe)?: \./cabal-config already exists\.$
+ cabal/cabal-install/tests/IntegrationTests/user-config/doesnt_overwrite_without_f.sh view
@@ -0,0 +1,5 @@+. ./common.sh++rm -f ./cabal-config+cabal --config-file=./cabal-config user-config init > /dev/null+! cabal --config-file=./cabal-config user-config init
+ cabal/cabal-install/tests/IntegrationTests/user-config/overwrites_with_f.out view
@@ -0,0 +1,2 @@+Writing default configuration to ./cabal-config+Writing default configuration to ./cabal-config
+ cabal/cabal-install/tests/IntegrationTests/user-config/overwrites_with_f.sh view
@@ -0,0 +1,9 @@+. ./common.sh++rm -f ./cabal-config+cabal --config-file=./cabal-config user-config init \+ || die "Couldn't create config file"+cabal --config-file=./cabal-config user-config -f init \+ || die "Couldn't create config file"+test -e ./cabal-config || die "Config file doesn't exist"+rm -f ./cabal-config
+ cabal/cabal-install/tests/IntegrationTests/user-config/runs_without_error.out view
@@ -0,0 +1,1 @@+Writing default configuration to ./cabal-config
+ cabal/cabal-install/tests/IntegrationTests/user-config/runs_without_error.sh view
@@ -0,0 +1,7 @@+. ./common.sh++rm -f ./cabal-config+cabal --config-file=./cabal-config user-config init \+ || die "Couldn't create config file"+test -e ./cabal-config || die "Config file doesn't exist"+rm -f ./cabal-config
+ cabal/cabal-install/tests/IntegrationTests/user-config/uses_CABAL_CONFIG.out view
@@ -0,0 +1,1 @@+Writing default configuration to ./my-config
+ cabal/cabal-install/tests/IntegrationTests/user-config/uses_CABAL_CONFIG.sh view
@@ -0,0 +1,5 @@+. ./common.sh++export CABAL_CONFIG=./my-config+cabal user-config init || die "Couldn't create config file"+test -e ./my-config || die "Config file doesn't exist"
− cabal/cabal-install/tests/PackageTests.hs
@@ -1,95 +0,0 @@--- | Groups black-box tests of cabal-install and configures them to test--- the correct binary.------ This file should do nothing but import tests from other modules and run--- them with the path to the correct cabal-install binary.-module Main- where---- Modules from Cabal.-import Distribution.Simple.Configure (findDistPrefOrDefault)-import Distribution.Simple.Program.Builtin (ghcPkgProgram)-import Distribution.Simple.Program.Db- (defaultProgramDb, requireProgram, setProgramSearchPath)-import Distribution.Simple.Program.Find- (ProgramSearchPathEntry(ProgramSearchPathDir), defaultProgramSearchPath)-import Distribution.Simple.Program.Types- ( Program(..), simpleProgram, programPath)-import Distribution.Simple.Setup ( Flag(..) )-import Distribution.Simple.Utils ( findProgramVersion )-import Distribution.Verbosity (normal)---- Third party modules.-import qualified Control.Exception.Extensible as E-import Distribution.Compat.Environment ( setEnv )-import System.Directory- ( canonicalizePath, getCurrentDirectory, setCurrentDirectory- , removeFile, doesFileExist )-import System.FilePath ((</>))-import Test.Tasty (TestTree, defaultMain, testGroup)-import Control.Monad ( when )---- Module containing common test code.--import PackageTests.PackageTester ( TestsPaths(..)- , packageTestsDirectory- , packageTestsConfigFile )---- Modules containing the tests.-import qualified PackageTests.Exec.Check-import qualified PackageTests.Freeze.Check-import qualified PackageTests.MultipleSource.Check---- List of tests to run. Each test will be called with the path to the--- cabal binary to use.-tests :: PackageTests.PackageTester.TestsPaths -> TestTree-tests paths = testGroup "Package Tests" $- [ testGroup "Freeze" $ PackageTests.Freeze.Check.tests paths- , testGroup "Exec" $ PackageTests.Exec.Check.tests paths- , testGroup "MultipleSource" $ PackageTests.MultipleSource.Check.tests paths- ]--cabalProgram :: Program-cabalProgram = (simpleProgram "cabal") {- programFindVersion = findProgramVersion "--numeric-version" id- }--main :: IO ()-main = do- -- Find the builddir used to build Cabal- distPref <- findDistPrefOrDefault NoFlag- -- Use the default builddir for all of the subsequent package tests- setEnv "CABAL_BUILDDIR" "dist"- buildDir <- canonicalizePath (distPref </> "build/cabal")- let programSearchPath = ProgramSearchPathDir buildDir : defaultProgramSearchPath- (cabal, _) <- requireProgram normal cabalProgram- (setProgramSearchPath programSearchPath defaultProgramDb)- (ghcPkg, _) <- requireProgram normal ghcPkgProgram defaultProgramDb- canonicalConfigPath <- canonicalizePath $ "tests" </> packageTestsDirectory-- let testsPaths = TestsPaths {- cabalPath = programPath cabal,- ghcPkgPath = programPath ghcPkg,- configPath = canonicalConfigPath </> packageTestsConfigFile- }-- putStrLn $ "Using cabal: " ++ cabalPath testsPaths- putStrLn $ "Using ghc-pkg: " ++ ghcPkgPath testsPaths-- cwd <- getCurrentDirectory- let confFile = packageTestsDirectory </> "cabal-config"- removeConf = do- b <- doesFileExist confFile- when b $ removeFile confFile- let runTests = do- setCurrentDirectory "tests"- removeConf -- assert that there is no existing config file- -- (we want deterministic testing with the default- -- config values)- defaultMain $ tests testsPaths- runTests `E.finally` do- -- remove the default config file that got created by the tests- removeConf- -- Change back to the old working directory so that the tests can be- -- repeatedly run in `cabal repl` via `:main`.- setCurrentDirectory cwd
− cabal/cabal-install/tests/PackageTests/Exec/Check.hs
@@ -1,145 +0,0 @@-{-# LANGUAGE CPP #-}-module PackageTests.Exec.Check- ( tests- ) where---import PackageTests.PackageTester--import Test.Tasty-import Test.Tasty.HUnit--#if !MIN_VERSION_base(4,8,0)-import Control.Applicative ((<$>))-#endif-import Control.Monad (when)-import Data.List (intercalate, isInfixOf)-import System.FilePath ((</>))-import System.Directory (getDirectoryContents)--dir :: FilePath-dir = packageTestsDirectory </> "Exec"--tests :: TestsPaths -> [TestTree]-tests paths =- [ testCase "exits with failure if given no argument" $ do- result <- cabal_exec paths dir []- assertExecFailed result-- , testCase "prints error message if given no argument" $ do- result <- cabal_exec paths dir []- assertExecFailed result- let output = outputText result- expected = "specify an executable to run"- errMsg = "should have requested an executable be specified\n" ++- output- assertBool errMsg $- expected `isInfixOf` (intercalate " " . lines $ output)-- , testCase "runs the given command" $ do- result <- cabal_exec paths dir ["echo", "this", "string"]- assertExecSucceeded result- let output = outputText result- expected = "this string"- errMsg = "should have ran the given command\n" ++ output- assertBool errMsg $- expected `isInfixOf` (intercalate " " . lines $ output)-- , testCase "can run executables installed in the sandbox" $ do- -- Test that an executable installed into the sandbox can be found.- -- We do this by removing any existing sandbox. Checking that the- -- executable cannot be found. Creating a new sandbox. Installing- -- the executable and checking it can be run.-- cleanPreviousBuilds paths- assertMyExecutableNotFound paths- assertPackageInstall paths-- result <- cabal_exec paths dir ["my-executable"]- assertExecSucceeded result- let output = outputText result- expected = "This is my-executable"- errMsg = "should have found a my-executable\n" ++ output- assertBool errMsg $- expected `isInfixOf` (intercalate " " . lines $ output)-- , testCase "adds the sandbox bin directory to the PATH" $ do- cleanPreviousBuilds paths- assertMyExecutableNotFound paths- assertPackageInstall paths-- result <- cabal_exec paths dir ["bash", "--", "-c", "my-executable"]- assertExecSucceeded result- let output = outputText result- expected = "This is my-executable"- errMsg = "should have found a my-executable\n" ++ output- assertBool errMsg $- expected `isInfixOf` (intercalate " " . lines $ output)-- , testCase "configures GHC to use the sandbox" $ do- let libNameAndVersion = "my-0.1"-- cleanPreviousBuilds paths- assertPackageInstall paths-- assertMyLibIsNotAvailableOutsideofSandbox paths libNameAndVersion-- result <- cabal_exec paths dir ["ghc-pkg", "list"]- assertExecSucceeded result- let output = outputText result- errMsg = "my library should have been found"- assertBool errMsg $- libNameAndVersion `isInfixOf` (intercalate " " . lines $ output)- -- -- , testCase "can find executables built from the package" $ do-- , testCase "configures cabal to use the sandbox" $ do- let libNameAndVersion = "my-0.1"-- cleanPreviousBuilds paths- assertPackageInstall paths-- assertMyLibIsNotAvailableOutsideofSandbox paths libNameAndVersion-- result <- cabal_exec paths dir ["bash", "--", "-c", "cd subdir ; cabal sandbox hc-pkg list"]- assertExecSucceeded result- let output = outputText result- errMsg = "my library should have been found"- assertBool errMsg $- libNameAndVersion `isInfixOf` (intercalate " " . lines $ output)- ]--cleanPreviousBuilds :: TestsPaths -> IO ()-cleanPreviousBuilds paths = do- sandboxExists <- not . null . filter (== "cabal.sandbox.config") <$>- getDirectoryContents dir- assertCleanSucceeded =<< cabal_clean paths dir []- when sandboxExists $ do- assertSandboxSucceeded =<< cabal_sandbox paths dir ["delete"]---assertPackageInstall :: TestsPaths -> IO ()-assertPackageInstall paths = do- assertSandboxSucceeded =<< cabal_sandbox paths dir ["init"]- assertInstallSucceeded =<< cabal_install paths dir []---assertMyExecutableNotFound :: TestsPaths -> IO ()-assertMyExecutableNotFound paths = do- result <- cabal_exec paths dir ["my-executable"]- assertExecFailed result- let output = outputText result- expected = "The program 'my-executable' is required but it " ++- "could not be found"- errMsg = "should not have found a my-executable\n" ++ output- assertBool errMsg $- expected `isInfixOf` (intercalate " " . lines $ output)----assertMyLibIsNotAvailableOutsideofSandbox :: TestsPaths -> String -> IO ()-assertMyLibIsNotAvailableOutsideofSandbox paths libNameAndVersion = do- (_, _, output) <- run (Just $ dir) (ghcPkgPath paths) ["list"]- assertBool "my library should not have been found" $ not $- libNameAndVersion `isInfixOf` (intercalate " " . lines $ output)
− cabal/cabal-install/tests/PackageTests/Exec/Foo.hs
@@ -1,4 +0,0 @@-module Foo where--foo :: String-foo = "foo"
− cabal/cabal-install/tests/PackageTests/Exec/My.hs
@@ -1,5 +0,0 @@-module Main where--main :: IO ()-main = do- putStrLn "This is my-executable"
− cabal/cabal-install/tests/PackageTests/Exec/my.cabal
@@ -1,14 +0,0 @@-name: my-version: 0.1-license: BSD3-cabal-version: >= 1.2-build-type: Simple--library- exposed-modules: Foo- build-depends: base---executable my-executable- main-is: My.hs- build-depends: base
− cabal/cabal-install/tests/PackageTests/Exec/subdir/.gitkeep
− cabal/cabal-install/tests/PackageTests/Freeze/Check.hs
@@ -1,116 +0,0 @@-{-# LANGUAGE ScopedTypeVariables #-}--module PackageTests.Freeze.Check- ( tests- ) where--import PackageTests.PackageTester--import Test.Tasty-import Test.Tasty.HUnit--import qualified Control.Exception.Extensible as E-import Data.List (intercalate, isInfixOf)-import System.Directory (doesFileExist, removeFile)-import System.FilePath ((</>))-import System.IO.Error (isDoesNotExistError)--dir :: FilePath-dir = packageTestsDirectory </> "Freeze"--tests :: TestsPaths -> [TestTree]-tests paths =- [ testCase "runs without error" $ do- removeCabalConfig- result <- cabal_freeze paths dir []- assertFreezeSucceeded result-- , testCase "freezes direct dependencies" $ do- removeCabalConfig- result <- cabal_freeze paths dir []- assertFreezeSucceeded result- c <- readCabalConfig- assertBool ("should have frozen base\n" ++ c) $- " base ==" `isInfixOf` (intercalate " " $ lines $ c)-- , testCase "freezes transitory dependencies" $ do- removeCabalConfig- result <- cabal_freeze paths dir []- assertFreezeSucceeded result- c <- readCabalConfig- assertBool ("should have frozen ghc-prim\n" ++ c) $- " ghc-prim ==" `isInfixOf` (intercalate " " $ lines $ c)-- , testCase "does not freeze packages which are not dependend upon" $ do- -- TODO: Test this against a package installed in the sandbox but- -- not depended upon.- removeCabalConfig- result <- cabal_freeze paths dir []- assertFreezeSucceeded result- c <- readCabalConfig- assertBool ("should not have frozen exceptions\n" ++ c) $ not $- " exceptions ==" `isInfixOf` (intercalate " " $ lines $ c)-- , testCase "does not include a constraint for the package being frozen" $ do- removeCabalConfig- result <- cabal_freeze paths dir []- assertFreezeSucceeded result- c <- readCabalConfig- assertBool ("should not have frozen self\n" ++ c) $ not $- " my ==" `isInfixOf` (intercalate " " $ lines $ c)-- , testCase "--dry-run does not modify the cabal.config file" $ do- removeCabalConfig- result <- cabal_freeze paths dir ["--dry-run"]- assertFreezeSucceeded result- c <- doesFileExist $ dir </> "cabal.config"- assertBool "cabal.config file should not have been created" (not c)-- , testCase "--enable-tests freezes test dependencies" $ do- removeCabalConfig- result <- cabal_freeze paths dir ["--enable-tests"]- assertFreezeSucceeded result- c <- readCabalConfig- assertBool ("should have frozen test-framework\n" ++ c) $- " test-framework ==" `isInfixOf` (intercalate " " $ lines $ c)-- , testCase "--disable-tests does not freeze test dependencies" $ do- removeCabalConfig- result <- cabal_freeze paths dir ["--disable-tests"]- assertFreezeSucceeded result- c <- readCabalConfig- assertBool ("should not have frozen test-framework\n" ++ c) $ not $- " test-framework ==" `isInfixOf` (intercalate " " $ lines $ c)-- , testCase "--enable-benchmarks freezes benchmark dependencies" $ do- removeCabalConfig- result <- cabal_freeze paths dir ["--disable-benchmarks"]- assertFreezeSucceeded result- c <- readCabalConfig- assertBool ("should not have frozen criterion\n" ++ c) $ not $- " criterion ==" `isInfixOf` (intercalate " " $ lines $ c)-- , testCase "--disable-benchmarks does not freeze benchmark dependencies" $ do- removeCabalConfig- result <- cabal_freeze paths dir ["--disable-benchmarks"]- assertFreezeSucceeded result- c <- readCabalConfig- assertBool ("should not have frozen criterion\n" ++ c) $ not $- " criterion ==" `isInfixOf` (intercalate " " $ lines $ c)- ]--removeCabalConfig :: IO ()-removeCabalConfig = do- removeFile (dir </> "cabal.config")- `E.catch` \ (e :: IOError) ->- if isDoesNotExistError e- then return ()- else E.throw e---readCabalConfig :: IO String-readCabalConfig = do- config <- readFile $ dir </> "cabal.config"- -- Ensure that the file is closed so that it can be- -- deleted by the next test on Windows.- length config `seq` return config
− cabal/cabal-install/tests/PackageTests/Freeze/my.cabal
@@ -1,21 +0,0 @@-name: my-version: 0.1-license: BSD3-cabal-version: >= 1.20.0-build-type: Simple--library- exposed-modules: Foo- build-depends: base--test-suite test-Foo- type: exitcode-stdio-1.0- hs-source-dirs: tests- main-is: test-Foo.hs- build-depends: base, my, test-framework--benchmark bench-Foo- type: exitcode-stdio-1.0- hs-source-dirs: benchmarks- main-is: benchmark-Foo.hs- build-depends: base, my, criterion
− cabal/cabal-install/tests/PackageTests/MultipleSource/Check.hs
@@ -1,28 +0,0 @@-module PackageTests.MultipleSource.Check- ( tests- ) where---import PackageTests.PackageTester--import Test.Tasty-import Test.Tasty.HUnit--import Control.Monad (void, when)-import System.Directory (doesDirectoryExist)-import System.FilePath ((</>))--dir :: FilePath-dir = packageTestsDirectory </> "MultipleSource"--tests :: TestsPaths -> [TestTree]-tests paths =- [ testCase "finds second source of multiple source" $ do- sandboxExists <- doesDirectoryExist $ dir </> ".cabal-sandbox"- when sandboxExists $- void $ cabal_sandbox paths dir ["delete"]- assertSandboxSucceeded =<< cabal_sandbox paths dir ["init"]- assertSandboxSucceeded =<< cabal_sandbox paths dir ["add-source", "p"]- assertSandboxSucceeded =<< cabal_sandbox paths dir ["add-source", "q"]- assertInstallSucceeded =<< cabal_install paths dir ["q"]- ]
− cabal/cabal-install/tests/PackageTests/MultipleSource/p/LICENSE
− cabal/cabal-install/tests/PackageTests/MultipleSource/p/Setup.hs
@@ -1,2 +0,0 @@-import Distribution.Simple-main = defaultMain
− cabal/cabal-install/tests/PackageTests/MultipleSource/p/p.cabal
@@ -1,11 +0,0 @@-name: p-version: 0.1.0.0-license-file: LICENSE-author: Edward Z. Yang-maintainer: ezyang@cs.stanford.edu-build-type: Simple-cabal-version: >=1.10--library- build-depends: base- default-language: Haskell2010
− cabal/cabal-install/tests/PackageTests/MultipleSource/q/LICENSE
− cabal/cabal-install/tests/PackageTests/MultipleSource/q/Setup.hs
@@ -1,2 +0,0 @@-import Distribution.Simple-main = defaultMain
− cabal/cabal-install/tests/PackageTests/MultipleSource/q/q.cabal
@@ -1,11 +0,0 @@-name: q-version: 0.1.0.0-license-file: LICENSE-author: Edward Z. Yang-maintainer: ezyang@cs.stanford.edu-build-type: Simple-cabal-version: >=1.10--library- build-depends: base- default-language: Haskell2010
− cabal/cabal-install/tests/PackageTests/PackageTester.hs
@@ -1,232 +0,0 @@-{-# LANGUAGE ScopedTypeVariables #-}---- TODO This module was originally based on the PackageTests.PackageTester--- module in Cabal, however it has a few differences. I suspect that as--- this module ages the two modules will diverge further. As such, I have--- not attempted to merge them into a single module nor to extract a common--- module from them. Refactor this module and/or Cabal's--- PackageTests.PackageTester to remove commonality.--- 2014-05-15 Ben Armston---- | Routines for black-box testing cabal-install.------ Instead of driving the tests by making library calls into--- Distribution.Simple.* or Distribution.Client.* this module only every--- executes the `cabal-install` binary.------ You can set the following VERBOSE environment variable to control--- the verbosity of the output generated by this module.-module PackageTests.PackageTester- ( TestsPaths(..)- , Result(..)-- , packageTestsDirectory- , packageTestsConfigFile-- -- * Running cabal commands- , cabal_clean- , cabal_exec- , cabal_freeze- , cabal_install- , cabal_sandbox- , run-- -- * Test helpers- , assertCleanSucceeded- , assertExecFailed- , assertExecSucceeded- , assertFreezeSucceeded- , assertInstallSucceeded- , assertSandboxSucceeded- ) where--import qualified Control.Exception.Extensible as E-import Control.Monad (when, unless)-import Data.Maybe (fromMaybe)-import System.Directory (canonicalizePath, doesFileExist)-import System.Environment (getEnv)-import System.Exit (ExitCode(ExitSuccess))-import System.FilePath ( (<.>) )-import System.IO (hClose, hGetChar, hIsEOF)-import System.IO.Error (isDoesNotExistError)-import System.Process (runProcess, waitForProcess)-import Test.Tasty.HUnit (Assertion, assertFailure)--import Distribution.Simple.BuildPaths (exeExtension)-import Distribution.Simple.Utils (printRawCommandAndArgs)-import Distribution.Compat.CreatePipe (createPipe)-import Distribution.ReadE (readEOrFail)-import Distribution.Verbosity (Verbosity, flagToVerbosity, normal)--data Success = Failure- -- | ConfigureSuccess- -- | BuildSuccess- -- | TestSuccess- -- | BenchSuccess- | CleanSuccess- | ExecSuccess- | FreezeSuccess- | InstallSuccess- | SandboxSuccess- deriving (Eq, Show)--data TestsPaths = TestsPaths- { cabalPath :: FilePath -- ^ absolute path to cabal executable.- , ghcPkgPath :: FilePath -- ^ absolute path to ghc-pkg executable.- , configPath :: FilePath -- ^ absolute path of the default config file- -- to use for tests (tests are free to use- -- a different one).- }--data Result = Result- { successful :: Bool- , success :: Success- , outputText :: String- } deriving Show--nullResult :: Result-nullResult = Result True Failure ""----------------------------------------------------------------------------- * Config--packageTestsDirectory :: FilePath-packageTestsDirectory = "PackageTests"--packageTestsConfigFile :: FilePath-packageTestsConfigFile = "cabal-config"----------------------------------------------------------------------------- * Running cabal commands--recordRun :: (String, ExitCode, String) -> Success -> Result -> Result-recordRun (cmd, exitCode, exeOutput) thisSucc res =- res { successful = successful res && exitCode == ExitSuccess- , success = if exitCode == ExitSuccess then thisSucc- else success res- , outputText =- (if null $ outputText res then "" else outputText res ++ "\n") ++- cmd ++ "\n" ++ exeOutput- }---- | Run the clean command and return its result.-cabal_clean :: TestsPaths -> FilePath -> [String] -> IO Result-cabal_clean paths dir args = do- res <- cabal paths dir (["clean"] ++ args)- return $ recordRun res CleanSuccess nullResult---- | Run the exec command and return its result.-cabal_exec :: TestsPaths -> FilePath -> [String] -> IO Result-cabal_exec paths dir args = do- res <- cabal paths dir (["exec"] ++ args)- return $ recordRun res ExecSuccess nullResult---- | Run the freeze command and return its result.-cabal_freeze :: TestsPaths -> FilePath -> [String] -> IO Result-cabal_freeze paths dir args = do- res <- cabal paths dir (["freeze"] ++ args)- return $ recordRun res FreezeSuccess nullResult---- | Run the install command and return its result.-cabal_install :: TestsPaths -> FilePath -> [String] -> IO Result-cabal_install paths dir args = do- res <- cabal paths dir (["install"] ++ args)- return $ recordRun res InstallSuccess nullResult---- | Run the sandbox command and return its result.-cabal_sandbox :: TestsPaths -> FilePath -> [String] -> IO Result-cabal_sandbox paths dir args = do- res <- cabal paths dir (["sandbox"] ++ args)- return $ recordRun res SandboxSuccess nullResult---- | Returns the command that was issued, the return code, and the output text.-cabal :: TestsPaths -> FilePath -> [String] -> IO (String, ExitCode, String)-cabal paths dir cabalArgs = do- run (Just dir) (cabalPath paths) args- where- args = configFileArg : cabalArgs- configFileArg = "--config-file=" ++ configPath paths---- | Returns the command that was issued, the return code, and the output text-run :: Maybe FilePath -> String -> [String] -> IO (String, ExitCode, String)-run cwd path args = do- verbosity <- getVerbosity- -- path is relative to the current directory; canonicalizePath makes it- -- absolute, so that runProcess will find it even when changing directory.- path' <- do pathExists <- doesFileExist path- canonicalizePath (if pathExists then path else path <.> exeExtension)- printRawCommandAndArgs verbosity path' args- (readh, writeh) <- createPipe- pid <- runProcess path' args cwd Nothing Nothing (Just writeh) (Just writeh)-- -- fork off a thread to start consuming the output- out <- suckH [] readh- hClose readh-- -- wait for the program to terminate- exitcode <- waitForProcess pid- let fullCmd = unwords (path' : args)- return ("\"" ++ fullCmd ++ "\" in " ++ fromMaybe "" cwd, exitcode, out)- where- suckH output h = do- eof <- hIsEOF h- if eof- then return (reverse output)- else do- c <- hGetChar h- suckH (c:output) h----------------------------------------------------------------------------- * Test helpers--assertCleanSucceeded :: Result -> Assertion-assertCleanSucceeded result = unless (successful result) $- assertFailure $- "expected: \'cabal clean\' should succeed\n" ++- " output: " ++ outputText result--assertExecSucceeded :: Result -> Assertion-assertExecSucceeded result = unless (successful result) $- assertFailure $- "expected: \'cabal exec\' should succeed\n" ++- " output: " ++ outputText result--assertExecFailed :: Result -> Assertion-assertExecFailed result = when (successful result) $- assertFailure $- "expected: \'cabal exec\' should fail\n" ++- " output: " ++ outputText result--assertFreezeSucceeded :: Result -> Assertion-assertFreezeSucceeded result = unless (successful result) $- assertFailure $- "expected: \'cabal freeze\' should succeed\n" ++- " output: " ++ outputText result--assertInstallSucceeded :: Result -> Assertion-assertInstallSucceeded result = unless (successful result) $- assertFailure $- "expected: \'cabal install\' should succeed\n" ++- " output: " ++ outputText result--assertSandboxSucceeded :: Result -> Assertion-assertSandboxSucceeded result = unless (successful result) $- assertFailure $- "expected: \'cabal sandbox\' should succeed\n" ++- " output: " ++ outputText result----------------------------------------------------------------------------- Verbosity--lookupEnv :: String -> IO (Maybe String)-lookupEnv name =- (fmap Just $ getEnv name)- `E.catch` \ (e :: IOError) ->- if isDoesNotExistError e- then return Nothing- else E.throw e---- TODO: Convert to a "-v" flag instead.-getVerbosity :: IO Verbosity-getVerbosity = do- maybe normal (readEOrFail flagToVerbosity) `fmap` lookupEnv "VERBOSE"
− cabal/cabal-install/tests/README
@@ -1,1 +0,0 @@-See Cabal/tests/README.
+ cabal/cabal-install/tests/README.md view
@@ -0,0 +1,27 @@+Integration Tests+=================++Each test is a shell script. Tests that share files (e.g., `.cabal` files) are+grouped under a common sub-directory of [IntegrationTests]. The framework+copies the whole group's directory before running each test, which allows tests+to reuse files, yet run independently. A group's tests are further divided into+`should_run` and `should_fail` directories, based on the expected exit status.+For example, the test+`IntegrationTests/exec/should_fail/exit_with_failure_without_args.sh` has access+to all files under `exec` and is expected to fail.++Tests can specify their expected output. For a test named `x.sh`, `x.out`+specifies `stdout` and `x.err` specifies `stderr`. Both files are optional.+The framework expects an exact match between lines in the file and output,+except for lines beginning with "RE:", which are interpreted as regular+expressions.++[IntegrationTests.hs] defines several environment variables:++* `CABAL` - The path to the executable being tested.+* `GHC_PKG` - The path to ghc-pkg.+* `CABAL_ARGS` - A common set of arguments for running cabal.+* `CABAL_ARGS_NO_CONFIG_FILE` - `CABAL_ARGS` without `--config-file`.++[IntegrationTests]: IntegrationTests+[IntegrationTests.hs]: IntegrationTests.hs
+ cabal/cabal-install/tests/SolverQuickCheck.hs view
@@ -0,0 +1,16 @@+module Main where++import Test.Tasty++import qualified UnitTests.Distribution.Client.Dependency.Modular.QuickCheck+++tests :: TestTree+tests =+ testGroup "Solver QuickCheck"+ [ testGroup "UnitTests.Distribution.Client.Dependency.Modular.QuickCheck"+ UnitTests.Distribution.Client.Dependency.Modular.QuickCheck.tests+ ]++main :: IO ()+main = defaultMain tests
cabal/cabal-install/tests/UnitTests.hs view
@@ -1,39 +1,106 @@+{-# LANGUAGE ScopedTypeVariables #-}+ module Main where import Test.Tasty-import Test.Tasty.Options -import qualified UnitTests.Distribution.Client.Sandbox-import qualified UnitTests.Distribution.Client.UserConfig-import qualified UnitTests.Distribution.Client.Targets-import qualified UnitTests.Distribution.Client.GZipUtils+import Control.Monad+import Data.Time.Clock+import System.FilePath++import Distribution.Simple.Utils+import Distribution.Verbosity++import Distribution.Client.Compat.Time++import qualified UnitTests.Distribution.Client.Compat.Time import qualified UnitTests.Distribution.Client.Dependency.Modular.PSQ import qualified UnitTests.Distribution.Client.Dependency.Modular.Solver+import qualified UnitTests.Distribution.Client.FileMonitor+import qualified UnitTests.Distribution.Client.Glob+import qualified UnitTests.Distribution.Client.GZipUtils+import qualified UnitTests.Distribution.Client.Sandbox+import qualified UnitTests.Distribution.Client.Sandbox.Timestamp+import qualified UnitTests.Distribution.Client.Tar+import qualified UnitTests.Distribution.Client.Targets+import qualified UnitTests.Distribution.Client.UserConfig+import qualified UnitTests.Distribution.Client.ProjectConfig -tests :: TestTree-tests = testGroup "Unit Tests" [- testGroup "UnitTests.Distribution.Client.UserConfig"- UnitTests.Distribution.Client.UserConfig.tests- ,testGroup "Distribution.Client.Sandbox"- UnitTests.Distribution.Client.Sandbox.tests- ,testGroup "Distribution.Client.Targets"- UnitTests.Distribution.Client.Targets.tests- ,testGroup "Distribution.Client.GZipUtils"- UnitTests.Distribution.Client.GZipUtils.tests- ,testGroup "UnitTests.Distribution.Client.Dependency.Modular.PSQ"+import UnitTests.Options+++tests :: Int -> TestTree+tests mtimeChangeCalibrated =+ askOption $ \(OptionMtimeChangeDelay mtimeChangeProvided) ->+ let mtimeChange = if mtimeChangeProvided /= 0+ then mtimeChangeProvided+ else mtimeChangeCalibrated+ in+ testGroup "Unit Tests"+ [ testGroup "UnitTests.Distribution.Client.Compat.Time" $+ UnitTests.Distribution.Client.Compat.Time.tests mtimeChange+ , testGroup "UnitTests.Distribution.Client.Dependency.Modular.PSQ" UnitTests.Distribution.Client.Dependency.Modular.PSQ.tests- ,testGroup "UnitTests.Distribution.Client.Dependency.Modular.Solver"+ , testGroup "UnitTests.Distribution.Client.Dependency.Modular.Solver" UnitTests.Distribution.Client.Dependency.Modular.Solver.tests- ]---- Extra options for running the test suite-extraOptions :: [OptionDescription]-extraOptions = concat [- UnitTests.Distribution.Client.Dependency.Modular.Solver.options+ , testGroup "UnitTests.Distribution.Client.FileMonitor" $+ UnitTests.Distribution.Client.FileMonitor.tests mtimeChange+ , testGroup "UnitTests.Distribution.Client.Glob"+ UnitTests.Distribution.Client.Glob.tests+ , testGroup "Distribution.Client.GZipUtils"+ UnitTests.Distribution.Client.GZipUtils.tests+ , testGroup "Distribution.Client.Sandbox"+ UnitTests.Distribution.Client.Sandbox.tests+ , testGroup "Distribution.Client.Sandbox.Timestamp"+ UnitTests.Distribution.Client.Sandbox.Timestamp.tests+ , testGroup "Distribution.Client.Tar"+ UnitTests.Distribution.Client.Tar.tests+ , testGroup "Distribution.Client.Targets"+ UnitTests.Distribution.Client.Targets.tests+ , testGroup "UnitTests.Distribution.Client.UserConfig"+ UnitTests.Distribution.Client.UserConfig.tests+ , testGroup "UnitTests.Distribution.Client.ProjectConfig"+ UnitTests.Distribution.Client.ProjectConfig.tests ] main :: IO ()-main = defaultMainWithIngredients+main = do+ mtimeChangeDelay <- calibrateMtimeChangeDelay+ defaultMainWithIngredients (includingOptions extraOptions : defaultIngredients)- tests+ (tests mtimeChangeDelay)++-- Based on code written by Neill Mitchell for Shake. See+-- 'sleepFileTimeCalibrate' in 'Test.Type'. The returned delay is never smaller+-- than 10 ms, but never larger than 1 second.+calibrateMtimeChangeDelay :: IO Int+calibrateMtimeChangeDelay = do+ withTempDirectory silent "." "calibration-" $ \dir -> do+ let fileName = dir </> "probe"+ mtimes <- forM [1..25] $ \(i::Int) -> time $ do+ writeFile fileName $ show i+ t0 <- getModTime fileName+ let spin j = do+ writeFile fileName $ show (i,j)+ t1 <- getModTime fileName+ unless (t0 < t1) (spin $ j + 1)+ spin (0::Int)+ let mtimeChange = maximum mtimes+ mtimeChange' = min 1000000 $ (max 10000 mtimeChange) * 2+ notice normal $ "File modification time resolution calibration completed, "+ ++ "maximum delay observed: "+ ++ (show . toMillis $ mtimeChange ) ++ " ms. "+ ++ "Will be using delay of " ++ (show . toMillis $ mtimeChange')+ ++ " for test runs."+ return mtimeChange'+ where+ toMillis :: Int -> Double+ toMillis x = fromIntegral x / 1000.0++ time :: IO () -> IO Int+ time act = do+ t0 <- getCurrentTime+ act+ t1 <- getCurrentTime+ return . ceiling $! (t1 `diffUTCTime` t0) * 1e6 -- microseconds
+ cabal/cabal-install/tests/UnitTests/Distribution/Client/ArbitraryInstances.hs view
@@ -0,0 +1,172 @@+{-# LANGUAGE CPP #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module UnitTests.Distribution.Client.ArbitraryInstances (+ adjustSize,+ shortListOf,+ shortListOf1,+ arbitraryFlag,+ ShortToken(..),+ arbitraryShortToken,+ NonMEmpty(..),+ NoShrink(..),+ ) where++import Data.Char+import Data.List+#if !MIN_VERSION_base(4,8,0)+import Data.Monoid+import Control.Applicative+#endif+import Control.Monad++import Distribution.Version+import Distribution.Package+import Distribution.System+import Distribution.Verbosity++import Distribution.Simple.Setup+import Distribution.Simple.InstallDirs++import Distribution.Utils.NubList++import Test.QuickCheck+++adjustSize :: (Int -> Int) -> Gen a -> Gen a+adjustSize adjust gen = sized (\n -> resize (adjust n) gen)++shortListOf :: Int -> Gen a -> Gen [a]+shortListOf bound gen =+ sized $ \n -> do+ k <- choose (0, (n `div` 2) `min` bound)+ vectorOf k gen++shortListOf1 :: Int -> Gen a -> Gen [a]+shortListOf1 bound gen =+ sized $ \n -> do+ k <- choose (1, 1 `max` ((n `div` 2) `min` bound))+ vectorOf k gen++newtype ShortToken = ShortToken { getShortToken :: String }+ deriving Show++instance Arbitrary ShortToken where+ arbitrary =+ ShortToken <$>+ (shortListOf1 5 (choose ('#', '~'))+ `suchThat` (not . ("[]" `isPrefixOf`)))+ --TODO: [code cleanup] need to replace parseHaskellString impl to stop+ -- accepting Haskell list syntax [], ['a'] etc, just allow String syntax.+ -- Workaround, don't generate [] as this does not round trip.+++ shrink (ShortToken cs) =+ [ ShortToken cs' | cs' <- shrink cs, not (null cs') ]++arbitraryShortToken :: Gen String+arbitraryShortToken = getShortToken <$> arbitrary++instance Arbitrary Version where+ arbitrary = do+ branch <- shortListOf1 4 $+ frequency [(3, return 0)+ ,(3, return 1)+ ,(2, return 2)+ ,(1, return 3)]+ return (Version branch []) -- deliberate []+ where++ shrink (Version branch []) =+ [ Version branch' [] | branch' <- shrink branch, not (null branch') ]+ shrink (Version branch _tags) =+ [ Version branch [] ]++instance Arbitrary VersionRange where+ arbitrary = canonicaliseVersionRange <$> sized verRangeExp+ where+ verRangeExp n = frequency $+ [ (2, return anyVersion)+ , (1, liftM thisVersion arbitrary)+ , (1, liftM laterVersion arbitrary)+ , (1, liftM orLaterVersion arbitrary)+ , (1, liftM orLaterVersion' arbitrary)+ , (1, liftM earlierVersion arbitrary)+ , (1, liftM orEarlierVersion arbitrary)+ , (1, liftM orEarlierVersion' arbitrary)+ , (1, liftM withinVersion arbitrary)+ , (2, liftM VersionRangeParens arbitrary)+ ] ++ if n == 0 then [] else+ [ (2, liftM2 unionVersionRanges verRangeExp2 verRangeExp2)+ , (2, liftM2 intersectVersionRanges verRangeExp2 verRangeExp2)+ ]+ where+ verRangeExp2 = verRangeExp (n `div` 2)++ orLaterVersion' v =+ unionVersionRanges (laterVersion v) (thisVersion v)+ orEarlierVersion' v =+ unionVersionRanges (earlierVersion v) (thisVersion v)++ canonicaliseVersionRange = fromVersionIntervals . toVersionIntervals++instance Arbitrary PackageName where+ arbitrary = PackageName . intercalate "-" <$> shortListOf1 2 nameComponent+ where+ nameComponent = shortListOf1 5 (elements packageChars)+ `suchThat` (not . all isDigit)+ packageChars = filter isAlphaNum ['\0'..'\127']++instance Arbitrary Dependency where+ arbitrary = Dependency <$> arbitrary <*> arbitrary++instance Arbitrary OS where+ arbitrary = elements knownOSs++instance Arbitrary Arch where+ arbitrary = elements knownArches++instance Arbitrary Platform where+ arbitrary = Platform <$> arbitrary <*> arbitrary++instance Arbitrary a => Arbitrary (Flag a) where+ arbitrary = arbitraryFlag arbitrary+ shrink NoFlag = []+ shrink (Flag x) = NoFlag : [ Flag x' | x' <- shrink x ]++arbitraryFlag :: Gen a -> Gen (Flag a)+arbitraryFlag genA =+ sized $ \sz ->+ case sz of+ 0 -> pure NoFlag+ _ -> frequency [ (1, pure NoFlag)+ , (3, Flag <$> genA) ]+++instance (Arbitrary a, Ord a) => Arbitrary (NubList a) where+ arbitrary = toNubList <$> arbitrary+ shrink xs = [ toNubList [] | (not . null) (fromNubList xs) ]+ -- try empty, otherwise don't shrink as it can loop++instance Arbitrary Verbosity where+ arbitrary = elements [minBound..maxBound]++instance Arbitrary PathTemplate where+ arbitrary = toPathTemplate <$> arbitraryShortToken+ shrink t = [ toPathTemplate s | s <- shrink (show t), not (null s) ]+++newtype NonMEmpty a = NonMEmpty { getNonMEmpty :: a }+ deriving (Eq, Ord, Show)++instance (Arbitrary a, Monoid a, Eq a) => Arbitrary (NonMEmpty a) where+ arbitrary = NonMEmpty <$> (arbitrary `suchThat` (/= mempty))+ shrink (NonMEmpty x) = [ NonMEmpty x' | x' <- shrink x, x' /= mempty ]++newtype NoShrink a = NoShrink { getNoShrink :: a }+ deriving (Eq, Ord, Show)++instance Arbitrary a => Arbitrary (NoShrink a) where+ arbitrary = NoShrink <$> arbitrary+ shrink _ = []+
+ cabal/cabal-install/tests/UnitTests/Distribution/Client/Compat/Time.hs view
@@ -0,0 +1,49 @@+module UnitTests.Distribution.Client.Compat.Time (tests) where++import Control.Concurrent (threadDelay)+import System.FilePath++import Distribution.Simple.Utils (withTempDirectory)+import Distribution.Verbosity++import Distribution.Client.Compat.Time++import Test.Tasty+import Test.Tasty.HUnit++tests :: Int -> [TestTree]+tests mtimeChange =+ [ testCase "getModTime has sub-second resolution" $ getModTimeTest mtimeChange+ , testCase "getCurTime works as expected" $ getCurTimeTest mtimeChange+ ]++getModTimeTest :: Int -> Assertion+getModTimeTest mtimeChange =+ withTempDirectory silent "." "getmodtime-" $ \dir -> do+ let fileName = dir </> "foo"+ writeFile fileName "bar"+ t0 <- getModTime fileName+ threadDelay mtimeChange+ writeFile fileName "baz"+ t1 <- getModTime fileName+ assertBool "expected different file mtimes" (t1 > t0)+++getCurTimeTest :: Int -> Assertion+getCurTimeTest mtimeChange =+ withTempDirectory silent "." "getmodtime-" $ \dir -> do+ let fileName = dir </> "foo"+ writeFile fileName "bar"+ t0 <- getModTime fileName+ threadDelay mtimeChange+ t1 <- getCurTime+ assertBool("expected file mtime (" ++ show t0+ ++ ") to be earlier than current time (" ++ show t1 ++ ")")+ (t0 < t1)++ threadDelay mtimeChange+ writeFile fileName "baz"+ t2 <- getModTime fileName+ assertBool ("expected current time (" ++ show t1+ ++ ") to be earlier than file mtime (" ++ show t2 ++ ")")+ (t1 < t2)
cabal/cabal-install/tests/UnitTests/Distribution/Client/Dependency/Modular/DSL.hs view
@@ -2,17 +2,31 @@ -- | DSL for testing the modular solver module UnitTests.Distribution.Client.Dependency.Modular.DSL ( ExampleDependency(..)+ , Dependencies(..)+ , ExTest(..)+ , ExPreference(..) , ExampleDb+ , ExampleVersionRange+ , ExamplePkgVersion+ , ExamplePkgName+ , ExampleAvailable(..)+ , ExampleInstalled(..)+ , IndepGoals(..)+ , ReorderGoals(..) , exAv , exInst+ , exFlag , exResolve , extractInstallPlan , withSetupDeps+ , withTest+ , withTests ) where -- base import Data.Either (partitionEithers) import Data.Maybe (catMaybes)+import Data.List (nub) import Data.Monoid import Data.Version import qualified Data.Map as Map@@ -21,11 +35,12 @@ import qualified Distribution.Compiler as C import qualified Distribution.InstalledPackageInfo as C import qualified Distribution.Package as C- hiding (HasInstalledPackageId(..))+ hiding (HasUnitId(..)) import qualified Distribution.PackageDescription as C import qualified Distribution.Simple.PackageIndex as C.PackageIndex import qualified Distribution.System as C import qualified Distribution.Version as C+import Language.Haskell.Extension (Extension(..), Language) -- cabal-install import Distribution.Client.ComponentDeps (ComponentDeps)@@ -34,6 +49,7 @@ import Distribution.Client.Types import qualified Distribution.Client.InstallPlan as CI.InstallPlan import qualified Distribution.Client.PackageIndex as CI.PackageIndex+import qualified Distribution.Client.PkgConfigDb as PC import qualified Distribution.Client.ComponentDeps as CD {-------------------------------------------------------------------------------@@ -59,7 +75,7 @@ * The difference between `GenericPackageDescription` and `PackageDescription` is that `PackageDescription` describes a particular _configuration_ of a package (for instance, see documentation for `checkPackage`). A- `GenericPackageDescription` can be returned into a `PackageDescription` in+ `GenericPackageDescription` can be turned into a `PackageDescription` in two ways: a. `finalizePackageDescription` does the proper translation, by taking@@ -83,7 +99,11 @@ type ExamplePkgHash = String -- for example "installed" packages type ExampleFlagName = String type ExampleTestName = String+type ExampleVersionRange = C.VersionRange +data Dependencies = NotBuildable | Buildable [ExampleDependency]+ deriving Show+ data ExampleDependency = -- | Simple dependency on any version ExAny ExamplePkgName@@ -92,56 +112,110 @@ | ExFix ExamplePkgName ExamplePkgVersion -- | Dependencies indexed by a flag- | ExFlag ExampleFlagName [ExampleDependency] [ExampleDependency]+ | ExFlag ExampleFlagName Dependencies Dependencies - -- | Dependency if tests are enabled- | ExTest ExampleTestName [ExampleDependency]+ -- | Dependency on a language extension+ | ExExt Extension + -- | Dependency on a language version+ | ExLang Language++ -- | Dependency on a pkg-config package+ | ExPkg (ExamplePkgName, ExamplePkgVersion)+ deriving Show++data ExTest = ExTest ExampleTestName [ExampleDependency]++exFlag :: ExampleFlagName -> [ExampleDependency] -> [ExampleDependency]+ -> ExampleDependency+exFlag n t e = ExFlag n (Buildable t) (Buildable e)++data ExPreference = ExPref String ExampleVersionRange+ data ExampleAvailable = ExAv { exAvName :: ExamplePkgName , exAvVersion :: ExamplePkgVersion , exAvDeps :: ComponentDeps [ExampleDependency]- }+ } deriving Show +-- | Constructs an 'ExampleAvailable' package for the 'ExampleDb',+-- given:+--+-- 1. The name 'ExamplePkgName' of the available package,+-- 2. The version 'ExamplePkgVersion' available+-- 3. The list of dependency constraints 'ExampleDependency'+-- that this package has. 'ExampleDependency' provides+-- a number of pre-canned dependency types to look at.+-- exAv :: ExamplePkgName -> ExamplePkgVersion -> [ExampleDependency] -> ExampleAvailable exAv n v ds = ExAv { exAvName = n, exAvVersion = v- , exAvDeps = CD.fromLibraryDeps ds }+ , exAvDeps = CD.fromLibraryDeps n ds } withSetupDeps :: ExampleAvailable -> [ExampleDependency] -> ExampleAvailable withSetupDeps ex setupDeps = ex { exAvDeps = exAvDeps ex <> CD.fromSetupDeps setupDeps } +withTest :: ExampleAvailable -> ExTest -> ExampleAvailable+withTest ex test = withTests ex [test]++withTests :: ExampleAvailable -> [ExTest] -> ExampleAvailable+withTests ex tests =+ let testCDs = CD.fromList [(CD.ComponentTest name, deps)+ | ExTest name deps <- tests]+ in ex { exAvDeps = exAvDeps ex <> testCDs }++-- | An installed package in 'ExampleDb'; construct me with 'exInst'. data ExampleInstalled = ExInst { exInstName :: ExamplePkgName , exInstVersion :: ExamplePkgVersion , exInstHash :: ExamplePkgHash- , exInstBuildAgainst :: [ExampleInstalled]- }+ , exInstBuildAgainst :: [ExamplePkgHash]+ } deriving Show +-- | Constructs an example installed package given:+--+-- 1. The name of the package 'ExamplePkgName', i.e., 'String'+-- 2. The version of the package 'ExamplePkgVersion', i.e., 'Int'+-- 3. The IPID for the package 'ExamplePkgHash', i.e., 'String'+-- (just some unique identifier for the package.)+-- 4. The 'ExampleInstalled' packages which this package was+-- compiled against.)+-- exInst :: ExamplePkgName -> ExamplePkgVersion -> ExamplePkgHash -> [ExampleInstalled] -> ExampleInstalled-exInst = ExInst+exInst pn v hash deps = ExInst pn v hash (map exInstHash deps) +-- | An example package database is a list of installed packages+-- 'ExampleInstalled' and available packages 'ExampleAvailable'.+-- Generally, you want to use 'exInst' and 'exAv' to construct+-- these packages. type ExampleDb = [Either ExampleInstalled ExampleAvailable] type DependencyTree a = C.CondTree C.ConfVar [C.Dependency] a +newtype IndepGoals = IndepGoals Bool+ deriving Show++newtype ReorderGoals = ReorderGoals Bool+ deriving Show+ exDbPkgs :: ExampleDb -> [ExamplePkgName] exDbPkgs = map (either exInstName exAvName) -exAvSrcPkg :: ExampleAvailable -> SourcePackage+exAvSrcPkg :: ExampleAvailable -> UnresolvedSourcePackage exAvSrcPkg ex =- let (libraryDeps, testSuites) = splitTopLevel (CD.libraryDeps (exAvDeps ex))+ let (libraryDeps, exts, mlang, pcpkgs) = splitTopLevel (CD.libraryDeps (exAvDeps ex))+ testSuites = [(name, deps) | (CD.ComponentTest name, deps) <- CD.toList (exAvDeps ex)] in SourcePackage { packageInfoId = exAvPkgId ex , packageSource = LocalTarballPackage "<<path>>" , packageDescrOverride = Nothing- , packageDescription = C.GenericPackageDescription{+ , packageDescription = C.GenericPackageDescription { C.packageDescription = C.emptyPackageDescription { C.package = exAvPkgId ex- , C.library = error "not yet configured: library"+ , C.libraries = error "not yet configured: library" , C.executables = error "not yet configured: executables" , C.testSuites = error "not yet configured: testSuites" , C.benchmarks = error "not yet configured: benchmarks"@@ -150,47 +224,75 @@ C.setupDepends = mkSetupDeps (CD.setupDeps (exAvDeps ex)) } }- , C.genPackageFlags = concatMap extractFlags- (CD.libraryDeps (exAvDeps ex))- , C.condLibrary = Just $ mkCondTree libraryDeps+ , C.genPackageFlags = nub $ concatMap extractFlags $+ CD.libraryDeps (exAvDeps ex) ++ concatMap snd testSuites+ , C.condLibraries = [(exAvName ex, mkCondTree (extsLib exts <> langLib mlang <> pcpkgLib pcpkgs)+ disableLib+ (Buildable libraryDeps))] , C.condExecutables = []- , C.condTestSuites = map (\(t, deps) -> (t, mkCondTree deps))- testSuites+ , C.condTestSuites =+ let mkTree = mkCondTree mempty disableTest . Buildable+ in map (\(t, deps) -> (t, mkTree deps)) testSuites , C.condBenchmarks = [] } } where+ -- Split the set of dependencies into the set of dependencies of the library,+ -- the dependencies of the test suites and extensions. splitTopLevel :: [ExampleDependency] -> ( [ExampleDependency]- , [(ExampleTestName, [ExampleDependency])]+ , [Extension]+ , Maybe Language+ , [(ExamplePkgName, ExamplePkgVersion)] -- pkg-config )- splitTopLevel [] = ([], [])- splitTopLevel (ExTest t a:deps) =- let (other, testSuites) = splitTopLevel deps- in (other, (t, a):testSuites)- splitTopLevel (dep:deps) =- let (other, testSuites) = splitTopLevel deps- in (dep:other, testSuites)+ splitTopLevel [] =+ ([], [], Nothing, [])+ splitTopLevel (ExExt ext:deps) =+ let (other, exts, lang, pcpkgs) = splitTopLevel deps+ in (other, ext:exts, lang, pcpkgs)+ splitTopLevel (ExLang lang:deps) =+ case splitTopLevel deps of+ (other, exts, Nothing, pcpkgs) -> (other, exts, Just lang, pcpkgs)+ _ -> error "Only 1 Language dependency is supported"+ splitTopLevel (ExPkg pkg:deps) =+ let (other, exts, lang, pcpkgs) = splitTopLevel deps+ in (other, exts, lang, pkg:pcpkgs)+ splitTopLevel (dep:deps) =+ let (other, exts, lang, pcpkgs) = splitTopLevel deps+ in (dep:other, exts, lang, pcpkgs) + -- Extract the total set of flags used extractFlags :: ExampleDependency -> [C.Flag] extractFlags (ExAny _) = [] extractFlags (ExFix _ _) = [] extractFlags (ExFlag f a b) = C.MkFlag { C.flagName = C.FlagName f , C.flagDescription = ""- , C.flagDefault = False+ , C.flagDefault = True , C.flagManual = False }- : concatMap extractFlags (a ++ b)- extractFlags (ExTest _ a) = concatMap extractFlags a+ : concatMap extractFlags (deps a ++ deps b)+ where+ deps :: Dependencies -> [ExampleDependency]+ deps NotBuildable = []+ deps (Buildable ds) = ds+ extractFlags (ExExt _) = []+ extractFlags (ExLang _) = []+ extractFlags (ExPkg _) = [] - mkCondTree :: Monoid a => [ExampleDependency] -> DependencyTree a- mkCondTree deps =+ mkCondTree :: Monoid a => a -> (a -> a) -> Dependencies -> DependencyTree a+ mkCondTree x dontBuild NotBuildable =+ C.CondNode {+ C.condTreeData = dontBuild x+ , C.condTreeConstraints = []+ , C.condTreeComponents = []+ }+ mkCondTree x dontBuild (Buildable deps) = let (directDeps, flaggedDeps) = splitDeps deps in C.CondNode {- C.condTreeData = mempty -- irrelevant to the solver- , C.condTreeConstraints = map mkDirect directDeps- , C.condTreeComponents = map mkFlagged flaggedDeps+ C.condTreeData = x -- Necessary for language extensions+ , C.condTreeConstraints = map mkDirect directDeps+ , C.condTreeComponents = map (mkFlagged dontBuild) flaggedDeps } mkDirect :: (ExamplePkgName, Maybe ExamplePkgVersion) -> C.Dependency@@ -200,17 +302,25 @@ v = Version [n, 0, 0] [] mkFlagged :: Monoid a- => (ExampleFlagName, [ExampleDependency], [ExampleDependency])+ => (a -> a)+ -> (ExampleFlagName, Dependencies, Dependencies) -> (C.Condition C.ConfVar , DependencyTree a, Maybe (DependencyTree a))- mkFlagged (f, a, b) = ( C.Var (C.Flag (C.FlagName f))- , mkCondTree a- , Just (mkCondTree b)- )+ mkFlagged dontBuild (f, a, b) = ( C.Var (C.Flag (C.FlagName f))+ , mkCondTree mempty dontBuild a+ , Just (mkCondTree mempty dontBuild b)+ ) + -- Split a set of dependencies into direct dependencies and flagged+ -- dependencies. A direct dependency is a tuple of the name of package and+ -- maybe its version (no version means any version) meant to be converted+ -- to a 'C.Dependency' with 'mkDirect' for example. A flagged dependency is+ -- the set of dependencies guarded by a flag.+ --+ -- TODO: Take care of flagged language extensions and language flavours. splitDeps :: [ExampleDependency] -> ( [(ExamplePkgName, Maybe Int)]- , [(ExampleFlagName, [ExampleDependency], [ExampleDependency])]+ , [(ExampleFlagName, Dependencies, Dependencies)] ) splitDeps [] = ([], [])@@ -223,14 +333,34 @@ splitDeps (ExFlag f a b:deps) = let (directDeps, flaggedDeps) = splitDeps deps in (directDeps, (f, a, b):flaggedDeps)- splitDeps (ExTest _ _:_) =- error "Unexpected nested test"+ splitDeps (_:deps) = splitDeps deps -- Currently we only support simple setup dependencies mkSetupDeps :: [ExampleDependency] -> [C.Dependency] mkSetupDeps deps = let (directDeps, []) = splitDeps deps in map mkDirect directDeps + -- A 'C.Library' with just the given extensions in its 'BuildInfo'+ extsLib :: [Extension] -> C.Library+ extsLib es = mempty { C.libBuildInfo = mempty { C.otherExtensions = es } }++ -- A 'C.Library' with just the given extensions in its 'BuildInfo'+ langLib :: Maybe Language -> C.Library+ langLib (Just lang) = mempty { C.libBuildInfo = mempty { C.defaultLanguage = Just lang } }+ langLib _ = mempty++ disableLib :: C.Library -> C.Library+ disableLib lib =+ lib { C.libBuildInfo = (C.libBuildInfo lib) { C.buildable = False }}++ disableTest :: C.TestSuite -> C.TestSuite+ disableTest test =+ test { C.testBuildInfo = (C.testBuildInfo test) { C.buildable = False }}++ -- A 'C.Library' with just the given pkgconfig-depends in its 'BuildInfo'+ pcpkgLib :: [(ExamplePkgName, ExamplePkgVersion)] -> C.Library+ pcpkgLib ds = mempty { C.libBuildInfo = mempty { C.pkgconfigDepends = [mkDirect (n, (Just v)) | (n,v) <- ds] } }+ exAvPkgId :: ExampleAvailable -> C.PackageIdentifier exAvPkgId ex = C.PackageIdentifier { pkgName = C.PackageName (exAvName ex)@@ -239,11 +369,9 @@ exInstInfo :: ExampleInstalled -> C.InstalledPackageInfo exInstInfo ex = C.emptyInstalledPackageInfo {- C.installedPackageId = C.InstalledPackageId (exInstHash ex)+ C.installedUnitId = C.mkUnitId (exInstHash ex) , C.sourcePackageId = exInstPkgId ex- , C.packageKey = exInstKey ex- , C.depends = map (C.InstalledPackageId . exInstHash)- (exInstBuildAgainst ex)+ , C.depends = map C.mkUnitId (exInstBuildAgainst ex) } exInstPkgId :: ExampleInstalled -> C.PackageIdentifier@@ -252,46 +380,51 @@ , pkgVersion = Version [exInstVersion ex, 0, 0] [] } -exInstLibName :: ExampleInstalled -> C.LibraryName-exInstLibName ex = C.packageKeyLibraryName (exInstPkgId ex) (exInstKey ex)--exInstKey :: ExampleInstalled -> C.PackageKey-exInstKey ex =- C.mkPackageKey True- (exInstPkgId ex)- (map exInstLibName (exInstBuildAgainst ex))--exAvIdx :: [ExampleAvailable] -> CI.PackageIndex.PackageIndex SourcePackage+exAvIdx :: [ExampleAvailable] -> CI.PackageIndex.PackageIndex UnresolvedSourcePackage exAvIdx = CI.PackageIndex.fromList . map exAvSrcPkg exInstIdx :: [ExampleInstalled] -> C.PackageIndex.InstalledPackageIndex exInstIdx = C.PackageIndex.fromList . map exInstInfo exResolve :: ExampleDb+ -- List of extensions supported by the compiler, or Nothing if unknown.+ -> Maybe [Extension]+ -- List of languages supported by the compiler, or Nothing if unknown.+ -> Maybe [Language]+ -> PC.PkgConfigDb -> [ExamplePkgName]- -> Bool+ -> Solver+ -> IndepGoals+ -> ReorderGoals+ -> [ExPreference] -> ([String], Either String CI.InstallPlan.InstallPlan)-exResolve db targets indepGoals = runProgress $+exResolve db exts langs pkgConfigDb targets solver (IndepGoals indepGoals) (ReorderGoals reorder) prefs = runProgress $ resolveDependencies C.buildPlatform- (C.unknownCompilerInfo C.buildCompilerId C.NoAbiTag)- Modular+ compiler pkgConfigDb+ solver params where+ defaultCompiler = C.unknownCompilerInfo C.buildCompilerId C.NoAbiTag+ compiler = defaultCompiler { C.compilerInfoExtensions = exts+ , C.compilerInfoLanguages = langs+ } (inst, avai) = partitionEithers db instIdx = exInstIdx inst avaiIdx = SourcePackageDb { packageIndex = exAvIdx avai , packagePreferences = Map.empty }- enableTests = map (\p -> PackageConstraintStanzas+ enableTests = fmap (\p -> PackageConstraintStanzas (C.PackageName p) [TestStanzas]) (exDbPkgs db)- targets' = map (\p -> NamedPackage (C.PackageName p) []) targets- params = addConstraints (map toLpc enableTests)- $ (standardInstallPolicy instIdx avaiIdx targets') {- depResolverIndependentGoals = indepGoals- }+ targets' = fmap (\p -> NamedPackage (C.PackageName p) []) targets+ params = addPreferences (fmap toPref prefs)+ $ addConstraints (fmap toLpc enableTests)+ $ setIndependentGoals indepGoals+ $ setReorderGoals reorder+ $ standardInstallPolicy instIdx avaiIdx targets' toLpc pc = LabeledPackageConstraint pc ConstraintSourceUnknown+ toPref (ExPref n v) = PackageVersionPreference (C.PackageName n) v extractInstallPlan :: CI.InstallPlan.InstallPlan -> [(ExamplePkgName, ExamplePkgVersion)]@@ -301,10 +434,10 @@ confPkg (CI.InstallPlan.Configured pkg) = Just $ srcPkg pkg confPkg _ = Nothing - srcPkg :: ConfiguredPackage -> (String, Int)- srcPkg (ConfiguredPackage pkg _flags _stanzas _deps) =+ srcPkg :: ConfiguredPackage UnresolvedPkgLoc -> (String, Int)+ srcPkg cpkg = let C.PackageIdentifier (C.PackageName p) (Version (n:_) _) =- packageInfoId pkg+ packageInfoId (confPkgSource cpkg) in (p, n) {-------------------------------------------------------------------------------
+ cabal/cabal-install/tests/UnitTests/Distribution/Client/Dependency/Modular/QuickCheck.hs view
@@ -0,0 +1,291 @@+{-# LANGUAGE CPP #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module UnitTests.Distribution.Client.Dependency.Modular.QuickCheck (tests) where++import Control.Monad (foldM)+import Data.Either (lefts)+import Data.Function (on)+import Data.List (groupBy, nub, sort)+import Data.Maybe (isJust)++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative ((<$>), (<*>))+import Data.Monoid (Monoid)+#endif++import Text.Show.Pretty (parseValue, valToStr)++import Test.Tasty (TestTree)+import Test.Tasty.QuickCheck++import qualified Distribution.Client.ComponentDeps as CD+import Distribution.Client.ComponentDeps ( Component(..)+ , ComponentDep, ComponentDeps)+import Distribution.Client.Dependency.Types (Solver(..))+import Distribution.Client.PkgConfigDb (pkgConfigDbFromList)++import UnitTests.Distribution.Client.Dependency.Modular.DSL++tests :: [TestTree]+tests = [+ -- This test checks that certain solver parameters do not affect the+ -- existence of a solution. It runs the solver twice, and only sets those+ -- parameters on the second run. The test also applies parameters that+ -- can affect the existence of a solution to both runs.+ testProperty "target order and --reorder-goals do not affect solvability" $+ \(SolverTest db targets) targetOrder reorderGoals indepGoals solver ->+ let r1 = solve (ReorderGoals False) indepGoals solver targets db+ r2 = solve reorderGoals indepGoals solver targets2 db+ targets2 = case targetOrder of+ SameOrder -> targets+ ReverseOrder -> reverse targets+ in counterexample (showResults r1 r2) $+ isJust (resultPlan r1) === isJust (resultPlan r2)+ ]+ where+ showResults :: Result -> Result -> String+ showResults r1 r2 = showResult 1 r1 ++ showResult 2 r2++ showResult :: Int -> Result -> String+ showResult n result =+ unlines $ ["", "Run " ++ show n ++ ":"]+ ++ resultLog result+ ++ ["result: " ++ show (resultPlan result)]++solve :: ReorderGoals -> IndepGoals -> Solver -> [PN] -> TestDb -> Result+solve reorder indep solver targets (TestDb db) =+ let (lg, result) =+ exResolve db Nothing Nothing+ (pkgConfigDbFromList [])+ (map unPN targets)+ solver indep reorder []+ in Result {+ resultLog = lg+ , resultPlan =+ case result of+ Left _ -> Nothing+ Right plan -> Just (extractInstallPlan plan)+ }++-- | How to modify the order of the input targets.+data TargetOrder = SameOrder | ReverseOrder+ deriving Show++instance Arbitrary TargetOrder where+ arbitrary = elements [SameOrder, ReverseOrder]++ shrink SameOrder = []+ shrink ReverseOrder = [SameOrder]++data Result = Result {+ resultLog :: [String]+ , resultPlan :: Maybe [(ExamplePkgName, ExamplePkgVersion)]+ }++-- | Package name.+newtype PN = PN { unPN :: String }+ deriving (Eq, Ord, Show)++instance Arbitrary PN where+ arbitrary = PN <$> elements ("base" : [[pn] | pn <- ['A'..'G']])++-- | Package version.+newtype PV = PV { unPV :: Int }+ deriving (Eq, Ord, Show)++instance Arbitrary PV where+ arbitrary = PV <$> elements [1..10]++type TestPackage = Either ExampleInstalled ExampleAvailable++getName :: TestPackage -> PN+getName = PN . either exInstName exAvName++getVersion :: TestPackage -> PV+getVersion = PV . either exInstVersion exAvVersion++data SolverTest = SolverTest {+ testDb :: TestDb+ , testTargets :: [PN]+ }++-- | Pretty-print the test when quickcheck calls 'show'.+instance Show SolverTest where+ show test =+ let str = "SolverTest {testDb = " ++ show (testDb test)+ ++ ", testTargets = " ++ show (testTargets test) ++ "}"+ in maybe str valToStr $ parseValue str++instance Arbitrary SolverTest where+ arbitrary = do+ db <- arbitrary+ let pkgs = nub $ map getName (unTestDb db)+ Positive n <- arbitrary+ targets <- randomSubset n pkgs+ return (SolverTest db targets)++ shrink test =+ [test { testDb = db } | db <- shrink (testDb test)]+ ++ [test { testTargets = targets } | targets <- shrink (testTargets test)]++-- | Collection of source and installed packages.+newtype TestDb = TestDb { unTestDb :: ExampleDb }+ deriving Show++instance Arbitrary TestDb where+ arbitrary = do+ -- Avoid cyclic dependencies by grouping packages by name and only+ -- allowing each package to depend on packages in the groups before it.+ groupedPkgs <- shuffle . groupBy ((==) `on` fst) . nub . sort =<<+ boundedListOf 10 arbitrary+ db <- foldM nextPkgs (TestDb []) groupedPkgs+ TestDb <$> shuffle (unTestDb db)+ where+ nextPkgs :: TestDb -> [(PN, PV)] -> Gen TestDb+ nextPkgs db pkgs = TestDb . (++ unTestDb db) <$> mapM (nextPkg db) pkgs++ nextPkg :: TestDb -> (PN, PV) -> Gen TestPackage+ nextPkg db (pn, v) = do+ installed <- arbitrary+ if installed+ then Left <$> arbitraryExInst pn v (lefts $ unTestDb db)+ else Right <$> arbitraryExAv pn v db++ shrink (TestDb pkgs) = map TestDb $ shrink pkgs++arbitraryExAv :: PN -> PV -> TestDb -> Gen ExampleAvailable+arbitraryExAv pn v db =+ ExAv (unPN pn) (unPV v) <$> arbitraryComponentDeps db++arbitraryExInst :: PN -> PV -> [ExampleInstalled] -> Gen ExampleInstalled+arbitraryExInst pn v pkgs = do+ hash <- vectorOf 10 $ elements $ ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9']+ numDeps <- min 3 <$> arbitrary+ deps <- randomSubset numDeps pkgs+ return $ ExInst (unPN pn) (unPV v) hash (map exInstHash deps)++arbitraryComponentDeps :: TestDb -> Gen (ComponentDeps [ExampleDependency])+arbitraryComponentDeps (TestDb []) = return $ CD.fromList []+arbitraryComponentDeps db =+ -- CD.fromList combines duplicate components.+ CD.fromList <$> boundedListOf 3 (arbitraryComponentDep db)++arbitraryComponentDep :: TestDb -> Gen (ComponentDep [ExampleDependency])+arbitraryComponentDep db = do+ comp <- arbitrary+ deps <- case comp of+ ComponentSetup -> smallListOf (arbitraryExDep db Setup)+ _ -> boundedListOf 5 (arbitraryExDep db NonSetup)+ return (comp, deps)++-- | Location of an 'ExampleDependency'. It determines which values are valid.+data ExDepLocation = Setup | NonSetup++arbitraryExDep :: TestDb -> ExDepLocation -> Gen ExampleDependency+arbitraryExDep db@(TestDb pkgs) level =+ let flag = ExFlag <$> arbitraryFlagName+ <*> arbitraryDeps db+ <*> arbitraryDeps db+ other = [+ ExAny . unPN <$> elements (map getName pkgs)++ -- existing version+ , let fixed pkg = ExFix (unPN $ getName pkg) (unPV $ getVersion pkg)+ in fixed <$> elements pkgs++ -- random version of an existing package+ , ExFix . unPN . getName <$> elements pkgs <*> (unPV <$> arbitrary)+ ]+ in oneof $+ case level of+ NonSetup -> flag : other+ Setup -> other++arbitraryDeps :: TestDb -> Gen Dependencies+arbitraryDeps db = frequency+ [ (1, return NotBuildable)+ , (20, Buildable <$> smallListOf (arbitraryExDep db NonSetup))+ ]++arbitraryFlagName :: Gen String+arbitraryFlagName = (:[]) <$> elements ['A'..'E']++arbitraryComponentName :: Gen String+arbitraryComponentName = (:[]) <$> elements "ABC"++instance Arbitrary ReorderGoals where+ arbitrary = ReorderGoals <$> arbitrary++ shrink (ReorderGoals reorder) = [ReorderGoals False | reorder]++instance Arbitrary IndepGoals where+ arbitrary = IndepGoals <$> arbitrary++ shrink (IndepGoals indep) = [IndepGoals False | indep]++instance Arbitrary Solver where+ arbitrary = frequency [ (1, return TopDown)+ , (5, return Modular) ]++ shrink Modular = []+ shrink TopDown = [Modular]++instance Arbitrary Component where+ arbitrary = oneof [ ComponentLib <$> arbitraryComponentName+ , ComponentExe <$> arbitraryComponentName+ , ComponentTest <$> arbitraryComponentName+ , ComponentBench <$> arbitraryComponentName+ , return ComponentSetup+ ]++ shrink (ComponentLib "") = []+ shrink _ = [ComponentLib ""]++instance Arbitrary ExampleInstalled where+ arbitrary = error "arbitrary not implemented: ExampleInstalled"++ shrink ei = [ ei { exInstBuildAgainst = deps }+ | deps <- shrinkList shrinkNothing (exInstBuildAgainst ei)]++instance Arbitrary ExampleAvailable where+ arbitrary = error "arbitrary not implemented: ExampleAvailable"++ shrink ea = [ea { exAvDeps = deps } | deps <- shrink (exAvDeps ea)]++instance (Arbitrary a, Monoid a) => Arbitrary (ComponentDeps a) where+ arbitrary = error "arbitrary not implemented: ComponentDeps"++ shrink = map CD.fromList . shrink . CD.toList++instance Arbitrary ExampleDependency where+ arbitrary = error "arbitrary not implemented: ExampleDependency"++ shrink (ExAny _) = []+ shrink (ExFix pn _) = [ExAny pn]+ shrink (ExFlag flag th el) =+ deps th ++ deps el+ ++ [ExFlag flag th' el | th' <- shrink th]+ ++ [ExFlag flag th el' | el' <- shrink el]+ where+ deps NotBuildable = []+ deps (Buildable ds) = ds+ shrink dep = error $ "Dependency not handled: " ++ show dep++instance Arbitrary Dependencies where+ arbitrary = error "arbitrary not implemented: Dependencies"++ shrink NotBuildable = [Buildable []]+ shrink (Buildable deps) = map Buildable (shrink deps)++randomSubset :: Int -> [a] -> Gen [a]+randomSubset n xs = take n <$> shuffle xs++boundedListOf :: Int -> Gen a -> Gen [a]+boundedListOf n gen = take n <$> listOf gen++-- | Generates lists with average length less than 1.+smallListOf :: Gen a -> Gen [a]+smallListOf gen =+ frequency [ (fr, vectorOf n gen)+ | (fr, n) <- [(3, 0), (5, 1), (2, 2)]]
cabal/cabal-install/tests/UnitTests/Distribution/Client/Dependency/Modular/Solver.hs view
@@ -1,20 +1,27 @@ {-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE DeriveDataTypeable #-}-module UnitTests.Distribution.Client.Dependency.Modular.Solver (tests, options) where+module UnitTests.Distribution.Client.Dependency.Modular.Solver (tests)+ where -- base import Control.Monad import Data.Maybe (isNothing)-import Data.Proxy-import Data.Typeable +import qualified Data.Version as V+import qualified Distribution.Version as V+ -- test-framework import Test.Tasty as TF import Test.Tasty.HUnit (testCase, assertEqual, assertBool)-import Test.Tasty.Options +-- Cabal+import Language.Haskell.Extension ( Extension(..)+ , KnownExtension(..), Language(..))+ -- cabal-install+import Distribution.Client.PkgConfigDb (PkgConfigDb, pkgConfigDbFromList)+import Distribution.Client.Dependency.Types (Solver(Modular)) import UnitTests.Distribution.Client.Dependency.Modular.DSL+import UnitTests.Options tests :: [TF.TestTree] tests = [@@ -67,39 +74,155 @@ , runTest $ mkTest db12 "baseShim5" ["D"] Nothing , runTest $ mkTest db12 "baseShim6" ["E"] (Just [("E", 1), ("syb", 2)]) ]+ , testGroup "Cycles" [+ runTest $ mkTest db14 "simpleCycle1" ["A"] Nothing+ , runTest $ mkTest db14 "simpleCycle2" ["A", "B"] Nothing+ , runTest $ mkTest db14 "cycleWithFlagChoice1" ["C"] (Just [("C", 1), ("E", 1)])+ , runTest $ mkTest db15 "cycleThroughSetupDep1" ["A"] Nothing+ , runTest $ mkTest db15 "cycleThroughSetupDep2" ["B"] Nothing+ , runTest $ mkTest db15 "cycleThroughSetupDep3" ["C"] (Just [("C", 2), ("D", 1)])+ , runTest $ mkTest db15 "cycleThroughSetupDep4" ["D"] (Just [("D", 1)])+ , runTest $ mkTest db15 "cycleThroughSetupDep5" ["E"] (Just [("C", 2), ("D", 1), ("E", 1)])+ ]+ , testGroup "Extensions" [+ runTest $ mkTestExts [EnableExtension CPP] dbExts1 "unsupported" ["A"] Nothing+ , runTest $ mkTestExts [EnableExtension CPP] dbExts1 "unsupportedIndirect" ["B"] Nothing+ , runTest $ mkTestExts [EnableExtension RankNTypes] dbExts1 "supported" ["A"] (Just [("A",1)])+ , runTest $ mkTestExts (map EnableExtension [CPP,RankNTypes]) dbExts1 "supportedIndirect" ["C"] (Just [("A",1),("B",1), ("C",1)])+ , runTest $ mkTestExts [EnableExtension CPP] dbExts1 "disabledExtension" ["D"] Nothing+ , runTest $ mkTestExts (map EnableExtension [CPP,RankNTypes]) dbExts1 "disabledExtension" ["D"] Nothing+ , runTest $ mkTestExts (UnknownExtension "custom" : map EnableExtension [CPP,RankNTypes]) dbExts1 "supportedUnknown" ["E"] (Just [("A",1),("B",1),("C",1),("E",1)])+ ]+ , testGroup "Languages" [+ runTest $ mkTestLangs [Haskell98] dbLangs1 "unsupported" ["A"] Nothing+ , runTest $ mkTestLangs [Haskell98,Haskell2010] dbLangs1 "supported" ["A"] (Just [("A",1)])+ , runTest $ mkTestLangs [Haskell98] dbLangs1 "unsupportedIndirect" ["B"] Nothing+ , runTest $ mkTestLangs [Haskell98, Haskell2010, UnknownLanguage "Haskell3000"] dbLangs1 "supportedUnknown" ["C"] (Just [("A",1),("B",1),("C",1)])+ ]++ , testGroup "Soft Constraints" [+ runTest $ soft [ ExPref "A" $ mkvrThis 1] $ mkTest db13 "selectPreferredVersionSimple" ["A"] (Just [("A", 1)])+ , runTest $ soft [ ExPref "A" $ mkvrOrEarlier 2] $ mkTest db13 "selectPreferredVersionSimple2" ["A"] (Just [("A", 2)])+ , runTest $ soft [ ExPref "A" $ mkvrOrEarlier 2+ , ExPref "A" $ mkvrOrEarlier 1] $ mkTest db13 "selectPreferredVersionMultiple" ["A"] (Just [("A", 1)])+ , runTest $ soft [ ExPref "A" $ mkvrOrEarlier 1+ , ExPref "A" $ mkvrOrEarlier 2] $ mkTest db13 "selectPreferredVersionMultiple2" ["A"] (Just [("A", 1)])+ , runTest $ soft [ ExPref "A" $ mkvrThis 1+ , ExPref "A" $ mkvrThis 2] $ mkTest db13 "selectPreferredVersionMultiple3" ["A"] (Just [("A", 2)])+ , runTest $ soft [ ExPref "A" $ mkvrThis 1+ , ExPref "A" $ mkvrOrEarlier 2] $ mkTest db13 "selectPreferredVersionMultiple4" ["A"] (Just [("A", 1)])+ ]+ , testGroup "Buildable Field" [+ testBuildable "avoid building component with unknown dependency" (ExAny "unknown")+ , testBuildable "avoid building component with unknown extension" (ExExt (UnknownExtension "unknown"))+ , testBuildable "avoid building component with unknown language" (ExLang (UnknownLanguage "unknown"))+ , runTest $ mkTest dbBuildable1 "choose flags that set buildable to false" ["pkg"] (Just [("flag1-false", 1), ("flag2-true", 1), ("pkg", 1)])+ , runTest $ mkTest dbBuildable2 "choose version that sets buildable to false" ["A"] (Just [("A", 1), ("B", 2)])+ ]+ , testGroup "Pkg-config dependencies" [+ runTest $ mkTestPCDepends [] dbPC1 "noPkgs" ["A"] Nothing+ , runTest $ mkTestPCDepends [("pkgA", "0")] dbPC1 "tooOld" ["A"] Nothing+ , runTest $ mkTestPCDepends [("pkgA", "1.0.0"), ("pkgB", "1.0.0")] dbPC1 "pruneNotFound" ["C"] (Just [("A", 1), ("B", 1), ("C", 1)])+ , runTest $ mkTestPCDepends [("pkgA", "1.0.0"), ("pkgB", "2.0.0")] dbPC1 "chooseNewest" ["C"] (Just [("A", 1), ("B", 2), ("C", 1)])+ ] ] where- indep test = test { testIndepGoals = True }+ -- | Combinator to turn on --independent-goals behavior, i.e. solve+ -- for the goals as if we were solving for each goal independently.+ -- (This doesn't really work well at the moment, see #2842)+ indep test = test { testIndepGoals = IndepGoals True }+ soft prefs test = test { testSoftConstraints = prefs }+ mkvrThis = V.thisVersion . makeV+ mkvrOrEarlier = V.orEarlierVersion . makeV+ makeV v = V.Version [v,0,0] [] {------------------------------------------------------------------------------- Solver tests -------------------------------------------------------------------------------} data SolverTest = SolverTest {- testLabel :: String- , testTargets :: [String]- , testResult :: Maybe [(String, Int)]- , testIndepGoals :: Bool- , testDb :: ExampleDb+ testLabel :: String+ , testTargets :: [String]+ , testResult :: Maybe [(String, Int)]+ , testIndepGoals :: IndepGoals+ , testSoftConstraints :: [ExPreference]+ , testDb :: ExampleDb+ , testSupportedExts :: Maybe [Extension]+ , testSupportedLangs :: Maybe [Language]+ , testPkgConfigDb :: PkgConfigDb } +-- | Makes a solver test case, consisting of the following components:+--+-- 1. An 'ExampleDb', representing the package database (both+-- installed and remote) we are doing dependency solving over,+-- 2. A 'String' name for the test,+-- 3. A list '[String]' of package names to solve for+-- 4. The expected result, either 'Nothing' if there is no+-- satisfying solution, or a list '[(String, Int)]' of+-- packages to install, at which versions.+--+-- See 'UnitTests.Distribution.Client.Dependency.Modular.DSL' for how+-- to construct an 'ExampleDb', as well as definitions of 'db1' etc.+-- in this file. mkTest :: ExampleDb -> String -> [String] -> Maybe [(String, Int)] -> SolverTest-mkTest db label targets result = SolverTest {- testLabel = label- , testTargets = targets- , testResult = result- , testIndepGoals = False- , testDb = db+mkTest = mkTestExtLangPC Nothing Nothing []++mkTestExts :: [Extension]+ -> ExampleDb+ -> String+ -> [String]+ -> Maybe [(String, Int)]+ -> SolverTest+mkTestExts exts = mkTestExtLangPC (Just exts) Nothing []++mkTestLangs :: [Language]+ -> ExampleDb+ -> String+ -> [String]+ -> Maybe [(String, Int)]+ -> SolverTest+mkTestLangs langs = mkTestExtLangPC Nothing (Just langs) []++mkTestPCDepends :: [(String, String)]+ -> ExampleDb+ -> String+ -> [String]+ -> Maybe [(String, Int)]+ -> SolverTest+mkTestPCDepends pkgConfigDb = mkTestExtLangPC Nothing Nothing pkgConfigDb++mkTestExtLangPC :: Maybe [Extension]+ -> Maybe [Language]+ -> [(String, String)]+ -> ExampleDb+ -> String+ -> [String]+ -> Maybe [(String, Int)]+ -> SolverTest+mkTestExtLangPC exts langs pkgConfigDb db label targets result = SolverTest {+ testLabel = label+ , testTargets = targets+ , testResult = result+ , testIndepGoals = IndepGoals False+ , testSoftConstraints = []+ , testDb = db+ , testSupportedExts = exts+ , testSupportedLangs = langs+ , testPkgConfigDb = pkgConfigDbFromList pkgConfigDb } runTest :: SolverTest -> TF.TestTree runTest SolverTest{..} = askOption $ \(OptionShowSolverLog showSolverLog) -> testCase testLabel $ do- let (_msgs, result) = exResolve testDb testTargets testIndepGoals+ let (_msgs, result) = exResolve testDb testSupportedExts+ testSupportedLangs testPkgConfigDb testTargets+ Modular testIndepGoals (ReorderGoals False)+ testSoftConstraints when showSolverLog $ mapM_ putStrLn _msgs case result of Left err -> assertBool ("Unexpected error:\n" ++ err) (isNothing testResult)@@ -139,27 +262,27 @@ db3 = [ Right $ exAv "A" 1 [] , Right $ exAv "A" 2 []- , Right $ exAv "B" 1 [ExFlag "flagB" [ExFix "A" 1] [ExFix "A" 2]]+ , Right $ exAv "B" 1 [exFlag "flagB" [ExFix "A" 1] [ExFix "A" 2]] , Right $ exAv "C" 1 [ExFix "A" 1, ExAny "B"] , Right $ exAv "D" 1 [ExFix "A" 2, ExAny "B"] ] --- | Like exampleDb2, but the flag picks a different package rather than a+-- | Like db3, but the flag picks a different package rather than a -- different package version ----- In exampleDb2 we cannot install C and D as independent goals because:+-- In db3 we cannot install C and D as independent goals because: -- -- * The multiple instance restriction says C and D _must_ share B--- * Since C relies on A.1, C needs B to be compiled with flagB on--- * Since D relies on A.2, D needs B to be compiled with flagsB off+-- * Since C relies on A-1, C needs B to be compiled with flagB on+-- * Since D relies on A-2, D needs B to be compiled with flagB off -- * Hence C and D have incompatible requirements on B's flags. -- -- However, _even_ if we don't check explicitly that we pick the same flag -- assignment for 0.B and 1.B, we will still detect the problem because -- 0.B depends on 0.A-1, 1.B depends on 1.A-2, hence we cannot link 0.A to--- 1.B and therefore we cannot link 0.B to 1.B.+-- 1.A and therefore we cannot link 0.B to 1.B. ----- In exampleDb3 the situation however is trickier. We again cannot install+-- In db4 the situation however is trickier. We again cannot install -- packages C and D as independent goals because: -- -- * As above, the multiple instance restriction says that C and D _must_ share B@@ -171,10 +294,10 @@ -- we don't see the problem: -- -- * We link 0.B to 1.B--- * 0.B relies on Ay.1--- * 1.B relies on Ax.1+-- * 0.B relies on Ay-1+-- * 1.B relies on Ax-1 ----- We will insist that 0.Ay will be linked to 1.Ay, and 0.Ax to 1.A, but since+-- We will insist that 0.Ay will be linked to 1.Ay, and 0.Ax to 1.Ax, but since -- we only ever assign to one of these, these constraints are never broken. db4 :: ExampleDb db4 = [@@ -182,7 +305,7 @@ , Right $ exAv "Ax" 2 [] , Right $ exAv "Ay" 1 [] , Right $ exAv "Ay" 2 []- , Right $ exAv "B" 1 [ExFlag "flagB" [ExFix "Ax" 1] [ExFix "Ay" 1]]+ , Right $ exAv "B" 1 [exFlag "flagB" [ExFix "Ax" 1] [ExFix "Ay" 1]] , Right $ exAv "C" 1 [ExFix "Ax" 2, ExAny "B"] , Right $ exAv "D" 1 [ExFix "Ay" 2, ExAny "B"] ]@@ -207,11 +330,11 @@ Right $ exAv "A" 1 [] , Right $ exAv "A" 2 [] , Right $ exAv "B" 1 []- , Right $ exAv "C" 1 [ExTest "testC" [ExAny "A"]]- , Right $ exAv "D" 1 [ExTest "testD" [ExFix "B" 2]]- , Right $ exAv "E" 1 [ExFix "A" 1, ExTest "testE" [ExAny "A"]]- , Right $ exAv "F" 1 [ExFix "A" 1, ExTest "testF" [ExFix "A" 2]]- , Right $ exAv "G" 1 [ExFix "A" 2, ExTest "testG" [ExAny "A"]]+ , Right $ exAv "C" 1 [] `withTest` ExTest "testC" [ExAny "A"]+ , Right $ exAv "D" 1 [] `withTest` ExTest "testD" [ExFix "B" 2]+ , Right $ exAv "E" 1 [ExFix "A" 1] `withTest` ExTest "testE" [ExAny "A"]+ , Right $ exAv "F" 1 [ExFix "A" 1] `withTest` ExTest "testF" [ExFix "A" 2]+ , Right $ exAv "G" 1 [ExFix "A" 2] `withTest` ExTest "testG" [ExAny "A"] ] -- Now the _dependencies_ have test suites@@ -226,7 +349,7 @@ db6 = [ Right $ exAv "A" 1 [] , Right $ exAv "A" 2 []- , Right $ exAv "B" 1 [ExTest "testA" [ExAny "A"]]+ , Right $ exAv "B" 1 [] `withTest` ExTest "testA" [ExAny "A"] , Right $ exAv "C" 1 [ExFix "A" 1, ExAny "B"] , Right $ exAv "D" 1 [ExAny "B"] ]@@ -340,21 +463,126 @@ , Right $ exAv "E" 1 [ExFix "base" 4, ExFix "syb" 2] ] -{-------------------------------------------------------------------------------- Test options--------------------------------------------------------------------------------}+db13 :: ExampleDb+db13 = [+ Right $ exAv "A" 1 []+ , Right $ exAv "A" 2 []+ , Right $ exAv "A" 3 []+ ] -options :: [OptionDescription]-options = [- Option (Proxy :: Proxy OptionShowSolverLog)+-- | Database with some cycles+--+-- * Simplest non-trivial cycle: A -> B and B -> A+-- * There is a cycle C -> D -> C, but it can be broken by picking the+-- right flag assignment.+db14 :: ExampleDb+db14 = [+ Right $ exAv "A" 1 [ExAny "B"]+ , Right $ exAv "B" 1 [ExAny "A"]+ , Right $ exAv "C" 1 [exFlag "flagC" [ExAny "D"] [ExAny "E"]]+ , Right $ exAv "D" 1 [ExAny "C"]+ , Right $ exAv "E" 1 [] ] -newtype OptionShowSolverLog = OptionShowSolverLog Bool- deriving Typeable+-- | Cycles through setup dependencies+--+-- The first cycle is unsolvable: package A has a setup dependency on B,+-- B has a regular dependency on A, and we only have a single version available+-- for both.+--+-- The second cycle can be broken by picking different versions: package C-2.0+-- has a setup dependency on D, and D has a regular dependency on C-*. However,+-- version C-1.0 is already available (perhaps it didn't have this setup dep).+-- Thus, we should be able to break this cycle even if we are installing package+-- E, which explictly depends on C-2.0.+db15 :: ExampleDb+db15 = [+ -- First example (real cycle, no solution)+ Right $ exAv "A" 1 [] `withSetupDeps` [ExAny "B"]+ , Right $ exAv "B" 1 [ExAny "A"]+ -- Second example (cycle can be broken by picking versions carefully)+ , Left $ exInst "C" 1 "C-1-inst" []+ , Right $ exAv "C" 2 [] `withSetupDeps` [ExAny "D"]+ , Right $ exAv "D" 1 [ExAny "C" ]+ , Right $ exAv "E" 1 [ExFix "C" 2]+ ] -instance IsOption OptionShowSolverLog where- defaultValue = OptionShowSolverLog False- parseValue = fmap OptionShowSolverLog . safeRead- optionName = return "show-solver-log"- optionHelp = return "Show full log from the solver"- optionCLParser = flagCLParser Nothing (OptionShowSolverLog True)+dbExts1 :: ExampleDb+dbExts1 = [+ Right $ exAv "A" 1 [ExExt (EnableExtension RankNTypes)]+ , Right $ exAv "B" 1 [ExExt (EnableExtension CPP), ExAny "A"]+ , Right $ exAv "C" 1 [ExAny "B"]+ , Right $ exAv "D" 1 [ExExt (DisableExtension CPP), ExAny "B"]+ , Right $ exAv "E" 1 [ExExt (UnknownExtension "custom"), ExAny "C"]+ ]++dbLangs1 :: ExampleDb+dbLangs1 = [+ Right $ exAv "A" 1 [ExLang Haskell2010]+ , Right $ exAv "B" 1 [ExLang Haskell98, ExAny "A"]+ , Right $ exAv "C" 1 [ExLang (UnknownLanguage "Haskell3000"), ExAny "B"]+ ]++-- | cabal must set enable-lib to false in order to avoid the unavailable+-- dependency. Flags are true by default. The flag choice causes "pkg" to+-- depend on "false-dep".+testBuildable :: String -> ExampleDependency -> TestTree+testBuildable testName unavailableDep =+ runTest $ mkTestExtLangPC (Just []) (Just []) [] db testName ["pkg"] expected+ where+ expected = Just [("false-dep", 1), ("pkg", 1)]+ db = [+ Right $ exAv "pkg" 1+ [ unavailableDep+ , ExFlag "enable-lib" (Buildable []) NotBuildable ]+ `withTest`+ ExTest "test" [exFlag "enable-lib"+ [ExAny "true-dep"]+ [ExAny "false-dep"]]+ , Right $ exAv "true-dep" 1 []+ , Right $ exAv "false-dep" 1 []+ ]++-- | cabal must choose -flag1 +flag2 for "pkg", which requires packages+-- "flag1-false" and "flag2-true".+dbBuildable1 :: ExampleDb+dbBuildable1 = [+ Right $ exAv "pkg" 1+ [ ExAny "unknown"+ , ExFlag "flag1" (Buildable []) NotBuildable+ , ExFlag "flag2" (Buildable []) NotBuildable]+ `withTests`+ [ ExTest "optional-test"+ [ ExAny "unknown"+ , ExFlag "flag1"+ (Buildable [])+ (Buildable [ExFlag "flag2" NotBuildable (Buildable [])])]+ , ExTest "test" [ exFlag "flag1" [ExAny "flag1-true"] [ExAny "flag1-false"]+ , exFlag "flag2" [ExAny "flag2-true"] [ExAny "flag2-false"]]+ ]+ , Right $ exAv "flag1-true" 1 []+ , Right $ exAv "flag1-false" 1 []+ , Right $ exAv "flag2-true" 1 []+ , Right $ exAv "flag2-false" 1 []+ ]++-- | Package databases for testing @pkg-config@ dependencies.+dbPC1 :: ExampleDb+dbPC1 = [+ Right $ exAv "A" 1 [ExPkg ("pkgA", 1)]+ , Right $ exAv "B" 1 [ExPkg ("pkgB", 1), ExAny "A"]+ , Right $ exAv "B" 2 [ExPkg ("pkgB", 2), ExAny "A"]+ , Right $ exAv "C" 1 [ExAny "B"]+ ]++-- | cabal must pick B-2 to avoid the unknown dependency.+dbBuildable2 :: ExampleDb+dbBuildable2 = [+ Right $ exAv "A" 1 [ExAny "B"]+ , Right $ exAv "B" 1 [ExAny "unknown"]+ , Right $ exAv "B" 2+ [ ExAny "unknown"+ , ExFlag "disable-lib" NotBuildable (Buildable [])+ ]+ , Right $ exAv "B" 3 [ExAny "unknown"]+ ]
+ cabal/cabal-install/tests/UnitTests/Distribution/Client/FileMonitor.hs view
@@ -0,0 +1,769 @@+module UnitTests.Distribution.Client.FileMonitor (tests) where++import Control.Monad+import Control.Exception+import Control.Concurrent (threadDelay)+import qualified Data.Set as Set+import System.FilePath+import qualified System.Directory as IO+import Prelude hiding (writeFile)+import qualified Prelude as IO (writeFile)++import Distribution.Text (simpleParse)+import Distribution.Compat.Binary+import Distribution.Simple.Utils (withTempDirectory)+import Distribution.Verbosity (silent)++import Distribution.Client.FileMonitor+import Distribution.Client.Compat.Time++import Test.Tasty+import Test.Tasty.HUnit+++tests :: Int -> [TestTree]+tests mtimeChange =+ [ testCase "sanity check mtimes" $ testFileMTimeSanity mtimeChange+ , testCase "no monitor cache" testNoMonitorCache+ , testCase "corrupt monitor cache" testCorruptMonitorCache+ , testCase "empty monitor" testEmptyMonitor+ , testCase "missing file" testMissingFile+ , testCase "change file" $ testChangedFile mtimeChange+ , testCase "file mtime vs content" $ testChangedFileMtimeVsContent mtimeChange+ , testCase "update during action" $ testUpdateDuringAction mtimeChange+ , testCase "remove file" testRemoveFile+ , testCase "non-existent file" testNonExistentFile+ , testCase "changed file type" $ testChangedFileType mtimeChange++ , testGroup "glob matches"+ [ testCase "no change" testGlobNoChange+ , testCase "add match" $ testGlobAddMatch mtimeChange+ , testCase "remove match" $ testGlobRemoveMatch mtimeChange+ , testCase "change match" $ testGlobChangeMatch mtimeChange++ , testCase "add match subdir" $ testGlobAddMatchSubdir mtimeChange+ , testCase "remove match subdir" $ testGlobRemoveMatchSubdir mtimeChange+ , testCase "change match subdir" $ testGlobChangeMatchSubdir mtimeChange++ , testCase "match toplevel dir" $ testGlobMatchTopDir mtimeChange+ , testCase "add non-match" $ testGlobAddNonMatch mtimeChange+ , testCase "remove non-match" $ testGlobRemoveNonMatch mtimeChange++ , testCase "add non-match" $ testGlobAddNonMatchSubdir mtimeChange+ , testCase "remove non-match" $ testGlobRemoveNonMatchSubdir mtimeChange++ , testCase "invariant sorted 1" $ testInvariantMonitorStateGlobFiles+ mtimeChange+ , testCase "invariant sorted 2" $ testInvariantMonitorStateGlobDirs+ mtimeChange++ , testCase "match dirs" $ testGlobMatchDir mtimeChange+ , testCase "match dirs only" $ testGlobMatchDirOnly mtimeChange+ , testCase "change file type" $ testGlobChangeFileType mtimeChange+ , testCase "absolute paths" $ testGlobAbsolutePath mtimeChange+ ]++ , testCase "value unchanged" testValueUnchanged+ , testCase "value changed" testValueChanged+ , testCase "value & file changed" $ testValueAndFileChanged mtimeChange+ , testCase "value updated" testValueUpdated+ ]++-- we rely on file mtimes having a reasonable resolution+testFileMTimeSanity :: Int -> Assertion+testFileMTimeSanity mtimeChange =+ withTempDirectory silent "." "file-status-" $ \dir -> do+ replicateM_ 10 $ do+ IO.writeFile (dir </> "a") "content"+ t1 <- getModTime (dir </> "a")+ threadDelay mtimeChange+ IO.writeFile (dir </> "a") "content"+ t2 <- getModTime (dir </> "a")+ assertBool "expected different file mtimes" (t2 > t1)++-- first run, where we don't even call updateMonitor+testNoMonitorCache :: Assertion+testNoMonitorCache =+ withFileMonitor $ \root monitor -> do+ reason <- expectMonitorChanged root (monitor :: FileMonitor () ()) ()+ reason @?= MonitorFirstRun++-- write garbage into the binary cache file+testCorruptMonitorCache :: Assertion+testCorruptMonitorCache =+ withFileMonitor $ \root monitor -> do+ IO.writeFile (fileMonitorCacheFile monitor) "broken"+ reason <- expectMonitorChanged root monitor ()+ reason @?= MonitorCorruptCache++ updateMonitor root monitor [] () ()+ (res, files) <- expectMonitorUnchanged root monitor ()+ res @?= ()+ files @?= []++ IO.writeFile (fileMonitorCacheFile monitor) "broken"+ reason2 <- expectMonitorChanged root monitor ()+ reason2 @?= MonitorCorruptCache++-- no files to monitor+testEmptyMonitor :: Assertion+testEmptyMonitor =+ withFileMonitor $ \root monitor -> do+ touchFile root "a"+ updateMonitor root monitor [] () ()+ touchFile root "b"+ (res, files) <- expectMonitorUnchanged root monitor ()+ res @?= ()+ files @?= []++-- monitor a file that is expected to exist+testMissingFile :: Assertion+testMissingFile = do+ test monitorFile touchFile "a"+ test monitorFileHashed touchFile "a"+ test monitorFile touchFile ("dir" </> "a")+ test monitorFileHashed touchFile ("dir" </> "a")+ test monitorDirectory touchDir "a"+ test monitorDirectory touchDir ("dir" </> "a")+ where+ test :: (FilePath -> MonitorFilePath)+ -> (RootPath -> FilePath -> IO ())+ -> FilePath+ -> IO ()+ test monitorKind touch file =+ withFileMonitor $ \root monitor -> do+ -- a file that doesn't exist at snapshot time is considered to have+ -- changed+ updateMonitor root monitor [monitorKind file] () ()+ reason <- expectMonitorChanged root monitor ()+ reason @?= MonitoredFileChanged file++ -- a file doesn't exist at snapshot time, but gets added afterwards is+ -- also considered to have changed+ updateMonitor root monitor [monitorKind file] () ()+ touch root file+ reason2 <- expectMonitorChanged root monitor ()+ reason2 @?= MonitoredFileChanged file+++testChangedFile :: Int -> Assertion+testChangedFile mtimeChange = do+ test monitorFile touchFile touchFile "a"+ test monitorFileHashed touchFile touchFileContent "a"+ test monitorFile touchFile touchFile ("dir" </> "a")+ test monitorFileHashed touchFile touchFileContent ("dir" </> "a")+ test monitorDirectory touchDir touchDir "a"+ test monitorDirectory touchDir touchDir ("dir" </> "a")+ where+ test :: (FilePath -> MonitorFilePath)+ -> (RootPath -> FilePath -> IO ())+ -> (RootPath -> FilePath -> IO ())+ -> FilePath+ -> IO ()+ test monitorKind touch touch' file =+ withFileMonitor $ \root monitor -> do+ touch root file+ updateMonitor root monitor [monitorKind file] () ()+ threadDelay mtimeChange+ touch' root file+ reason <- expectMonitorChanged root monitor ()+ reason @?= MonitoredFileChanged file+++testChangedFileMtimeVsContent :: Int -> Assertion+testChangedFileMtimeVsContent mtimeChange =+ withFileMonitor $ \root monitor -> do+ -- if we don't touch the file, it's unchanged+ touchFile root "a"+ updateMonitor root monitor [monitorFile "a"] () ()+ (res, files) <- expectMonitorUnchanged root monitor ()+ res @?= ()+ files @?= [monitorFile "a"]++ -- if we do touch the file, it's changed if we only consider mtime+ updateMonitor root monitor [monitorFile "a"] () ()+ threadDelay mtimeChange+ touchFile root "a"+ reason <- expectMonitorChanged root monitor ()+ reason @?= MonitoredFileChanged "a"++ -- but if we touch the file, it's unchanged if we consider content hash+ updateMonitor root monitor [monitorFileHashed "a"] () ()+ threadDelay mtimeChange+ touchFile root "a"+ (res2, files2) <- expectMonitorUnchanged root monitor ()+ res2 @?= ()+ files2 @?= [monitorFileHashed "a"]++ -- finally if we change the content it's changed+ updateMonitor root monitor [monitorFileHashed "a"] () ()+ threadDelay mtimeChange+ touchFileContent root "a"+ reason2 <- expectMonitorChanged root monitor ()+ reason2 @?= MonitoredFileChanged "a"+++testUpdateDuringAction :: Int -> Assertion+testUpdateDuringAction mtimeChange = do+ test (monitorFile "a") touchFile "a"+ test (monitorFileHashed "a") touchFile "a"+ test (monitorDirectory "a") touchDir "a"+ test (monitorFileGlobStr "*") touchFile "a"+ test (monitorFileGlobStr "*") { monitorKindDir = DirModTime }+ touchDir "a"+ where+ test :: MonitorFilePath+ -> (RootPath -> FilePath -> IO ())+ -> FilePath+ -> IO ()+ test monitorSpec touch file =+ withFileMonitor $ \root monitor -> do+ touch root file+ updateMonitor root monitor [monitorSpec] () ()++ -- start doing an update action...+ threadDelay mtimeChange -- some time passes+ touch root file -- a file gets updates during the action+ threadDelay mtimeChange -- some time passes then we finish+ updateMonitor root monitor [monitorSpec] () ()+ -- we don't notice this change since we took the timestamp after the+ -- action finished+ (res, files) <- expectMonitorUnchanged root monitor ()+ res @?= ()+ files @?= [monitorSpec]++ -- Let's try again, this time taking the timestamp before the action+ timestamp' <- beginUpdateFileMonitor+ threadDelay mtimeChange -- some time passes+ touch root file -- a file gets updates during the action+ threadDelay mtimeChange -- some time passes then we finish+ updateMonitorWithTimestamp root monitor timestamp' [monitorSpec] () ()+ -- now we do notice the change since we took the snapshot before the+ -- action finished+ reason <- expectMonitorChanged root monitor ()+ reason @?= MonitoredFileChanged file+++testRemoveFile :: Assertion+testRemoveFile = do+ test monitorFile touchFile removeFile "a"+ test monitorFileHashed touchFile removeFile "a"+ test monitorFile touchFile removeFile ("dir" </> "a")+ test monitorFileHashed touchFile removeFile ("dir" </> "a")+ test monitorDirectory touchDir removeDir "a"+ test monitorDirectory touchDir removeDir ("dir" </> "a")+ where+ test :: (FilePath -> MonitorFilePath)+ -> (RootPath -> FilePath -> IO ())+ -> (RootPath -> FilePath -> IO ())+ -> FilePath+ -> IO ()+ test monitorKind touch remove file =+ withFileMonitor $ \root monitor -> do+ touch root file+ updateMonitor root monitor [monitorKind file] () ()+ remove root file+ reason <- expectMonitorChanged root monitor ()+ reason @?= MonitoredFileChanged file+++-- monitor a file that we expect not to exist+testNonExistentFile :: Assertion+testNonExistentFile =+ withFileMonitor $ \root monitor -> do+ -- a file that doesn't exist at snapshot time or check time is unchanged+ updateMonitor root monitor [monitorNonExistentFile "a"] () ()+ (res, files) <- expectMonitorUnchanged root monitor ()+ res @?= ()+ files @?= [monitorNonExistentFile "a"]++ -- if the file then exists it has changed+ touchFile root "a"+ reason <- expectMonitorChanged root monitor ()+ reason @?= MonitoredFileChanged "a"++ -- if the file then exists at snapshot and check time it has changed+ updateMonitor root monitor [monitorNonExistentFile "a"] () ()+ reason2 <- expectMonitorChanged root monitor ()+ reason2 @?= MonitoredFileChanged "a"++ -- but if the file existed at snapshot time and doesn't exist at check time+ -- it is consider unchanged. This is unlike files we expect to exist, but+ -- that's because files that exist can have different content and actions+ -- can depend on that content, whereas if the action expected a file not to+ -- exist and it now does not, it'll give the same result, irrespective of+ -- the fact that the file might have existed in the meantime.+ updateMonitor root monitor [monitorNonExistentFile "a"] () ()+ removeFile root "a"+ (res2, files2) <- expectMonitorUnchanged root monitor ()+ res2 @?= ()+ files2 @?= [monitorNonExistentFile "a"]+++testChangedFileType :: Int-> Assertion+testChangedFileType mtimeChange = do+ test (monitorFile "a") touchFile removeFile createDir+ test (monitorFileHashed "a") touchFile removeFile createDir++ test (monitorDirectory "a") createDir removeDir touchFile+ test (monitorFileOrDirectory "a") createDir removeDir touchFile++ test (monitorFileGlobStr "*") { monitorKindDir = DirModTime }+ touchFile removeFile createDir+ test (monitorFileGlobStr "*") { monitorKindDir = DirModTime }+ createDir removeDir touchFile+ where+ test :: MonitorFilePath+ -> (RootPath -> String -> IO ())+ -> (RootPath -> String -> IO ())+ -> (RootPath -> String -> IO ())+ -> IO ()+ test monitorKind touch remove touch' =+ withFileMonitor $ \root monitor -> do+ touch root "a"+ updateMonitor root monitor [monitorKind] () ()+ threadDelay mtimeChange+ remove root "a"+ touch' root "a"+ reason <- expectMonitorChanged root monitor ()+ reason @?= MonitoredFileChanged "a"+++------------------+-- globs+--++testGlobNoChange :: Assertion+testGlobNoChange =+ withFileMonitor $ \root monitor -> do+ touchFile root ("dir" </> "good-a")+ touchFile root ("dir" </> "good-b")+ updateMonitor root monitor [monitorFileGlobStr "dir/good-*"] () ()+ (res, files) <- expectMonitorUnchanged root monitor ()+ res @?= ()+ files @?= [monitorFileGlobStr "dir/good-*"]++testGlobAddMatch :: Int -> Assertion+testGlobAddMatch mtimeChange =+ withFileMonitor $ \root monitor -> do+ touchFile root ("dir" </> "good-a")+ updateMonitor root monitor [monitorFileGlobStr "dir/good-*"] () ()+ (res, files) <- expectMonitorUnchanged root monitor ()+ res @?= ()+ files @?= [monitorFileGlobStr "dir/good-*"]+ threadDelay mtimeChange+ touchFile root ("dir" </> "good-b")+ reason <- expectMonitorChanged root monitor ()+ reason @?= MonitoredFileChanged ("dir" </> "good-b")++testGlobRemoveMatch :: Int -> Assertion+testGlobRemoveMatch mtimeChange =+ withFileMonitor $ \root monitor -> do+ touchFile root ("dir" </> "good-a")+ touchFile root ("dir" </> "good-b")+ updateMonitor root monitor [monitorFileGlobStr "dir/good-*"] () ()+ threadDelay mtimeChange+ removeFile root "dir/good-a"+ reason <- expectMonitorChanged root monitor ()+ reason @?= MonitoredFileChanged ("dir" </> "good-a")++testGlobChangeMatch :: Int -> Assertion+testGlobChangeMatch mtimeChange =+ withFileMonitor $ \root monitor -> do+ touchFile root ("dir" </> "good-a")+ touchFile root ("dir" </> "good-b")+ updateMonitor root monitor [monitorFileGlobStr "dir/good-*"] () ()+ threadDelay mtimeChange+ touchFile root ("dir" </> "good-b")+ (res, files) <- expectMonitorUnchanged root monitor ()+ res @?= ()+ files @?= [monitorFileGlobStr "dir/good-*"]++ touchFileContent root ("dir" </> "good-b")+ reason <- expectMonitorChanged root monitor ()+ reason @?= MonitoredFileChanged ("dir" </> "good-b")++testGlobAddMatchSubdir :: Int -> Assertion+testGlobAddMatchSubdir mtimeChange =+ withFileMonitor $ \root monitor -> do+ touchFile root ("dir" </> "a" </> "good-a")+ updateMonitor root monitor [monitorFileGlobStr "dir/*/good-*"] () ()+ threadDelay mtimeChange+ touchFile root ("dir" </> "b" </> "good-b")+ reason <- expectMonitorChanged root monitor ()+ reason @?= MonitoredFileChanged ("dir" </> "b" </> "good-b")++testGlobRemoveMatchSubdir :: Int -> Assertion+testGlobRemoveMatchSubdir mtimeChange =+ withFileMonitor $ \root monitor -> do+ touchFile root ("dir" </> "a" </> "good-a")+ touchFile root ("dir" </> "b" </> "good-b")+ updateMonitor root monitor [monitorFileGlobStr "dir/*/good-*"] () ()+ threadDelay mtimeChange+ removeDir root ("dir" </> "a")+ reason <- expectMonitorChanged root monitor ()+ reason @?= MonitoredFileChanged ("dir" </> "a" </> "good-a")++testGlobChangeMatchSubdir :: Int -> Assertion+testGlobChangeMatchSubdir mtimeChange =+ withFileMonitor $ \root monitor -> do+ touchFile root ("dir" </> "a" </> "good-a")+ touchFile root ("dir" </> "b" </> "good-b")+ updateMonitor root monitor [monitorFileGlobStr "dir/*/good-*"] () ()+ threadDelay mtimeChange+ touchFile root ("dir" </> "b" </> "good-b")+ (res, files) <- expectMonitorUnchanged root monitor ()+ res @?= ()+ files @?= [monitorFileGlobStr "dir/*/good-*"]++ touchFileContent root "dir/b/good-b"+ reason <- expectMonitorChanged root monitor ()+ reason @?= MonitoredFileChanged ("dir" </> "b" </> "good-b")++-- check nothing goes squiffy with matching in the top dir+testGlobMatchTopDir :: Int -> Assertion+testGlobMatchTopDir mtimeChange =+ withFileMonitor $ \root monitor -> do+ updateMonitor root monitor [monitorFileGlobStr "*"] () ()+ threadDelay mtimeChange+ touchFile root "a"+ reason <- expectMonitorChanged root monitor ()+ reason @?= MonitoredFileChanged "a"++testGlobAddNonMatch :: Int -> Assertion+testGlobAddNonMatch mtimeChange =+ withFileMonitor $ \root monitor -> do+ touchFile root ("dir" </> "good-a")+ updateMonitor root monitor [monitorFileGlobStr "dir/good-*"] () ()+ threadDelay mtimeChange+ touchFile root ("dir" </> "bad")+ (res, files) <- expectMonitorUnchanged root monitor ()+ res @?= ()+ files @?= [monitorFileGlobStr "dir/good-*"]++testGlobRemoveNonMatch :: Int -> Assertion+testGlobRemoveNonMatch mtimeChange =+ withFileMonitor $ \root monitor -> do+ touchFile root ("dir" </> "good-a")+ touchFile root ("dir" </> "bad")+ updateMonitor root monitor [monitorFileGlobStr "dir/good-*"] () ()+ threadDelay mtimeChange+ removeFile root "dir/bad"+ (res, files) <- expectMonitorUnchanged root monitor ()+ res @?= ()+ files @?= [monitorFileGlobStr "dir/good-*"]++testGlobAddNonMatchSubdir :: Int -> Assertion+testGlobAddNonMatchSubdir mtimeChange =+ withFileMonitor $ \root monitor -> do+ touchFile root ("dir" </> "a" </> "good-a")+ updateMonitor root monitor [monitorFileGlobStr "dir/*/good-*"] () ()+ threadDelay mtimeChange+ touchFile root ("dir" </> "b" </> "bad")+ (res, files) <- expectMonitorUnchanged root monitor ()+ res @?= ()+ files @?= [monitorFileGlobStr "dir/*/good-*"]++testGlobRemoveNonMatchSubdir :: Int -> Assertion+testGlobRemoveNonMatchSubdir mtimeChange =+ withFileMonitor $ \root monitor -> do+ touchFile root ("dir" </> "a" </> "good-a")+ touchFile root ("dir" </> "b" </> "bad")+ updateMonitor root monitor [monitorFileGlobStr "dir/*/good-*"] () ()+ threadDelay mtimeChange+ removeDir root ("dir" </> "b")+ (res, files) <- expectMonitorUnchanged root monitor ()+ res @?= ()+ files @?= [monitorFileGlobStr "dir/*/good-*"]+++-- try and tickle a bug that happens if we don't maintain the invariant that+-- MonitorStateGlobFiles entries are sorted+testInvariantMonitorStateGlobFiles :: Int -> Assertion+testInvariantMonitorStateGlobFiles mtimeChange =+ withFileMonitor $ \root monitor -> do+ touchFile root ("dir" </> "a")+ touchFile root ("dir" </> "b")+ touchFile root ("dir" </> "c")+ touchFile root ("dir" </> "d")+ updateMonitor root monitor [monitorFileGlobStr "dir/*"] () ()+ threadDelay mtimeChange+ -- so there should be no change (since we're doing content checks)+ -- but if we can get the dir entries to appear in the wrong order+ -- then if the sorted invariant is not maintained then we can fool+ -- the 'probeGlobStatus' into thinking there's changes+ removeFile root ("dir" </> "a")+ removeFile root ("dir" </> "b")+ removeFile root ("dir" </> "c")+ removeFile root ("dir" </> "d")+ touchFile root ("dir" </> "d")+ touchFile root ("dir" </> "c")+ touchFile root ("dir" </> "b")+ touchFile root ("dir" </> "a")+ (res, files) <- expectMonitorUnchanged root monitor ()+ res @?= ()+ files @?= [monitorFileGlobStr "dir/*"]++-- same thing for the subdirs case+testInvariantMonitorStateGlobDirs :: Int -> Assertion+testInvariantMonitorStateGlobDirs mtimeChange =+ withFileMonitor $ \root monitor -> do+ touchFile root ("dir" </> "a" </> "file")+ touchFile root ("dir" </> "b" </> "file")+ touchFile root ("dir" </> "c" </> "file")+ touchFile root ("dir" </> "d" </> "file")+ updateMonitor root monitor [monitorFileGlobStr "dir/*/file"] () ()+ threadDelay mtimeChange+ removeDir root ("dir" </> "a")+ removeDir root ("dir" </> "b")+ removeDir root ("dir" </> "c")+ removeDir root ("dir" </> "d")+ touchFile root ("dir" </> "d" </> "file")+ touchFile root ("dir" </> "c" </> "file")+ touchFile root ("dir" </> "b" </> "file")+ touchFile root ("dir" </> "a" </> "file")+ (res, files) <- expectMonitorUnchanged root monitor ()+ res @?= ()+ files @?= [monitorFileGlobStr "dir/*/file"]++-- ensure that a glob can match a directory as well as a file+testGlobMatchDir :: Int -> Assertion+testGlobMatchDir mtimeChange =+ withFileMonitor $ \root monitor -> do+ createDir root ("dir" </> "a")+ updateMonitor root monitor [monitorFileGlobStr "dir/*"] () ()+ threadDelay mtimeChange+ -- nothing changed yet+ (res, files) <- expectMonitorUnchanged root monitor ()+ res @?= ()+ files @?= [monitorFileGlobStr "dir/*"]+ -- expect dir/b to match and be detected as changed+ createDir root ("dir" </> "b")+ reason <- expectMonitorChanged root monitor ()+ reason @?= MonitoredFileChanged ("dir" </> "b")+ -- now remove dir/a and expect it to be detected as changed+ updateMonitor root monitor [monitorFileGlobStr "dir/*"] () ()+ threadDelay mtimeChange+ removeDir root ("dir" </> "a")+ reason2 <- expectMonitorChanged root monitor ()+ reason2 @?= MonitoredFileChanged ("dir" </> "a")++testGlobMatchDirOnly :: Int -> Assertion+testGlobMatchDirOnly mtimeChange =+ withFileMonitor $ \root monitor -> do+ updateMonitor root monitor [monitorFileGlobStr "dir/*/"] () ()+ threadDelay mtimeChange+ -- expect file dir/a to not match, so not detected as changed+ touchFile root ("dir" </> "a")+ (res, files) <- expectMonitorUnchanged root monitor ()+ res @?= ()+ files @?= [monitorFileGlobStr "dir/*/"]+ -- note that checking the file monitor for changes can updates the+ -- cached dir mtimes (when it has to record that there's new matches)+ -- so we need an extra mtime delay+ threadDelay mtimeChange+ -- but expect dir/b to match+ createDir root ("dir" </> "b")+ reason <- expectMonitorChanged root monitor ()+ reason @?= MonitoredFileChanged ("dir" </> "b")++testGlobChangeFileType :: Int -> Assertion+testGlobChangeFileType mtimeChange =+ withFileMonitor $ \root monitor -> do+ -- change file to dir+ touchFile root ("dir" </> "a")+ updateMonitor root monitor [monitorFileGlobStr "dir/*"] () ()+ threadDelay mtimeChange+ removeFile root ("dir" </> "a")+ createDir root ("dir" </> "a")+ reason <- expectMonitorChanged root monitor ()+ reason @?= MonitoredFileChanged ("dir" </> "a")+ -- change dir to file+ updateMonitor root monitor [monitorFileGlobStr "dir/*"] () ()+ threadDelay mtimeChange+ removeDir root ("dir" </> "a")+ touchFile root ("dir" </> "a")+ reason2 <- expectMonitorChanged root monitor ()+ reason2 @?= MonitoredFileChanged ("dir" </> "a")++testGlobAbsolutePath :: Int -> Assertion+testGlobAbsolutePath mtimeChange =+ withFileMonitor $ \root monitor -> do+ root' <- absoluteRoot root+ -- absolute glob, removing a file+ touchFile root ("dir/good-a")+ touchFile root ("dir/good-b")+ updateMonitor root monitor [monitorFileGlobStr (root' </> "dir/good-*")] () ()+ threadDelay mtimeChange+ removeFile root "dir/good-a"+ reason <- expectMonitorChanged root monitor ()+ reason @?= MonitoredFileChanged (root' </> "dir/good-a")+ -- absolute glob, adding a file+ updateMonitor root monitor [monitorFileGlobStr (root' </> "dir/good-*")] () ()+ threadDelay mtimeChange+ touchFile root ("dir/good-a")+ reason2 <- expectMonitorChanged root monitor ()+ reason2 @?= MonitoredFileChanged (root' </> "dir/good-a")+ -- absolute glob, changing a file+ updateMonitor root monitor [monitorFileGlobStr (root' </> "dir/good-*")] () ()+ threadDelay mtimeChange+ touchFileContent root "dir/good-b"+ reason3 <- expectMonitorChanged root monitor ()+ reason3 @?= MonitoredFileChanged (root' </> "dir/good-b")+++------------------+-- value changes+--++testValueUnchanged :: Assertion+testValueUnchanged =+ withFileMonitor $ \root monitor -> do+ touchFile root "a"+ updateMonitor root monitor [monitorFile "a"] (42 :: Int) "ok"+ (res, files) <- expectMonitorUnchanged root monitor 42+ res @?= "ok"+ files @?= [monitorFile "a"]++testValueChanged :: Assertion+testValueChanged =+ withFileMonitor $ \root monitor -> do+ touchFile root "a"+ updateMonitor root monitor [monitorFile "a"] (42 :: Int) "ok"+ reason <- expectMonitorChanged root monitor 43+ reason @?= MonitoredValueChanged 42++testValueAndFileChanged :: Int -> Assertion+testValueAndFileChanged mtimeChange =+ withFileMonitor $ \root monitor -> do+ touchFile root "a"++ -- we change the value and the file, and the value change is reported+ updateMonitor root monitor [monitorFile "a"] (42 :: Int) "ok"+ threadDelay mtimeChange+ touchFile root "a"+ reason <- expectMonitorChanged root monitor 43+ reason @?= MonitoredValueChanged 42++ -- if fileMonitorCheckIfOnlyValueChanged then if only the value changed+ -- then it's reported as MonitoredValueChanged+ let monitor' :: FileMonitor Int String+ monitor' = monitor { fileMonitorCheckIfOnlyValueChanged = True }+ updateMonitor root monitor' [monitorFile "a"] 42 "ok"+ reason2 <- expectMonitorChanged root monitor' 43+ reason2 @?= MonitoredValueChanged 42++ -- but if a file changed too then we don't report MonitoredValueChanged+ updateMonitor root monitor' [monitorFile "a"] 42 "ok"+ threadDelay mtimeChange+ touchFile root "a"+ reason3 <- expectMonitorChanged root monitor' 43+ reason3 @?= MonitoredFileChanged "a"++testValueUpdated :: Assertion+testValueUpdated =+ withFileMonitor $ \root monitor -> do+ touchFile root "a"++ let monitor' :: FileMonitor (Set.Set Int) String+ monitor' = (monitor :: FileMonitor (Set.Set Int) String) {+ fileMonitorCheckIfOnlyValueChanged = True,+ fileMonitorKeyValid = Set.isSubsetOf+ }++ updateMonitor root monitor' [monitorFile "a"] (Set.fromList [42,43]) "ok"+ (res,_files) <- expectMonitorUnchanged root monitor' (Set.fromList [42])+ res @?= "ok"++ reason <- expectMonitorChanged root monitor' (Set.fromList [42,44])+ reason @?= MonitoredValueChanged (Set.fromList [42,43])+++-------------+-- Utils++newtype RootPath = RootPath FilePath++touchFile :: RootPath -> FilePath -> IO ()+touchFile (RootPath root) fname = do+ let path = root </> fname+ IO.createDirectoryIfMissing True (takeDirectory path)+ IO.writeFile path "touched"++touchFileContent :: RootPath -> FilePath -> IO ()+touchFileContent (RootPath root) fname = do+ let path = root </> fname+ IO.createDirectoryIfMissing True (takeDirectory path)+ IO.writeFile path "different"++removeFile :: RootPath -> FilePath -> IO ()+removeFile (RootPath root) fname = IO.removeFile (root </> fname)++touchDir :: RootPath -> FilePath -> IO ()+touchDir root@(RootPath rootdir) dname = do+ IO.createDirectoryIfMissing True (rootdir </> dname)+ touchFile root (dname </> "touch")+ removeFile root (dname </> "touch")++createDir :: RootPath -> FilePath -> IO ()+createDir (RootPath root) dname = do+ let path = root </> dname+ IO.createDirectoryIfMissing True (takeDirectory path)+ IO.createDirectory path++removeDir :: RootPath -> FilePath -> IO ()+removeDir (RootPath root) dname = IO.removeDirectoryRecursive (root </> dname)++absoluteRoot :: RootPath -> IO FilePath+absoluteRoot (RootPath root) = IO.canonicalizePath root++monitorFileGlobStr :: String -> MonitorFilePath+monitorFileGlobStr globstr+ | Just glob <- simpleParse globstr = monitorFileGlob glob+ | otherwise = error $ "Failed to parse " ++ globstr+++expectMonitorChanged :: (Binary a, Binary b)+ => RootPath -> FileMonitor a b -> a+ -> IO (MonitorChangedReason a)+expectMonitorChanged root monitor key = do+ res <- checkChanged root monitor key+ case res of+ MonitorChanged reason -> return reason+ MonitorUnchanged _ _ -> throwIO $ HUnitFailure "expected change"++expectMonitorUnchanged :: (Binary a, Binary b)+ => RootPath -> FileMonitor a b -> a+ -> IO (b, [MonitorFilePath])+expectMonitorUnchanged root monitor key = do+ res <- checkChanged root monitor key+ case res of+ MonitorChanged _reason -> throwIO $ HUnitFailure "expected no change"+ MonitorUnchanged b files -> return (b, files)++checkChanged :: (Binary a, Binary b)+ => RootPath -> FileMonitor a b+ -> a -> IO (MonitorChanged a b)+checkChanged (RootPath root) monitor key =+ checkFileMonitorChanged monitor root key++updateMonitor :: (Binary a, Binary b)+ => RootPath -> FileMonitor a b+ -> [MonitorFilePath] -> a -> b -> IO ()+updateMonitor (RootPath root) monitor files key result =+ updateFileMonitor monitor root Nothing files key result++updateMonitorWithTimestamp :: (Binary a, Binary b)+ => RootPath -> FileMonitor a b -> MonitorTimestamp+ -> [MonitorFilePath] -> a -> b -> IO ()+updateMonitorWithTimestamp (RootPath root) monitor timestamp files key result =+ updateFileMonitor monitor root (Just timestamp) files key result++withFileMonitor :: Eq a => (RootPath -> FileMonitor a b -> IO c) -> IO c+withFileMonitor action = do+ withTempDirectory silent "." "file-status-" $ \root -> do+ let file = root <.> "monitor"+ monitor = newFileMonitor file+ finally (action (RootPath root) monitor) $ do+ exists <- IO.doesFileExist file+ when exists $ IO.removeFile file
+ cabal/cabal-install/tests/UnitTests/Distribution/Client/Glob.hs view
@@ -0,0 +1,202 @@+{-# LANGUAGE CPP #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module UnitTests.Distribution.Client.Glob (tests) where++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative+#endif+import Data.Char+import Data.List+import Distribution.Text (display, parse, simpleParse)+import Distribution.Compat.ReadP++import Distribution.Client.Glob+import UnitTests.Distribution.Client.ArbitraryInstances++import Test.Tasty+import Test.Tasty.QuickCheck+import Test.Tasty.HUnit+import Control.Exception+++tests :: [TestTree]+tests =+ [ testProperty "print/parse roundtrip" prop_roundtrip_printparse+ , testCase "parse examples" testParseCases+ ]++--TODO: [nice to have] tests for trivial globs, tests for matching,+-- tests for windows style file paths++prop_roundtrip_printparse :: FilePathGlob -> Bool+prop_roundtrip_printparse pathglob =+ -- can't use simpleParse because it mis-handles trailing spaces+ case [ x | (x, []) <- readP_to_S parse (display pathglob) ] of+ xs@(_:_) -> last xs == pathglob+ _ -> False++-- first run, where we don't even call updateMonitor+testParseCases :: Assertion+testParseCases = do++ FilePathGlob FilePathUnixRoot GlobDirTrailing <- testparse "/"+ FilePathGlob FilePathHomeDir GlobDirTrailing <- testparse "~/"++ FilePathGlob (FilePathWinDrive 'A') GlobDirTrailing <- testparse "A:/"+ FilePathGlob (FilePathWinDrive 'Z') GlobDirTrailing <- testparse "z:/"+ FilePathGlob (FilePathWinDrive 'C') GlobDirTrailing <- testparse "C:\\"+ FilePathGlob FilePathRelative (GlobFile [Literal "_:"]) <- testparse "_:"++ FilePathGlob FilePathRelative+ (GlobFile [Literal "."]) <- testparse "."++ FilePathGlob FilePathRelative+ (GlobFile [Literal "~"]) <- testparse "~"++ FilePathGlob FilePathRelative+ (GlobDir [Literal "."] GlobDirTrailing) <- testparse "./"++ FilePathGlob FilePathRelative+ (GlobFile [Literal "foo"]) <- testparse "foo"++ FilePathGlob FilePathRelative+ (GlobDir [Literal "foo"]+ (GlobFile [Literal "bar"])) <- testparse "foo/bar"++ FilePathGlob FilePathRelative+ (GlobDir [Literal "foo"]+ (GlobDir [Literal "bar"] GlobDirTrailing)) <- testparse "foo/bar/"++ FilePathGlob FilePathUnixRoot+ (GlobDir [Literal "foo"]+ (GlobDir [Literal "bar"] GlobDirTrailing)) <- testparse "/foo/bar/"++ FilePathGlob FilePathRelative+ (GlobFile [WildCard]) <- testparse "*"++ FilePathGlob FilePathRelative+ (GlobFile [WildCard,WildCard]) <- testparse "**" -- not helpful but valid++ FilePathGlob FilePathRelative+ (GlobFile [WildCard, Literal "foo", WildCard]) <- testparse "*foo*"++ FilePathGlob FilePathRelative+ (GlobFile [Literal "foo", WildCard, Literal "bar"]) <- testparse "foo*bar"++ FilePathGlob FilePathRelative+ (GlobFile [Union [[WildCard], [Literal "foo"]]]) <- testparse "{*,foo}"++ parseFail "{"+ parseFail "}"+ parseFail ","+ parseFail "{"+ parseFail "{{}"+ parseFail "{}"+ parseFail "{,}"+ parseFail "{foo,}"+ parseFail "{,foo}"++ return ()++testparse :: String -> IO FilePathGlob+testparse s =+ case simpleParse s of+ Just p -> return p+ Nothing -> throwIO $ HUnitFailure ("expected parse of: " ++ s)++parseFail :: String -> Assertion+parseFail s =+ case simpleParse s :: Maybe FilePathGlob of+ Just _ -> throwIO $ HUnitFailure ("expected no parse of: " ++ s)+ Nothing -> return ()++instance Arbitrary FilePathGlob where+ arbitrary = (FilePathGlob <$> arbitrary <*> arbitrary)+ `suchThat` validFilePathGlob++ shrink (FilePathGlob root pathglob) =+ [ FilePathGlob root' pathglob'+ | (root', pathglob') <- shrink (root, pathglob)+ , validFilePathGlob (FilePathGlob root' pathglob') ]++validFilePathGlob :: FilePathGlob -> Bool+validFilePathGlob (FilePathGlob FilePathRelative pathglob) =+ case pathglob of+ GlobDirTrailing -> False+ GlobDir [Literal "~"] _ -> False+ GlobDir [Literal (d:":")] _ + | isLetter d -> False+ _ -> True+validFilePathGlob _ = True++instance Arbitrary FilePathRoot where+ arbitrary =+ frequency+ [ (3, pure FilePathRelative)+ , (1, pure FilePathUnixRoot)+ , (1, pure FilePathHomeDir)+ , (1, FilePathWinDrive <$> choose ('A', 'Z'))+ ]++ shrink FilePathRelative = []+ shrink FilePathUnixRoot = [FilePathRelative]+ shrink FilePathHomeDir = [FilePathRelative]+ shrink (FilePathWinDrive d) = FilePathRelative+ : [ FilePathWinDrive d' | d' <- shrink d ]+++instance Arbitrary FilePathGlobRel where+ arbitrary = sized $ \sz ->+ oneof $ take (max 1 sz)+ [ pure GlobDirTrailing+ , GlobFile <$> (getGlobPieces <$> arbitrary)+ , GlobDir <$> (getGlobPieces <$> arbitrary)+ <*> resize (sz `div` 2) arbitrary+ ]++ shrink GlobDirTrailing = []+ shrink (GlobFile glob) =+ GlobDirTrailing+ : [ GlobFile (getGlobPieces glob') | glob' <- shrink (GlobPieces glob) ]+ shrink (GlobDir glob pathglob) =+ pathglob+ : GlobFile glob+ : [ GlobDir (getGlobPieces glob') pathglob'+ | (glob', pathglob') <- shrink (GlobPieces glob, pathglob) ]++newtype GlobPieces = GlobPieces { getGlobPieces :: [GlobPiece] }+ deriving Eq++instance Arbitrary GlobPieces where+ arbitrary = GlobPieces . mergeLiterals <$> shortListOf1 5 arbitrary++ shrink (GlobPieces glob) =+ [ GlobPieces (mergeLiterals (getNonEmpty glob'))+ | glob' <- shrink (NonEmpty glob) ]++mergeLiterals :: [GlobPiece] -> [GlobPiece]+mergeLiterals (Literal a : Literal b : ps) = mergeLiterals (Literal (a++b) : ps)+mergeLiterals (Union as : ps) = Union (map mergeLiterals as) : mergeLiterals ps+mergeLiterals (p:ps) = p : mergeLiterals ps+mergeLiterals [] = []++instance Arbitrary GlobPiece where+ arbitrary = sized $ \sz ->+ frequency+ [ (3, Literal <$> shortListOf1 10 (elements globLiteralChars))+ , (1, pure WildCard)+ , (1, Union <$> resize (sz `div` 2) (shortListOf1 5 (shortListOf1 5 arbitrary)))+ ]++ shrink (Literal str) = [ Literal str'+ | str' <- shrink str+ , not (null str')+ , all (`elem` globLiteralChars) str' ]+ shrink WildCard = []+ shrink (Union as) = [ Union (map getGlobPieces (getNonEmpty as'))+ | as' <- shrink (NonEmpty (map GlobPieces as)) ]++globLiteralChars :: [Char]+globLiteralChars = ['\0'..'\128'] \\ "*{},/\\"+
+ cabal/cabal-install/tests/UnitTests/Distribution/Client/ProjectConfig.hs view
@@ -0,0 +1,610 @@+{-# LANGUAGE CPP #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module UnitTests.Distribution.Client.ProjectConfig (tests) where++#if !MIN_VERSION_base(4,8,0)+import Data.Monoid+import Control.Applicative+#endif+import Data.Map (Map)+import qualified Data.Map as Map+import Data.List++import Distribution.Package+import Distribution.PackageDescription hiding (Flag)+import Distribution.Compiler+import Distribution.Version+import Distribution.ParseUtils+import Distribution.Simple.Compiler+import Distribution.Simple.Setup+import Distribution.Simple.InstallDirs+import qualified Distribution.Compat.ReadP as Parse+import Distribution.Simple.Utils+import Distribution.Simple.Program.Types+import Distribution.Simple.Program.Db++import Distribution.Client.Types+import Distribution.Client.Dependency.Types+import Distribution.Client.BuildReports.Types+import Distribution.Client.Targets+import Distribution.Utils.NubList+import Network.URI++import Distribution.Client.ProjectConfig+import Distribution.Client.ProjectConfig.Legacy++import UnitTests.Distribution.Client.ArbitraryInstances++import Test.Tasty+import Test.Tasty.QuickCheck++tests :: [TestTree]+tests =+ [ testGroup "ProjectConfig <-> LegacyProjectConfig round trip" $+ [ testProperty "packages" prop_roundtrip_legacytypes_packages+ , testProperty "buildonly" prop_roundtrip_legacytypes_buildonly+ , testProperty "specific" prop_roundtrip_legacytypes_specific+ ] +++ -- a couple tests seem to trigger a RTS fault in ghc-7.6 and older+ -- unclear why as of yet+ concat+ [ [ testProperty "shared" prop_roundtrip_legacytypes_shared+ , testProperty "local" prop_roundtrip_legacytypes_local+ , testProperty "all" prop_roundtrip_legacytypes_all+ ]+ | not usingGhc76orOlder+ ]++ , testGroup "individual parser tests"+ [ testProperty "package location" prop_parsePackageLocationTokenQ+ ]++ , testGroup "ProjectConfig printing/parsing round trip"+ [ testProperty "packages" prop_roundtrip_printparse_packages+ , testProperty "buildonly" prop_roundtrip_printparse_buildonly+ , testProperty "shared" prop_roundtrip_printparse_shared+ , testProperty "local" prop_roundtrip_printparse_local+ , testProperty "specific" prop_roundtrip_printparse_specific+ , testProperty "all" prop_roundtrip_printparse_all+ ]+ ]+ where+ usingGhc76orOlder =+ case buildCompilerId of+ CompilerId GHC v -> v < Version [7,7] []+ _ -> False+++------------------------------------------------+-- Round trip: conversion to/from legacy types+--++roundtrip :: Eq a => (a -> b) -> (b -> a) -> a -> Bool+roundtrip f f_inv x =+ (f_inv . f) x == x++roundtrip_legacytypes :: ProjectConfig -> Bool+roundtrip_legacytypes =+ roundtrip convertToLegacyProjectConfig+ convertLegacyProjectConfig+++prop_roundtrip_legacytypes_all :: ProjectConfig -> Bool+prop_roundtrip_legacytypes_all =+ roundtrip_legacytypes++prop_roundtrip_legacytypes_packages :: ProjectConfig -> Bool+prop_roundtrip_legacytypes_packages config =+ roundtrip_legacytypes+ config {+ projectConfigBuildOnly = mempty,+ projectConfigShared = mempty,+ projectConfigLocalPackages = mempty,+ projectConfigSpecificPackage = mempty+ }++prop_roundtrip_legacytypes_buildonly :: ProjectConfigBuildOnly -> Bool+prop_roundtrip_legacytypes_buildonly config =+ roundtrip_legacytypes+ mempty { projectConfigBuildOnly = config }++prop_roundtrip_legacytypes_shared :: ProjectConfigShared -> Bool+prop_roundtrip_legacytypes_shared config =+ roundtrip_legacytypes+ mempty { projectConfigShared = config }++prop_roundtrip_legacytypes_local :: PackageConfig -> Bool+prop_roundtrip_legacytypes_local config =+ roundtrip_legacytypes+ mempty { projectConfigLocalPackages = config }++prop_roundtrip_legacytypes_specific :: Map PackageName PackageConfig -> Bool+prop_roundtrip_legacytypes_specific config =+ roundtrip_legacytypes+ mempty { projectConfigSpecificPackage = config }+++--------------------------------------------+-- Round trip: printing and parsing config+--++roundtrip_printparse :: ProjectConfig -> Bool+roundtrip_printparse config =+ case (fmap convertLegacyProjectConfig+ . parseLegacyProjectConfig+ . showLegacyProjectConfig+ . convertToLegacyProjectConfig)+ config of+ ParseOk _ x -> x == config+ _ -> False+++prop_roundtrip_printparse_all :: ProjectConfig -> Bool+prop_roundtrip_printparse_all config =+ roundtrip_printparse config {+ projectConfigBuildOnly =+ hackProjectConfigBuildOnly (projectConfigBuildOnly config),++ projectConfigShared =+ hackProjectConfigShared (projectConfigShared config)+ }++prop_roundtrip_printparse_packages :: [PackageLocationString]+ -> [PackageLocationString]+ -> [SourceRepo]+ -> [Dependency]+ -> Bool+prop_roundtrip_printparse_packages pkglocstrs1 pkglocstrs2 repos named =+ roundtrip_printparse+ mempty {+ projectPackages = map getPackageLocationString pkglocstrs1,+ projectPackagesOptional = map getPackageLocationString pkglocstrs2,+ projectPackagesRepo = repos,+ projectPackagesNamed = named+ }++prop_roundtrip_printparse_buildonly :: ProjectConfigBuildOnly -> Bool+prop_roundtrip_printparse_buildonly config =+ roundtrip_printparse+ mempty {+ projectConfigBuildOnly = hackProjectConfigBuildOnly config+ }++hackProjectConfigBuildOnly :: ProjectConfigBuildOnly -> ProjectConfigBuildOnly+hackProjectConfigBuildOnly config =+ config {+ -- These two fields are only command line transitory things, not+ -- something to be recorded persistently in a config file+ projectConfigOnlyDeps = mempty,+ projectConfigDryRun = mempty+ }++prop_roundtrip_printparse_shared :: ProjectConfigShared -> Bool+prop_roundtrip_printparse_shared config =+ roundtrip_printparse+ mempty {+ projectConfigShared = hackProjectConfigShared config+ }++hackProjectConfigShared :: ProjectConfigShared -> ProjectConfigShared+hackProjectConfigShared config =+ config {+ projectConfigConstraints =+ --TODO: [required eventually] parse ambiguity in constraint+ -- "pkgname -any" as either any version or disabled flag "any".+ let ambiguous ((UserConstraintFlags _pkg flags), _) =+ (not . null) [ () | (FlagName name, False) <- flags+ , "any" `isPrefixOf` name ]+ ambiguous _ = False+ in filter (not . ambiguous) (projectConfigConstraints config)+ }+++prop_roundtrip_printparse_local :: PackageConfig -> Bool+prop_roundtrip_printparse_local config =+ roundtrip_printparse+ mempty {+ projectConfigLocalPackages = config+ }++prop_roundtrip_printparse_specific :: Map PackageName (NonMEmpty PackageConfig)+ -> Bool+prop_roundtrip_printparse_specific config =+ roundtrip_printparse+ mempty {+ projectConfigSpecificPackage = fmap getNonMEmpty config+ }+++----------------------------+-- Individual Parser tests +--++prop_parsePackageLocationTokenQ :: PackageLocationString -> Bool+prop_parsePackageLocationTokenQ (PackageLocationString str) =+ case [ x | (x,"") <- Parse.readP_to_S parsePackageLocationTokenQ+ (renderPackageLocationToken str) ] of+ [str'] -> str' == str+ _ -> False+++------------------------+-- Arbitrary instances+--++instance Arbitrary ProjectConfig where+ arbitrary =+ ProjectConfig+ <$> (map getPackageLocationString <$> arbitrary)+ <*> (map getPackageLocationString <$> arbitrary)+ <*> shortListOf 3 arbitrary+ <*> arbitrary+ <*> arbitrary <*> arbitrary+ <*> arbitrary+ <*> (fmap getNonMEmpty . Map.fromList <$> shortListOf 3 arbitrary)+ -- package entries with no content are equivalent to+ -- the entry not existing at all, so exclude empty++ shrink (ProjectConfig x0 x1 x2 x3 x4 x5 x6 x7) =+ [ ProjectConfig x0' x1' x2' x3' x4' x5' x6' (fmap getNonMEmpty x7')+ | ((x0', x1', x2', x3'), (x4', x5', x6', x7'))+ <- shrink ((x0, x1, x2, x3), (x4, x5, x6, fmap NonMEmpty x7))+ ]++newtype PackageLocationString+ = PackageLocationString { getPackageLocationString :: String }+ deriving Show++instance Arbitrary PackageLocationString where+ arbitrary =+ PackageLocationString <$>+ oneof+ [ show . getNonEmpty <$> (arbitrary :: Gen (NonEmptyList String))+ , arbitraryGlobLikeStr+ , show <$> (arbitrary :: Gen URI)+ ]++arbitraryGlobLikeStr :: Gen String+arbitraryGlobLikeStr = outerTerm+ where+ outerTerm = concat <$> shortListOf1 4+ (frequency [ (2, token)+ , (1, braces <$> innerTerm) ])+ innerTerm = intercalate "," <$> shortListOf1 3+ (frequency [ (3, token)+ , (1, braces <$> innerTerm) ])+ token = shortListOf1 4 (elements (['#'..'~'] \\ "{,}"))+ braces s = "{" ++ s ++ "}"+++instance Arbitrary ProjectConfigBuildOnly where+ arbitrary =+ ProjectConfigBuildOnly+ <$> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> (toNubList <$> shortListOf 2 arbitrary) -- 4+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> (fmap getShortToken <$> arbitrary) -- 8+ <*> arbitrary+ <*> arbitraryNumJobs+ <*> arbitrary+ <*> arbitrary -- 12+ <*> (fmap getShortToken <$> arbitrary)+ <*> arbitrary+ <*> (fmap getShortToken <$> arbitrary)+ <*> (fmap getShortToken <$> arbitrary) -- 16+ <*> (fmap getShortToken <$> arbitrary)+ <*> (fmap getShortToken <$> arbitrary)+ where+ arbitraryNumJobs = fmap (fmap getPositive) <$> arbitrary++ shrink (ProjectConfigBuildOnly+ x00 x01 x02 x03 x04 x05 x06 x07+ x08 x09 x10 x11 x12 x13 x14 x15+ x16 x17) =+ [ ProjectConfigBuildOnly+ x00' x01' x02' x03' x04'+ x05' x06' x07' x08' (postShrink_NumJobs x09')+ x10' x11' x12 x13' x14+ x15 x16 x17+ | ((x00', x01', x02', x03', x04'),+ (x05', x06', x07', x08', x09'),+ (x10', x11', x13'))+ <- shrink+ ((x00, x01, x02, x03, x04),+ (x05, x06, x07, x08, preShrink_NumJobs x09),+ (x10, x11, x13))+ ]+ where+ preShrink_NumJobs = fmap (fmap Positive)+ postShrink_NumJobs = fmap (fmap getPositive)++instance Arbitrary ProjectConfigShared where+ arbitrary =+ ProjectConfigShared+ <$> (Map.fromList <$> shortListOf 10 + ((,) <$> arbitraryProgramName+ <*> arbitraryShortToken))+ <*> (Map.fromList <$> shortListOf 10 + ((,) <$> arbitraryProgramName+ <*> listOf arbitraryShortToken))+ <*> (toNubList <$> listOf arbitraryShortToken)+ <*> arbitrary -- 4+ <*> arbitraryFlag arbitraryShortToken+ <*> arbitraryFlag arbitraryShortToken+ <*> arbitrary+ <*> arbitrary -- 8+ <*> (toNubList <$> listOf arbitraryShortToken)+ <*> arbitraryConstraints+ <*> arbitrary <*> shortListOf 2 arbitrary -- 12+ <*> arbitrary <*> arbitrary+ <*> arbitrary <*> arbitrary -- 16+ <*> arbitrary <*> arbitrary+ where+ arbitraryProgramName :: Gen String+ arbitraryProgramName =+ elements [ programName prog+ | (prog, _) <- knownPrograms (defaultProgramDb) ]++ arbitraryConstraints :: Gen [(UserConstraint, ConstraintSource)]+ arbitraryConstraints =+ map (\uc -> (uc, projectConfigConstraintSource)) <$> arbitrary++ shrink (ProjectConfigShared+ x00 x01 x02 x03 x04+ x05 x06 x07 x08 x09+ x10 x11 x12 x13 x14+ x15 x16 x17) =+ [ ProjectConfigShared+ (postShrink_Paths x00')+ (postShrink_Args x01')+ x02' x03'+ (fmap getNonEmpty x04')+ (fmap getNonEmpty x05')+ x06' x07' x08'+ (postShrink_Constraints x09')+ x10' x11' x12' x13' x14'+ x15' x16' x17'+ | ((x00', x01', x02', x03', x04'),+ (x05', x06', x07', x08', x09'),+ (x10', x11', x12', x13', x14'),+ (x15', x16', x17'))+ <- shrink+ ((preShrink_Paths x00,+ preShrink_Args x01,+ x02, x03, fmap NonEmpty x04),+ (fmap NonEmpty x05, x06, x07, x08, preShrink_Constraints x09),+ (x10, x11, x12, x13, x14),+ (x15, x16, x17))+ ]+ where+ preShrink_Paths = Map.map NonEmpty . Map.mapKeys NoShrink+ postShrink_Paths = Map.map getNonEmpty . Map.mapKeys getNoShrink+ preShrink_Args = Map.map (NonEmpty . map NonEmpty)+ . Map.mapKeys NoShrink+ postShrink_Args = Map.map (map getNonEmpty . getNonEmpty)+ . Map.mapKeys getNoShrink+ preShrink_Constraints = map fst+ postShrink_Constraints = map (\uc -> (uc, projectConfigConstraintSource))++projectConfigConstraintSource :: ConstraintSource+projectConfigConstraintSource = + ConstraintSourceProjectConfig "TODO"++instance Arbitrary PackageConfig where+ arbitrary =+ PackageConfig+ <$> arbitrary <*> arbitrary+ <*> arbitrary <*> arbitrary -- 4+ <*> arbitrary <*> arbitrary+ <*> arbitrary <*> arbitrary -- 8+ <*> shortListOf 5 arbitraryShortToken+ <*> arbitrary+ <*> arbitrary <*> arbitrary -- 12+ <*> shortListOf 5 arbitraryShortToken+ <*> shortListOf 5 arbitraryShortToken+ <*> shortListOf 5 arbitraryShortToken+ <*> arbitrary -- 16+ <*> arbitrary <*> arbitrary+ <*> arbitrary <*> arbitrary -- 20+ <*> arbitrary <*> arbitrary+ <*> arbitrary <*> arbitrary -- 24+ <*> arbitrary <*> arbitrary+ <*> arbitrary <*> arbitrary -- 28+ <*> arbitraryFlag arbitraryShortToken+ <*> arbitrary+ <*> arbitrary <*> arbitrary -- 32+ <*> arbitrary+ <*> arbitraryFlag arbitraryShortToken+ <*> arbitrary+ <*> arbitraryFlag arbitraryShortToken -- 36+ <*> arbitrary++ shrink (PackageConfig+ x00 x01 x02 x03 x04+ x05 x06 x07 x08 x09+ x10 x11 x12 x13 x14+ x15 x16 x17 x18 x19+ x20 x21 x22 x23 x24+ x25 x26 x27 x28 x29+ x30 x31 x32 x33 x34+ x35 x36) =+ [ PackageConfig+ x00' x01' x02' x03' x04'+ x05' x06' x07' (map getNonEmpty x08') x09'+ x10' x11'+ (map getNonEmpty x12')+ (map getNonEmpty x13')+ (map getNonEmpty x14')+ x15' x16' x17' x18' x19'+ x20' x21' x22' x23' x24'+ x25' x26' x27' x28' x29'+ x30' x31' x32' (fmap getNonEmpty x33') x34'+ (fmap getNonEmpty x35') x36'+ | (((x00', x01', x02', x03', x04'),+ (x05', x06', x07', x08', x09'),+ (x10', x11', x12', x13', x14'),+ (x15', x16', x17', x18', x19')),+ ((x20', x21', x22', x23', x24'),+ (x25', x26', x27', x28', x29'),+ (x30', x31', x32', x33', x34'),+ (x35', x36')))+ <- shrink+ (((x00, x01, x02, x03, x04),+ (x05, x06, x07, map NonEmpty x08, x09),+ (x10, x11,+ map NonEmpty x12,+ map NonEmpty x13,+ map NonEmpty x14),+ (x15, x16, x17, x18, x19)),+ ((x20, x21, x22, x23, x24),+ (x25, x26, x27, x28, x29),+ (x30, x31, x32, fmap NonEmpty x33, x34),+ (fmap NonEmpty x35, x36)))+ ]+++instance Arbitrary SourceRepo where+ arbitrary = (SourceRepo RepoThis+ <$> arbitrary+ <*> (fmap getShortToken <$> arbitrary)+ <*> (fmap getShortToken <$> arbitrary)+ <*> (fmap getShortToken <$> arbitrary)+ <*> (fmap getShortToken <$> arbitrary)+ <*> (fmap getShortToken <$> arbitrary))+ `suchThat` (/= emptySourceRepo)++ shrink (SourceRepo _ x1 x2 x3 x4 x5 x6) =+ [ repo+ | ((x1', x2', x3'), (x4', x5', x6'))+ <- shrink ((x1,+ fmap ShortToken x2,+ fmap ShortToken x3),+ (fmap ShortToken x4,+ fmap ShortToken x5,+ fmap ShortToken x6))+ , let repo = SourceRepo RepoThis x1'+ (fmap getShortToken x2')+ (fmap getShortToken x3')+ (fmap getShortToken x4')+ (fmap getShortToken x5')+ (fmap getShortToken x6')+ , repo /= emptySourceRepo+ ]++emptySourceRepo :: SourceRepo+emptySourceRepo = SourceRepo RepoThis Nothing Nothing Nothing+ Nothing Nothing Nothing+++instance Arbitrary RepoType where+ arbitrary = elements knownRepoTypes++instance Arbitrary ReportLevel where+ arbitrary = elements [NoReports .. DetailedReports]++instance Arbitrary CompilerFlavor where+ arbitrary = elements knownCompilerFlavors+ where+ --TODO: [code cleanup] export knownCompilerFlavors from D.Compiler+ -- it's already defined there, just need it exported.+ knownCompilerFlavors =+ [GHC, GHCJS, NHC, YHC, Hugs, HBC, Helium, JHC, LHC, UHC]++instance Arbitrary a => Arbitrary (InstallDirs a) where+ arbitrary =+ InstallDirs+ <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary -- 4+ <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary -- 8+ <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary -- 12+ <*> arbitrary <*> arbitrary -- 14++instance Arbitrary PackageDB where+ arbitrary = oneof [ pure GlobalPackageDB+ , pure UserPackageDB+ , SpecificPackageDB . getShortToken <$> arbitrary+ ]++instance Arbitrary RemoteRepo where+ arbitrary =+ RemoteRepo+ <$> arbitraryShortToken `suchThat` (not . (":" `isPrefixOf`))+ <*> arbitrary -- URI+ <*> arbitrary+ <*> listOf arbitraryRootKey+ <*> (fmap getNonNegative arbitrary)+ <*> pure False+ where+ arbitraryRootKey =+ shortListOf1 5 (oneof [ choose ('0', '9')+ , choose ('a', 'f') ])++instance Arbitrary UserConstraint where+ arbitrary =+ oneof+ [ UserConstraintVersion <$> arbitrary <*> arbitrary+ , UserConstraintInstalled <$> arbitrary+ , UserConstraintSource <$> arbitrary+ , UserConstraintFlags <$> arbitrary <*> shortListOf1 3 arbitrary+ , UserConstraintStanzas <$> arbitrary <*> ((\x->[x]) <$> arbitrary)+ ]++instance Arbitrary OptionalStanza where+ arbitrary = elements [minBound..maxBound]++instance Arbitrary FlagName where+ arbitrary = FlagName <$> flagident+ where+ flagident = lowercase <$> shortListOf1 5 (elements flagChars)+ `suchThat` (("-" /=) . take 1)+ flagChars = "-_" ++ ['a'..'z']++instance Arbitrary PreSolver where+ arbitrary = elements [minBound..maxBound]++instance Arbitrary AllowNewer where+ arbitrary = oneof [ pure AllowNewerNone+ , AllowNewerSome <$> shortListOf1 3 arbitrary+ , pure AllowNewerAll+ ]++instance Arbitrary AllowNewerDep where+ arbitrary = oneof [ AllowNewerDep <$> arbitrary+ , AllowNewerDepScoped <$> arbitrary <*> arbitrary+ ]++instance Arbitrary ProfDetailLevel where+ arbitrary = elements [ d | (_,_,d) <- knownProfDetailLevels ]++instance Arbitrary OptimisationLevel where+ arbitrary = elements [minBound..maxBound]++instance Arbitrary DebugInfoLevel where+ arbitrary = elements [minBound..maxBound]++instance Arbitrary URI where+ arbitrary =+ URI <$> elements ["file:", "http:", "https:"]+ <*> (Just <$> arbitrary)+ <*> (('/':) <$> arbitraryURIToken)+ <*> (('?':) <$> arbitraryURIToken)+ <*> pure ""++instance Arbitrary URIAuth where+ arbitrary =+ URIAuth <$> pure "" -- no password as this does not roundtrip+ <*> arbitraryURIToken+ <*> arbitraryURIPort++arbitraryURIToken :: Gen String+arbitraryURIToken =+ shortListOf1 6 (elements (filter isUnreserved ['\0'..'\255']))++arbitraryURIPort :: Gen String+arbitraryURIPort =+ oneof [ pure "", (':':) <$> shortListOf1 4 (choose ('0','9')) ]+
+ cabal/cabal-install/tests/UnitTests/Distribution/Client/Sandbox/Timestamp.hs view
@@ -0,0 +1,63 @@+module UnitTests.Distribution.Client.Sandbox.Timestamp (tests) where++import System.FilePath++import Distribution.Simple.Utils (withTempDirectory)+import Distribution.Verbosity++import Distribution.Client.Compat.Time+import Distribution.Client.Sandbox.Timestamp++import Test.Tasty+import Test.Tasty.HUnit++tests :: [TestTree]+tests =+ [ testCase "timestamp record version 1 can be read" timestampReadTest_v1+ , testCase "timestamp record version 2 can be read" timestampReadTest_v2+ , testCase "written timestamp record can be read" timestampReadWriteTest ]++timestampRecord_v1 :: String+timestampRecord_v1 =+ "[(\"i386-linux-ghc-8.0.0.20160204\",[(\"/foo/bar/Baz\",1455350946)])" +++ ",(\"i386-linux-ghc-7.10.3\",[(\"/foo/bar/Baz\",1455484719)])]\n"++timestampRecord_v2 :: String+timestampRecord_v2 =+ "2\n" +++ "[(\"i386-linux-ghc-8.0.0.20160204\",[(\"/foo/bar/Baz\",1455350946)])" +++ ",(\"i386-linux-ghc-7.10.3\",[(\"/foo/bar/Baz\",1455484719)])]"++timestampReadTest_v1 :: Assertion+timestampReadTest_v1 =+ timestampReadTest timestampRecord_v1 $+ map (\(i, ts) ->+ (i, map (\(p, ModTime t) ->+ (p, posixSecondsToModTime . fromIntegral $ t)) ts))+ timestampRecord++timestampReadTest_v2 :: Assertion+timestampReadTest_v2 = timestampReadTest timestampRecord_v2 timestampRecord++timestampReadTest :: FilePath -> [TimestampFileRecord] -> Assertion+timestampReadTest fileContent expected =+ withTempDirectory silent "." "cabal-timestamp-" $ \dir -> do+ let fileName = dir </> "timestamp-record"+ writeFile fileName fileContent+ tRec <- readTimestampFile fileName+ assertEqual "expected timestamp records to be equal"+ expected tRec++timestampRecord :: [TimestampFileRecord]+timestampRecord =+ [("i386-linux-ghc-8.0.0.20160204",[("/foo/bar/Baz",ModTime 1455350946)])+ ,("i386-linux-ghc-7.10.3",[("/foo/bar/Baz",ModTime 1455484719)])]++timestampReadWriteTest :: Assertion+timestampReadWriteTest =+ withTempDirectory silent "." "cabal-timestamp-" $ \dir -> do+ let fileName = dir </> "timestamp-record"+ writeTimestampFile fileName timestampRecord+ tRec <- readTimestampFile fileName+ assertEqual "expected timestamp records to be equal"+ timestampRecord tRec
+ cabal/cabal-install/tests/UnitTests/Distribution/Client/Tar.hs view
@@ -0,0 +1,75 @@+module UnitTests.Distribution.Client.Tar (+ tests+ ) where++import Distribution.Client.Tar ( filterEntries+ , filterEntriesM+ )+import Codec.Archive.Tar ( Entries(..)+ , foldEntries+ )+import Codec.Archive.Tar.Entry ( EntryContent(..)+ , simpleEntry+ , Entry(..)+ , toTarPath+ )++import Test.Tasty+import Test.Tasty.HUnit++import qualified Data.ByteString.Lazy as BS+import qualified Data.ByteString.Lazy.Char8 as BS.Char8+import Control.Monad.Writer.Lazy (runWriterT, tell)++tests :: [TestTree]+tests = [ testCase "filterEntries" filterTest+ , testCase "filterEntriesM" filterMTest+ ]++filterTest :: Assertion+filterTest = do+ let e1 = getFileEntry "file1" "x"+ e2 = getFileEntry "file2" "y"+ p = (\e -> let (NormalFile dta _) = entryContent e+ str = BS.Char8.unpack dta+ in not . (=="y") $ str)+ assertEqual "Unexpected result for filter" "xz" $+ entriesToString $ filterEntries p $ Next e1 $ Next e2 Done+ assertEqual "Unexpected result for filter" "z" $+ entriesToString $ filterEntries p $ Done+ assertEqual "Unexpected result for filter" "xf" $+ entriesToString $ filterEntries p $ Next e1 $ Next e2 $ Fail "f"++filterMTest :: Assertion+filterMTest = do+ let e1 = getFileEntry "file1" "x"+ e2 = getFileEntry "file2" "y"+ p = (\e -> let (NormalFile dta _) = entryContent e+ str = BS.Char8.unpack dta+ in tell "t" >> return (not . (=="y") $ str))++ (r, w) <- runWriterT $ filterEntriesM p $ Next e1 $ Next e2 Done+ assertEqual "Unexpected result for filterM" "xz" $ entriesToString r+ assertEqual "Unexpected result for filterM w" "tt" w++ (r1, w1) <- runWriterT $ filterEntriesM p $ Done+ assertEqual "Unexpected result for filterM" "z" $ entriesToString r1+ assertEqual "Unexpected result for filterM w" "" w1++ (r2, w2) <- runWriterT $ filterEntriesM p $ Next e1 $ Next e2 $ Fail "f"+ assertEqual "Unexpected result for filterM" "xf" $ entriesToString r2+ assertEqual "Unexpected result for filterM w" "tt" w2++getFileEntry :: FilePath -> [Char] -> Entry+getFileEntry pth dta =+ simpleEntry tp $ NormalFile dta' $ BS.length dta'+ where tp = case toTarPath False pth of+ Right tp' -> tp'+ Left e -> error e+ dta' = BS.Char8.pack dta++entriesToString :: Entries String -> String+entriesToString =+ foldEntries (\e acc -> let (NormalFile dta _) = entryContent e+ str = BS.Char8.unpack dta+ in str ++ acc) "z" id
cabal/cabal-install/tests/UnitTests/Distribution/Client/UserConfig.hs view
@@ -4,21 +4,24 @@ ) where import Control.Exception (bracket)+import Control.Monad (replicateM_) import Data.List (sort, nub) #if !MIN_VERSION_base(4,8,0) import Data.Monoid #endif-import System.Directory (getCurrentDirectory, removeDirectoryRecursive, createDirectoryIfMissing)-import System.FilePath (takeDirectory)+import System.Directory (doesFileExist,+ getCurrentDirectory, getTemporaryDirectory)+import System.FilePath ((</>)) import Test.Tasty import Test.Tasty.HUnit -import Distribution.Compat.Environment (lookupEnv, setEnv) import Distribution.Client.Config import Distribution.Utils.NubList (fromNubList) import Distribution.Client.Setup (GlobalFlags (..), InstallFlags (..))-import Distribution.Simple.Setup (ConfigFlags (..), fromFlag)+import Distribution.Client.Utils (removeExistingFile)+import Distribution.Simple.Setup (Flag (..), ConfigFlags (..), fromFlag)+import Distribution.Simple.Utils (withTempDirectory) import Distribution.Verbosity (silent) tests :: [TestTree]@@ -26,51 +29,48 @@ , testCase "canDetectDifference" canDetectDifference , testCase "canUpdateConfig" canUpdateConfig , testCase "doubleUpdateConfig" doubleUpdateConfig+ , testCase "newDefaultConfig" newDefaultConfig ] nullDiffOnCreateTest :: Assertion-nullDiffOnCreateTest = bracketTest . const $ do+nullDiffOnCreateTest = bracketTest $ \configFile -> do -- Create a new default config file in our test directory.- _ <- loadConfig silent mempty+ _ <- loadConfig silent (Flag configFile) -- Now we read it in and compare it against the default.- diff <- userConfigDiff mempty+ diff <- userConfigDiff $ globalFlags configFile assertBool (unlines $ "Following diff should be empty:" : diff) $ null diff canDetectDifference :: Assertion-canDetectDifference = bracketTest . const $ do+canDetectDifference = bracketTest $ \configFile -> do -- Create a new default config file in our test directory.- _ <- loadConfig silent mempty- cabalFile <- defaultConfigFile- appendFile cabalFile "verbose: 0\n"- diff <- userConfigDiff mempty+ _ <- loadConfig silent (Flag configFile)+ appendFile configFile "verbose: 0\n"+ diff <- userConfigDiff $ globalFlags configFile assertBool (unlines $ "Should detect a difference:" : diff) $ diff == [ "- verbose: 1", "+ verbose: 0" ] canUpdateConfig :: Assertion-canUpdateConfig = bracketTest . const $ do- cabalFile <- defaultConfigFile- createDirectoryIfMissing True $ takeDirectory cabalFile+canUpdateConfig = bracketTest $ \configFile -> do -- Write a trivial cabal file.- writeFile cabalFile "tests: True\n"+ writeFile configFile "tests: True\n" -- Update the config file.- userConfigUpdate silent mempty+ userConfigUpdate silent $ globalFlags configFile -- Load it again.- updated <- loadConfig silent mempty+ updated <- loadConfig silent (Flag configFile) assertBool ("Field 'tests' should be True") $ fromFlag (configTests $ savedConfigureFlags updated) doubleUpdateConfig :: Assertion-doubleUpdateConfig = bracketTest . const $ do+doubleUpdateConfig = bracketTest $ \configFile -> do -- Create a new default config file in our test directory.- _ <- loadConfig silent mempty- -- Update it.- userConfigUpdate silent mempty- userConfigUpdate silent mempty+ _ <- loadConfig silent (Flag configFile)+ -- Update it twice.+ replicateM_ 2 . userConfigUpdate silent $ globalFlags configFile -- Load it again.- updated <- loadConfig silent mempty+ updated <- loadConfig silent (Flag configFile) assertBool ("Field 'remote-repo' doesn't contain duplicates") $ listUnique (map show . fromNubList . globalRemoteRepos $ savedGlobalFlags updated)@@ -80,24 +80,33 @@ listUnique (map show . fromNubList . installSummaryFile $ savedInstallFlags updated) +newDefaultConfig :: Assertion+newDefaultConfig = do+ sysTmpDir <- getTemporaryDirectory+ withTempDirectory silent sysTmpDir "cabal-test" $ \tmpDir -> do+ let configFile = tmpDir </> "tmp.config"+ createDefaultConfigFile silent configFile+ exists <- doesFileExist configFile+ assertBool ("Config file should be written to " ++ configFile) exists+++globalFlags :: FilePath -> GlobalFlags+globalFlags configFile = mempty { globalConfigFile = Flag configFile }++ listUnique :: Ord a => [a] -> Bool listUnique xs = let sorted = sort xs in nub sorted == xs -bracketTest :: ((FilePath, FilePath) -> IO ()) -> Assertion+bracketTest :: (FilePath -> IO ()) -> Assertion bracketTest = bracket testSetup testTearDown where- testSetup :: IO (FilePath, FilePath)- testSetup = do- Just oldHome <- lookupEnv "HOME"- testdir <- fmap (++ "/test-user-config") getCurrentDirectory- setEnv "HOME" testdir- return (oldHome, testdir)+ testSetup :: IO FilePath+ testSetup = fmap (</> "test-user-config") getCurrentDirectory - testTearDown :: (FilePath, FilePath) -> IO ()- testTearDown (oldHome, testdir) = do- setEnv "HOME" oldHome- removeDirectoryRecursive testdir+ testTearDown :: FilePath -> IO ()+ testTearDown configFile =+ mapM_ removeExistingFile [configFile, configFile ++ ".backup"]
+ cabal/cabal-install/tests/UnitTests/Options.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE DeriveDataTypeable #-}++module UnitTests.Options ( OptionShowSolverLog(..)+ , OptionMtimeChangeDelay(..)+ , extraOptions )+ where++import Data.Proxy+import Data.Typeable++import Test.Tasty.Options++{-------------------------------------------------------------------------------+ Test options+-------------------------------------------------------------------------------}++extraOptions :: [OptionDescription]+extraOptions =+ [ Option (Proxy :: Proxy OptionShowSolverLog)+ , Option (Proxy :: Proxy OptionMtimeChangeDelay)+ ]++newtype OptionShowSolverLog = OptionShowSolverLog Bool+ deriving Typeable++instance IsOption OptionShowSolverLog where+ defaultValue = OptionShowSolverLog False+ parseValue = fmap OptionShowSolverLog . safeRead+ optionName = return "show-solver-log"+ optionHelp = return "Show full log from the solver"+ optionCLParser = flagCLParser Nothing (OptionShowSolverLog True)++newtype OptionMtimeChangeDelay = OptionMtimeChangeDelay Int+ deriving Typeable++instance IsOption OptionMtimeChangeDelay where+ defaultValue = OptionMtimeChangeDelay 0+ parseValue = fmap OptionMtimeChangeDelay . safeRead+ optionName = return "mtime-change-delay"+ optionHelp = return $ "How long to wait before attempting to detect"+ ++ "file modification, in microseconds"
cabal/setup-dev.sh view
@@ -6,45 +6,31 @@ exit 1 } -build_and_test() {- # Find sandbox location- local PACKAGEDB=`cabal exec -- sh -c 'echo $GHC_PACKAGE_PATH' | sed 's/:.*//'`- echo "Cabal package DB location: $PACKAGEDB"- # Do the building- ./Setup configure --enable-tests --package-db="$PACKAGEDB" || die "$1 'configure' failed"- ./Setup build || die "$1 'build' failed"-# Disabled for now: Tests fail on my system for some reason-# ./Setup test || die "$1 'test' failed"-}--install_deps() {- cabal install --only-dependencies --enable-tests || die "$1: Could not install needed dependencies"-}--create_setup() {- ghc --make -threaded Setup.hs || die "$1: Could not create 'Setup' executable"-}--init_sandbox() {- cabal sandbox delete # Ignore error status; probably just means sandbox doesn't exist- cabal sandbox init || die "$1: Could not initialize sandbox"-}--setup_cabal() {- init_sandbox Cabal- install_deps Cabal- create_setup Cabal- build_and_test Cabal-}--setup_cabalinstall() {- init_sandbox cabal-install- cabal sandbox add-source ../Cabal/ || die "cabal-install: Failed to add ../Cabal source"- install_deps cabal-install- create_setup cabal-install- build_and_test cabal-install+setup() {+ # Extract parameters+ local NAME="$1"+ shift+ local DEPS="$@"+ # (Re-)create sandbox+ cabal sandbox delete # Ignore error status; probably just means sandbox doesn't exist+ cabal sandbox init || die "$NAME: Could not initialize sandbox"+ # Add dependencies+ for DEP in $DEPS; do+ cabal sandbox add-source "$DEP"+ done+ # Install dependencies+ cabal install --only-dependencies --enable-tests || die "$NAME: Could not install needed dependencies"+ # Build the 'Setup' executable+ ghc --make -threaded -i -i. Setup.hs || die "$NAME: Could not create 'Setup' executable"+ # Build the package+ local PACKAGEDB=`cabal exec -- sh -c 'echo $GHC_PACKAGE_PATH' | sed 's/:.*//'`+ echo "Cabal package DB location: $PACKAGEDB"+ ./Setup configure --enable-tests --package-db="$PACKAGEDB" || die "$NAME: 'configure' failed"+ ./Setup build || die "$NAME: 'build' failed"+ # Run tests+ ./Setup test || die "$1 'test' failed" } # Build-(cd ${SCRIPT_DIR}/Cabal && setup_cabal ) || die "Failed to build Cabal"-(cd ${SCRIPT_DIR}/cabal-install && setup_cabalinstall) || die "Failed to build cabal-install"+(cd ${SCRIPT_DIR}/Cabal && setup "Cabal" ) || die "Failed to build Cabal"+(cd ${SCRIPT_DIR}/cabal-install && setup "cabal-install" ../Cabal) || die "Failed to build cabal-install"
+ cabal/travis-script.sh view
@@ -0,0 +1,91 @@+#!/usr/bin/env bash+set -ev++# Initial working directory: base directory of Git repository++# We depend on parsec nowadays, which isn't distributed with GHC <8.0+if [ "$PARSEC_BUNDLED" != "YES" ]; then+ cabal install parsec+fi++# ---------------------------------------------------------------------+# Cabal+# ---------------------------------------------------------------------++cd Cabal++# Test if gen-extra-source-files.sh was run recently enough+./misc/gen-extra-source-files.sh Cabal.cabal+./misc/travis-diff-files.sh++cd ../cabal-install+../Cabal/misc/gen-extra-source-files.sh cabal-install.cabal+../Cabal/misc/travis-diff-files.sh++cd ../Cabal++# Build the setup script in the same way that cabal-install would:+mkdir -p ./dist/setup+cp Setup.hs ./dist/setup/setup.hs+ghc --make \+ -odir ./dist/setup -hidir ./dist/setup -i -i. \+ ./dist/setup/setup.hs -o ./dist/setup/setup \+ -Wall -Werror -threaded++# Install test dependencies only after setup is built+cabal install --only-dependencies --enable-tests --enable-benchmarks+./dist/setup/setup configure \+ --user --ghc-option=-Werror --enable-tests --enable-benchmarks \+ -v2 # -v2 provides useful information for debugging++# Build all libraries and executables (including tests/benchmarks)+./dist/setup/setup build+./dist/setup/setup haddock # see https://github.com/haskell/cabal/issues/2198+./dist/setup/setup test --show-details=streaming --test-option=--hide-successes++# Redo the package tests with different versions of GHC+if [ "$TEST_OLDER" == "YES" ]; then+ CABAL_PACKAGETESTS_WITH_GHC=/opt/ghc/7.0.4/bin/ghc \+ ./dist/setup/setup test package-tests --show-details=streaming+ CABAL_PACKAGETESTS_WITH_GHC=/opt/ghc/7.2.2/bin/ghc \+ ./dist/setup/setup test package-tests --show-details=streaming+fi++cabal check+cabal sdist # tests that a source-distribution can be generated++# The following scriptlet checks that the resulting source distribution can be+# built & installed.+function install_from_tarball {+ export SRC_TGZ=$(cabal info . | awk '{print $2 ".tar.gz";exit}') ;+ if [ -f "dist/$SRC_TGZ" ]; then+ cabal install -j1 "dist/$SRC_TGZ" -v2;+ else+ echo "expected 'dist/$SRC_TGZ' not found";+ exit 1;+ fi+}++install_from_tarball++# ---------------------------------------------------------------------+# cabal-install+# ---------------------------------------------------------------------++cd ../cabal-install++cabal install happy+cabal install --only-dependencies --enable-tests --enable-benchmarks+cabal configure \+ --user --ghc-option=-Werror --enable-tests --enable-benchmarks \+ -v2 # -v2 provides useful information for debugging+cabal build+cabal haddock # see https://github.com/haskell/cabal/issues/2198+cabal test unit-tests --show-details=streaming --test-option=--hide-successes+cabal test integration-tests --show-details=streaming --test-option=--hide-successes+cabal check+./dist/setup/setup sdist+install_from_tarball++# Check what we got+$HOME/.cabal/bin/cabal --version
+ hackage-security/.git view
@@ -0,0 +1,1 @@+gitdir: ../.git/modules/hackage-security
+ hackage-security/.gitignore view
@@ -0,0 +1,5 @@+tags+prototype-state/client/+prototype-state/server/+prototype-state/offline/+.stack-work/
+ hackage-security/.travis.yml view
@@ -0,0 +1,125 @@+# see also https://github.com/hvr/multi-ghc-travis+language: c+sudo: false++cache:+ directories:+ - $HOME/.cabsnap+ - $HOME/.cabal/packages++before_cache:+ - rm -fv $HOME/.cabal/packages/hackage.haskell.org/build-reports.log+ - rm -fv $HOME/.cabal/packages/hackage.haskell.org/00-index.tar++matrix:+ include:+# cabal 1.16's solver is too weak for GHC 7.4+ - env: CABALVER=1.22 GHCVER=7.4.2+ compiler: ": #GHC 7.4.2"+ addons: {apt: {packages: [cabal-install-1.22,ghc-7.4.2], sources: [hvr-ghc]}}+ - env: CABALVER=1.16 GHCVER=7.6.3+ compiler: ": #GHC 7.6.3"+ addons: {apt: {packages: [cabal-install-1.16,ghc-7.6.3], sources: [hvr-ghc]}}+ - env: CABALVER=1.18 GHCVER=7.8.4+ compiler: ": #GHC 7.8.4"+ addons: {apt: {packages: [cabal-install-1.18,ghc-7.8.4], sources: [hvr-ghc]}}+ - env: CABALVER=1.22 GHCVER=7.10.2+ compiler: ": #GHC 7.10.2"+ addons: {apt: {packages: [cabal-install-1.22,ghc-7.10.2], sources: [hvr-ghc]}}++before_install:+ - unset CC+ - export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH+ - TARGETS="hackage-security/ hackage-security-curl/ hackage-security-HTTP/ hackage-root-tool/ hackage-repo-tool/ example-client/ hackage-security-http-client/";++install:+# Compute install plan (which we use as a travis cache key)+ - cabal --version+ - echo "$(ghc --version) [$(ghc --print-project-git-commit-id 2> /dev/null || echo '?')]"+ - if [ -f $HOME/.cabal/packages/hackage.haskell.org/00-index.tar.gz ];+ then+ zcat $HOME/.cabal/packages/hackage.haskell.org/00-index.tar.gz >+ $HOME/.cabal/packages/hackage.haskell.org/00-index.tar;+ fi+ - travis_retry cabal update -v+ - sed -i 's/^jobs:/-- jobs:/' ${HOME}/.cabal/config+ - cabal install --constraint="directory installed" --constraint="bytestring installed" --only-dependencies --enable-tests --dry -v $TARGETS > installplan.txt+ - sed -i -e '1,/^Resolving /d' installplan.txt; cat installplan.txt++# check whether current requested install-plan matches cached package-db snapshot+ - if diff -u installplan.txt $HOME/.cabsnap/installplan.txt;+ then+ echo "cabal build-cache HIT";+ rm -rfv .ghc;+ cp -a $HOME/.cabsnap/ghc $HOME/.ghc;+ cp -a $HOME/.cabsnap/lib $HOME/.cabsnap/share $HOME/.cabsnap/bin $HOME/.cabal/;+ else+ echo "cabal build-cache MISS";+ rm -rf $HOME/.cabsnap;+ mkdir -p $HOME/.ghc $HOME/.cabal/lib $HOME/.cabal/share $HOME/.cabal/bin;+ cabal install --constraint="directory installed" --constraint="bytestring installed" --only-dependencies --enable-tests $TARGETS;+ fi++# snapshot package-db on cache miss+ - if [ ! -d $HOME/.cabsnap ];+ then+ echo "snapshotting package-db to build-cache";+ mkdir $HOME/.cabsnap;+ cp -a $HOME/.ghc $HOME/.cabsnap/ghc;+ cp -a $HOME/.cabal/lib $HOME/.cabal/share $HOME/.cabal/bin installplan.txt $HOME/.cabsnap/;+ fi++# Here starts the actual work to be performed for the package under test;+# any command which exits with a non-zero exit code causes the build to fail.+script:+ - cd hackage-security/+ - cabal configure --enable-tests -v2+ - cabal build+ - cabal sdist+ - cabal test+ - SRC_TGZ=$(cabal info . | awk '{print $2;exit}').tar.gz && (cd dist && cabal install --force-reinstalls "$SRC_TGZ")+ - cd ..++ - cd hackage-security-curl/+ - cabal configure -v2+ - cabal build+ - cabal sdist+ - SRC_TGZ=$(cabal info . | awk '{print $2;exit}').tar.gz && (cd dist && cabal install --force-reinstalls "$SRC_TGZ")+ - cd ..++ - cd hackage-security-HTTP/+ - cabal configure -v2+ - cabal build+ - cabal sdist+ - SRC_TGZ=$(cabal info . | awk '{print $2;exit}').tar.gz && (cd dist && cabal install --force-reinstalls "$SRC_TGZ")+ - cd ..++ - cd hackage-root-tool/+ - cabal configure -v2+ - cabal build+ - cabal sdist+ - SRC_TGZ=$(cabal info . | awk '{print $2;exit}').tar.gz && (cd dist && cabal install --force-reinstalls "$SRC_TGZ")+ - cd ..++ - cd hackage-repo-tool/+ - cabal configure -v2+ - cabal build+ - cabal sdist+ - SRC_TGZ=$(cabal info . | awk '{print $2;exit}').tar.gz && (cd dist && cabal install --force-reinstalls "$SRC_TGZ")+ - cd ..++ - cd hackage-security-http-client/+ - cabal configure -v2+ - cabal build+ - cabal sdist+ - SRC_TGZ=$(cabal info . | awk '{print $2;exit}').tar.gz && (cd dist && cabal install --force-reinstalls "$SRC_TGZ")+ - cd ..++ - cd example-client/+ - cabal configure -v2+ - cabal build+ - cabal sdist+ - SRC_TGZ=$(cabal info . | awk '{print $2;exit}').tar.gz && (cd dist && cabal install --force-reinstalls "$SRC_TGZ")+ - cd ..++# EOF
+ hackage-security/README.md view
@@ -0,0 +1,694 @@+# Hackage Security++This is a library for Hackage security based on [TUF, The Update+Framework][TUF].++## Project phases and shortcuts++Phase 1 of the project will implement the basic TUF framework, but leave out+author signing; support for author signed packages (and other targets) will+added in phase 2. The main goal of phase 1 is to be able to have untrusted+mirrors of the Hackage server.++## Brief overview of Hackage and cabal-install++Hackage makes all packages available from a single directory `/package`; for+example, version 1.0 of package Foo is available at `/package/Foo-1.0.tar.gz`+(see [Footnote: Paths](#paths)).++Additionally, Hackage offers a tarball, variously known as “the+index” or “the index tarball”, located at `/00-index.tar.gz`.+The index tarball contains the `.cabal` for all packages available on the+server; `cabal-install` downloads this file whenever you call `cabal update` to+figure out which packages are available, and it uses this file when you `cabal+install` a package to figure out which dependencies to install. The `.cabal`+file for Foo-1.0 is located at `Foo/1.0/Foo.cabal` in the index. (One side goal+of the Hackage Security project is to make downloading the index incremental.)++Note that although Hackage additionally offers the (latest version of) the+`.cabal` file at `/package/Foo-1.0/Foo.cabal`, this is never used by+`cabal-install`.++In order to distinguish between paths on the server and paths in the index we+will qualify them as `<repo>/package/Foo-1.0.tar.gz` and+`<index>/Foo/1.0/Foo.cabal` respectively, both informally in this text and in+formal delegation rules.++## Comparison with TUF++In this section we highlight some of the differences in the specifics of the+implementation; generally we try to follow the TUF model as closely as+possible.++This section is not (currently) intended to be complete.++### Targets++In the current proposal we do not yet implement author signing, but when we do+implement author signing we want to have a smooth transition, and moreover we+want to be able to have a mixture of packages, some of which are author signed+and some of which are not. That is, package authors must be able to opt-in to+author signing (or not).++#### Unsigned packages++##### Package specific metadata++The package metadata files (“target files”) will be stored _in the+index_. The metadata for Foo 1.0 will be stored in+`<index>/Foo/1.0/package.json`, and will contain the hash and size of the+package tarball:++``` javascript+{ "signed" : {+ "_type" : "Targets"+ , "version" : VERSION+ , "expires" : never+ , "targets" : { "<repo>/package/Foo-1.0.tar.gz" : FILEINFO }+ }+, "signatures" : []+}+```++Note that expiry dates are relevant only for information that we expect to+change over time (such as the snapshot). Since packages are immutable, they+cannot expire. (Additionally, there is no point adding an expiry date to files+that are protected only the snapshot key, as the snapshot _itself_ will expire).++It is not necessary to list the file info of the `.cabal` files here: `.cabal`+files are listed by value in the index tarball, and are therefore already+protected by the snapshot key (but see [author signing](#author-signing)).++##### Delegation++[Conceptually speaking](#phase1-shortcuts) we then need a top-level target file+`<index>/targets.json` that contains the required delegation information:++``` javascript+{ "signed" : {+ "_type" : Targets+ , "version" : VERSION+ , "expires" : never+ , "targets" : []+ , "delegations" : {+ "keys" : []+ , "roles" : [+ { "name" : "<index>/$NAME/$VERSION/package.json"+ , "keyids" : []+ , "threshold" : 0+ , "path" : "<repo>/package/$NAME-$VERSION.tar.gz"+ }+ ]+ }+ }+, "signatures" : /* target keys */+}+```++This file itself is signed by the target keys (kept offline by the Hackage+admins). ++Note that this file uses various extension to TUF spec:++* We can use wildcards in names as well as in paths. This means that we list a+ <b>single</b> path with a <b>single</b> replacement name. (Alternatively, we+ could have a list of pairs of paths and names.)+* Paths contain namespaces (`<repo>` versus `<index>`)+* Wildcards have more structure than TUF provides for.++The first one of these is the most important, as it has some security+implications; see comments below.++New unsigned packages, as well as new versions of existing unsigned packages,+can be uploaded to Hackage without any intervention from the Hackage admins (the+offline target keys are not required).++##### Security++This setup is sufficient to allow for untrusted mirrors: since they do not have+access to the snapshot key, they (or a man-in-the-middle) cannot change which+packages are visible or change the packages themselves.++However, since the snapshot key is stored on the server, if the server itself is+compromised almost all security guarantees are void.++#### <a name="author-signing">Signed packages</a>++We sketch the design here only, we do not actually intend to implement this yet+in phase 1 of the project.++##### Package specific metadata++As for unsigned packages, we keep metadata specific for each package version.+Unlike for unsigned packages, however, we store two files: one that can be+signed by the package author, and one that can be signed by the Hackage+trustees, who can upload new `.cabal` file revisions but not change the+package contents.++As before we still have `<index>/Foo/1.0/package.json` containing++``` javascript+{ "signed" : {+ "_type" : "Targets"+ , "version" : VERSION+ , "expires" : never+ , "targets" : { "<repo>/package/Foo-1.0.tar.gz" : FILEINFO }+ }+, "signatures" : /* signatures from package authors */+}+```++It is not necessary to separately sign the `.cabal` file that is listed _inside_+the package `.tar.gz` file. However, this `.cabal` file may not match the one in+the index, either because a Hackage trustee uploaded a revision, or because of+an malicious attempt to fool the solver in installing different dependencies+than intended.++Therefore, unlike for unsigned packages, listing the file info for the `.cabal`+file in the index is useful for signed packages: although the `.cabal` files are+listed by value in the index tarball, the index is only signed by the snapshot+key. We may want to additionally check that the `.cabal` are properly author+signed too. We record this in a different file `<index>/Foo/1.0/revisions.json`,+which can be signed by either the package authors or the Hackage trustees.++``` javascript+{ "signed" : {+ "_type" : "Targets"+ , "version" : VERSION+ , "expires" : never+ , "targets" : { "<index>/Foo/1.0/Foo.cabal" : FILEINFO }+ }+, "signatures" : /* signatures from package authors or Hackage trustees */+}+```++##### Delegation++Delegation for signed packages is a bit more complicated. We extend the+top-level targets file to++``` javascript+{ "signed" : {+ "_type" : Targets+ , "version" : VERSION+ , "expires" : EXPIRES+ , "targets" : []+ , "delegations" : {+ "keys" : /* Hackage trustee keys */+ , "roles" : [+ // Delegation for unsigned packages+ { "name" : "<index>/$NAME/$VERSION/package.json"+ , "keyids" : []+ , "threshold" : 0+ , "path" : "<repo>/package/$NAME-$VERSION.tar.gz"+ }+ // Delegation for package Bar+ , { "name" : "<index>/Bar/authors.json"+ , "keyids" : /* top-level target keys */+ , "threshold" : THRESHOLD+ , "path" : "<repo>/package/Bar-$VERSION.tar.gz"+ }+ , { "name" : "<index>/Bar/authors.json"+ , "keyids" : /* top-level target keys */+ , "threshold" : THRESHOLD+ , "path" : "<index>/Bar/$VERSION/Bar.cabal"+ }+ , { "name" : "<index>/Bar/$VERSION/revisions.json"+ , "keyids" : /* Hackage trustee key IDs */+ , "threshold" : THRESHOLD+ , "path" : "<index>/Bar/$VERSION/Bar.cabal"+ }+ // .. delegation for other signed packages ..+ ]+ }+ }+, "signatures" : /* target keys */+}+```++Since this lists all signed packages, we must list an expiry date here so that+attackers cannot mount freeze attacks (although this is somewhat less of an+issue here as freezing this list would make an entire new package, rather than+a new package version, invisible).++This “middle level” targets file `<index>/Bar/authors.json`+introduces the package author/maintainer keys and contains further delegation+information:++``` javascript+{ "signed" : {+ "_type" : "Targets"+ , "version" : VERSION+ , "expires" : never+ , "targets" : {}+ , "delegations" : {+ "keys" : /* package maintainer keys */+ , "roles" : [+ { "name" : "<index>/Bar/$VERSION/package.json"+ , "keyids" : /* package maintainer key IDs */+ , "threshold" : THRESHOLD+ , "path" : "<repo>/Bar/$VERSION/Bar-$VERSION.tar.gz"+ }+ , { "name" : "<index>/Bar/$VERSION/revisions.json"+ , "keyids" : /* package maintainer key IDs */+ , "threshold" : THRESHOLD+ , "path" : "<index>/Bar/$VERSION/Bar.cabal"+ }+ ]+ }+, "signatures" : /* signatures from top-level target keys */+}+```++Some notes:++1. When a new signed package is introduced, the Hackage admins need to create+ and sign a new `targets.json` that lists the package author keys and+ appropriate delegation information, as well as add corresponding entries to+ the top-level delegation file. However, once this is done, the Hackage admins+ do not need to be involved when package authors wish to upload new versions.++2. When package authors upload a new version, they need to sign only a single+ file that contains the information about that particular version.++3. Both package authors (through the package-specific “middle level”+ delegation information) and Hackage trustees (through the top-level+ delegation information) can sign `.cabal` file revisions, but only authors+ can sign the packages themselves.++4. Hackage trustees are listed only in the top-level delegation information, so+ when the set of trustees changes we only need to modify one file (as opposed+ to each middle-level package delegation information).++5. For signed packages that do not want to allow Hackage trustees to sign+ `.cabal` file revisions we can just omit the corresponding entry from the+ top-level delegations file.++6. There are two kinds of rule overlaps in these delegation rules:+ `<repo>/package/Bar-1.0.tar.gz` will match against the rule for unsigned+ packages (`<repo>/package/$NAME-$VERSION.tar.gz`) and against the rule for+ signed packages (`<repo>/package/Bar-$VERSION.tar.gz`). It is important+ here that the signed rule take precedence, because author signed packages+ _must_ be author signed. The priority scheme can be simple: more specific+ rules should take precedence (the TUF specification leaves the priority+ scheme used open).++ The second kind of overlap occurs between the rule+ for the `.cabal` file in the the top-level `targets.json` and the+ corresponding rule in `authors.json`; in this case both rules match against+ precisely the same path (`<index>/Bar/$VERSION/Bar.cabal`), and indeed in+ this case there is no priority: as long as either rule matches+ (that is, the file is either signed by a package author or by a Hackage+ trustee) we're okay.++##### Transition packages from `unsigned` to `signed`++When a package that previously did not opt-in to author signing now wants+author-signing, we just need to add the appropriate entries to the top-level+delegation file and set up the appropriate middle-level delegation information.++##### Security++When the snapshot key is compromised, attackers still do not have access to+package author keys, which are strictly kept offline. However, they can still+mount freeze attacks on packages versions, because there is no file (which is+signed with offline key) listing which versions are available.++We could increase security here by changing the middle-level `authors.json` to+remove the wildcard rule, list all versions explicitly, and change the top-level+delegation information to say that the middle-level file should be signed by+the package authors instead.++Note that we do not use a wildcard for signed packages in the top-level+`targets.json` for a similar reason: by listing all packages that we expect to+be signed explicitly, we have a list of signed packages which is signed by+offline keys (in this case, the target keys).++### Snapshot++#### Interaction with the index tarball++According to the official specification we should have a file `snapshot.json`,+signed with the snapshot key, which lists the hashes of _all_ metadata files in+the repo. In Hackage however we have the index tarball, which _contains_ most of+the metadata files in the repo (that is, it contains all the `.cabal` files, but+it also contains all the various `.json` files). The only thing that is missing+from the index tarball, compared to the `snapshot.json` file from the TUF spec,+is the version, expiry time, and signatures. Therefore our `snapshot.json` looks+like++``` javascript+{ "signed" : {+ "_type" : "Snapshot"+ , "version" : VERSION+ , "expires" : EXPIRES+ , "meta" : {+ "root.json" : FILEINFO+ , "mirrors.json" : FILEINFO+ , "index.tar" : FILEINFO+ , "index.tar.gz" : FILEINFO+ }+ }+, "signatures" : /* signatures from snapshot key */+}+```++Then the combination of `snapshot.json` together with the index tarball is+a strict superset of the information in TUF's `snapshot.json` (instead of+containing the hashes of the metadata in the repo, it contains the actual+metadata themselves).++We list the file info of the root and mirrors metadata explicitly, rather than+recording it in the index tarball, so that we can check them for updates during+the update process (section 5.1, “The Client Application”, of the+TUF spec) without downloading the entire index tarball.++### Out-of-tarball targets++All versions of all packages are listed, along with their full `.cabal` file, in+the Hackage index. This is useful because the constraint solver needs most of+this information anyway. However, when we add additional kinds of targets we may+not wish to add these to the index: people who are not interested in these new+targets should not, or only minimally, be affected. In particular, new releases+of these new kinds of targets should not result in a linear increase in what+clients who are not interested in these targets need to download whenever they+call `cabal install`.++To support these out-of-tarball targets we can use the regular TUF setup. Since+the index does not serve as an exhaustive list of which targets (and which+target versions) are available, it becomes important to have target metadata+that list all targets exhaustively, to avoid freeze attacks. The file+information (hash and filesize) of all these target metadata files must, by the+TUF spec, be listed in the top-level snapshot; we should thus avoid introducing+too many of them (in particular, we should avoid requiring a new metadata file+for each new version of a particular kind of target).++It is important to store OOT targets under a different prefix than `/package` to+avoid name clashes.++#### Collections++[Package collections][CabalHell1] are a new Hackage feature that's [currently in+development][ZuriHac]. We want package collections to be signed, just like+anything else.++Like packages, collections are versioned and immutable, so we have++```+collection/StackageNightly-2015.06.02.collection+collection/StackageNightly-2015.06.03.collection+collection/StackageNightly-...+collection/DebianJessie-...+collection/...+```++As for packages, collections should be able to opt-in for author signing (once+we support author signing), but we should also support not-author-signed+(“unsigned”) collections. Moreover, it should be possible for people+to create new unsigned collections without the involvement of the Hackage+admins. This rules out listing all collections explicitly in the top-level+`targets.json` (which is signed with offline target keys).++Below we sketch a possible design (we may want to tweak this further).++##### Unsigned collections++For unsigned collections we add a single delegation rule to the top-level+`targets.json`:++``` javascript+{ "name" : "<repo>/collection/collections.json"+, "keyids" : /* snapshot key */+, "threshold" : 1+, "path" : "<repo>/collection/$NAME-$VERSION.collection"+}+```++The middle-level `collections.json`, signed with the snapshot role, lists+delegation rules for all available collections:++``` javascript+[ { "name" : "<repo>/collection/StackageNightly.json"+ , "keyids" : /* snapshot key */+ , "threshold" : 1+ , "path" : "<repo>/collection/StackageNightly-$VERSION.collection"+ }+, { "name" : "<repo>/collection/DebianJessie.json"+ , "keyids" : /* snapshot key */+ , "threshold" : 1+ , "path" : "<repo>/collection/DebianJessie-$VERSION.collection"+ }+, ...+]+```++where `StackageNightly` and `DebianJessie` are two package collection (the+[Stackage Nightly][StackageNightly] collection and the set of Haskell package+distributed with the [Debian Jessie][DebianJessie] Linux distribution).++The final per-collection targets metadata finally lists all versions:++``` javascript+{ "signed" : {+ "_type" : "Targets"+ , "version" : VERSION+ , "expires" : /* expiry */+ , "targets" : {+ "<repo>/collection/StackageNightly-2015.06.02.collection" : FILEINFO+ , "<repo>/collection/StackageNightly-2015.06.03.collection" : FILEINFO+ , ...+ }+ }+, "signatures" : /* signed with snapshot role */+}+```++Since we cannot rely on the index to have the list of all version we must list+all versions explicitly here rather than using wildcards. Note that this means+that the snapshot will get a new entry for each new collection introduced, but+not for each new _version_ of each collection.++##### Author-signed collections++For author-signed collections we only need to make a single change. Suppose that+the `DebianJessie` collection is signed. Then we move the rule for+`DebianJessie` from `collection/collections.json` and instead list it in the+top-level `targets.json` (as for packages, introducing a signed collection+necessarily requires the involvement of the Hackage admins):++``` javascript+{ "name" : "<repo>/collection/DebianJessie.json"+, "keyids" : /* DebianJessie maintainer keys */+, "threshold" : /* threshold */+, "path" : "<repo>/collection/DebianJessie-$VERSION.collection"+}+```++No other changes are required (apart from of course that+`<repo>/collection/DebianJessie.json` will now be signed with the+`DebianJessie` maintainer keys rather than the snapshot key). As for packages,+this requires a priority scheme for delegation rules.++(One difference between this scheme and the scheme we use for packages is that+this means that the top-level `targets.json` file lists all keys for all+collection authors. If we want to avoid that we need a further level of+indirection.)++Note that in a sense author-signed collections are snapshots of the server. As+such, it would be good if these collections listed the file info (hashes and+filesizes) of the packages the collection.++## Project phases and shortcuts++Phase 1 of the project will implement the basic TUF framework, but leave out+author signing; support for author signed packages (and other targets) will+added in phase 2.++### <a name="phase1-shortcuts">Shortcuts taken in phase 1 (aka TODOs for phase 2)</a>++This list is currenty not exhaustive.++#### Core library++* Although the infrastructure is in place for [target metadata][Targetshs],+ including typed data types representing [pattern matches and+ replacements][Patternshs], we have not yet actually implementing target+ delegation proper. We don't need to: we only support one kind of target+ (not-author-signed packages), and we know statically where the target+ information for packages can be found (in `/package/version/targets.json`).+ This is currently hardcoded. ++ Once we have author signing we need a proper implementation of delegation,+ including a priority scheme between rules. This will also involve lazily+ downloading additional target metadata.++* Out-of-tarballs targets are not yet implemented. The main difficulty here is+ that they require a proper implementation of delegation; once that is done+ (required anyway for author signing) support for OOT targets should be+ straightforward.++#### Integration in `cabal-install`++* The cabal integration uses the `hackage-security` library to check for updates+ (that is, update the local copy of the index) and to download packages+ (verifying the downloaded file against the file info that was recorded in+ the package metadata, which itself is stored in the index). However, it does+ not use the library to get a list of available packages, nor to access+ `.cabal` files.++ Once we have author signing however we may want to do additional checks:++ * We should look at the top-level `targets.json` file (in addition to the+ index) to figure out which packages are available. (The top-level targets+ file, signed by offline keys, will enumerate all author-signed packages.)++ * If we do allow package authors to sign list of package versions (as detailed+ above) we should use these “middle level” target files to figure+ out which versions are available for these packages.++ * We might want to verify the `.cabal` files to make sure that they match the+ file info listed in the now author-signed metadata.++ Therefore `cabal-install` should be modified to go through the+ `hackage-security` library get the list of available packages, package+ versions, and to access the actual `.cabal` files.++## Open questions / TODOs++* The set of maintainers of a package can change over time, and can even change+ so much that the old maintainers of a package are no longer maintainters.+ But we would still like to be able to install and verify old packages. How+ do we deal with this?++## <a name="paths">Footnotes</a>++### Footnote: Paths++The situation with paths in cabal/hackage is a bit of a mess. In this footnote+we describe the situation before the work on the Hackage Security library.++#### The index tarball++The index tarball contains paths of the form++```+<package-name>/<package-version>/<package-name>.cabal+```++For example:++```+mtl/1.0/mtl.cabal+```++as well as a single top-level `preferred-versions` file.++#### Package resources offered by Hackage++Hackage offers a number of resources for package tarballs: one+“official” one and a few redirects.++1. The official location of package tarballs on a Hackage server is++ ```+ /package/<package-id>/<package-id>.tar.gz+ ```++ for example++ ```+ /package/mtl-2.2.1/mtl-2.2.1.tar.gz+ ```++ (This was the official location from [very early on][63a8c728]).++2. It [provides a redirect][3cfe4de] for++ ```+ /package/<package-id>.tar.gz+ ```++ for example++ ```+ /package/mtl-2.2.1.tar.gz+ ```++ (that is, a request for `/package/mtl-2.2.1.tar.gz` will get a 301 Moved+ Permanently response, and is redirected to+ `/package/mtl-2.2.1/mtl-2.2.1.tar.gz`).++3. It provides a redirect for Hackage-1 style URLs of the form++ ```+ /packages/archive/<package-name>/<package-version>/<package-id>.tar.gz+ ```++ for example++ ```+ /packages/archive/mtl/2.2.1/mtl-2.2.1.tar.gz+ ```++#### Locations used by cabal-install to find packages++There are two kinds of repositories supported by `cabal-install`: local and+remote.++1. For a local repository `cabal-install` looks for packages at++ ```+ <local-dir>/<package-name>/<package-version>/<package-id>.tar.gz+ ```++2. For remote repositories however `cabal-install` looks for packages in one of+ two locations.++ a. If the remote repository (`<repo>`) is+ `http://hackage.haskell.org/packages/archive` (this value is hardcoded)+ then it looks for the package at++ <repo>/<package-name>/<package-version>/<package-id>.tar.gz++ b. For any other repository it looks for the package at++ <repo>/package/<package-id>.tar.gz++Some notes:++1. Files downloaded from a remote repository are cached locally as++ ```+ <cache>/<package-name>/<package-version>/<package-id>.tar.gz+ ```++ I.e., the layout of the local cache matches the layout of a local+ repository (and matches the structure of the index tarball too).++2. Somewhat bizarrely, when `cabal-install` creates a new initial `config`+ file it uses `http://hackage.haskell.org/packages/archive` as the repo base+ URI (even in newer versions of `cabal-install`; this was [changed only very+ recently][bfeb01f]).++3. However, notice that _even when we give `cabal` a “new-style”+ URI_ the address used by `cabal` _still_ causes a redirect (from+ `/package/<package-id>.tar.gz` to+ `/package/<package-id>/<package-id>.tar.gz`).++The most important observation however is the following: **It is not possible to+serve a local repository as a remote repository** (by poining a webserver at a+local repository) because the layouts are completely different. (Note that the+location of packages on Hackage-1 _did_ match the layout of local repositories,+but that doesn't help because the _only_ repository that `cabal-install` will+regard as a Hackage-1 repository is one hosted on `hackage.haskell.org`).++[TUF]: http://theupdateframework.com/+[CabalHell1]: http://www.well-typed.com/blog/2014/09/how-we-might-abolish-cabal-hell-part-1/+[ZuriHac]: http://www.well-typed.com/blog/2015/06/cabal-hackage-hacking-at-zurihac/+[StackageNightly]: http://www.stackage.org/+[DebianJessie]: https://wiki.debian.org/DebianJessie+[Targetshs]: https://github.com/well-typed/hackage-security/blob/master/hackage-security/src/Hackage/Security/TUF/Targets.hs+[Patternshs]: https://github.com/well-typed/hackage-security/blob/master/hackage-security/src/Hackage/Security/TUF/Patterns.hs+[bfeb01f]: https://github.com/haskell/cabal/commit/bfeb01f+[63a8c728]: https://github.com/haskell/hackage-server/commit/63a8c728+[3cfe4de]: https://github.com/haskell/hackage-server/commit/3cfe4de
+ hackage-security/example-client/LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2015, Well-Typed LLP++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Well-Typed LLP nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ hackage-security/example-client/example-client.cabal view
@@ -0,0 +1,48 @@+name: example-client+version: 0.1.0.0+synopsis: Example client using the Hackage security library+-- description:+license: BSD3+license-file: LICENSE+author: Edsko de Vries+maintainer: edsko@well-typed.com+copyright: Copyright 2015 Well-Typed LLP+category: Distribution+build-type: Simple+cabal-version: >=1.10++flag use-network-uri+ description: Are we using network-uri?+ manual: False++executable example-client+ main-is: Main.hs+ other-modules: Prelude ExampleClient.Options++ build-depends: base >= 4.4,+ bytestring >= 0.9,+ Cabal >= 1.12,+ directory >= 1.1,+ filepath >= 1.2,+ optparse-applicative >= 0.11,+ time >= 1.2,+ hackage-security >= 0.5,+ hackage-security-HTTP,+ hackage-security-curl,+ hackage-security-http-client+ hs-source-dirs: src+ default-language: Haskell2010+ default-extensions: DeriveDataTypeable+ FlexibleContexts+ RankNTypes+ RecordWildCards+ ScopedTypeVariables+ other-extensions: CPP+ ghc-options: -Wall++ -- see comments in hackage-security.cabal+ if flag(use-network-uri)+ build-depends: network-uri >= 2.6 && < 2.7,+ network >= 2.6 && < 2.7+ else+ build-depends: network >= 2.5 && < 2.6
+ hackage-security/example-client/src/ExampleClient/Options.hs view
@@ -0,0 +1,171 @@+module ExampleClient.Options (+ GlobalOpts(..)+ , Command(..)+ , NewOnly+ , getOptions+ -- * Re-exports+ , URI+ ) where++import Data.List (isPrefixOf)+import Network.URI (URI, parseURI)+import Options.Applicative+import System.IO.Unsafe (unsafePerformIO)++import Distribution.Package+import Distribution.Text++import Hackage.Security.Client+import Hackage.Security.Util.Path++{-------------------------------------------------------------------------------+ Datatypes+-------------------------------------------------------------------------------}++data GlobalOpts = GlobalOpts {+ -- | Path to the repository (local or remote)+ globalRepo :: Either (Path Absolute) URI++ -- | Directory to store the client cache+ , globalCache :: Path Absolute++ -- | HTTP client to use+ , globalHttpClient :: String++ -- | Trusted root key (used for bootstrapping)+ , globalRootKeys :: [KeyId]++ -- | Should we check expiry times?+ , globalCheckExpiry :: Bool++ -- | Command to execute+ , globalCommand :: Command+ }++data Command =+ -- | Get initial root info+ Bootstrap KeyThreshold++ -- | Check for updates on the server+ | Check++ -- | Download a specific package+ | Get PackageIdentifier++ -- | Enumerate the entries in the index+ | EnumIndex NewOnly++ -- | Extract a cabal file from the index+ | GetCabal PackageIdentifier++ -- | Read a package hash from the index+ | GetHash PackageIdentifier+ deriving Show++type NewOnly = Bool++{-------------------------------------------------------------------------------+ Parsers+-------------------------------------------------------------------------------}++getOptions :: IO GlobalOpts+getOptions = execParser opts+ where+ opts = info (helper <*> parseGlobalOptions) $ mconcat [+ fullDesc+ , header "Example Hackage client"+ ]++parseBootstrap :: Parser Command+parseBootstrap = Bootstrap <$> argument readKeyThreshold (metavar "THRESHOLD")++parseCheck :: Parser Command+parseCheck = pure Check++parseGet :: Parser Command+parseGet = Get <$> argument readPackageIdentifier (metavar "PKG")++parseEnumIndex :: Parser Command+parseEnumIndex = EnumIndex+ <$> (switch $ mconcat [+ long "new-only"+ , help "Only enumerate entries since last call to enum-index"+ ])++parseGetCabal :: Parser Command+parseGetCabal = GetCabal <$> argument readPackageIdentifier (metavar "PKG")++parseGetHash :: Parser Command+parseGetHash = GetHash <$> argument readPackageIdentifier (metavar "PKG")++parseGlobalOptions :: Parser GlobalOpts+parseGlobalOptions = GlobalOpts+ <$> (option (str >>= readRepo) $ mconcat [+ long "repo"+ , metavar "URL"+ , help "Location of the repository"+ ])+ <*> (option (str >>= readAbsolutePath) $ mconcat [+ long "cache"+ , metavar "PATH"+ , help "Path to client cache"+ ])+ <*> (strOption $ mconcat [+ long "http-client"+ , metavar "CLIENT"+ , value "HTTP"+ , showDefault+ , help "HTTP client to use (currently supported: HTTP, http-conduit, curl)"+ ])+ <*> (many . option readKeyId $ mconcat [+ long "root-key"+ , metavar "KEYID"+ , help "Root key (used for bootstrapping; can be used multiple times)"+ ])+ <*> (switch $ mconcat [+ long "ignore-expiry"+ , help "Don't check expiry dates (should only be used in exceptional circumstances)"+ ])+ <*> (subparser $ mconcat [+ command "bootstrap" $ info (helper <*> parseBootstrap) $+ progDesc "Get the initial root information. If using a key threshold larger than 0, you will need to use the --root-key option to specify one or more trusted root keys."+ , command "check" $ info (helper <*> parseCheck) $+ progDesc "Check for updates"+ , command "get" $ info (helper <*> parseGet) $+ progDesc "Download a package"+ , command "enum-index" $ info (helper <*> parseEnumIndex) $+ progDesc "Enumerate the index"+ , command "cabal" $ info (helper <*> parseGetCabal) $+ progDesc "Extract a cabal file from the index"+ , command "hash" $ info (helper <*> parseGetHash) $+ progDesc "Read a package hash from the index"+ ])++readKeyId :: ReadM KeyId+readKeyId = KeyId <$> str++readKeyThreshold :: ReadM KeyThreshold+readKeyThreshold = KeyThreshold <$> auto++readPackageIdentifier :: ReadM PackageIdentifier+readPackageIdentifier = do+ raw <- str+ case simpleParse raw of+ Just pkgId -> return pkgId+ Nothing -> fail $ "Invalid package ID " ++ show raw++-- Sadly, cannot do I/O actions inside ReadM+readAbsolutePath :: String -> ReadM (Path Absolute)+readAbsolutePath = return . unsafePerformIO . makeAbsolute . fromFilePath++readRepo :: String -> ReadM (Either (Path Absolute) URI)+readRepo filePath =+ if "http://" `isPrefixOf` filePath+ then Right <$> readURI filePath+ else Left <$> readAbsolutePath filePath++readURI :: String -> ReadM URI+readURI uriStr =+ case parseURI uriStr of+ Nothing -> fail $ "Invalid URI " ++ show uriStr+ Just uri -> return uri
+ hackage-security/example-client/src/Main.hs view
@@ -0,0 +1,182 @@+module Main where++-- stdlib+import Control.Exception+import Control.Monad+import Data.Time+import qualified Data.ByteString.Lazy as BS.L++-- Cabal+import Distribution.Package++-- hackage-security+import Hackage.Security.Client+import Hackage.Security.Util.Path+import Hackage.Security.Util.Pretty+import Hackage.Security.Util.Some+import Hackage.Security.Client.Repository.HttpLib+import qualified Hackage.Security.Client.Repository.Cache as Cache+import qualified Hackage.Security.Client.Repository.Local as Local+import qualified Hackage.Security.Client.Repository.Remote as Remote+import qualified Hackage.Security.Client.Repository.HttpLib.HTTP as HttpLib.HTTP+import qualified Hackage.Security.Client.Repository.HttpLib.Curl as HttpLib.Curl+import qualified Hackage.Security.Client.Repository.HttpLib.HttpClient as HttpLib.HttpClient++-- example-client+import ExampleClient.Options++main :: IO ()+main = do+ opts@GlobalOpts{..} <- getOptions+ case globalCommand of+ Bootstrap threshold -> cmdBootstrap opts threshold+ Check -> cmdCheck opts+ Get pkgId -> cmdGet opts pkgId+ EnumIndex newOnly -> cmdEnumIndex opts newOnly+ GetCabal pkgId -> cmdGetCabal opts pkgId+ GetHash pkgId -> cmdGetHash opts pkgId++{-------------------------------------------------------------------------------+ The commands are just thin wrappers around the hackage-security Client API+-------------------------------------------------------------------------------}++cmdBootstrap :: GlobalOpts -> KeyThreshold -> IO ()+cmdBootstrap opts threshold =+ withRepo opts $ \rep -> uncheckClientErrors $ do+ bootstrap rep (globalRootKeys opts) threshold+ putStrLn "OK"++cmdCheck :: GlobalOpts -> IO ()+cmdCheck opts =+ withRepo opts $ \rep -> uncheckClientErrors $ do+ mNow <- if globalCheckExpiry opts+ then Just `fmap` getCurrentTime+ else return Nothing+ print =<< checkForUpdates rep mNow++cmdGet :: GlobalOpts -> PackageIdentifier -> IO ()+cmdGet opts pkgId = do+ cwd <- getCurrentDirectory+ let localFile = cwd </> fragment tarGzName+ withRepo opts $ \rep -> uncheckClientErrors $+ downloadPackage rep pkgId localFile+ where+ tarGzName :: String+ tarGzName = takeFileName $ repoLayoutPkgTarGz hackageRepoLayout pkgId++cmdEnumIndex :: GlobalOpts -> NewOnly -> IO ()+cmdEnumIndex opts False =+ withRepo opts $ \rep -> uncheckClientErrors $ do+ dir <- getDirectory rep+ forM_ (directoryEntries dir) $ putStrLn . aux+ where+ aux :: (Pretty fp, Pretty file) => (DirectoryEntry, fp, Maybe file) -> String+ aux (_, _, Just file) = pretty file+ aux (_, fp, Nothing ) = "unrecognized: " ++ pretty fp+cmdEnumIndex opts True = do+ withRepo opts $ \rep -> uncheckClientErrors $ do+ withIndex rep $ \IndexCallbacks{..} -> do+ let go n = do (Some IndexEntry{..}, mNext) <- indexLookupEntry n+ putStrLn $ pretty indexEntryPath+ case mNext of+ Nothing -> return ()+ Just next -> go next+ startingPoint <- getStartingPoint (directoryFirst indexDirectory)+ if (startingPoint == directoryNext indexDirectory)+ then putStrLn "No new entries"+ else do+ go startingPoint+ saveStartingPoint $ directoryNext indexDirectory+ where+ getStartingPoint :: DirectoryEntry -> IO DirectoryEntry+ getStartingPoint def =+ catch (read <$> readFile marker)+ (\(SomeException _) -> return def)++ saveStartingPoint :: DirectoryEntry -> IO ()+ saveStartingPoint = writeFile marker . show++ marker :: FilePath+ marker = toFilePath (globalCache opts </> fragment "enum.marker")++cmdGetCabal :: GlobalOpts -> PackageIdentifier -> IO ()+cmdGetCabal opts pkgId =+ withRepo opts $ \rep -> uncheckClientErrors $+ withIndex rep $ \IndexCallbacks{..} ->+ BS.L.putStr . trusted =<< indexLookupCabal pkgId++cmdGetHash :: GlobalOpts -> PackageIdentifier -> IO ()+cmdGetHash opts pkgId =+ withRepo opts $ \rep -> uncheckClientErrors $+ withIndex rep $ \IndexCallbacks{..} ->+ print =<< indexLookupHash pkgId++{-------------------------------------------------------------------------------+ Common functionality+-------------------------------------------------------------------------------}++withRepo :: GlobalOpts+ -> (forall down. DownloadedFile down => Repository down -> IO a)+ -> IO a+withRepo GlobalOpts{..} = \callback ->+ case globalRepo of+ Left local -> withLocalRepo local callback+ Right remote -> withRemoteRepo remote callback+ where+ withLocalRepo :: Path Absolute -> (Repository Local.LocalFile -> IO a) -> IO a+ withLocalRepo repo =+ Local.withRepository repo+ cache+ hackageRepoLayout+ hackageIndexLayout+ logTUF++ withRemoteRepo :: URI -> (Repository Remote.RemoteTemp -> IO a) -> IO a+ withRemoteRepo baseURI callback = withClient $ \httpClient ->+ Remote.withRepository httpClient+ [baseURI]+ repoOpts+ cache+ hackageRepoLayout+ hackageIndexLayout+ logTUF+ callback++ repoOpts :: Remote.RepoOpts+ repoOpts = Remote.defaultRepoOpts++ withClient :: (HttpLib -> IO a) -> IO a+ withClient act =+ case globalHttpClient of+ "HTTP" ->+ HttpLib.HTTP.withClient $ \browser httpLib -> do+ HttpLib.HTTP.setProxy browser proxyConfig+ HttpLib.HTTP.setOutHandler browser logHTTP+ HttpLib.HTTP.setErrHandler browser logHTTP+ act httpLib+ "curl" ->+ HttpLib.Curl.withClient $ \httpLib ->+ act httpLib+ "http-client" ->+ HttpLib.HttpClient.withClient proxyConfig $ \_manager httpLib ->+ act httpLib+ otherClient ->+ error $ "unsupported HTTP client " ++ show otherClient++ -- use automatic proxy configuration+ proxyConfig :: forall a. ProxyConfig a+ proxyConfig = ProxyConfigAuto++ -- used for log messages from the Hackage.Security code+ logTUF :: LogMessage -> IO ()+ logTUF msg = putStrLn $ "# " ++ pretty msg++ -- used for log messages from the HTTP clients+ logHTTP :: String -> IO ()+ logHTTP = putStrLn++ cache :: Cache.Cache+ cache = Cache.Cache {+ cacheRoot = globalCache+ , cacheLayout = cabalCacheLayout+ }
+ hackage-security/example-client/src/Prelude.hs view
@@ -0,0 +1,27 @@+-- | Smooth over differences between various ghc versions by making older+-- preludes look like 4.8.0+{-# LANGUAGE PackageImports #-}+{-# LANGUAGE CPP #-}+module Prelude (+ module P+#if !MIN_VERSION_base(4,8,0)+ , Applicative(..)+ , Monoid(..)+ , (<$>)+ , (<$)+ , traverse+#endif+ ) where++#if MIN_VERSION_base(4,8,0)+import "base" Prelude as P+#else+#if MIN_VERSION_base(4,6,0)+import "base" Prelude as P+#else+import "base" Prelude as P hiding (catch)+#endif+import Control.Applicative+import Data.Monoid+import Data.Traversable (traverse)+#endif
+ hackage-security/hackage-repo-tool/ChangeLog.md view
@@ -0,0 +1,13 @@+0.1.1+-----+* Update for hackage-security-0.5++0.1.0.1+-------+* Add missing module to other-modules (#100)+* Allow for hackage-security-0.3+* Include ChangeLog.md in sdist (#98)++0.1.0.0+-------+* Initial (beta) release
+ hackage-security/hackage-repo-tool/LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2015, Well-Typed LLP++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Well-Typed LLP nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ hackage-security/hackage-repo-tool/Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ hackage-security/hackage-repo-tool/hackage-repo-tool.cabal view
@@ -0,0 +1,69 @@+name: hackage-repo-tool+version: 0.1.1+synopsis: Utility to manage secure file-based package repositories+description: This utility can be used to manage secure file-based+ repositories (creating TUF metadata as well as a Hackage+ index tarball). Currently it also provides various+ lower level utilities for creating and signing TUF files.+ .+ This is part of the Hackage Security infrastructure.+homepage: http://github.com/well-typed/hackage-security/+license: BSD3+license-file: LICENSE+author: Edsko de Vries+maintainer: edsko@well-typed.com+copyright: Copyright 2015 Well-Typed LLP+category: Distribution+homepage: https://github.com/well-typed/hackage-security+bug-reports: https://github.com/well-typed/hackage-security/issues+build-type: Simple+cabal-version: >=1.10++extra-source-files:+ ChangeLog.md++source-repository head+ type: git+ location: https://github.com/well-typed/hackage-security.git++flag use-network-uri+ description: Are we using network-uri?+ manual: False++executable hackage-repo-tool+ main-is: Main.hs+ other-modules: Hackage.Security.RepoTool.Options+ Hackage.Security.RepoTool.Layout+ Hackage.Security.RepoTool.Layout.Keys+ Hackage.Security.RepoTool.Paths+ Hackage.Security.RepoTool.Util.IO+ Prelude+ build-depends: base >= 4.4 && < 5,+ Cabal >= 1.12 && < 1.25,+ bytestring >= 0.9 && < 0.11,+ directory >= 1.1 && < 1.3,+ filepath >= 1.2 && < 1.5,+ optparse-applicative >= 0.11 && < 0.13,+ tar >= 0.4 && < 0.6,+ time >= 1.2 && < 1.6,+ unix >= 2.5 && < 2.8,+ zlib >= 0.5 && < 0.7,+ hackage-security >= 0.5 && < 0.6+ hs-source-dirs: src+ default-language: Haskell2010+ default-extensions: DeriveDataTypeable+ FlexibleContexts+ FlexibleInstances+ NoMonomorphismRestriction+ ScopedTypeVariables+ StandaloneDeriving+ RecordWildCards+ other-extensions: TemplateHaskell+ ghc-options: -Wall++ -- see comments in hackage-security.cabal+ if flag(use-network-uri)+ build-depends: network-uri >= 2.6 && < 2.7,+ network >= 2.6 && < 2.7+ else+ build-depends: network >= 2.5 && < 2.6
+ hackage-security/hackage-repo-tool/src/Hackage/Security/RepoTool/Layout.hs view
@@ -0,0 +1,89 @@+-- | Layout of the local repository as managed by this tool+--+-- The local repository follows a RepoLayout exactly, but adds some additional+-- files. In addition, we also manage a directory of keys (although this will+-- eventually need to be replaced with a proper key management system).+module Hackage.Security.RepoTool.Layout (+ -- * Additional paths in the repository+ repoLayoutIndexDir+ -- * Layout-parametrized version of TargetPath+ , TargetPath'(..)+ , prettyTargetPath'+ , applyTargetPath'+ -- * Utility+ , anchorIndexPath+ , anchorRepoPath+ , anchorKeyPath+ , anchorTargetPath'+ ) where++import Distribution.Package++import Hackage.Security.Client+import Hackage.Security.Util.Path+import Hackage.Security.Util.Pretty++import Hackage.Security.RepoTool.Layout.Keys+import Hackage.Security.RepoTool.Options+import Hackage.Security.RepoTool.Paths++{-------------------------------------------------------------------------------+ Additional paths specifically to the kind of repository this tool manages+-------------------------------------------------------------------------------}++-- | Directory containing the unpacked index+--+-- Since the layout of the tarball may not match the layout of the index,+-- we create a local directory with the unpacked contents of the index.+repoLayoutIndexDir :: RepoLayout -> RepoPath+repoLayoutIndexDir _ = rootPath $ fragment "index"++{-------------------------------------------------------------------------------+ TargetPath'+-------------------------------------------------------------------------------}++-- | This is a variation on 'TargetPath' parameterized by layout+data TargetPath' =+ InRep (RepoLayout -> RepoPath)+ | InIdx (IndexLayout -> IndexPath)+ | InRepPkg (RepoLayout -> PackageIdentifier -> RepoPath) PackageIdentifier+ | InIdxPkg (IndexLayout -> PackageIdentifier -> IndexPath) PackageIdentifier++prettyTargetPath' :: GlobalOpts -> TargetPath' -> String+prettyTargetPath' opts = pretty . applyTargetPath' opts++-- | Apply the layout+applyTargetPath' :: GlobalOpts -> TargetPath' -> TargetPath+applyTargetPath' GlobalOpts{..} targetPath =+ case targetPath of+ InRep file -> TargetPathRepo $ file globalRepoLayout+ InIdx file -> TargetPathIndex $ file globalIndexLayout+ InRepPkg file pkgId -> TargetPathRepo $ file globalRepoLayout pkgId+ InIdxPkg file pkgId -> TargetPathIndex $ file globalIndexLayout pkgId++{-------------------------------------------------------------------------------+ Utility+-------------------------------------------------------------------------------}++-- | Anchor a tarball path to the repo (see 'repoLayoutIndex')+anchorIndexPath :: GlobalOpts -> RepoLoc -> (IndexLayout -> IndexPath) -> Path Absolute+anchorIndexPath opts@GlobalOpts{..} repoLoc file =+ anchorRepoPath opts repoLoc repoLayoutIndexDir+ </> unrootPath (file globalIndexLayout)++anchorRepoPath :: GlobalOpts -> RepoLoc -> (RepoLayout -> RepoPath) -> Path Absolute+anchorRepoPath GlobalOpts{..} (RepoLoc repoLoc) file =+ anchorRepoPathLocally repoLoc $ file globalRepoLayout++anchorKeyPath :: GlobalOpts -> KeysLoc -> (KeysLayout -> KeyPath) -> Path Absolute+anchorKeyPath GlobalOpts{..} (KeysLoc keysLoc) dir =+ keysLoc </> unrootPath (dir globalKeysLayout)++anchorTargetPath' :: GlobalOpts -> RepoLoc -> TargetPath' -> Path Absolute+anchorTargetPath' opts repoLoc = go+ where+ go :: TargetPath' -> Path Absolute+ go (InRep file) = anchorRepoPath opts repoLoc file+ go (InIdx file) = anchorIndexPath opts repoLoc file+ go (InRepPkg file pkgId) = anchorRepoPath opts repoLoc (`file` pkgId)+ go (InIdxPkg file pkgId) = anchorIndexPath opts repoLoc (`file` pkgId)
+ hackage-security/hackage-repo-tool/src/Hackage/Security/RepoTool/Layout/Keys.hs view
@@ -0,0 +1,44 @@+-- | Layout of the directory containing the (private) keys+module Hackage.Security.RepoTool.Layout.Keys (+ -- * Layout of the keys directory+ KeysLayout(..)+ , defaultKeysLayout+ , keysLayoutKey+ ) where++import Hackage.Security.Client+import Hackage.Security.Util.Path+import Hackage.Security.Util.Some++import Hackage.Security.RepoTool.Paths++-- | Layout of the keys directory+--+-- Specifies the directories containing the keys (relative to the keys loc),+-- as well as the filename for individual keys.+data KeysLayout = KeysLayout {+ keysLayoutRoot :: KeyPath+ , keysLayoutTarget :: KeyPath+ , keysLayoutTimestamp :: KeyPath+ , keysLayoutSnapshot :: KeyPath+ , keysLayoutMirrors :: KeyPath+ , keysLayoutKeyFile :: Some Key -> Path Unrooted+ }++defaultKeysLayout :: KeysLayout+defaultKeysLayout = KeysLayout {+ keysLayoutRoot = rp $ fragment "root"+ , keysLayoutTarget = rp $ fragment "target"+ , keysLayoutTimestamp = rp $ fragment "timestamp"+ , keysLayoutSnapshot = rp $ fragment "snapshot"+ , keysLayoutMirrors = rp $ fragment "mirrors"+ , keysLayoutKeyFile = \key -> let kId = keyIdString (someKeyId key)+ in fragment kId <.> "private"+ }+ where+ rp :: Path Unrooted -> KeyPath+ rp = rootPath++keysLayoutKey :: (KeysLayout -> KeyPath) -> Some Key -> KeysLayout -> KeyPath+keysLayoutKey dir key keysLayout@KeysLayout{..} =+ dir keysLayout </> keysLayoutKeyFile key
+ hackage-security/hackage-repo-tool/src/Hackage/Security/RepoTool/Options.hs view
@@ -0,0 +1,198 @@+module Hackage.Security.RepoTool.Options (+ GlobalOpts(..)+ , Command(..)+ , KeyLoc+ , DeleteExistingSignatures+ , getOptions+ ) where++import Network.URI (URI, parseURI)+import Options.Applicative+import System.IO.Unsafe (unsafePerformIO)++import Hackage.Security.Client+import Hackage.Security.Util.Path++import Hackage.Security.RepoTool.Layout.Keys+import Hackage.Security.RepoTool.Paths++{-------------------------------------------------------------------------------+ Types+-------------------------------------------------------------------------------}++-- | Command line options+data GlobalOpts = GlobalOpts {+ -- | Key directory layout+ globalKeysLayout :: KeysLayout++ -- | Local repository layout+ , globalRepoLayout :: RepoLayout++ -- | Local index layout+ , globalIndexLayout :: IndexLayout++ -- | Should we be verbose?+ , globalVerbose :: Bool++ -- | Expiry time when creating root (in years)+ , globalExpireRoot :: Integer++ -- | Expiry time when creating mirrors (in years)+ , globalExpireMirrors :: Integer++ -- | Command to execute+ , globalCommand :: Command+ }++data Command =+ -- | Create keys+ CreateKeys KeysLoc++ -- | Bootstrap a secure local repository+ | Bootstrap KeysLoc RepoLoc++ -- | Update a previously bootstrapped local repository+ | Update KeysLoc RepoLoc++ -- | Create root metadta+ | CreateRoot KeysLoc (Path Absolute)++ -- | Create mirrors metadata+ | CreateMirrors KeysLoc (Path Absolute) [URI]++ -- | Create a directory with symlinks in cabal-local-rep layout+ | SymlinkCabalLocalRepo RepoLoc RepoLoc++ -- | Sign an individual file+ | Sign [KeyLoc] DeleteExistingSignatures (Path Absolute)++type KeyLoc = Path Absolute+type DeleteExistingSignatures = Bool++{-------------------------------------------------------------------------------+ Parsers+-------------------------------------------------------------------------------}++getOptions :: IO GlobalOpts+getOptions = execParser opts+ where+ opts = info (helper <*> parseGlobalOptions) $ mconcat [+ fullDesc+ , header "Manage local Hackage repositories"+ ]++parseRepoLoc :: Parser RepoLoc+parseRepoLoc = RepoLoc <$> (option (str >>= readAbsolutePath) $ mconcat [+ long "repo"+ , metavar "PATH"+ , help "Path to local repository"+ ])++parseKeysLoc :: Parser KeysLoc+parseKeysLoc = KeysLoc <$> (option (str >>= readAbsolutePath) $ mconcat [+ long "keys"+ , metavar "PATH"+ , help "Path to key store"+ ])++parseCreateKeys :: Parser Command+parseCreateKeys = CreateKeys <$> parseKeysLoc++parseBootstrap :: Parser Command+parseBootstrap = Bootstrap <$> parseKeysLoc <*> parseRepoLoc++parseUpdate :: Parser Command+parseUpdate = Update <$> parseKeysLoc <*> parseRepoLoc++parseCreateRoot :: Parser Command+parseCreateRoot = CreateRoot+ <$> parseKeysLoc+ <*> (option (str >>= readAbsolutePath) $ mconcat [+ short 'o'+ , metavar "FILE"+ , help "Location of the root file"+ ])++parseCreateMirrors :: Parser Command+parseCreateMirrors = CreateMirrors+ <$> parseKeysLoc+ <*> (option (str >>= readAbsolutePath) $ mconcat [+ short 'o'+ , metavar "FILE"+ , help "Location of the mirrors file"+ ])+ <*> (many $ argument (str >>= readURI) (metavar "MIRROR"))++parseSymlinkCabalLocalRepo :: Parser Command+parseSymlinkCabalLocalRepo = SymlinkCabalLocalRepo+ <$> parseRepoLoc+ <*> (option (str >>= liftA RepoLoc . readAbsolutePath) $ mconcat [+ long "cabal-repo"+ , help "Location of the cabal repo"+ ])++parseSign :: Parser Command+parseSign = Sign+ <$> (many . option (str >>= readAbsolutePath) $ mconcat [+ long "key"+ , help "Path to private key (can be specified multiple times)"+ ])+ <*> (switch $ mconcat [+ long "delete-existing"+ , help "Delete any existing signatures"+ ])+ <*> argument (str >>= readAbsolutePath) (metavar "FILE")++-- | Global options+--+-- TODO: Make repo and keys layout configurable+parseGlobalOptions :: Parser GlobalOpts+parseGlobalOptions = GlobalOpts+ <$> (pure defaultKeysLayout)+ <*> (pure hackageRepoLayout)+ <*> (pure hackageIndexLayout)+ <*> (switch $ mconcat [+ long "verbose"+ , short 'v'+ , help "Verbose logging"+ ])+ <*> (option auto $ mconcat [+ long "expire-root"+ , metavar "YEARS"+ , help "Expiry time for the root info"+ , value 1+ , showDefault+ ])+ <*> (option auto $ mconcat [+ long "expire-mirrors"+ , metavar "YEARS"+ , help "Expiry time for the mirrors"+ , value 10+ , showDefault+ ])+ <*> (subparser $ mconcat [+ command "create-keys" $ info (helper <*> parseCreateKeys) $+ progDesc "Create keys"+ , command "bootstrap" $ info (helper <*> parseBootstrap) $+ progDesc "Bootstrap a local repository"+ , command "update" $ info (helper <*> parseUpdate) $+ progDesc "Update a (previously bootstrapped) local repository"+ , command "create-root" $ info (helper <*> parseCreateRoot) $+ progDesc "Create root metadata"+ , command "create-mirrors" $ info (helper <*> parseCreateMirrors) $+ progDesc "Create mirrors metadata. All MIRRORs specified on the command line will be written to the file."+ , command "symlink-cabal-local-repo" $ info (helper <*> parseSymlinkCabalLocalRepo) $+ progDesc "Create a directory in cabal-local-repo layout with symlinks to the specified repository."+ , command "sign" $ info (helper <*> parseSign) $+ progDesc "Sign a file"+ ])++readURI :: String -> ReadM URI+readURI uriStr =+ case parseURI uriStr of+ Nothing -> fail $ "Invalid URI " ++ show uriStr+ Just uri -> return uri++-- Sadly, cannot do I/O actions inside ReadM+readAbsolutePath :: String -> ReadM (Path Absolute)+readAbsolutePath = return . unsafePerformIO . makeAbsolute . fromFilePath
+ hackage-security/hackage-repo-tool/src/Hackage/Security/RepoTool/Paths.hs view
@@ -0,0 +1,33 @@+-- | Additional paths+module Hackage.Security.RepoTool.Paths (+ -- * Repo+ RepoLoc(..)+ -- * Keys+ , KeyRoot+ , KeyPath+ , KeysLoc(..)+ ) where++import Hackage.Security.Util.Path+import Hackage.Security.Util.Pretty++{-------------------------------------------------------------------------------+ Repo+-------------------------------------------------------------------------------}++newtype RepoLoc = RepoLoc { repoLocPath :: Path Absolute }+ deriving Eq++{-------------------------------------------------------------------------------+ Keys+-------------------------------------------------------------------------------}++-- | The key directory+data KeyRoot+type KeyPath = Path KeyRoot++instance Pretty (Path KeyRoot) where+ pretty (Path fp) = "<keys>/" ++ fp++newtype KeysLoc = KeysLoc { keysLocPath :: Path Absolute }+ deriving Eq
+ hackage-security/hackage-repo-tool/src/Hackage/Security/RepoTool/Util/IO.hs view
@@ -0,0 +1,112 @@+-- | IO utilities+{-# LANGUAGE CPP #-}+module Hackage.Security.RepoTool.Util.IO (+ -- * Miscellaneous+ compress+ , getFileModTime+ , createSymbolicLink+ -- * Tar archives+ , TarGzError+ , tarExtractFile+ ) where++import Control.Exception+import Data.Typeable+import System.IO.Error+import qualified Codec.Archive.Tar as Tar+import qualified Codec.Archive.Tar.Entry as Tar+import qualified Codec.Compression.GZip as GZip+import qualified Data.ByteString.Lazy as BS.L++-- Unlike the hackage-security library properly,+-- this currently works on unix systems only+import System.Posix.Types (EpochTime)+import qualified System.Posix.Files as Posix++-- hackage-security+import Hackage.Security.Util.Path++-- hackage-repo-tool+import Hackage.Security.RepoTool.Options+import Hackage.Security.RepoTool.Layout+import Hackage.Security.RepoTool.Paths++-- | Get the modification time of the specified file+--+-- Returns 0 if the file does not exist .+getFileModTime :: GlobalOpts -> RepoLoc -> TargetPath' -> IO EpochTime+getFileModTime opts repoLoc targetPath =+ handle handler $+ Posix.modificationTime <$> Posix.getFileStatus (toFilePath fp)+ where+ fp :: Path Absolute+ fp = anchorTargetPath' opts repoLoc targetPath++ handler :: IOException -> IO EpochTime+ handler ex = if isDoesNotExistError ex then return 0+ else throwIO ex++compress :: Path Absolute -> Path Absolute -> IO ()+compress src dst =+ withFile dst WriteMode $ \h ->+ BS.L.hPut h =<< GZip.compress <$> readLazyByteString src++-- | Create a symbolic link (unix only)+--+-- Create the directory for the target if it does not exist.+--+-- TODO: Currently this always creates links to absolute locations, whether the+-- user specified an absolute or a relative target.+createSymbolicLink :: (FsRoot root, FsRoot root')+ => Path root -- ^ Link target+ -> Path root' -- ^ Link location+ -> IO ()+createSymbolicLink linkTarget linkLoc = do+ createDirectoryIfMissing True (takeDirectory linkLoc)+ linkTarget' <- toAbsoluteFilePath linkTarget+ linkLoc' <- toAbsoluteFilePath linkLoc+ Posix.createSymbolicLink linkTarget' linkLoc'++{-------------------------------------------------------------------------------+ Working with tar archives+-------------------------------------------------------------------------------}++-- | Extract a file from a tar archive+--+-- Throws an exception if there is an error in the archive or when the entry+-- is not a file. Returns nothing if the entry cannot be found.+tarExtractFile :: GlobalOpts+ -> RepoLoc+ -> TargetPath'+ -> FilePath+ -> IO (Maybe (BS.L.ByteString, Tar.FileSize))+tarExtractFile opts repoLoc pathTarGz pathToExtract =+ handle (throwIO . TarGzError (prettyTargetPath' opts pathTarGz)) $ do+ let pathTarGz' = anchorTargetPath' opts repoLoc pathTarGz+ go =<< Tar.read . GZip.decompress <$> readLazyByteString pathTarGz'+ where+ go :: Exception e => Tar.Entries e -> IO (Maybe (BS.L.ByteString, Tar.FileSize))+ go Tar.Done = return Nothing+ go (Tar.Fail err) = throwIO err+ go (Tar.Next e es) =+ if Tar.entryPath e == pathToExtract+ then case Tar.entryContent e of+ Tar.NormalFile bs sz -> return $ Just (bs, sz)+ _ -> throwIO $ userError+ $ "tarExtractFile: "+ ++ pathToExtract ++ " not a normal file"+ else do -- putStrLn $ show (Tar.entryPath e) ++ " /= " ++ show path+ go es++data TarGzError = TarGzError FilePath SomeException+ deriving (Typeable)++instance Exception TarGzError where+#if MIN_VERSION_base(4,8,0)+ displayException (TarGzError path e) = path ++ ": " ++ displayException e++deriving instance Show TarGzError+#else+instance Show TarGzError where+ show (TarGzError path e) = path ++ ": " ++ show e+#endif
+ hackage-security/hackage-repo-tool/src/Main.hs view
@@ -0,0 +1,673 @@+{-# LANGUAGE CPP #-}+module Main (main) where++import Control.Exception+import Control.Monad+import Data.List (nub)+import Data.Maybe (catMaybes, mapMaybe)+import Data.Time+import GHC.Conc.Sync (setUncaughtExceptionHandler)+import Network.URI (URI)+import System.Exit+import System.IO.Error (isAlreadyExistsError)+import qualified Data.ByteString.Lazy as BS.L+import qualified System.FilePath as FilePath++-- Cabal+import Distribution.Package+import Distribution.Text++-- hackage-security+import Hackage.Security.Server+import Hackage.Security.Util.Some+import Hackage.Security.Util.IO+import Hackage.Security.Util.Path+import Hackage.Security.Util.Pretty+import qualified Hackage.Security.Key.Env as KeyEnv+import qualified Hackage.Security.TUF.FileMap as FileMap+import qualified Hackage.Security.Util.Lens as Lens+import Text.JSON.Canonical (JSValue)++-- hackage-repo-tool+import Hackage.Security.RepoTool.Options+import Hackage.Security.RepoTool.Layout+import Hackage.Security.RepoTool.Layout.Keys+import Hackage.Security.RepoTool.Paths+import Hackage.Security.RepoTool.Util.IO++{-------------------------------------------------------------------------------+ Main application driver+-------------------------------------------------------------------------------}++main :: IO ()+main = do+ setUncaughtExceptionHandler topLevelExceptionHandler+ opts@GlobalOpts{..} <- getOptions+ case globalCommand of+ CreateKeys keysLoc ->+ createKeys opts keysLoc+ Bootstrap keysLoc repoLoc ->+ bootstrapOrUpdate opts keysLoc repoLoc True+ Update keysLoc repoLoc ->+ bootstrapOrUpdate opts keysLoc repoLoc False+ CreateRoot keysLoc rootLoc ->+ createRoot opts keysLoc rootLoc+ CreateMirrors keysLoc mirrorsLoc mirrors ->+ createMirrors opts keysLoc mirrorsLoc mirrors+ SymlinkCabalLocalRepo repoLoc cabalRepoLoc ->+ symlinkCabalLocalRepo opts repoLoc cabalRepoLoc+ Sign keys deleteExisting file ->+ signFile keys deleteExisting file++-- | Top-level exception handler that uses 'displayException'+--+-- Although base 4.8 introduces 'displayException', the top-level exception+-- handler still uses 'show', sadly. See "PROPOSAL: Add displayException to+-- Exception typeclass" thread on the libraries mailing list.+--+-- NOTE: This is a terrible hack. See the above thread for some insights into+-- how we should do this better. For now it will do however.+topLevelExceptionHandler :: SomeException -> IO ()+topLevelExceptionHandler e = do+ putStrLn $ displayException e+ exitFailure++#if !MIN_VERSION_base(4,8,0)+displayException :: Exception e => e -> String+displayException = show+#endif++{-------------------------------------------------------------------------------+ Creating keys+-------------------------------------------------------------------------------}++createKeys :: GlobalOpts -> KeysLoc -> IO ()+createKeys opts keysLoc = do+ privateRoot <- replicateM 3 $ createKey' KeyTypeEd25519+ privateTarget <- replicateM 3 $ createKey' KeyTypeEd25519+ privateTimestamp <- replicateM 1 $ createKey' KeyTypeEd25519+ privateSnapshot <- replicateM 1 $ createKey' KeyTypeEd25519+ privateMirrors <- replicateM 3 $ createKey' KeyTypeEd25519+ writeKeys opts keysLoc PrivateKeys{..}++{-------------------------------------------------------------------------------+ Dealing with (private) keys+-------------------------------------------------------------------------------}++data PrivateKeys = PrivateKeys {+ privateRoot :: [Some Key]+ , privateTarget :: [Some Key]+ , privateTimestamp :: [Some Key]+ , privateSnapshot :: [Some Key]+ , privateMirrors :: [Some Key]+ }++readKeys :: GlobalOpts -> KeysLoc -> IO PrivateKeys+readKeys opts keysLoc =+ PrivateKeys <$> readKeysAt opts keysLoc keysLayoutRoot+ <*> readKeysAt opts keysLoc keysLayoutTarget+ <*> readKeysAt opts keysLoc keysLayoutTimestamp+ <*> readKeysAt opts keysLoc keysLayoutSnapshot+ <*> readKeysAt opts keysLoc keysLayoutMirrors++writeKeys :: GlobalOpts -> KeysLoc -> PrivateKeys -> IO ()+writeKeys opts keysLoc PrivateKeys{..} = do+ forM_ privateRoot $ writeKey opts keysLoc keysLayoutRoot+ forM_ privateTarget $ writeKey opts keysLoc keysLayoutTarget+ forM_ privateTimestamp $ writeKey opts keysLoc keysLayoutTimestamp+ forM_ privateSnapshot $ writeKey opts keysLoc keysLayoutSnapshot+ forM_ privateMirrors $ writeKey opts keysLoc keysLayoutMirrors++readKeysAt :: GlobalOpts -> KeysLoc -> (KeysLayout -> KeyPath) -> IO [Some Key]+readKeysAt opts keysLoc subDir = catMaybes <$> do+ entries <- getDirectoryContents absPath+ forM entries $ \entry -> do+ let path = absPath </> entry+ mKey <- readJSON_NoKeys_NoLayout path+ case mKey of+ Left _err -> do logWarn opts $ "Skipping unrecognized " ++ pretty path+ return Nothing+ Right key -> return $ Just key+ where+ absPath = anchorKeyPath opts keysLoc subDir++writeKey :: GlobalOpts -> KeysLoc -> (KeysLayout -> KeyPath) -> Some Key -> IO ()+writeKey opts@GlobalOpts{..} keysLoc subDir key = do+ logInfo opts $ "Writing " ++ pretty (relPath globalKeysLayout)+ createDirectoryIfMissing True (takeDirectory absPath)+ writeJSON_NoLayout absPath key+ where+ relPath = keysLayoutKey subDir key+ absPath = anchorKeyPath opts keysLoc relPath++{-------------------------------------------------------------------------------+ Creating individual files++ We translate absolute paths to repo layout to fit with rest of infrastructure.+-------------------------------------------------------------------------------}++createRoot :: GlobalOpts -> KeysLoc -> Path Absolute -> IO ()+createRoot opts@GlobalOpts{..} keysLoc rootLoc = do+ keys <- readKeys opts keysLoc+ now <- getCurrentTime+ updateRoot opts { globalRepoLayout = layout }+ repoLoc+ WriteUpdate+ keys+ now+ where+ repoLoc = RepoLoc $ takeDirectory rootLoc+ layout = globalRepoLayout {+ repoLayoutRoot = rootFragment $ takeFileName rootLoc+ }++createMirrors :: GlobalOpts -> KeysLoc -> Path Absolute -> [URI] -> IO ()+createMirrors opts@GlobalOpts{..} keysLoc mirrorsLoc mirrors = do+ keys <- readKeys opts keysLoc+ now <- getCurrentTime+ updateMirrors opts { globalRepoLayout = layout }+ repoLoc+ WriteUpdate+ keys+ now+ mirrors+ where+ repoLoc = RepoLoc $ takeDirectory mirrorsLoc+ layout = globalRepoLayout {+ repoLayoutMirrors = rootFragment $ takeFileName mirrorsLoc+ }++rootFragment :: String -> RepoPath+rootFragment = rootPath . fragment++{-------------------------------------------------------------------------------+ Bootstrapping / updating++ TODO: Some of this functionality should be moved to+ @Hackage.Security.Server.*@ (to be shared by both, say, Hackage, and+ secure-local), but I'm not sure precisely in what form yet.+-------------------------------------------------------------------------------}++bootstrapOrUpdate :: GlobalOpts -> KeysLoc -> RepoLoc -> Bool -> IO ()+bootstrapOrUpdate opts@GlobalOpts{..} keysLoc repoLoc isBootstrap = do+ -- Collect info+ keys <- readKeys opts keysLoc+ now <- getCurrentTime+ pkgs <- findPackages opts repoLoc++ -- Sanity check+ repoLayoutOk <- checkRepoLayout opts repoLoc pkgs+ unless repoLayoutOk $+ throwIO $ userError "Unexpected repository layout"++ -- We overwrite files during bootstrap process, but update them only+ -- if necessary during an update. Note that we _only_ write the updated+ -- files to the tarball, so the user deletes the tarball and then calls+ -- update (rather than bootstrap) the tarball will be missing files.+ let whenWrite = if isBootstrap+ then WriteInitial+ else WriteUpdate++ -- If doing bootstrap: create root and mirrors+ when isBootstrap $ do+ updateRoot opts repoLoc whenWrite keys now+ updateMirrors opts repoLoc whenWrite keys now []++ -- Create targets.json for each package version+ forM_ pkgs $ \pkgId -> do+ createPackageMetadata opts repoLoc whenWrite pkgId+ extractCabalFile opts repoLoc whenWrite pkgId++ -- Recreate index tarball+ newFiles <- findNewIndexFiles opts repoLoc whenWrite+ case (whenWrite, null newFiles) of+ (WriteInitial, _) -> do+ -- If we are recreating all files, also recreate the index+ _didExist <- handleDoesNotExist $ removeFile pathIndexTar+ logInfo opts $ "Writing " ++ prettyRepo repoLayoutIndexTar+ (WriteUpdate, True) -> do+ logInfo opts $ "Skipping " ++ prettyRepo repoLayoutIndexTar+ (WriteUpdate, False) ->+ logInfo opts $ "Appending " ++ show (length newFiles)+ ++ " file(s) to " ++ prettyRepo repoLayoutIndexTar+ unless (null newFiles) $ do+ tarAppend+ (anchorRepoPath opts repoLoc repoLayoutIndexTar)+ (anchorRepoPath opts repoLoc repoLayoutIndexDir)+ (map castRoot newFiles)++ logInfo opts $ "Writing " ++ prettyRepo repoLayoutIndexTarGz+ compress (anchorRepoPath opts repoLoc repoLayoutIndexTar)+ (anchorRepoPath opts repoLoc repoLayoutIndexTarGz)++ -- Create snapshot+ -- TODO: If we are updating we should be incrementing the version, not+ -- keeping it the same+ rootInfo <- computeFileInfo' repoLayoutRoot+ mirrorsInfo <- computeFileInfo' repoLayoutMirrors+ tarInfo <- computeFileInfo' repoLayoutIndexTar+ tarGzInfo <- computeFileInfo' repoLayoutIndexTarGz+ let snapshot = Snapshot {+ snapshotVersion = versionInitial+ , snapshotExpires = expiresInDays now 3+ , snapshotInfoRoot = rootInfo+ , snapshotInfoMirrors = mirrorsInfo+ , snapshotInfoTar = Just tarInfo+ , snapshotInfoTarGz = tarGzInfo+ }+ updateFile opts+ repoLoc+ whenWrite+ (InRep repoLayoutSnapshot)+ (withSignatures globalRepoLayout (privateSnapshot keys))+ snapshot++ -- Finally, create the timestamp+ snapshotInfo <- computeFileInfo' repoLayoutSnapshot+ let timestamp = Timestamp {+ timestampVersion = versionInitial+ , timestampExpires = expiresInDays now 3+ , timestampInfoSnapshot = snapshotInfo+ }+ updateFile opts+ repoLoc+ whenWrite+ (InRep repoLayoutTimestamp)+ (withSignatures globalRepoLayout (privateTimestamp keys))+ timestamp+ where+ pathIndexTar :: Path Absolute+ pathIndexTar = anchorRepoPath opts repoLoc repoLayoutIndexTar++ -- | Compute file information for a file in the repo+ computeFileInfo' :: (RepoLayout -> RepoPath) -> IO FileInfo+ computeFileInfo' = computeFileInfo . anchorRepoPath opts repoLoc++ prettyRepo :: (RepoLayout -> RepoPath) -> String+ prettyRepo = prettyTargetPath' opts . InRep++-- | Create root metadata+updateRoot :: GlobalOpts+ -> RepoLoc+ -> WhenWrite+ -> PrivateKeys+ -> UTCTime+ -> IO ()+updateRoot opts repoLoc whenWrite keys now =+ updateFile opts+ repoLoc+ whenWrite+ (InRep repoLayoutRoot)+ (withSignatures' (privateRoot keys))+ root+ where+ root :: Root+ root = Root {+ rootVersion = versionInitial+ , rootExpires = expiresInDays now (globalExpireRoot opts * 365)+ , rootKeys = KeyEnv.fromKeys $ concat [+ privateRoot keys+ , privateTarget keys+ , privateSnapshot keys+ , privateTimestamp keys+ , privateMirrors keys+ ]+ , rootRoles = RootRoles {+ rootRolesRoot = RoleSpec {+ roleSpecKeys = map somePublicKey (privateRoot keys)+ , roleSpecThreshold = KeyThreshold 2+ }+ , rootRolesTargets = RoleSpec {+ roleSpecKeys = map somePublicKey (privateTarget keys)+ , roleSpecThreshold = KeyThreshold 1+ }+ , rootRolesSnapshot = RoleSpec {+ roleSpecKeys = map somePublicKey (privateSnapshot keys)+ , roleSpecThreshold = KeyThreshold 1+ }+ , rootRolesTimestamp = RoleSpec {+ roleSpecKeys = map somePublicKey (privateTimestamp keys)+ , roleSpecThreshold = KeyThreshold 1+ }+ , rootRolesMirrors = RoleSpec {+ roleSpecKeys = map somePublicKey (privateMirrors keys)+ , roleSpecThreshold = KeyThreshold 1+ }+ }+ }++-- | Create root metadata+updateMirrors :: GlobalOpts+ -> RepoLoc+ -> WhenWrite+ -> PrivateKeys+ -> UTCTime+ -> [URI]+ -> IO ()+updateMirrors opts repoLoc whenWrite keys now uris =+ updateFile opts+ repoLoc+ whenWrite+ (InRep repoLayoutMirrors)+ (withSignatures' (privateMirrors keys))+ mirrors+ where+ mirrors :: Mirrors+ mirrors = Mirrors {+ mirrorsVersion = versionInitial+ , mirrorsExpires = expiresInDays now (globalExpireMirrors opts * 365)+ , mirrorsMirrors = map mkMirror uris+ }++ mkMirror :: URI -> Mirror+ mkMirror uri = Mirror uri MirrorFull++-- | Create package metadata+createPackageMetadata :: GlobalOpts -> RepoLoc -> WhenWrite -> PackageIdentifier -> IO ()+createPackageMetadata opts repoLoc whenWrite pkgId = do+ srcTS <- getFileModTime opts repoLoc src+ dstTS <- getFileModTime opts repoLoc dst+ let skip = case whenWrite of+ WriteInitial -> False+ WriteUpdate -> dstTS >= srcTS++ if skip+ then logInfo opts $ "Skipping " ++ prettyTargetPath' opts dst+ else do+ fileMapEntries <- mapM computeFileMapEntry fileMapFiles+ let targets = Targets {+ targetsVersion = versionInitial+ , targetsExpires = expiresNever+ , targetsTargets = FileMap.fromList fileMapEntries+ , targetsDelegations = Nothing+ }++ -- Currently we "sign" with no keys+ updateFile opts+ repoLoc+ whenWrite+ dst+ (withSignatures' [])+ targets+ where+ computeFileMapEntry :: TargetPath' -> IO (TargetPath, FileInfo)+ computeFileMapEntry file = do+ info <- computeFileInfo $ anchorTargetPath' opts repoLoc file+ return (applyTargetPath' opts file, info)++ -- The files we need to add to the package targets file+ -- Currently this is just the .tar.gz file+ fileMapFiles :: [TargetPath']+ fileMapFiles = [src]++ src, dst :: TargetPath'+ src = InRepPkg repoLayoutPkgTarGz pkgId+ dst = InIdxPkg indexLayoutPkgMetadata pkgId++{-------------------------------------------------------------------------------+ Working with the index+-------------------------------------------------------------------------------}++-- | Find the files we need to add to the index+findNewIndexFiles :: GlobalOpts -> RepoLoc -> WhenWrite -> IO [IndexPath]+findNewIndexFiles opts@GlobalOpts{..} repoLoc whenWrite = do+ indexTS <- getFileModTime opts repoLoc (InRep repoLayoutIndexTar)+ indexFiles <- getRecursiveContents absIndexDir++ let indexFiles' :: [IndexPath]+ indexFiles' = map rootPath indexFiles++ case whenWrite of+ WriteInitial -> return indexFiles'+ WriteUpdate -> liftM catMaybes $+ forM indexFiles' $ \indexFile -> do+ fileTS <- getFileModTime opts repoLoc $ InIdx (const indexFile)+ if fileTS > indexTS then return $ Just indexFile+ else return Nothing+ where+ absIndexDir :: Path Absolute+ absIndexDir = anchorRepoPath opts repoLoc repoLayoutIndexDir++-- | Extract the cabal file from the package tarball and copy it to the index+extractCabalFile :: GlobalOpts -> RepoLoc -> WhenWrite -> PackageIdentifier -> IO ()+extractCabalFile opts@GlobalOpts{..} repoLoc whenWrite pkgId = do+ srcTS <- getFileModTime opts repoLoc src+ dstTS <- getFileModTime opts repoLoc dst+ let skip = case whenWrite of+ WriteInitial -> False+ WriteUpdate -> dstTS >= srcTS+ if skip+ then logInfo opts $ "Skipping " ++ prettyTargetPath' opts dst+ else do+ mCabalFile <- try $ tarExtractFile opts repoLoc src pathCabalInTar+ case mCabalFile of+ Left (ex :: SomeException) ->+ logWarn opts $ "Failed to extract .cabal from package " ++ display pkgId+ ++ ": " ++ displayException ex+ Right Nothing ->+ logWarn opts $ ".cabal file missing for package " ++ display pkgId+ Right (Just (cabalFile, _cabalSize)) -> do+ logInfo opts $ "Writing "+ ++ prettyTargetPath' opts dst+ ++ " (extracted from "+ ++ prettyTargetPath' opts src+ ++ ")"+ withFile pathCabalInIdx WriteMode $ \h -> BS.L.hPut h cabalFile+ where+ pathCabalInTar :: FilePath+ pathCabalInTar = FilePath.joinPath [+ display pkgId+ , display (packageName pkgId)+ ] FilePath.<.> "cabal"++ pathCabalInIdx :: Path Absolute+ pathCabalInIdx = anchorTargetPath' opts repoLoc dst++ src, dst :: TargetPath'+ dst = InIdxPkg indexLayoutPkgCabal pkgId+ src = InRepPkg repoLayoutPkgTarGz pkgId++{-------------------------------------------------------------------------------+ Updating files in the repo or in the index+-------------------------------------------------------------------------------}++data WhenWrite =+ -- | Write the initial version of a file+ --+ -- If applicable, set file version to 1.+ WriteInitial++ -- | Update an existing+ --+ -- If applicable, increment file version number.+ | WriteUpdate++-- | Write canonical JSON+--+-- We write the file to a temporary location and compare file info with the file+-- that was already in the target location (if any). If it's the same (modulo+-- version number) we don't overwrite it and return Nothing; otherwise we+-- increment the version number, write the file, and (if it's in the index)+-- copy it to the unpacked index directory.+updateFile :: forall a. (ToJSON WriteJSON (Signed a), HasHeader a)+ => GlobalOpts+ -> RepoLoc+ -> WhenWrite+ -> TargetPath'+ -> (a -> Signed a) -- ^ Signing function+ -> a -- ^ Unsigned file contents+ -> IO ()+updateFile opts@GlobalOpts{..} repoLoc whenWrite fileLoc signPayload a = do+ mOldHeader :: Maybe (Either DeserializationError (UninterpretedSignatures Header)) <-+ handleDoesNotExist $ readJSON_NoKeys_NoLayout fp++ case (whenWrite, mOldHeader) of+ (WriteInitial, _) ->+ writeDoc writing a+ (WriteUpdate, Nothing) -> -- no previous version+ writeDoc creating a+ (WriteUpdate, Just (Left _err)) -> -- old file corrupted+ writeDoc overwriting a+ (WriteUpdate, Just (Right (UninterpretedSignatures oldHeader _oldSigs))) -> do+ -- We cannot quite read the entire old file, because we don't know what+ -- key environment to use. Instead, we render the _new_ file, but+ -- setting the version number to be equal to the version number of the+ -- old file. If the result turns out to be equal to the old file (same+ -- FileInfo), we skip writing this file. However, if this is NOT equal,+ -- we set the version number of the new file to be equal to the version+ -- number of the old plus one, and write it.+ oldFileInfo <- computeFileInfo fp++ let oldVersion :: FileVersion+ oldVersion = headerVersion oldHeader++ wOldVersion, wIncVersion :: a+ wOldVersion = Lens.set fileVersion oldVersion a+ wIncVersion = Lens.set fileVersion (versionIncrement oldVersion) a++ wOldSigned :: Signed a+ wOldSigned = signPayload wOldVersion++ wOldRendered :: BS.L.ByteString+ wOldRendered = renderJSON globalRepoLayout wOldSigned++ -- TODO: We could be be more efficient here and verify file size+ -- first; however, these files are tiny so it doesn't really matter.+ wOldFileInfo :: FileInfo+ wOldFileInfo = fileInfo wOldRendered++ if knownFileInfoEqual oldFileInfo wOldFileInfo+ then logInfo opts $ "Unchanged " ++ prettyTargetPath' opts fileLoc+ else writeDoc updating wIncVersion+ where+ -- | Actually write the file+ writeDoc :: String -> a -> IO ()+ writeDoc reason doc = do+ logInfo opts reason+ createDirectoryIfMissing True (takeDirectory fp)+ writeJSON globalRepoLayout fp (signPayload doc)++ fp :: Path Absolute+ fp = anchorTargetPath' opts repoLoc fileLoc++ writing, creating, overwriting, updating :: String+ writing = "Writing " ++ prettyTargetPath' opts fileLoc+ creating = "Creating " ++ prettyTargetPath' opts fileLoc+ overwriting = "Overwriting " ++ prettyTargetPath' opts fileLoc ++ " (old file corrupted)"+ updating = "Updating " ++ prettyTargetPath' opts fileLoc++{-------------------------------------------------------------------------------+ Inspect the repo layout+-------------------------------------------------------------------------------}++-- | Find packages+--+-- Repository layouts are configurable, but we don't know if the layout of the+-- current directory matches the specified layout. We therefore here just search+-- through the directory looking for anything that looks like a package.+-- We can then verify that this list of packages actually matches the layout as+-- a separate step.+findPackages :: GlobalOpts -> RepoLoc -> IO [PackageIdentifier]+findPackages GlobalOpts{..} (RepoLoc repoLoc) =+ nub . mapMaybe isPackage <$> getRecursiveContents repoLoc+ where+ isPackage :: Path Unrooted -> Maybe PackageIdentifier+ isPackage path = do+ guard $ not (isIndex path)+ pkg <- hasExtensions path [".tar", ".gz"]+ simpleParse pkg++ isIndex :: Path Unrooted -> Bool+ isIndex = (==) (unrootPath (repoLayoutIndexTarGz globalRepoLayout))++-- | Check that packages are in their expected location+checkRepoLayout :: GlobalOpts -> RepoLoc -> [PackageIdentifier] -> IO Bool+checkRepoLayout opts repoLoc = liftM and . mapM checkPackage+ where+ checkPackage :: PackageIdentifier -> IO Bool+ checkPackage pkgId = do+ existsTarGz <- doesFileExist $ anchorTargetPath' opts repoLoc expectedTarGz+ unless existsTarGz $+ logWarn opts $ "Package tarball " ++ display pkgId+ ++ " expected in location "+ ++ prettyTargetPath' opts expectedTarGz++ return existsTarGz+ where+ expectedTarGz :: TargetPath'+ expectedTarGz = InRepPkg repoLayoutPkgTarGz pkgId++{-------------------------------------------------------------------------------+ Creating Cabal-local-repo+-------------------------------------------------------------------------------}++symlinkCabalLocalRepo :: GlobalOpts -> RepoLoc -> RepoLoc -> IO ()+symlinkCabalLocalRepo opts@GlobalOpts{..} repoLoc cabalRepoLoc = do+ symlink repoLayoutIndexTar+ pkgs <- findPackages opts repoLoc+ forM_ pkgs $ \pkgId -> symlink (`repoLayoutPkgTarGz` pkgId)+ where+ -- TODO: This gives a warning for files that we previously linked, as well+ -- as for files that we _never_ need to link (because the location of both+ -- repos is the same). This is potentially confusing.+ symlink :: (RepoLayout -> RepoPath) -> IO ()+ symlink file =+ catch (createSymbolicLink target loc) $ \ex ->+ if isAlreadyExistsError ex+ then logWarn opts $ "Skipping " ++ pretty (file globalRepoLayout)+ ++ " (already exists)"+ else throwIO ex+ where+ target = anchorRepoPath opts repoLoc file+ loc = anchorRepoPath opts' cabalRepoLoc file+ opts' = opts { globalRepoLayout = cabalLocalRepoLayout }++{-------------------------------------------------------------------------------+ Signing individual files+-------------------------------------------------------------------------------}++signFile :: [KeyLoc] -> DeleteExistingSignatures -> Path Absolute -> IO ()+signFile keyLocs deleteExisting fp = do+ UninterpretedSignatures (payload :: JSValue) oldSigs <-+ throwErrors =<< readJSON_NoKeys_NoLayout fp+ keys :: [Some Key] <- forM keyLocs $ \keyLoc ->+ throwErrors =<< readJSON_NoKeys_NoLayout keyLoc+ let newSigs = concat [+ if deleteExisting then [] else oldSigs+ , toPreSignatures (signRendered keys $ renderJSON_NoLayout payload)+ ]+ writeJSON_NoLayout fp $ UninterpretedSignatures payload newSigs++{-------------------------------------------------------------------------------+ Logging+-------------------------------------------------------------------------------}++logInfo :: GlobalOpts -> String -> IO ()+logInfo GlobalOpts{..} str = when globalVerbose $+ putStrLn $ "Info: " ++ str++logWarn :: GlobalOpts -> String -> IO ()+logWarn _opts str =+ putStrLn $ "Warning: " ++ str++{-------------------------------------------------------------------------------+ Auxiliary+-------------------------------------------------------------------------------}++-- | Check that a file has the given extensions+--+-- Returns the filename without the verified extensions. For example:+--+-- > hasExtensions "foo.tar.gz" [".tar", ".gz"] == Just "foo"+hasExtensions :: Path a -> [String] -> Maybe String+hasExtensions = \fp exts -> go (takeFileName fp) (reverse exts)+ where+ go :: FilePath -> [String] -> Maybe String+ go fp [] = return fp+ go fp (e:es) = do let (fp', e') = FilePath.splitExtension fp+ guard $ e == e'+ go fp' es++throwErrors :: Exception e => Either e a -> IO a+throwErrors (Left err) = throwIO err+throwErrors (Right a) = return a
+ hackage-security/hackage-repo-tool/src/Prelude.hs view
@@ -0,0 +1,27 @@+-- | Smooth over differences between various ghc versions by making older+-- preludes look like 4.8.0+{-# LANGUAGE PackageImports #-}+{-# LANGUAGE CPP #-}+module Prelude (+ module P+#if !MIN_VERSION_base(4,8,0)+ , Applicative(..)+ , Monoid(..)+ , (<$>)+ , (<$)+ , traverse+#endif+ ) where++#if MIN_VERSION_base(4,8,0)+import "base" Prelude as P+#else+#if MIN_VERSION_base(4,6,0)+import "base" Prelude as P+#else+import "base" Prelude as P hiding (catch)+#endif+import Control.Applicative+import Data.Monoid+import Data.Traversable (traverse)+#endif
+ hackage-security/hackage-root-tool/ChangeLog.md view
@@ -0,0 +1,4 @@+0.1.0.0+-------+* Initial hack, just to make it possible to create root keys+ and re-sign root info.
+ hackage-security/hackage-root-tool/LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2015, Well-Typed LLP++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Well-Typed LLP nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ hackage-security/hackage-root-tool/Main.hs view
@@ -0,0 +1,159 @@+{-# LANGUAGE CPP, ScopedTypeVariables, RecordWildCards #-}+module Main (main) where++import Data.Monoid+import Control.Exception+import GHC.Conc.Sync (setUncaughtExceptionHandler)+import System.Exit++-- hackage-security+import Hackage.Security.Server+import Hackage.Security.Util.Some+import Hackage.Security.Util.Path+import Text.JSON.Canonical (JSValue)++import Options.Applicative+++{-------------------------------------------------------------------------------+ Main application driver+-------------------------------------------------------------------------------}++main :: IO ()+main = do+ setUncaughtExceptionHandler topLevelExceptionHandler+ GlobalOpts{..} <- getOptions+ case globalCommand of+ CreateKeys ->+ createKeys++ Sign key file -> do+ key' <- makeAbsolute (fromFilePath key)+ file' <- makeAbsolute (fromFilePath file)+ signFile key' file'++-- | Top-level exception handler that uses 'displayException'+--+-- Although base 4.8 introduces 'displayException', the top-level exception+-- handler still uses 'show', sadly. See "PROPOSAL: Add displayException to+-- Exception typeclass" thread on the libraries mailing list.+--+-- NOTE: This is a terrible hack. See the above thread for some insights into+-- how we should do this better. For now it will do however.+topLevelExceptionHandler :: SomeException -> IO ()+topLevelExceptionHandler e = do+ putStrLn $ displayException e+ exitFailure++#if !MIN_VERSION_base(4,8,0)+displayException :: Exception e => e -> String+displayException = show+#endif++{-------------------------------------------------------------------------------+ Creating keys+-------------------------------------------------------------------------------}++createKeys :: IO ()+createKeys = do+ privateRoot <- createKey' KeyTypeEd25519+ writeKey privateRoot++{-------------------------------------------------------------------------------+ Dealing with (private) keys+-------------------------------------------------------------------------------}++writeKey :: Some Key -> IO ()+writeKey keypair = do+ let keypath = keyIdString (someKeyId keypair)+ keypathabs <- makeAbsolute (fromFilePath keypath)++ logInfo $ "Writing new key:\n " ++ keypath ++ ".{private,public}"++ writeJSON_NoLayout (keypathabs <.> "private") keypair+ writeJSON_NoLayout (keypathabs <.> "public") keypublic+ where+ keypublic :: Some PublicKey+ keypublic = somePublicKey keypair++{-------------------------------------------------------------------------------+ Signing individual files+-------------------------------------------------------------------------------}++signFile :: KeyLoc -> Path Absolute -> IO ()+signFile keyLoc fp = do+ UninterpretedSignatures (payload :: JSValue) _oldSigs <-+ throwErrors =<< readJSON_NoKeys_NoLayout fp+ key :: Some Key <-+ throwErrors =<< readJSON_NoKeys_NoLayout keyLoc+ let newSig = toPreSignatures (signRendered [key]+ (renderJSON_NoLayout payload))+ writeJSON_NoLayout (fp <.> "sig") newSig++{-------------------------------------------------------------------------------+ Logging+-------------------------------------------------------------------------------}++logInfo :: String -> IO ()+logInfo msg = putStrLn msg++{-------------------------------------------------------------------------------+ Auxiliary+-------------------------------------------------------------------------------}++throwErrors :: Exception e => Either e a -> IO a+throwErrors (Left err) = throwIO err+throwErrors (Right a) = return a++{-------------------------------------------------------------------------------+ Types+-------------------------------------------------------------------------------}++-- | Command line options+data GlobalOpts = GlobalOpts {++ -- | Command to execute+ globalCommand :: Command+ }++data Command =+ -- | Create keys+ CreateKeys++ -- | Sign an individual file+ | Sign FilePath FilePath++type KeyLoc = Path Absolute++{-------------------------------------------------------------------------------+ Parsers+-------------------------------------------------------------------------------}++getOptions :: IO GlobalOpts+getOptions = execParser opts+ where+ opts = info (helper <*> parseGlobalOptions) $ mconcat [+ fullDesc+ , header "Manage local Hackage repositories"+ ]++parseCreateKeys :: Parser Command+parseCreateKeys = pure CreateKeys++parseSign :: Parser Command+parseSign = Sign+ <$> argument str (metavar "KEY")+ <*> argument str (metavar "FILE")++-- | Global options+--+-- TODO: Make repo and keys layout configurable+parseGlobalOptions :: Parser GlobalOpts+parseGlobalOptions =+ GlobalOpts+ <$> (subparser $ mconcat [+ command "create-key" $ info (helper <*> parseCreateKeys) $+ progDesc "Create keys"+ , command "sign" $ info (helper <*> parseSign) $+ progDesc "Sign a file"+ ])
+ hackage-security/hackage-root-tool/Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ hackage-security/hackage-root-tool/hackage-root-tool.cabal view
@@ -0,0 +1,34 @@+name: hackage-root-tool+version: 0.1.0.0+synopsis: Utility for Hackage key holders to generate keys and sign root info.+description: A command line tool for people who hold Hackage root keys.+ It can generate new keys, and can sign root information.+ .+ This is part of the Hackage Security infrastructure.+homepage: http://github.com/well-typed/hackage-security/+license: BSD3+license-file: LICENSE+author: Duncan Coutts, Edsko de Vries+maintainer: duncan@well-typed.com+copyright: Copyright 2015 Well-Typed LLP+category: Distribution+build-type: Simple+cabal-version: >=1.10++extra-source-files:+ ChangeLog.md++source-repository head+ type: git+ location: http://github.com/well-typed/hackage-security+ subdir: hackage-root-tool++executable hackage-root-tool+ main-is: Main.hs+ build-depends: base >= 4.4 && < 5,+ filepath >= 1.2 && < 1.5,+ optparse-applicative >= 0.11 && < 0.13,+ hackage-security >= 0.2 && < 0.6+ default-language: Haskell2010+ other-extensions: CPP, ScopedTypeVariables, RecordWildCards+ ghc-options: -Wall
+ hackage-security/hackage-security-HTTP/ChangeLog.md view
@@ -0,0 +1,17 @@+0.1.1+-----+* Implement updated HttpLib API from hackage-security 0.5++0.1.0.2+-------+* Allow for hackage-security 0.3.*+* Include ChangeLog.md in sdist (#98)++0.1.0.1+-------+* Allow for hackage-security 0.2.*+* Allow for network-2.5 (rather than network-uri-2.6.*)++0.1.0.0+-------+* Initial beta release
+ hackage-security/hackage-security-HTTP/LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2015, Well-Typed LLP++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Well-Typed LLP nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ hackage-security/hackage-security-HTTP/Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ hackage-security/hackage-security-HTTP/hackage-security-HTTP.cabal view
@@ -0,0 +1,55 @@+name: hackage-security-HTTP+version: 0.1.1+synopsis: Hackage security bindings against the HTTP library+description: The hackage security library provides a 'HttpLib'+ abstraction to allow to bind against different HTTP+ libraries. This library implements this abstraction using+ the @HTTP@ library.+homepage: http://github.com/well-typed/hackage-security/+license: BSD3+license-file: LICENSE+author: Edsko de Vries+maintainer: edsko@well-typed.com+copyright: Copyright 2015 Well-Typed LLP+category: Distribution+homepage: https://github.com/well-typed/hackage-security+bug-reports: https://github.com/well-typed/hackage-security/issues+build-type: Simple+cabal-version: >=1.10++extra-source-files:+ ChangeLog.md++source-repository head+ type: git+ location: https://github.com/well-typed/hackage-security.git++flag use-network-uri+ description: Are we using network-uri?+ manual: False++library+ exposed-modules: Hackage.Security.Client.Repository.HttpLib.HTTP+ build-depends: base >= 4.4 && < 5,+ bytestring >= 0.9 && < 0.11,+ HTTP >= 4000.2.19 && < 4000.4,+ mtl >= 2.2 && < 2.3,+ zlib >= 0.5 && < 0.7,+ hackage-security >= 0.5 && < 0.6+ hs-source-dirs: src+ default-language: Haskell2010+ default-extensions: DeriveDataTypeable+ FlexibleContexts+ RankNTypes+ RecordWildCards+ ScopedTypeVariables+ StandaloneDeriving+ other-extensions: CPP+ ghc-options: -Wall++ -- See comments in hackage-security.cabal+ if flag(use-network-uri)+ build-depends: network-uri >= 2.6 && < 2.7,+ network >= 2.6 && < 2.7+ else+ build-depends: network >= 2.5 && < 2.6
+ hackage-security/hackage-security-HTTP/src/Hackage/Security/Client/Repository/HttpLib/HTTP.hs view
@@ -0,0 +1,274 @@+{-# LANGUAGE CPP #-}+-- | Implementation of 'HttpClient' using the HTTP package+module Hackage.Security.Client.Repository.HttpLib.HTTP (+ withClient+ -- ** Additional operations+ , setOutHandler+ , setErrHandler+ , setProxy+ , request+ -- ** Low-level API+ , Browser -- opaque+ , withBrowser+ -- * Exception types+ , UnexpectedResponse(..)+ , InvalidProxy(..)+ ) where++import Control.Concurrent+import Control.Exception+import Control.Monad+import Data.List (intercalate)+import Data.Typeable (Typeable)+import Network.URI+import qualified Data.ByteString.Lazy as BS.L+import qualified Control.Monad.State as State+import qualified Network.Browser as HTTP+import qualified Network.HTTP as HTTP+import qualified Network.HTTP.Proxy as HTTP++import Hackage.Security.Client+import Hackage.Security.Client.Repository.HttpLib+import Hackage.Security.Util.Checked+import Hackage.Security.Util.Pretty+import qualified Hackage.Security.Util.Lens as Lens++{-------------------------------------------------------------------------------+ Top-level API+-------------------------------------------------------------------------------}++-- | Initialize the client+--+-- TODO: This currently uses the lazy bytestring API offered by the HTTP+-- library. Unfortunately this provides no way of closing the connection when+-- the callback decides it doens't require any further input. It seems+-- impossible however to implement a proper streaming API.+-- See <https://github.com/haskell/HTTP/issues/86>.+withClient :: (Browser -> HttpLib -> IO a) -> IO a+withClient callback =+ bracket browserInit browserCleanup $ \browser ->+ callback browser HttpLib {+ httpGet = get browser+ , httpGetRange = getRange browser+ }++{-------------------------------------------------------------------------------+ Individual methods+-------------------------------------------------------------------------------}++get :: Throws SomeRemoteError+ => Browser+ -> [HttpRequestHeader] -> URI+ -> ([HttpResponseHeader] -> BodyReader -> IO a)+ -> IO a+get browser reqHeaders uri callback = wrapCustomEx $ do+ response <- request browser+ $ setRequestHeaders reqHeaders+ -- avoid silly `Content-Length: 0` header inserted by `mkRequest`+ $ removeHeader HTTP.HdrContentLength+ $ HTTP.mkRequest HTTP.GET uri+ case HTTP.rspCode response of+ (2, 0, 0) -> withResponse response callback+ otherCode -> throwChecked $ UnexpectedResponse uri otherCode++getRange :: Throws SomeRemoteError+ => Browser+ -> [HttpRequestHeader] -> URI -> (Int, Int)+ -> (HttpStatus -> [HttpResponseHeader] -> BodyReader -> IO a)+ -> IO a+getRange browser reqHeaders uri (from, to) callback = wrapCustomEx $ do+ response <- request browser+ $ setRange from to+ $ setRequestHeaders reqHeaders+ -- avoid silly `Content-Length: 0` header inserted by `mkRequest`+ $ removeHeader HTTP.HdrContentLength+ $ HTTP.mkRequest HTTP.GET uri+ case HTTP.rspCode response of+ (2, 0, 0) -> withResponse response $ callback HttpStatus200OK+ (2, 0, 6) -> withResponse response $ callback HttpStatus206PartialContent+ otherCode -> throwChecked $ UnexpectedResponse uri otherCode++removeHeader :: HTTP.HasHeaders a => HTTP.HeaderName -> a -> a+removeHeader name h = HTTP.setHeaders h newHeaders+ where+ newHeaders = [ x | x@(HTTP.Header n _) <- HTTP.getHeaders h, name /= n ]++{-------------------------------------------------------------------------------+ Auxiliary methods used to implement the HttpClient interface+-------------------------------------------------------------------------------}++withResponse :: Throws SomeRemoteError+ => HTTP.Response BS.L.ByteString+ -> ([HttpResponseHeader] -> BodyReader -> IO a)+ -> IO a+withResponse response callback = wrapCustomEx $ do+ br <- bodyReaderFromBS $ HTTP.rspBody response+ callback responseHeaders $ wrapCustomEx br+ where+ responseHeaders = getResponseHeaders response++{-------------------------------------------------------------------------------+ Custom exception types+-------------------------------------------------------------------------------}++wrapCustomEx :: ( ( Throws UnexpectedResponse+ , Throws IOException+ ) => IO a)+ -> (Throws SomeRemoteError => IO a)+wrapCustomEx act = handleChecked (\(ex :: UnexpectedResponse) -> go ex)+ $ handleChecked (\(ex :: IOException) -> go ex)+ $ act+ where+ go ex = throwChecked (SomeRemoteError ex)++data UnexpectedResponse = UnexpectedResponse URI (Int, Int, Int)+ deriving (Typeable)++data InvalidProxy = InvalidProxy String+ deriving (Typeable)++instance Pretty UnexpectedResponse where+ pretty (UnexpectedResponse uri code) = "Unexpected response " ++ show code+ ++ "for " ++ show uri++instance Pretty InvalidProxy where+ pretty (InvalidProxy p) = "Invalid proxy " ++ show p++#if MIN_VERSION_base(4,8,0)+deriving instance Show UnexpectedResponse+deriving instance Show InvalidProxy+instance Exception UnexpectedResponse where displayException = pretty+instance Exception InvalidProxy where displayException = pretty+#else+instance Show UnexpectedResponse where show = pretty+instance Show InvalidProxy where show = pretty+instance Exception UnexpectedResponse+instance Exception InvalidProxy+#endif++{-------------------------------------------------------------------------------+ Additional operations+-------------------------------------------------------------------------------}++setProxy :: Browser -> ProxyConfig String -> IO ()+setProxy browser proxyConfig = do+ proxy <- case proxyConfig of+ ProxyConfigNone -> return HTTP.NoProxy+ ProxyConfigAuto -> HTTP.fetchProxy True+ ProxyConfigUse p -> case HTTP.parseProxy p of+ Nothing -> throwUnchecked $ InvalidProxy p+ Just p' -> return p'+ withBrowser browser $ HTTP.setProxy (emptyAsNone proxy)+ where+ emptyAsNone :: HTTP.Proxy -> HTTP.Proxy+ emptyAsNone (HTTP.Proxy uri _) | null uri = HTTP.NoProxy+ emptyAsNone p = p++setOutHandler :: Browser -> (String -> IO ()) -> IO ()+setOutHandler browser = withBrowser browser . HTTP.setOutHandler++setErrHandler :: Browser -> (String -> IO ()) -> IO ()+setErrHandler browser = withBrowser browser . HTTP.setErrHandler++-- | Execute a single request+request :: Throws IOException+ => Browser+ -> HTTP.Request BS.L.ByteString+ -> IO (HTTP.Response BS.L.ByteString)+request browser = checkIO . liftM snd . withBrowser browser . HTTP.request++{-------------------------------------------------------------------------------+ Browser state+-------------------------------------------------------------------------------}++type LazyStream = HTTP.HandleStream BS.L.ByteString++data Browser = Browser {+ browserState :: MVar (HTTP.BrowserState LazyStream)+ }++-- | Run a browser action+--+-- IMPLEMENTATION NOTE: the 'browse' action doesn't itself create any+-- connections, they are created on demand; we just need to make sure to carry+-- this state from one invocation of 'browse' to another.+withBrowser :: forall a. Browser -> HTTP.BrowserAction LazyStream a -> IO a+withBrowser Browser{..} act = modifyMVar browserState $ \bst -> HTTP.browse $ do+ State.put bst+ result <- act+ bst' <- State.get+ return (bst', result)++-- | Initial browser state+--+-- Throws an 'InvalidProxy' exception if the proxy definition is invalid.+--+-- TODO: If the proxy configuration is automatic, the _only_ way that we can+-- find out from the @HTTP@ library is to pass @True@ as the argument to+-- 'fetchProxy'; but this prints to standard error when the proxy is invalid,+-- rather than throwing an exception :-O+browserInit :: IO Browser+browserInit = do+ browserState <- newMVar =<< HTTP.browse State.get+ return Browser{..}++-- | Cleanup browser state+--+-- NOTE: Calling 'withBrowser' after 'browserCleanup' will result in deadlock.+--+-- IMPLEMENTATION NOTE: "HTTP" does not provide any explicit API for resource+-- cleanup, so we can only rely on the garbage collector to do for us.+browserCleanup :: Browser -> IO ()+browserCleanup Browser{..} = void $ takeMVar browserState++{-------------------------------------------------------------------------------+ HTTP auxiliary+-------------------------------------------------------------------------------}++hAcceptRanges :: HTTP.HeaderName+hAcceptRanges = HTTP.HdrCustom "Accept-Ranges"++setRange :: HTTP.HasHeaders a => Int -> Int -> a -> a+setRange from to = HTTP.insertHeader HTTP.HdrRange rangeHeader+ where+ -- Content-Range header uses inclusive rather than exclusive bounds+ -- See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html>+ rangeHeader = "bytes=" ++ show from ++ "-" ++ show (to - 1)++setRequestHeaders :: HTTP.HasHeaders a => [HttpRequestHeader] -> a -> a+setRequestHeaders =+ foldr (.) id . map (uncurry HTTP.insertHeader) . trOpt []+ where+ trOpt :: [(HTTP.HeaderName, [String])]+ -> [HttpRequestHeader]+ -> [(HTTP.HeaderName, String)]+ trOpt acc [] =+ concatMap finalizeHeader acc+ trOpt acc (HttpRequestMaxAge0:os) =+ trOpt (insert HTTP.HdrCacheControl ["max-age=0"] acc) os+ trOpt acc (HttpRequestNoTransform:os) =+ trOpt (insert HTTP.HdrCacheControl ["no-transform"] acc) os++ -- Some headers are comma-separated, others need multiple headers for+ -- multiple options.+ --+ -- TODO: Right we we just comma-separate all of them.+ finalizeHeader :: (HTTP.HeaderName, [String]) -> [(HTTP.HeaderName, String)]+ finalizeHeader (name, strs) = [(name, intercalate ", " (reverse strs))]++ insert :: Eq a => a -> [b] -> [(a, [b])] -> [(a, [b])]+ insert x y = Lens.modify (Lens.lookupM x) (++ y)++getResponseHeaders :: HTTP.Response a -> [HttpResponseHeader]+getResponseHeaders response = concat [+ -- Check the @Accept-Ranges@ header.+ --+ -- @Accept-Ranges@ takes a _single_ argument, but there might potentially+ -- be more than one of them (although the spec does not explicitly say so).++ -- See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.5>+ -- and <http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.12>+ [ HttpResponseAcceptRangesBytes+ | "bytes" `elem` map HTTP.hdrValue (HTTP.retrieveHeaders hAcceptRanges response)+ ]+ ]
+ hackage-security/hackage-security-curl/LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2015, Well-Typed LLP++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Well-Typed LLP nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ hackage-security/hackage-security-curl/Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ hackage-security/hackage-security-curl/hackage-security-curl.cabal view
@@ -0,0 +1,33 @@+name: hackage-security-curl+version: 0.1.0.0+synopsis: Hackage security bindings against curl (and other external downloaders)+homepage: http://github.com/well-typed/hackage-security/+license: BSD3+license-file: LICENSE+author: Edsko de Vries+maintainer: edsko@well-typed.com+copyright: Copyright 2015 Well-Typed LLP+category: Distribution+build-type: Simple+cabal-version: >=1.10++flag use-network-uri+ description: Are we using network-uri?+ manual: False++library+ exposed-modules: Hackage.Security.Client.Repository.HttpLib.Curl+ build-depends: base >= 4.4,+ bytestring >= 0.9,+ process >= 1.1,+ hackage-security+ hs-source-dirs: src+ default-language: Haskell2010+ ghc-options: -Wall++ -- See comments in hackage-security.cabal+ if flag(use-network-uri)+ build-depends: network-uri >= 2.6 && < 2.7,+ network >= 2.6 && < 2.7+ else+ build-depends: network >= 2.5 && < 2.6
+ hackage-security/hackage-security-curl/src/Hackage/Security/Client/Repository/HttpLib/Curl.hs view
@@ -0,0 +1,70 @@+-- | Implementation of 'HttpClient' using external downloader+module Hackage.Security.Client.Repository.HttpLib.Curl (+ withClient+ ) where++import Control.Monad+import Network.URI+import System.Process+import System.IO+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy.Internal as BS.L++import Hackage.Security.Client.Repository.HttpLib++{-------------------------------------------------------------------------------+ Top-level API+-------------------------------------------------------------------------------}++-- | HttpClient using external 'curl' executable+--+-- This is currently just a proof of concept. With a bit of luck we'll be+-- able to reuse most of https://github.com/haskell/cabal/pull/2613 for a+-- more serious implementation. Current TODOs:+--+-- * Set HTTP request headers in 'get'+-- * Get HTTP response headers in 'get'+-- * Support range requests+-- * Deal with proxy settings+-- * Configure logging+--+-- Probably we will want to add a @Browser@ or @Manager@ like abstraction (see+-- @hackage-security-HTTP@ and @hackage-security-http-client@, respectively)+-- in order to allow to change settings as we go.+withClient :: (HttpLib -> IO a) -> IO a+withClient callback = do+ callback HttpLib {+ httpGet = get+ , httpGetRange = undefined -- TODO: support range requests+ }++{-------------------------------------------------------------------------------+ Implementation of the individual methods+-------------------------------------------------------------------------------}++get :: [HttpRequestHeader] -> URI+ -> ([HttpResponseHeader] -> BodyReader -> IO a)+ -> IO a+get _httpOpts uri callback = do+ (Nothing, Just hOut, Nothing, hProc) <- createProcess curl+ callback [] (bodyReader hProc hOut)+ where+ curl :: CreateProcess+ curl = (proc "curl" [show uri]) { std_out = CreatePipe }++{-------------------------------------------------------------------------------+ Auxiliary+-------------------------------------------------------------------------------}++-- | Construct a body reader for a process+--+-- TODO: We should also deal with stderr+-- TODO: Perhaps this should be in the main library, alongside bodyReaderFromBS.+bodyReader :: ProcessHandle -> Handle -> BodyReader+bodyReader hProc hOut = do+ chunk <- BS.hGetSome hOut BS.L.smallChunkSize+ when (BS.null chunk) $ do+ _exitCode <- waitForProcess hProc+ -- TODO: Check for successful termination+ return ()+ return chunk
+ hackage-security/hackage-security-http-client/ChangeLog.md view
@@ -0,0 +1,7 @@+0.1.1+-----+* Implement updated HttpLib API from hackage-security 0.5++0.1.0.0+-------+* Initial beta release
+ hackage-security/hackage-security-http-client/LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2015, Well-Typed LLP++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Well-Typed LLP nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ hackage-security/hackage-security-http-client/Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ hackage-security/hackage-security-http-client/hackage-security-http-client.cabal view
@@ -0,0 +1,39 @@+name: hackage-security-http-client+version: 0.1.1+synopsis: Hackage security bindings for the http-client library+homepage: http://github.com/well-typed/hackage-security/+license: BSD3+license-file: LICENSE+author: Edsko de Vries+maintainer: edsko@well-typed.com+copyright: Copyright 2015 Well-Typed LLP+category: Distribution+build-type: Simple+cabal-version: >=1.10++flag use-network-uri+ description: Are we using network-uri?+ manual: False++library+ exposed-modules: Hackage.Security.Client.Repository.HttpLib.HttpClient+ build-depends: base >= 4.4,+ bytestring >= 0.9,+ data-default-class >= 0.0,+ http-client >= 0.4,+ http-types >= 0.8,+ hackage-security >= 0.5 && < 0.6+ hs-source-dirs: src+ default-language: Haskell2010+ default-extensions: FlexibleContexts+ RankNTypes+ ScopedTypeVariables+ other-extensions: OverloadedStrings+ ghc-options: -Wall++ -- see comments in hackage-security.cabal+ if flag(use-network-uri)+ build-depends: network-uri >= 2.6 && < 2.7,+ network >= 2.6 && < 2.7+ else+ build-depends: network >= 2.5 && < 2.6
+ hackage-security/hackage-security-http-client/src/Hackage/Security/Client/Repository/HttpLib/HttpClient.hs view
@@ -0,0 +1,164 @@+{-# LANGUAGE OverloadedStrings #-}+module Hackage.Security.Client.Repository.HttpLib.HttpClient (+ withClient+ -- ** Re-exports+ , Manager -- opaque+ ) where++import Control.Exception+import Data.ByteString (ByteString)+import Data.Default.Class (def)+import Network.URI+import Network.HTTP.Client (Manager)+import qualified Data.ByteString as BS+import qualified Data.ByteString.Char8 as BS.C8+import qualified Network.HTTP.Client as HttpClient+import qualified Network.HTTP.Client.Internal as HttpClient+import qualified Network.HTTP.Types as HttpClient++import Hackage.Security.Client hiding (Header)+import Hackage.Security.Client.Repository.HttpLib+import Hackage.Security.Util.Checked+import qualified Hackage.Security.Util.Lens as Lens++{-------------------------------------------------------------------------------+ Top-level API+-------------------------------------------------------------------------------}++-- | Initialization+--+-- The proxy must be specified at initialization because @http-client@ does not+-- allow to change the proxy once the 'Manager' is created.+withClient :: ProxyConfig HttpClient.Proxy -> (Manager -> HttpLib -> IO a) -> IO a+withClient proxyConfig callback = do+ manager <- HttpClient.newManager (setProxy HttpClient.defaultManagerSettings)+ callback manager HttpLib {+ httpGet = get manager+ , httpGetRange = getRange manager+ }+ where+ setProxy = HttpClient.managerSetProxy $+ case proxyConfig of+ ProxyConfigNone -> HttpClient.noProxy+ ProxyConfigUse p -> HttpClient.useProxy p+ ProxyConfigAuto -> HttpClient.proxyEnvironment Nothing++{-------------------------------------------------------------------------------+ Individual methods+-------------------------------------------------------------------------------}++get :: Throws SomeRemoteError+ => Manager+ -> [HttpRequestHeader] -> URI+ -> ([HttpResponseHeader] -> BodyReader -> IO a)+ -> IO a+get manager reqHeaders uri callback = wrapCustomEx $ do+ -- TODO: setUri fails under certain circumstances; in particular, when+ -- the URI contains URL auth. Not sure if this is a concern.+ request' <- HttpClient.setUri def uri+ let request = setRequestHeaders reqHeaders+ $ request'+ checkHttpException $ HttpClient.withResponse request manager $ \response -> do+ let br = wrapCustomEx $ HttpClient.responseBody response+ callback (getResponseHeaders response) br++getRange :: Throws SomeRemoteError+ => Manager+ -> [HttpRequestHeader] -> URI -> (Int, Int)+ -> (HttpStatus -> [HttpResponseHeader] -> BodyReader -> IO a)+ -> IO a+getRange manager reqHeaders uri (from, to) callback = wrapCustomEx $ do+ request' <- HttpClient.setUri def uri+ let request = setRange from to+ $ setRequestHeaders reqHeaders+ $ request'+ checkHttpException $ HttpClient.withResponse request manager $ \response -> do+ let br = wrapCustomEx $ HttpClient.responseBody response+ case () of+ () | HttpClient.responseStatus response == HttpClient.partialContent206 ->+ callback HttpStatus206PartialContent (getResponseHeaders response) br+ () | HttpClient.responseStatus response == HttpClient.ok200 ->+ callback HttpStatus200OK (getResponseHeaders response) br+ _otherwise ->+ throwChecked $ HttpClient.StatusCodeException+ (HttpClient.responseStatus response)+ (HttpClient.responseHeaders response)+ (HttpClient.responseCookieJar response)++-- | Wrap custom exceptions+--+-- NOTE: The only other exception defined in @http-client@ is @TimeoutTriggered@+-- but it is currently disabled <https://github.com/snoyberg/http-client/issues/116>+wrapCustomEx :: (Throws HttpClient.HttpException => IO a)+ -> (Throws SomeRemoteError => IO a)+wrapCustomEx act = handleChecked (\(ex :: HttpClient.HttpException) -> go ex)+ $ act+ where+ go ex = throwChecked (SomeRemoteError ex)++checkHttpException :: Throws HttpClient.HttpException => IO a -> IO a+checkHttpException = handle $ \(ex :: HttpClient.HttpException) ->+ throwChecked ex++{-------------------------------------------------------------------------------+ http-client auxiliary+-------------------------------------------------------------------------------}++hAcceptRanges :: HttpClient.HeaderName+hAcceptRanges = "Accept-Ranges"++hAcceptEncoding :: HttpClient.HeaderName+hAcceptEncoding = "Accept-Encoding"++setRange :: Int -> Int+ -> HttpClient.Request -> HttpClient.Request+setRange from to req = req {+ HttpClient.requestHeaders = (HttpClient.hRange, rangeHeader)+ : HttpClient.requestHeaders req+ }+ where+ -- Content-Range header uses inclusive rather than exclusive bounds+ -- See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html>+ rangeHeader = BS.C8.pack $ "bytes=" ++ show from ++ "-" ++ show (to - 1)++-- | Set request headers+setRequestHeaders :: [HttpRequestHeader]+ -> HttpClient.Request -> HttpClient.Request+setRequestHeaders opts req = req {+ HttpClient.requestHeaders = trOpt disallowCompressionByDefault opts+ }+ where+ trOpt :: [(HttpClient.HeaderName, [ByteString])]+ -> [HttpRequestHeader]+ -> [HttpClient.Header]+ trOpt acc [] =+ concatMap finalizeHeader acc+ trOpt acc (HttpRequestMaxAge0:os) =+ trOpt (insert HttpClient.hCacheControl ["max-age=0"] acc) os+ trOpt acc (HttpRequestNoTransform:os) =+ trOpt (insert HttpClient.hCacheControl ["no-transform"] acc) os++ -- disable content compression (potential security issue)+ disallowCompressionByDefault :: [(HttpClient.HeaderName, [ByteString])]+ disallowCompressionByDefault = [(hAcceptEncoding, [])]++ -- Some headers are comma-separated, others need multiple headers for+ -- multiple options.+ --+ -- TODO: Right we we just comma-separate all of them.+ finalizeHeader :: (HttpClient.HeaderName, [ByteString])+ -> [HttpClient.Header]+ finalizeHeader (name, strs) = [(name, BS.intercalate ", " (reverse strs))]++ insert :: Eq a => a -> [b] -> [(a, [b])] -> [(a, [b])]+ insert x y = Lens.modify (Lens.lookupM x) (++ y)++-- | Extract the response headers+getResponseHeaders :: HttpClient.Response a -> [HttpResponseHeader]+getResponseHeaders response = concat [+ [ HttpResponseAcceptRangesBytes+ | (hAcceptRanges, "bytes") `elem` headers+ ]+ ]+ where+ headers = HttpClient.responseHeaders response
+ hackage-security/hackage-security/ChangeLog.md view
@@ -0,0 +1,58 @@+0.5.0.2+-------+* Use tar 0.5.0+* Relax lower bound on directory++0.5.0.1+-------+* Relaxed dependency bounds++0.5.0.0+-------+* Treat deserialization errors as verification errors (#108, #75)+* Avoid `Content-Length: 0` in GET requests (#103)+* Fix bug in Trusted+* Build tar-index incrementally (#22)+* Generalize 'Repository' over the representation of downloaded remote files.+* Update index incrementally by downloading delta of `.tar.gz` and writing only+ tail of local `.tar` file (#101). Content compression no longer used.+* Take a lock on the cache directory before updating it, and no longer use+ atomic file ops (pointless since we now update some files incrementally)+* Code refactoring/simplification.+* Support for ed25519 >= 0.0.4+* `downloadPackage` no longer takes a callback.+* API for accessing the Hackage index contents changed; it should now be+ easier for clients to do their own incremental updates should they wish+ to do so.+* Relies on tar >= 0.4.4+* Removed obsolete option for downloading the compressed index (we now _always_+ download the compressed index)+* Path module now works on Windows (#118)+* Dropped support for ghc 7.2+* Replaced uses of Int with Int54, to make sure canonical JSON really is+ canonical (#141).++0.4.0.0+-------+* Allow clients to pass in their own time for expiry verification+ (this is an API change hence the major version bump)+* Export .Client.Formats (necessary to define new Repositories)+* Start work on basic test framework++0.3.0.0+-------+* Don't use compression for range requests (#101)+* Download index.tar.gz, not index.tar, if range request fails (#99)+* Minor change in the LogMessage type (hence the API version bumb)+* Include ChangeLog.md in the tarball (#98)++0.2.0.0+-------+* Allow for network-2.5 (rather than network-uri-2.6)+* Use cryptohash rather than SHA+* Various bugfixes+* API change: introduce RepoOpts in the Remote repository++0.1.0.0+-------+* Initial beta release
+ hackage-security/hackage-security/LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2015, Well-Typed LLP++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Well-Typed LLP nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ hackage-security/hackage-security/Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ hackage-security/hackage-security/hackage-security.cabal view
@@ -0,0 +1,232 @@+name: hackage-security+version: 0.5.0.2+synopsis: Hackage security library+description: The hackage security library provides both server and+ client utilities for securing the Hackage package server+ (<http://hackage.haskell.org/>). It is based on The Update+ Framework (<http://theupdateframework.com/>), a set of+ recommendations developed by security researchers at+ various universities in the US as well as developers on the+ Tor project (<https://www.torproject.org/>).+ .+ The current implementation supports only index signing,+ thereby enabling untrusted mirrors. It does not yet provide+ facilities for author package signing.+ .+ The library has two main entry points:+ "Hackage.Security.Client" is the main entry point for+ clients (the typical example being @cabal@), and+ "Hackage.Security.Server" is the main entry point for+ servers (the typical example being @hackage-server@).+ .+ This is a beta release.+license: BSD3+license-file: LICENSE+author: Edsko de Vries+maintainer: edsko@well-typed.com+copyright: Copyright 2015 Well-Typed LLP+category: Distribution+homepage: https://github.com/well-typed/hackage-security+bug-reports: https://github.com/well-typed/hackage-security/issues+build-type: Simple+cabal-version: >=1.10++extra-source-files:+ ChangeLog.md++source-repository head+ type: git+ location: https://github.com/well-typed/hackage-security.git++flag base48+ description: Are we using base 4.8 or later?+ manual: False++flag use-network-uri+ description: Are we using network-uri?+ manual: False++library+ -- Most functionality is exported through the top-level entry points .Client+ -- and .Server; the other exported modules are intended for qualified imports.+ exposed-modules: Hackage.Security.Client+ Hackage.Security.Client.Formats+ Hackage.Security.Client.Repository+ Hackage.Security.Client.Repository.Cache+ Hackage.Security.Client.Repository.Local+ Hackage.Security.Client.Repository.Remote+ Hackage.Security.Client.Repository.HttpLib+ Hackage.Security.Client.Verify+ Hackage.Security.JSON+ Hackage.Security.Key.Env+ Hackage.Security.Server+ Hackage.Security.Trusted+ Hackage.Security.TUF.FileMap+ Hackage.Security.Util.Checked+ Hackage.Security.Util.IO+ Hackage.Security.Util.Lens+ Hackage.Security.Util.Path+ Hackage.Security.Util.Pretty+ Hackage.Security.Util.Some+ Text.JSON.Canonical+ other-modules: Hackage.Security.Key+ Hackage.Security.Trusted.TCB+ Hackage.Security.TUF+ Hackage.Security.TUF.Common+ Hackage.Security.TUF.FileInfo+ Hackage.Security.TUF.Header+ Hackage.Security.TUF.Layout.Cache+ Hackage.Security.TUF.Layout.Index+ Hackage.Security.TUF.Layout.Repo+ Hackage.Security.TUF.Mirrors+ Hackage.Security.TUF.Paths+ Hackage.Security.TUF.Patterns+ Hackage.Security.TUF.Root+ Hackage.Security.TUF.Signed+ Hackage.Security.TUF.Snapshot+ Hackage.Security.TUF.Targets+ Hackage.Security.TUF.Timestamp+ Hackage.Security.Util.Base64+ Hackage.Security.Util.JSON+ Hackage.Security.Util.Stack+ Hackage.Security.Util.TypedEmbedded+ Prelude+ -- We support ghc 7.4 (bundled with Cabal 1.14) and up+ build-depends: base >= 4.5 && < 5,+ base64-bytestring >= 1.0 && < 1.1,+ bytestring >= 0.9 && < 0.11,+ Cabal >= 1.14 && < 1.26,+ containers >= 0.4 && < 0.6,+ directory >= 1.1.0.2 && < 1.3,+ ed25519 >= 0.0 && < 0.1,+ filepath >= 1.2 && < 1.5,+ mtl >= 2.2 && < 2.3,+ parsec >= 3.1 && < 3.2,+ cryptohash >= 0.11 && < 0.12,+ -- 0.4.2 introduces TarIndex, 0.4.4 introduces more+ -- functionality, 0.5.0 changes type of serialise+ tar >= 0.5 && < 0.6,+ time >= 1.2 && < 1.7,+ transformers >= 0.4 && < 0.6,+ zlib >= 0.5 && < 0.7,+ -- whatever versions are bundled with ghc:+ template-haskell,+ ghc-prim+ hs-source-dirs: src+ default-language: Haskell2010+ default-extensions: DefaultSignatures+ DeriveDataTypeable+ DeriveFunctor+ FlexibleContexts+ FlexibleInstances+ GADTs+ GeneralizedNewtypeDeriving+ KindSignatures+ MultiParamTypeClasses+ NamedFieldPuns+ NoMonomorphismRestriction+ RankNTypes+ RecordWildCards+ ScopedTypeVariables+ StandaloneDeriving+ TupleSections+ TypeFamilies+ TypeOperators+ ViewPatterns+ other-extensions: BangPatterns+ CPP+ OverlappingInstances+ PackageImports+ UndecidableInstances+ -- use the new stage1/cross-compile-friendly Quotes subset of TH for new GHCs+ if impl(ghc >= 8.0)+ -- place holder until Hackage allows to edit in the new extension token+ -- other-extensions: TemplateHaskellQuotes+ other-extensions:+ else+ other-extensions: TemplateHaskell++ ghc-options: -Wall++ if flag(base48)+ build-depends: base >= 4.8+ else+ build-depends: old-locale >= 1.0++ -- The URI type got split out off the network package after version 2.5, and+ -- moved to a separate network-uri package. Since we don't need the rest of+ -- network here, it would suffice to rely only on network-uri:+ --+ -- > if flag(use-network-uri)+ -- > build-depends: network-uri >= 2.6 && < 2.7+ -- > else+ -- > build-depends: network >= 2.5 && < 2.6+ --+ -- However, if we did the same in hackage-security-HTTP, Cabal would consider+ -- those two flag choices (hackage-security:use-network-uri and+ -- hackage-security-HTTP:use-network-uri) to be completely independent; but+ -- they aren't: if it links hackage-security against network-uri and+ -- hackage-security-HTTP against network, we will get type errors when+ -- hackage-security-HTTP tries to pass a URI to hackage-security.+ --+ -- It might seem we can solve this problem by re-exporting the URI type in+ -- hackage-security and avoid the dependency in hackage-security-HTTP+ -- altogether. However, this merely shifts the problem: hackage-security-HTTP+ -- relies on the HTTP library which--surprise!--makes the same choice between+ -- depending on network or network-uri. Cabal will not notice that we cannot+ -- build hackage-security and hackage-security-HTTP against network-uri but+ -- HTTP against network.+ --+ -- We solve the problem by explicitly relying on network-2.6 when choosing+ -- network-uri. This dependency is redundant, strictly speaking. However, it+ -- serves as a proxy for forcing flag choices: since all packages in a+ -- solution must be linked against the same version of network, having one+ -- version of network in one branch of the conditional and another version of+ -- network in the other branch forces the choice to be consistent throughout.+ -- (Note that the HTTP library does the same thing, though in this case the+ -- dependency in network is not redundant.)+ if flag(use-network-uri)+ build-depends: network-uri >= 2.6 && < 2.7,+ network >= 2.6 && < 2.7+ else+ build-depends: network >= 2.5 && < 2.6++ if impl(ghc >= 7.8)+ other-extensions: RoleAnnotations++ if impl(ghc >= 7.10)+ other-extensions: AllowAmbiguousTypes+-- StaticPointers+-- ^^^ Temporarily disabled because Hackage doesn't know yet about this+-- extension and will therefore reject this package.++test-suite TestSuite+ type: exitcode-stdio-1.0+ main-is: TestSuite.hs+ other-modules: TestSuite.InMemCache+ TestSuite.InMemRepo+ TestSuite.InMemRepository+ TestSuite.PrivateKeys+ TestSuite.Util.StrictMVar+ build-depends: base,+ Cabal,+ containers,+ HUnit,+ bytestring,+ hackage-security,+ network-uri,+ tar,+ tasty,+ tasty-hunit,+ temporary,+ time,+ zlib+ hs-source-dirs: tests+ default-language: Haskell2010+ default-extensions: FlexibleContexts+ GADTs+ KindSignatures+ RankNTypes+ RecordWildCards+ ScopedTypeVariables+ ghc-options: -Wall
+ hackage-security/hackage-security/src/Hackage/Security/Client.hs view
@@ -0,0 +1,1031 @@+{-# LANGUAGE CPP #-}+#if __GLASGOW_HASKELL__ >= 710+{-# LANGUAGE StaticPointers #-}+#endif+-- | Main entry point into the Hackage Security framework for clients+module Hackage.Security.Client (+ -- * Checking for updates+ checkForUpdates+ , HasUpdates(..)+ -- * Downloading targets+ , downloadPackage+ , downloadPackage'+ -- * Access to the Hackage index+ , Directory(..)+ , DirectoryEntry(..)+ , getDirectory+ , IndexFile(..)+ , IndexEntry(..)+ , IndexCallbacks(..)+ , withIndex+ -- * Bootstrapping+ , requiresBootstrap+ , bootstrap+ -- * Re-exports+ , module Hackage.Security.TUF+ , module Hackage.Security.Key+ , trusted+ -- ** We only a few bits from .Repository+ -- TODO: Maybe this is a sign that these should be in a different module?+ , Repository -- opaque+ , DownloadedFile(..)+ , SomeRemoteError(..)+ , LogMessage(..)+ -- * Exceptions+ , uncheckClientErrors+ , VerificationError(..)+ , VerificationHistory+ , RootUpdated(..)+ , InvalidPackageException(..)+ , InvalidFileInIndex(..)+ , LocalFileCorrupted(..)+ ) where++import Prelude hiding (log)+import Control.Arrow (first)+import Control.Exception+import Control.Monad+import Control.Monad.IO.Class+import Data.List (sortBy)+import Data.Maybe (isNothing)+import Data.Ord (comparing)+import Data.Time+import Data.Traversable (for)+import Data.Typeable (Typeable)+import qualified Codec.Archive.Tar as Tar+import qualified Codec.Archive.Tar.Entry as Tar+import qualified Codec.Archive.Tar.Index as Tar+import qualified Data.ByteString.Lazy as BS.L+import qualified Data.ByteString.Lazy.Char8 as BS.L.C8++import Distribution.Package (PackageIdentifier)+import Distribution.Text (display)++import Hackage.Security.Client.Formats+import Hackage.Security.Client.Repository+import Hackage.Security.Client.Verify+import Hackage.Security.JSON+import Hackage.Security.Key+import Hackage.Security.Key.Env (KeyEnv)+import Hackage.Security.Trusted+import Hackage.Security.Trusted.TCB+import Hackage.Security.TUF+import Hackage.Security.Util.Checked+import Hackage.Security.Util.Path+import Hackage.Security.Util.Pretty+import Hackage.Security.Util.Some+import Hackage.Security.Util.Stack+import qualified Hackage.Security.Key.Env as KeyEnv++{-------------------------------------------------------------------------------+ Checking for updates+-------------------------------------------------------------------------------}++data HasUpdates = HasUpdates | NoUpdates+ deriving (Show, Eq, Ord)++-- | Generic logic for checking if there are updates+--+-- This implements the logic described in Section 5.1, "The client application",+-- of the TUF spec. It checks which of the server metadata has changed, and+-- downloads all changed metadata to the local cache. (Metadata here refers+-- both to the TUF security metadata as well as the Hackage packge index.)+--+-- You should pass @Nothing@ for the UTCTime _only_ under exceptional+-- circumstances (such as when the main server is down for longer than the+-- expiry dates used in the timestamp files on mirrors).+checkForUpdates :: (Throws VerificationError, Throws SomeRemoteError)+ => Repository down+ -> Maybe UTCTime -- ^ To check expiry times against (if using)+ -> IO HasUpdates+checkForUpdates rep@Repository{..} mNow =+ withMirror rep $ limitIterations []+ where+ -- More or less randomly chosen maximum iterations+ -- See <https://github.com/theupdateframework/tuf/issues/287>.+ maxNumIterations :: Int+ maxNumIterations = 5++ -- The spec stipulates that on a verification error we must download new+ -- root information and start over. However, in order to prevent DoS attacks+ -- we limit how often we go round this loop.+ -- See als <https://github.com/theupdateframework/tuf/issues/287>.+ limitIterations :: (Throws VerificationError, Throws SomeRemoteError)+ => VerificationHistory -> IO HasUpdates+ limitIterations history | length history >= maxNumIterations =+ throwChecked $ VerificationErrorLoop (reverse history)+ limitIterations history = do+ -- Get all cached info+ --+ -- NOTE: Although we don't normally update any cached files until the+ -- whole verification process successfully completes, in case of a+ -- verification error, or in case of a regular update of the root info,+ -- we DO update the local files. Hence, we must re-read all local files+ -- on each iteration.+ cachedInfo <- getCachedInfo rep++ mHasUpdates <- tryChecked -- catch RootUpdated+ $ tryChecked -- catch VerificationError+ $ runVerify repLockCache+ $ go attemptNr cachedInfo+ case mHasUpdates of+ Left ex -> do+ -- NOTE: This call to updateRoot is not itself protected by an+ -- exception handler, and may therefore throw a VerificationError.+ -- This is intentional: if we get verification errors during the+ -- update process, _and_ we cannot update the main root info, then+ -- we cannot do anything.+ log rep $ LogVerificationError ex+ let history' = Right ex : history+ attemptNr' = attemptNr + 1+ updateRoot rep mNow attemptNr' cachedInfo (Left ex)+ limitIterations history'+ Right (Left RootUpdated) -> do+ log rep $ LogRootUpdated+ let history' = Left RootUpdated : history+ limitIterations history'+ Right (Right hasUpdates) ->+ return hasUpdates+ where+ attemptNr :: AttemptNr+ attemptNr = fromIntegral $ length history++ -- The 'Verify' monad only caches the downloaded files after verification.+ -- See also <https://github.com/theupdateframework/tuf/issues/283>.+ go :: Throws RootUpdated => AttemptNr -> CachedInfo -> Verify HasUpdates+ go attemptNr cachedInfo@CachedInfo{..} = do+ -- Get the new timestamp+ newTS <- getRemoteFile' RemoteTimestamp+ let newInfoSS = static timestampInfoSnapshot <$$> newTS++ -- Check if the snapshot has changed+ if not (fileChanged cachedInfoSnapshot newInfoSS)+ then return NoUpdates+ else do+ -- Get the new snapshot+ newSS <- getRemoteFile' (RemoteSnapshot newInfoSS)+ let newInfoRoot = static snapshotInfoRoot <$$> newSS+ newInfoMirrors = static snapshotInfoMirrors <$$> newSS+ newInfoTarGz = static snapshotInfoTarGz <$$> newSS+ mNewInfoTar = trustElems (static snapshotInfoTar <$$> newSS)++ -- If root metadata changed, download and restart+ when (rootChanged cachedInfoRoot newInfoRoot) $ liftIO $ do+ updateRoot rep mNow attemptNr cachedInfo (Right newInfoRoot)+ -- By throwing 'RootUpdated' as an exception we make sure that+ -- any files previously downloaded (to temporary locations)+ -- will not be cached.+ throwChecked RootUpdated++ -- If mirrors changed, download and verify+ when (fileChanged cachedInfoMirrors newInfoMirrors) $+ newMirrors =<< getRemoteFile' (RemoteMirrors newInfoMirrors)++ -- If index changed, download and verify+ when (fileChanged cachedInfoTarGz newInfoTarGz) $+ updateIndex newInfoTarGz mNewInfoTar++ return HasUpdates+ where+ getRemoteFile' :: ( VerifyRole a+ , FromJSON ReadJSON_Keys_Layout (Signed a)+ )+ => RemoteFile (f :- ()) Metadata -> Verify (Trusted a)+ getRemoteFile' = liftM fst . getRemoteFile rep cachedInfo attemptNr mNow++ -- Update the index and check against the appropriate hash+ updateIndex :: Trusted FileInfo -- info about @.tar.gz@+ -> Maybe (Trusted FileInfo) -- info about @.tar@+ -> Verify ()+ updateIndex newInfoTarGz Nothing = do+ (targetPath, tempPath) <- getRemote' rep attemptNr $+ RemoteIndex (HFZ FGz) (FsGz newInfoTarGz)+ verifyFileInfo' (Just newInfoTarGz) targetPath tempPath+ updateIndex newInfoTarGz (Just newInfoTar) = do+ (format, targetPath, tempPath) <- getRemote rep attemptNr $+ RemoteIndex (HFS (HFZ FGz)) (FsUnGz newInfoTar newInfoTarGz)+ case format of+ Some FGz -> verifyFileInfo' (Just newInfoTarGz) targetPath tempPath+ Some FUn -> verifyFileInfo' (Just newInfoTar) targetPath tempPath++ -- Unlike for other files, if we didn't have an old snapshot, consider the+ -- root info unchanged (otherwise we would loop indefinitely).+ -- See also <https://github.com/theupdateframework/tuf/issues/286>+ rootChanged :: Maybe (Trusted FileInfo) -> Trusted FileInfo -> Bool+ rootChanged Nothing _ = False+ rootChanged (Just old) new = not (trustedFileInfoEqual old new)++ -- For any file other than the root we consider the file to have changed+ -- if we do not yet have a local snapshot to tell us the old info.+ fileChanged :: Maybe (Trusted FileInfo) -> Trusted FileInfo -> Bool+ fileChanged Nothing _ = True+ fileChanged (Just old) new = not (trustedFileInfoEqual old new)++ -- We don't actually _do_ anything with the mirrors file until the next call+ -- to 'checkUpdates', because we want to use a single server for a single+ -- check-for-updates request. If validation was successful the repository+ -- will have cached the mirrors file and it will be available on the next+ -- request.+ newMirrors :: Trusted Mirrors -> Verify ()+ newMirrors _ = return ()++-- | Update the root metadata+--+-- Note that the new root metadata is verified using the old root metadata,+-- and only then trusted.+--+-- We don't always have root file information available. If we notice during+-- the normal update process that the root information has changed then the+-- snapshot will give us the new file information; but if we need to update+-- the root information due to a verification error we do not.+--+-- We additionally delete the cached cached snapshot and timestamp. This is+-- necessary for two reasons:+--+-- 1. If during the normal update process we notice that the root info was+-- updated (because the hash of @root.json@ in the new snapshot is different+-- from the old snapshot) we download new root info and start over, without+-- (yet) downloading a (potential) new index. This means it is important that+-- we not overwrite our local cached snapshot, because if we did we would+-- then on the next iteration conclude there were no updates and we would+-- fail to notice that we should have updated the index. However, unless we+-- do something, this means that we would conclude on the next iteration once+-- again that the root info has changed (because the hash in the new shapshot+-- still doesn't match the hash in the cached snapshot), and we would loop+-- until we throw a 'VerificationErrorLoop' exception. By deleting the local+-- snapshot we basically reset the client to its initial state, and we will+-- not try to download the root info once again. The only downside of this is+-- that we will also re-download the index after every root info change.+-- However, this should be infrequent enough that this isn't an issue.+-- See also <https://github.com/theupdateframework/tuf/issues/285>.+--+-- 2. Additionally, deleting the local timestamp and snapshot protects against+-- an attack where an attacker has set the file version of the snapshot or+-- timestamp to MAX_INT, thereby making further updates impossible.+-- (Such an attack would require a timestamp/snapshot key compromise.)+--+-- However, we _ONLY_ do this when the root information has actually changed.+-- If we did this unconditionally it would mean that we delete the locally+-- cached timestamp whenever the version on the remote timestamp is invalid,+-- thereby rendering the file version on the timestamp and the snapshot useless.+-- See <https://github.com/theupdateframework/tuf/issues/283#issuecomment-115739521>+updateRoot :: (Throws VerificationError, Throws SomeRemoteError)+ => Repository down+ -> Maybe UTCTime+ -> AttemptNr+ -> CachedInfo+ -> Either VerificationError (Trusted FileInfo)+ -> IO ()+updateRoot rep@Repository{..} mNow isRetry cachedInfo eFileInfo = do+ rootReallyChanged <- runVerify repLockCache $ do+ (_newRoot :: Trusted Root, rootTempFile) <- getRemoteFile+ rep+ cachedInfo+ isRetry+ mNow+ (RemoteRoot (eitherToMaybe eFileInfo))++ -- NOTE: It is important that we do this check within the evalContT,+ -- because the temporary file will be deleted once we leave its scope.+ case eFileInfo of+ Right _ ->+ -- We are downloading the root info because the hash in the snapshot+ -- changed. In this case the root definitely changed.+ return True+ Left _e -> liftIO $ do+ -- We are downloading the root because of a verification error. In+ -- this case the root info may or may not have changed. In most cases+ -- it would suffice to compare the file version now; however, in the+ -- (exceptional) circumstance where the root info has changed but+ -- the file version has not, this would result in the same infinite+ -- loop described above. Hence, we must compare file hashes, and they+ -- must be computed on the raw file, not the parsed file.+ oldRootFile <- repGetCachedRoot+ oldRootInfo <- DeclareTrusted <$> computeFileInfo oldRootFile+ not <$> downloadedVerify rootTempFile oldRootInfo++ when rootReallyChanged $ clearCache rep++{-------------------------------------------------------------------------------+ Convenience functions for downloading and parsing various files+-------------------------------------------------------------------------------}++data CachedInfo = CachedInfo {+ cachedRoot :: Trusted Root+ , cachedKeyEnv :: KeyEnv+ , cachedTimestamp :: Maybe (Trusted Timestamp)+ , cachedSnapshot :: Maybe (Trusted Snapshot)+ , cachedMirrors :: Maybe (Trusted Mirrors)+ , cachedInfoSnapshot :: Maybe (Trusted FileInfo)+ , cachedInfoRoot :: Maybe (Trusted FileInfo)+ , cachedInfoMirrors :: Maybe (Trusted FileInfo)+ , cachedInfoTarGz :: Maybe (Trusted FileInfo)+ }++cachedVersion :: CachedInfo -> RemoteFile fs typ -> Maybe FileVersion+cachedVersion CachedInfo{..} remoteFile =+ case mustCache remoteFile of+ CacheAs CachedTimestamp -> timestampVersion . trusted <$> cachedTimestamp+ CacheAs CachedSnapshot -> snapshotVersion . trusted <$> cachedSnapshot+ CacheAs CachedMirrors -> mirrorsVersion . trusted <$> cachedMirrors+ CacheAs CachedRoot -> Just . rootVersion . trusted $ cachedRoot+ CacheIndex -> Nothing+ DontCache -> Nothing++-- | Get all cached info (if any)+getCachedInfo :: (Applicative m, MonadIO m) => Repository down -> m CachedInfo+getCachedInfo rep = do+ (cachedRoot, cachedKeyEnv) <- readLocalRoot rep+ cachedTimestamp <- readLocalFile rep cachedKeyEnv CachedTimestamp+ cachedSnapshot <- readLocalFile rep cachedKeyEnv CachedSnapshot+ cachedMirrors <- readLocalFile rep cachedKeyEnv CachedMirrors++ let cachedInfoSnapshot = fmap (static timestampInfoSnapshot <$$>) cachedTimestamp+ cachedInfoRoot = fmap (static snapshotInfoRoot <$$>) cachedSnapshot+ cachedInfoMirrors = fmap (static snapshotInfoMirrors <$$>) cachedSnapshot+ cachedInfoTarGz = fmap (static snapshotInfoTarGz <$$>) cachedSnapshot++ return CachedInfo{..}++readLocalRoot :: MonadIO m => Repository down -> m (Trusted Root, KeyEnv)+readLocalRoot rep = do+ cachedPath <- liftIO $ repGetCachedRoot rep+ signedRoot <- throwErrorsUnchecked LocalFileCorrupted =<<+ readCachedJSON rep KeyEnv.empty cachedPath+ return (trustLocalFile signedRoot, rootKeys (signed signedRoot))++readLocalFile :: ( FromJSON ReadJSON_Keys_Layout (Signed a)+ , MonadIO m, Applicative m+ )+ => Repository down -> KeyEnv -> CachedFile -> m (Maybe (Trusted a))+readLocalFile rep cachedKeyEnv file = do+ mCachedPath <- liftIO $ repGetCached rep file+ for mCachedPath $ \cachedPath -> do+ signed <- throwErrorsUnchecked LocalFileCorrupted =<<+ readCachedJSON rep cachedKeyEnv cachedPath+ return $ trustLocalFile signed++getRemoteFile :: ( Throws VerificationError+ , Throws SomeRemoteError+ , VerifyRole a+ , FromJSON ReadJSON_Keys_Layout (Signed a)+ )+ => Repository down+ -> CachedInfo+ -> AttemptNr+ -> Maybe UTCTime+ -> RemoteFile (f :- ()) Metadata+ -> Verify (Trusted a, down Metadata)+getRemoteFile rep@Repository{..} cachedInfo@CachedInfo{..} isRetry mNow file = do+ (targetPath, tempPath) <- getRemote' rep isRetry file+ verifyFileInfo' (remoteFileDefaultInfo file) targetPath tempPath+ signed <- throwErrorsChecked (VerificationErrorDeserialization targetPath) =<<+ readDownloadedJSON rep cachedKeyEnv tempPath+ verified <- throwErrorsChecked id $ verifyRole+ cachedRoot+ targetPath+ (cachedVersion cachedInfo file)+ mNow+ signed+ return (trustVerified verified, tempPath)++{-------------------------------------------------------------------------------+ Downloading target files+-------------------------------------------------------------------------------}++-- | Download a package+downloadPackage :: ( Throws SomeRemoteError+ , Throws VerificationError+ , Throws InvalidPackageException+ )+ => Repository down -- ^ Repository+ -> PackageIdentifier -- ^ Package to download+ -> Path Absolute -- ^ Destination (see also 'downloadPackage'')+ -> IO ()+downloadPackage rep@Repository{..} pkgId dest =+ withMirror rep $+ withIndex rep $ \IndexCallbacks{..} -> runVerify repLockCache $ do+ -- Get the metadata (from the previously updated index)+ targetFileInfo <- liftIO $ indexLookupFileInfo pkgId++ -- TODO: should we check if cached package available? (spec says no)+ tarGz <- do+ (targetPath, downloaded) <- getRemote' rep (AttemptNr 0) $+ RemotePkgTarGz pkgId targetFileInfo+ verifyFileInfo' (Just targetFileInfo) targetPath downloaded+ return downloaded++ -- If all checks succeed, copy file to its target location.+ liftIO $ downloadedCopyTo tarGz dest++-- | Variation on 'downloadPackage' that takes a FilePath instead.+downloadPackage' :: ( Throws SomeRemoteError+ , Throws VerificationError+ , Throws InvalidPackageException+ )+ => Repository down -- ^ Repository+ -> PackageIdentifier -- ^ Package to download+ -> FilePath -- ^ Destination+ -> IO ()+downloadPackage' rep pkgId dest =+ downloadPackage rep pkgId =<< makeAbsolute (fromFilePath dest)++{-------------------------------------------------------------------------------+ Access to the tar index (the API is exported and used internally)++ NOTE: The files inside the index as evaluated lazily.++ 1. The index tarball contains delegated target.json files for both unsigned+ and signed packages. We need to verify the signatures of all signed+ metadata (that is: the metadata for signed packages).++ 2. Since the tarball also contains the .cabal files, we should also verify the+ hashes of those .cabal files against the hashes recorded in signed metadata+ (there is no point comparing against hashes recorded in unsigned metadata+ because attackers could just change those).++ Since we don't have author signing yet, we don't have any additional signed+ metadata and therefore we currently don't have to do anything here.++ TODO: If we have explicit, author-signed, lists of versions for a package (as+ described in @README.md@), then evaluating these "middle-level" delegation+ files lazily opens us up to a rollback attack: if we've never downloaded the+ delegations for a package before, then we have nothing to compare the version+ number in the file that we downloaded against. One option is to always+ download and verify all these middle level files (strictly); other is to+ include the version number of all of these files in the snapshot. This is+ described in more detail in+ <https://github.com/theupdateframework/tuf/issues/282#issuecomment-102468421>.++ TODO: Currently we hardcode the location of the package specific metadata. By+ rights we should read the global targets file and apply the delegation rules.+ Until we have author signing however this is unnecessary.+-------------------------------------------------------------------------------}++-- | Index directory+data Directory = Directory {+ -- | The first entry in the dictionary+ directoryFirst :: DirectoryEntry++ -- | The next available (i.e., one after last) directory entry+ , directoryNext :: DirectoryEntry++ -- | Lookup an entry in the dictionary+ --+ -- This is an efficient operation.+ , directoryLookup :: forall dec. IndexFile dec -> Maybe DirectoryEntry++ -- | An enumeration of all entries+ --+ -- This field is lazily constructed, so if you don't need it, it does not+ -- incur a performance overhead. Moreover, the 'IndexFile' is also created+ -- lazily so if you only need the raw 'IndexPath' there is no parsing+ -- overhead.+ --+ -- The entries are ordered by 'DirectoryEntry' so that the entries can+ -- efficiently be read in sequence.+ --+ -- NOTE: This means that there are two ways to enumerate all entries in the+ -- tar file, since when lookup an entry using 'indexLookupEntry' the+ -- 'DirectoryEntry' of the next entry is also returned. However, this+ -- involves reading through the entire @tar@ file. If you only need to read+ -- /some/ files, it is significantly more efficient to enumerate the tar+ -- entries using 'directoryEntries' instead and only call 'indexLookupEntry'+ -- when required.+ , directoryEntries :: [(DirectoryEntry, IndexPath, Maybe (Some IndexFile))]+ }++-- | Entry into the Hackage index.+newtype DirectoryEntry = DirectoryEntry {+ -- | (Low-level) block number of the tar index entry+ --+ -- Exposed for the benefit of clients who read the @.tar@ file directly.+ -- For this reason also the 'Show' and 'Read' instances for 'DirectoryEntry'+ -- just print and parse the underlying 'TarEntryOffset'.+ directoryEntryBlockNo :: Tar.TarEntryOffset+ }+ deriving (Eq, Ord)++instance Show DirectoryEntry where+ show = show . directoryEntryBlockNo++instance Read DirectoryEntry where+ readsPrec p = map (first DirectoryEntry) . readsPrec p++-- | Read the Hackage index directory+--+-- Should only be called after 'checkForUpdates'.+getDirectory :: Repository down -> IO Directory+getDirectory Repository{..} = mkDirectory <$> repGetIndexIdx+ where+ mkDirectory :: Tar.TarIndex -> Directory+ mkDirectory idx = Directory {+ directoryFirst = DirectoryEntry 0+ , directoryNext = DirectoryEntry $ Tar.indexEndEntryOffset idx+ , directoryLookup = liftM dirEntry . Tar.lookup idx . filePath+ , directoryEntries = map mkEntry $ sortBy (comparing snd) (Tar.toList idx)+ }++ mkEntry :: (FilePath, Tar.TarEntryOffset)+ -> (DirectoryEntry, IndexPath, Maybe (Some IndexFile))+ mkEntry (fp, off) = (DirectoryEntry off, path, indexFile path)+ where+ path = indexPath fp++ dirEntry :: Tar.TarIndexEntry -> DirectoryEntry+ dirEntry (Tar.TarFileEntry offset) = DirectoryEntry offset+ dirEntry (Tar.TarDir _) = error "directoryLookup: unexpected directory"++ indexFile :: IndexPath -> Maybe (Some IndexFile)+ indexFile = indexFileFromPath repIndexLayout++ indexPath :: FilePath -> IndexPath+ indexPath = rootPath . fromUnrootedFilePath++ filePath :: IndexFile dec -> FilePath+ filePath = toUnrootedFilePath . unrootPath . indexFileToPath repIndexLayout++-- | Entry from the Hackage index; see 'withIndex'.+data IndexEntry dec = IndexEntry {+ -- | The raw path in the tarfile+ indexEntryPath :: IndexPath++ -- | The parsed file (if recognised)+ , indexEntryPathParsed :: Maybe (IndexFile dec)++ -- | The raw contents+ --+ -- Although this is a lazy bytestring, this is actually read into memory+ -- strictly (i.e., it can safely be used outside the scope of withIndex and+ -- friends).+ , indexEntryContent :: BS.L.ByteString++ -- | The parsed contents+ --+ -- This field is lazily constructed; the parser is not unless you do a+ -- pattern match on this value.+ , indexEntryContentParsed :: Either SomeException dec++ -- | The time of the entry in the tarfile.+ , indexEntryTime :: Tar.EpochTime+ }++-- | Various operations that we can perform on the index once its open+--+-- Note that 'IndexEntry' contains a fields both for the raw file contents and+-- the parsed file contents; clients can choose which to use.+--+-- In principle these callbacks will do verification (once we have implemented+-- author signing). Right now they don't need to do that, because the index as a+-- whole will have been verified.+data IndexCallbacks = IndexCallbacks {+ -- | Look up an entry by 'DirectoryEntry'+ --+ -- Since these 'DirectoryEntry's must come from somewhere (probably from the+ -- 'Directory'), it is assumed that they are valid; if they are not, an+ -- (unchecked) exception will be thrown.+ --+ -- This function also returns the 'DirectoryEntry' of the /next/ file in the+ -- index (if any) for the benefit of clients who wish to walk through the+ -- entire index.+ indexLookupEntry :: DirectoryEntry+ -> IO (Some IndexEntry, Maybe DirectoryEntry)++ -- | Look up an entry by 'IndexFile'+ --+ -- Returns 'Nothing' if the 'IndexFile' does not refer to an existing file.+ , indexLookupFile :: forall dec.+ IndexFile dec+ -> IO (Maybe (IndexEntry dec))++ -- | Variation if both the 'DirectoryEntry' and the 'IndexFile' are known+ --+ -- You might use this when scanning the index using 'directoryEntries'.+ , indexLookupFileEntry :: forall dec.+ DirectoryEntry+ -> IndexFile dec+ -> IO (IndexEntry dec)++ -- | Get (raw) cabal file (wrapper around 'indexLookupFile')+ , indexLookupCabal :: Throws InvalidPackageException+ => PackageIdentifier+ -> IO (Trusted BS.L.ByteString)++ -- | Lookup package metadata (wrapper around 'indexLookupFile')+ --+ -- This will throw an (unchecked) exception if the @targets.json@ file+ -- could not be parsed.+ , indexLookupMetadata :: Throws InvalidPackageException+ => PackageIdentifier+ -> IO (Trusted Targets)++ -- | Get file info (including hash) (wrapper around 'indexLookupFile')+ , indexLookupFileInfo :: ( Throws InvalidPackageException+ , Throws VerificationError+ )+ => PackageIdentifier+ -> IO (Trusted FileInfo)++ -- | Get the SHA256 hash for a package (wrapper around 'indexLookupInfo')+ --+ -- In addition to the exceptions thrown by 'indexLookupInfo', this will also+ -- throw an exception if the SHA256 is not listed in the 'FileMap' (again,+ -- this will not happen with a well-formed Hackage index.)+ , indexLookupHash :: ( Throws InvalidPackageException+ , Throws VerificationError+ )+ => PackageIdentifier+ -> IO (Trusted Hash)++ -- | The 'Directory' for the index+ --+ -- We provide this here because 'withIndex' will have read this anyway.+ , indexDirectory :: Directory+ }++-- | Look up entries in the Hackage index+--+-- This is in 'withFile' style so that clients can efficiently look up multiple+-- files from the index.+--+-- Should only be called after 'checkForUpdates'.+withIndex :: Repository down -> (IndexCallbacks -> IO a) -> IO a+withIndex rep@Repository{..} callback = do+ -- We need the cached root information in order to resolve key IDs and+ -- verify signatures. Note that whenever we read a JSON file, we verify+ -- signatures (even if we don't verify the keys); if this is a problem+ -- (for performance) we need to parameterize parseJSON.+ (_cachedRoot, keyEnv) <- readLocalRoot rep++ -- We need the directory to resolve 'IndexFile's and to know the index of+ -- the last entry.+ dir@Directory{..} <- getDirectory rep++ -- Open the index+ repWithIndex $ \h -> do+ let getEntry :: DirectoryEntry+ -> IO (Some IndexEntry, Maybe DirectoryEntry)+ getEntry entry = do+ (tarEntry, content, next) <- getTarEntry entry+ let path = indexPath tarEntry+ case indexFile path of+ Nothing ->+ return (Some (mkEntry tarEntry content Nothing), next)+ Just (Some file) ->+ return (Some (mkEntry tarEntry content (Just file)), next)++ getFile :: IndexFile dec -> IO (Maybe (IndexEntry dec))+ getFile file =+ case directoryLookup file of+ Nothing -> return Nothing+ Just dirEntry -> Just <$> getFileEntry dirEntry file++ getFileEntry :: DirectoryEntry+ -> IndexFile dec+ -> IO (IndexEntry dec)+ getFileEntry dirEntry file = do+ (tarEntry, content, _next) <- getTarEntry dirEntry+ return $ mkEntry tarEntry content (Just file)++ mkEntry :: Tar.Entry+ -> BS.L.ByteString+ -> Maybe (IndexFile dec)+ -> IndexEntry dec+ mkEntry tarEntry content mFile = IndexEntry {+ indexEntryPath = indexPath tarEntry+ , indexEntryPathParsed = mFile+ , indexEntryContent = content+ , indexEntryContentParsed = parseContent mFile content+ , indexEntryTime = Tar.entryTime tarEntry+ }++ parseContent :: Maybe (IndexFile dec)+ -> BS.L.ByteString -> Either SomeException dec+ parseContent Nothing _ = Left pathNotRecognized+ parseContent (Just file) raw = case file of+ IndexPkgPrefs _ ->+ Right () -- We don't currently parse preference files+ IndexPkgCabal _ ->+ Right () -- We don't currently parse .cabal files+ IndexPkgMetadata _ ->+ let mkEx = either+ (Left . SomeException . InvalidFileInIndex file raw)+ Right+ in mkEx $ parseJSON_Keys_NoLayout keyEnv raw++ -- Read an entry from the tar file. Returns entry content separately,+ -- throwing an exception if the entry is not a regular file.+ -- Also throws an exception if the 'DirectoryEntry' is invalid.+ getTarEntry :: DirectoryEntry+ -> IO (Tar.Entry, BS.L.ByteString, Maybe DirectoryEntry)+ getTarEntry (DirectoryEntry offset) = do+ entry <- Tar.hReadEntry h offset+ content <- case Tar.entryContent entry of+ Tar.NormalFile content _sz -> return content+ _ -> throwIO $ userError "withIndex: unexpected entry"+ let next = DirectoryEntry $ Tar.nextEntryOffset entry offset+ mNext = guard (next < directoryNext) >> return next+ return (entry, content, mNext)++ -- Get cabal file+ getCabal :: Throws InvalidPackageException+ => PackageIdentifier -> IO (Trusted BS.L.ByteString)+ getCabal pkgId = do+ mCabal <- getFile $ IndexPkgCabal pkgId+ case mCabal of+ Nothing ->+ throwChecked $ InvalidPackageException pkgId+ Just IndexEntry{..} ->+ return $ DeclareTrusted indexEntryContent++ -- Get package metadata+ getMetadata :: Throws InvalidPackageException+ => PackageIdentifier -> IO (Trusted Targets)+ getMetadata pkgId = do+ mEntry <- getFile $ IndexPkgMetadata pkgId+ case mEntry of+ Nothing ->+ throwChecked $ InvalidPackageException pkgId+ Just IndexEntry{indexEntryContentParsed = Left ex} ->+ throwUnchecked $ ex+ Just IndexEntry{indexEntryContentParsed = Right signed} ->+ return $ trustLocalFile signed++ -- Get package info+ getFileInfo :: ( Throws InvalidPackageException+ , Throws VerificationError+ )+ => PackageIdentifier -> IO (Trusted FileInfo)+ getFileInfo pkgId = do+ targets <- getMetadata pkgId++ let mTargetMetadata :: Maybe (Trusted FileInfo)+ mTargetMetadata = trustElems+ $ trustStatic (static targetsLookup)+ `trustApply` DeclareTrusted (targetPath pkgId)+ `trustApply` targets++ case mTargetMetadata of+ Nothing ->+ throwChecked $ VerificationErrorUnknownTarget (targetPath pkgId)+ Just info ->+ return info++ -- Get package SHA256+ getHash :: ( Throws InvalidPackageException+ , Throws VerificationError+ )+ => PackageIdentifier -> IO (Trusted Hash)+ getHash pkgId = do+ info <- getFileInfo pkgId++ let mTrustedHash :: Maybe (Trusted Hash)+ mTrustedHash = trustElems+ $ trustStatic (static fileInfoSHA256)+ `trustApply` info++ case mTrustedHash of+ Nothing ->+ throwChecked $ VerificationErrorMissingSHA256 (targetPath pkgId)+ Just hash ->+ return hash++ callback IndexCallbacks{+ indexLookupEntry = getEntry+ , indexLookupFile = getFile+ , indexLookupFileEntry = getFileEntry+ , indexDirectory = dir+ , indexLookupCabal = getCabal+ , indexLookupMetadata = getMetadata+ , indexLookupFileInfo = getFileInfo+ , indexLookupHash = getHash+ }+ where+ indexPath :: Tar.Entry -> IndexPath+ indexPath = rootPath . fromUnrootedFilePath . Tar.entryPath++ indexFile :: IndexPath -> Maybe (Some IndexFile)+ indexFile = indexFileFromPath repIndexLayout++ targetPath :: PackageIdentifier -> TargetPath+ targetPath = TargetPathRepo . repoLayoutPkgTarGz repLayout++ pathNotRecognized :: SomeException+ pathNotRecognized = SomeException (userError "Path not recognized")++{-------------------------------------------------------------------------------+ Bootstrapping+-------------------------------------------------------------------------------}++-- | Check if we need to bootstrap (i.e., if we have root info)+requiresBootstrap :: Repository down -> IO Bool+requiresBootstrap rep = isNothing <$> repGetCached rep CachedRoot++-- | Bootstrap the chain of trust+--+-- New clients might need to obtain a copy of the root metadata. This however+-- represents a chicken-and-egg problem: how can we verify the root metadata+-- we downloaded? The only possibility is to be provided with a set of an+-- out-of-band set of root keys and an appropriate threshold.+--+-- Clients who provide a threshold of 0 can do an initial "unsafe" update+-- of the root information, if they wish.+--+-- The downloaded root information will _only_ be verified against the+-- provided keys, and _not_ against previously downloaded root info (if any).+-- It is the responsibility of the client to call `bootstrap` only when this+-- is the desired behaviour.+bootstrap :: (Throws SomeRemoteError, Throws VerificationError)+ => Repository down -> [KeyId] -> KeyThreshold -> IO ()+bootstrap rep@Repository{..} trustedRootKeys keyThreshold = withMirror rep $ runVerify repLockCache $ do+ _newRoot :: Trusted Root <- do+ (targetPath, tempPath) <- getRemote' rep (AttemptNr 0) (RemoteRoot Nothing)+ signed <- throwErrorsChecked (VerificationErrorDeserialization targetPath) =<<+ readDownloadedJSON rep KeyEnv.empty tempPath+ verified <- throwErrorsChecked id $ verifyFingerprints+ trustedRootKeys+ keyThreshold+ targetPath+ signed+ return $ trustVerified verified++ clearCache rep++{-------------------------------------------------------------------------------+ Wrapper around the Repository functions+-------------------------------------------------------------------------------}++getRemote :: forall fs down typ. Throws SomeRemoteError+ => Repository down+ -> AttemptNr+ -> RemoteFile fs typ+ -> Verify (Some Format, TargetPath, down typ)+getRemote r attemptNr file = do+ (Some format, downloaded) <- repGetRemote r attemptNr file+ let targetPath = TargetPathRepo $ remoteRepoPath' (repLayout r) file format+ return (Some (hasFormatGet format), targetPath, downloaded)++-- | Variation on getRemote where we only expect one type of result+getRemote' :: forall f down typ. Throws SomeRemoteError+ => Repository down+ -> AttemptNr+ -> RemoteFile (f :- ()) typ+ -> Verify (TargetPath, down typ)+getRemote' r isRetry file = ignoreFormat <$> getRemote r isRetry file+ where+ ignoreFormat (_format, targetPath, tempPath) = (targetPath, tempPath)++clearCache :: MonadIO m => Repository down -> m ()+clearCache r = liftIO $ repClearCache r++log :: MonadIO m => Repository down -> LogMessage -> m ()+log r msg = liftIO $ repLog r msg++-- Tries to load the cached mirrors file+withMirror :: Repository down -> IO a -> IO a+withMirror rep callback = do+ mMirrors <- repGetCached rep CachedMirrors+ mirrors <- case mMirrors of+ Nothing -> return Nothing+ Just fp -> filterMirrors <$>+ (throwErrorsUnchecked LocalFileCorrupted =<<+ readJSON_NoKeys_NoLayout fp)+ repWithMirror rep mirrors $ callback+ where+ filterMirrors :: UninterpretedSignatures Mirrors -> Maybe [Mirror]+ filterMirrors = Just+ . filter (canUseMirror . mirrorContent)+ . mirrorsMirrors+ . uninterpretedSigned++ -- Once we add support for partial mirrors, we wil need an additional+ -- argument to 'repWithMirror' (here, not in the Repository API itself)+ -- that tells us which files we will be requested from the mirror.+ -- We can then compare that against the specification of the partial mirror+ -- to see if all of those files are available from this mirror.+ canUseMirror :: MirrorContent -> Bool+ canUseMirror MirrorFull = True++{-------------------------------------------------------------------------------+ Exceptions+-------------------------------------------------------------------------------}++-- | Re-throw all exceptions thrown by the client API as unchecked exceptions+uncheckClientErrors :: ( ( Throws VerificationError+ , Throws SomeRemoteError+ , Throws InvalidPackageException+ ) => IO a )+ -> IO a+uncheckClientErrors act = handleChecked rethrowVerificationError+ $ handleChecked rethrowSomeRemoteError+ $ handleChecked rethrowInvalidPackageException+ $ act+ where+ rethrowVerificationError :: VerificationError -> IO a+ rethrowVerificationError = throwIO++ rethrowSomeRemoteError :: SomeRemoteError -> IO a+ rethrowSomeRemoteError = throwIO++ rethrowInvalidPackageException :: InvalidPackageException -> IO a+ rethrowInvalidPackageException = throwIO++data InvalidPackageException = InvalidPackageException PackageIdentifier+ deriving (Typeable)++data LocalFileCorrupted = LocalFileCorrupted DeserializationError+ deriving (Typeable)++data InvalidFileInIndex = forall dec. InvalidFileInIndex {+ invalidFileInIndex :: IndexFile dec+ , invalidFileInIndexRaw :: BS.L.ByteString+ , invalidFileInIndexError :: DeserializationError+ }+ deriving (Typeable)++#if MIN_VERSION_base(4,8,0)+deriving instance Show InvalidPackageException+deriving instance Show LocalFileCorrupted+deriving instance Show InvalidFileInIndex+instance Exception InvalidPackageException where displayException = pretty+instance Exception LocalFileCorrupted where displayException = pretty+instance Exception InvalidFileInIndex where displayException = pretty+#else+instance Show InvalidPackageException where show = pretty+instance Show LocalFileCorrupted where show = pretty+instance Show InvalidFileInIndex where show = pretty+instance Exception InvalidPackageException+instance Exception LocalFileCorrupted+instance Exception InvalidFileInIndex+#endif++instance Pretty InvalidPackageException where+ pretty (InvalidPackageException pkgId) = "Invalid package " ++ display pkgId++instance Pretty LocalFileCorrupted where+ pretty (LocalFileCorrupted err) = "Local file corrupted: " ++ pretty err++instance Pretty InvalidFileInIndex where+ pretty (InvalidFileInIndex file raw err) = unlines [+ "Invalid file in index: " ++ pretty file+ , "Error: " ++ pretty err+ , "Unparsed file: " ++ BS.L.C8.unpack raw+ ]++{-------------------------------------------------------------------------------+ Auxiliary+-------------------------------------------------------------------------------}++-- | Local files are assumed trusted+--+-- There is no point tracking chain of trust for local files because that chain+-- would necessarily have to start at an implicitly trusted (though unverified)+-- file: the root metadata.+trustLocalFile :: Signed a -> Trusted a+trustLocalFile Signed{..} = DeclareTrusted signed++-- | Just a simple wrapper around 'verifyFileInfo'+--+-- Throws a VerificationError if verification failed.+verifyFileInfo' :: (MonadIO m, DownloadedFile down)+ => Maybe (Trusted FileInfo)+ -> TargetPath -- ^ For error messages+ -> down typ -- ^ File to verify+ -> m ()+verifyFileInfo' Nothing _ _ = return ()+verifyFileInfo' (Just info) targetPath tempPath = liftIO $ do+ verified <- downloadedVerify tempPath info+ unless verified $ throw $ VerificationErrorFileInfo targetPath++readCachedJSON :: (MonadIO m, FromJSON ReadJSON_Keys_Layout a)+ => Repository down -> KeyEnv -> Path Absolute+ -> m (Either DeserializationError a)+readCachedJSON Repository{..} keyEnv fp = liftIO $ do+ bs <- readLazyByteString fp+ evaluate $ parseJSON_Keys_Layout keyEnv repLayout bs++readDownloadedJSON :: (MonadIO m, FromJSON ReadJSON_Keys_Layout a)+ => Repository down -> KeyEnv -> down Metadata+ -> m (Either DeserializationError a)+readDownloadedJSON Repository{..} keyEnv fp = liftIO $ do+ bs <- downloadedRead fp+ evaluate $ parseJSON_Keys_Layout keyEnv repLayout bs++throwErrorsUnchecked :: ( MonadIO m+ , Exception e'+ )+ => (e -> e') -> Either e a -> m a+throwErrorsUnchecked f (Left err) = liftIO $ throwUnchecked (f err)+throwErrorsUnchecked _ (Right a) = return a++throwErrorsChecked :: ( Throws e'+ , MonadIO m+ , Exception e'+ )+ => (e -> e') -> Either e a -> m a+throwErrorsChecked f (Left err) = liftIO $ throwChecked (f err)+throwErrorsChecked _ (Right a) = return a++eitherToMaybe :: Either a b -> Maybe b+eitherToMaybe (Left _) = Nothing+eitherToMaybe (Right b) = Just b
+ hackage-security/hackage-security/src/Hackage/Security/Client/Formats.hs view
@@ -0,0 +1,116 @@+module Hackage.Security.Client.Formats (+ -- * Formats+ -- ** Type level+ FormatUn+ , FormatGz+ -- ** Term level+ , Format(..)+ , Formats(..)+ -- * Key membership+ , HasFormat(..)+ -- ** Utility+ , hasFormatAbsurd+ , hasFormatGet+ -- * Map-like operations+ , formatsMap+ , formatsMember+ , formatsLookup+ ) where++import Hackage.Security.Util.Stack+import Hackage.Security.Util.TypedEmbedded++{-------------------------------------------------------------------------------+ Formats+-------------------------------------------------------------------------------}++data FormatUn+data FormatGz++-- | Format is a singleton type (reflection type to term level)+--+-- NOTE: In the future we might add further compression formats.+data Format :: * -> * where+ FUn :: Format FormatUn+ FGz :: Format FormatGz++deriving instance Show (Format f)+deriving instance Eq (Format f)++instance Unify Format where+ unify FUn FUn = Just Refl+ unify FGz FGz = Just Refl+ unify _ _ = Nothing++{-------------------------------------------------------------------------------+ Products+-------------------------------------------------------------------------------}++-- | Available formats+--+-- Rather than having a general list here, we enumerate all possibilities.+-- This means we are very precise about what we expect, and we avoid any runtime+-- errors about unexpect format definitions.+--+-- NOTE: If we add additional cases here (for dealing with additional formats)+-- all calls to @error "inaccessible"@ need to be reevaluated.+data Formats :: * -> * -> * where+ FsNone :: Formats () a+ FsUn :: a -> Formats (FormatUn :- ()) a+ FsGz :: a -> Formats (FormatGz :- ()) a+ FsUnGz :: a -> a -> Formats (FormatUn :- FormatGz :- ()) a++deriving instance Eq a => Eq (Formats fs a)+deriving instance Show a => Show (Formats fs a)++instance Functor (Formats fs) where+ fmap g = formatsMap (\_format -> g)++{-------------------------------------------------------------------------------+ Key membership+-------------------------------------------------------------------------------}++-- | @HasFormat fs f@ is a proof that @f@ is a key in @fs@.+--+-- See 'formatsMember' and 'formatsLookup' for typical usage.+data HasFormat :: * -> * -> * where+ HFZ :: Format f -> HasFormat (f :- fs) f+ HFS :: HasFormat fs f -> HasFormat (f' :- fs) f++deriving instance Eq (HasFormat fs f)+deriving instance Show (HasFormat fs f)++hasFormatAbsurd :: HasFormat () f -> a+hasFormatAbsurd _ = error "inaccessible"++hasFormatGet :: HasFormat fs f -> Format f+hasFormatGet (HFZ f) = f+hasFormatGet (HFS hf) = hasFormatGet hf++{-------------------------------------------------------------------------------+ Map-like functionality+-------------------------------------------------------------------------------}++formatsMap :: (forall f. Format f -> a -> b) -> Formats fs a -> Formats fs b+formatsMap _ FsNone = FsNone+formatsMap f (FsUn a) = FsUn (f FUn a)+formatsMap f (FsGz a) = FsGz (f FGz a)+formatsMap f (FsUnGz a a') = FsUnGz (f FUn a) (f FGz a')++formatsMember :: Format f -> Formats fs a -> Maybe (HasFormat fs f)+formatsMember _ FsNone = Nothing+formatsMember FUn (FsUn _ ) = Just $ HFZ FUn+formatsMember FUn (FsGz _) = Nothing+formatsMember FUn (FsUnGz _ _) = Just $ HFZ FUn+formatsMember FGz (FsUn _ ) = Nothing+formatsMember FGz (FsGz _) = Just $ HFZ FGz+formatsMember FGz (FsUnGz _ _) = Just $ HFS (HFZ FGz)++formatsLookup :: HasFormat fs f -> Formats fs a -> a+formatsLookup (HFZ FUn) (FsUn a ) = a+formatsLookup (HFZ FUn) (FsUnGz a _) = a+formatsLookup (HFZ FGz) (FsGz a) = a+formatsLookup (HFS hf) (FsUn _ ) = hasFormatAbsurd hf+formatsLookup (HFS hf) (FsGz _) = hasFormatAbsurd hf+formatsLookup (HFS hf) (FsUnGz _ a) = formatsLookup hf (FsGz a)+formatsLookup _ _ = error "inaccessible"
+ hackage-security/hackage-security/src/Hackage/Security/Client/Repository.hs view
@@ -0,0 +1,462 @@+-- | Abstract definition of a Repository+--+-- Most clients should only need to import this module if they wish to define+-- their own Repository implementations.+{-# LANGUAGE CPP #-}+module Hackage.Security.Client.Repository (+ -- * Files+ Metadata -- type index (really a kind)+ , Binary -- type index (really a kind)+ , RemoteFile(..)+ , CachedFile(..)+ , IndexFile(..)+ , remoteFileDefaultFormat+ , remoteFileDefaultInfo+ -- * Repository proper+ , Repository(..)+ , AttemptNr(..)+ , LogMessage(..)+ , UpdateFailure(..)+ , SomeRemoteError(..)+ -- ** Downloaded files+ , DownloadedFile(..)+ -- ** Helpers+ , mirrorsUnsupported+ -- * Paths+ , remoteRepoPath+ , remoteRepoPath'+ -- * Utility+ , IsCached(..)+ , mustCache+ ) where++import Control.Exception+import Data.Typeable (Typeable)+import qualified Codec.Archive.Tar.Index as Tar+import qualified Data.ByteString.Lazy as BS.L++import Distribution.Package+import Distribution.Text++import Hackage.Security.Client.Formats+import Hackage.Security.Client.Verify+import Hackage.Security.Trusted+import Hackage.Security.TUF+import Hackage.Security.Util.Checked+import Hackage.Security.Util.Path+import Hackage.Security.Util.Pretty+import Hackage.Security.Util.Some+import Hackage.Security.Util.Stack++{-------------------------------------------------------------------------------+ Files+-------------------------------------------------------------------------------}++data Metadata+data Binary++-- | Abstract definition of files we might have to download+--+-- 'RemoteFile' is parametrized by the type of the formats that we can accept+-- from the remote repository, as well as with information on whether this file+-- is metadata actual binary content.+--+-- NOTE: Haddock lacks GADT support so constructors have only regular comments.+data RemoteFile :: * -> * -> * where+ -- Timestamp metadata (@timestamp.json@)+ --+ -- We never have (explicit) file length available for timestamps.+ RemoteTimestamp :: RemoteFile (FormatUn :- ()) Metadata++ -- Root metadata (@root.json@)+ --+ -- For root information we may or may not have the file info available:+ --+ -- - If during the normal update process the new snapshot tells us the root+ -- information has changed, we can use the file info from the snapshot.+ -- - If however we need to update the root metadata due to a verification+ -- exception we do not know the file info.+ -- - We also do not know the file info during bootstrapping.+ RemoteRoot :: Maybe (Trusted FileInfo) -> RemoteFile (FormatUn :- ()) Metadata++ -- Snapshot metadata (@snapshot.json@)+ --+ -- We get file info of the snapshot from the timestamp.+ RemoteSnapshot :: Trusted FileInfo -> RemoteFile (FormatUn :- ()) Metadata++ -- Mirrors metadata (@mirrors.json@)+ --+ -- We get the file info from the snapshot.+ RemoteMirrors :: Trusted FileInfo -> RemoteFile (FormatUn :- ()) Metadata++ -- Index+ --+ -- The index file length comes from the snapshot.+ --+ -- When we request that the index is downloaded, it is up to the repository+ -- to decide whether to download @00-index.tar@ or @00-index.tar.gz@.+ -- The callback is told which format was requested.+ --+ -- It is a bug to request a file that the repository does not provide+ -- (the snapshot should make it clear which files are available).+ RemoteIndex :: HasFormat fs FormatGz+ -> Formats fs (Trusted FileInfo)+ -> RemoteFile fs Binary++ -- Actual package+ --+ -- Package file length comes from the corresponding @targets.json@.+ RemotePkgTarGz :: PackageIdentifier+ -> Trusted FileInfo+ -> RemoteFile (FormatGz :- ()) Binary++deriving instance Show (RemoteFile fs typ)++instance Pretty (RemoteFile fs typ) where+ pretty RemoteTimestamp = "timestamp"+ pretty (RemoteRoot _) = "root"+ pretty (RemoteSnapshot _) = "snapshot"+ pretty (RemoteMirrors _) = "mirrors"+ pretty (RemoteIndex _ _) = "index"+ pretty (RemotePkgTarGz pkgId _) = "package " ++ display pkgId++-- | Files that we might request from the local cache+data CachedFile =+ -- | Timestamp metadata (@timestamp.json@)+ CachedTimestamp++ -- | Root metadata (@root.json@)+ | CachedRoot++ -- | Snapshot metadata (@snapshot.json@)+ | CachedSnapshot++ -- | Mirrors list (@mirrors.json@)+ | CachedMirrors+ deriving (Eq, Ord, Show)++instance Pretty CachedFile where+ pretty CachedTimestamp = "timestamp"+ pretty CachedRoot = "root"+ pretty CachedSnapshot = "snapshot"+ pretty CachedMirrors = "mirrors"++-- | Default format for each file type+--+-- For most file types we don't have a choice; for the index the repository+-- is only required to offer the GZip-compressed format so that is the default.+remoteFileDefaultFormat :: RemoteFile fs typ -> Some (HasFormat fs)+remoteFileDefaultFormat RemoteTimestamp = Some $ HFZ FUn+remoteFileDefaultFormat (RemoteRoot _) = Some $ HFZ FUn+remoteFileDefaultFormat (RemoteSnapshot _) = Some $ HFZ FUn+remoteFileDefaultFormat (RemoteMirrors _) = Some $ HFZ FUn+remoteFileDefaultFormat (RemotePkgTarGz _ _) = Some $ HFZ FGz+remoteFileDefaultFormat (RemoteIndex pf _) = Some pf++-- | Default file info (see also 'remoteFileDefaultFormat')+remoteFileDefaultInfo :: RemoteFile fs typ -> Maybe (Trusted FileInfo)+remoteFileDefaultInfo RemoteTimestamp = Nothing+remoteFileDefaultInfo (RemoteRoot info) = info+remoteFileDefaultInfo (RemoteSnapshot info) = Just info+remoteFileDefaultInfo (RemoteMirrors info) = Just info+remoteFileDefaultInfo (RemotePkgTarGz _ info) = Just info+remoteFileDefaultInfo (RemoteIndex pf info) = Just $ formatsLookup pf info++{-------------------------------------------------------------------------------+ Repository proper+-------------------------------------------------------------------------------}++-- | Repository+--+-- This is an abstract representation of a repository. It simply provides a way+-- to download metafiles and target files, without specifying how this is done.+-- For instance, for a local repository this could just be doing a file read,+-- whereas for remote repositories this could be using any kind of HTTP client.+data Repository down = DownloadedFile down => Repository {+ -- | Get a file from the server+ --+ -- Responsibilies of 'repGetRemote':+ --+ -- * Download the file from the repository and make it available at a+ -- temporary location+ -- * Use the provided file length to protect against endless data attacks.+ -- (Repositories such as local repositories that are not suspectible to+ -- endless data attacks can safely ignore this argument.)+ -- * Move the file from its temporary location to its permanent location+ -- if verification succeeds.+ --+ -- NOTE: Calls to 'repGetRemote' should _always_ be in the scope of+ -- 'repWithMirror'.+ repGetRemote :: forall fs typ. Throws SomeRemoteError+ => AttemptNr+ -> RemoteFile fs typ+ -> Verify (Some (HasFormat fs), down typ)++ -- | Get a cached file (if available)+ , repGetCached :: CachedFile -> IO (Maybe (Path Absolute))++ -- | Get the cached root+ --+ -- This is a separate method only because clients must ALWAYS have root+ -- information available.+ , repGetCachedRoot :: IO (Path Absolute)++ -- | Clear all cached data+ --+ -- In particular, this should remove the snapshot and the timestamp.+ -- It would also be okay, but not required, to delete the index.+ , repClearCache :: IO ()++ -- | Open the tarball for reading+ --+ -- This function has this shape so that:+ --+ -- * We can read multiple files from the tarball without having to open+ -- and close the handle each time+ -- * We can close the handle immediately when done.+ , repWithIndex :: forall a. (Handle -> IO a) -> IO a++ -- | Read the index index+ , repGetIndexIdx :: IO Tar.TarIndex++ -- | Lock the cache (during updates)+ , repLockCache :: IO () -> IO ()++ -- | Mirror selection+ --+ -- The purpose of 'repWithMirror' is to scope mirror selection. The idea+ -- is that if we have+ --+ -- > repWithMirror mirrorList $+ -- > someCallback+ --+ -- then the repository may pick a mirror before calling @someCallback@,+ -- catch exceptions thrown by @someCallback@, and potentially try the+ -- callback again with a different mirror.+ --+ -- The list of mirrors may be @Nothing@ if we haven't yet downloaded the+ -- list of mirrors from the repository, or when our cached list of mirrors+ -- is invalid. Of course, if we did download it, then the list of mirrors+ -- may still be empty. In this case the repository must fall back to its+ -- primary download mechanism.+ --+ -- Mirrors as currently defined (in terms of a "base URL") are inherently a+ -- HTTP (or related) concept, so in repository implementations such as the+ -- local-repo 'repWithMirrors' is probably just an identity operation (see+ -- 'ignoreMirrors'). Conversely, HTTP implementations of repositories may+ -- have other, out-of-band information (for example, coming from a cabal+ -- config file) that they may use to influence mirror selection.+ , repWithMirror :: forall a. Maybe [Mirror] -> IO a -> IO a++ -- | Logging+ , repLog :: LogMessage -> IO ()++ -- | Layout of this repository+ , repLayout :: RepoLayout++ -- | Layout of the index+ --+ -- Since the repository hosts the index, the layout of the index is+ -- not independent of the layout of the repository.+ , repIndexLayout :: IndexLayout++ -- | Description of the repository (used in the show instance)+ , repDescription :: String+ }++instance Show (Repository down) where+ show = repDescription++-- | Helper function to implement 'repWithMirrors'.+mirrorsUnsupported :: Maybe [Mirror] -> IO a -> IO a+mirrorsUnsupported _ = id++-- | Are we requesting this information because of a previous validation error?+--+-- Clients can take advantage of this to tell caches to revalidate files.+newtype AttemptNr = AttemptNr Int+ deriving (Eq, Ord, Num)++-- | Log messages+--+-- We use a 'RemoteFile' rather than a 'RepoPath' here because we might not have+-- a 'RepoPath' for the file that we were trying to download (that is, for+-- example if the server does not provide an uncompressed tarball, it doesn't+-- make much sense to list the path to that non-existing uncompressed tarball).+data LogMessage =+ -- | Root information was updated+ --+ -- This message is issued when the root information is updated as part of+ -- the normal check for updates procedure. If the root information is+ -- updated because of a verification error WarningVerificationError is+ -- issued instead.+ LogRootUpdated++ -- | A verification error+ --+ -- Verification errors can be temporary, and may be resolved later; hence+ -- these are just warnings. (Verification errors that cannot be resolved+ -- are thrown as exceptions.)+ | LogVerificationError VerificationError++ -- | Download a file from a repository+ | forall fs typ. LogDownloading (RemoteFile fs typ)++ -- | Incrementally updating a file from a repository+ | forall fs. LogUpdating (RemoteFile fs Binary)++ -- | Selected a particular mirror+ | LogSelectedMirror MirrorDescription++ -- | Updating a file failed+ -- (we will instead download it whole)+ | forall fs. LogCannotUpdate (RemoteFile fs Binary) UpdateFailure++ -- | We got an exception with a particular mirror+ -- (we will try with a different mirror if any are available)+ | LogMirrorFailed MirrorDescription SomeException++-- | Records why we are downloading a file rather than updating it.+data UpdateFailure =+ -- | Server does not support incremental downloads+ UpdateImpossibleUnsupported++ -- | We don't have a local copy of the file to update+ | UpdateImpossibleNoLocalCopy++ -- | Update failed twice+ --+ -- If we attempt an incremental update the first time, and it fails, we let+ -- it go round the loop, update local security information, and try again.+ -- But if an incremental update then fails _again_, we instead attempt a+ -- regular download.+ | UpdateFailedTwice++ -- | Update failed (for example: perhaps the local file got corrupted)+ | UpdateFailed SomeException++{-------------------------------------------------------------------------------+ Downloaded files+-------------------------------------------------------------------------------}++class DownloadedFile (down :: * -> *) where+ -- | Verify a download file+ downloadedVerify :: down a -> Trusted FileInfo -> IO Bool++ -- | Read the file we just downloaded into memory+ --+ -- We never read binary data, only metadata.+ downloadedRead :: down Metadata -> IO BS.L.ByteString++ -- | Copy a downloaded file to its destination+ downloadedCopyTo :: down a -> Path Absolute -> IO ()++{-------------------------------------------------------------------------------+ Exceptions thrown by specific Repository implementations+-------------------------------------------------------------------------------}++-- | Repository-specific exceptions+--+-- For instance, for repositories using HTTP this might correspond to a 404;+-- for local repositories this might correspond to file-not-found, etc.+data SomeRemoteError :: * where+ SomeRemoteError :: Exception e => e -> SomeRemoteError+ deriving (Typeable)++#if MIN_VERSION_base(4,8,0)+deriving instance Show SomeRemoteError+instance Exception SomeRemoteError where displayException = pretty+#else+instance Exception SomeRemoteError+instance Show SomeRemoteError where show = pretty+#endif++instance Pretty SomeRemoteError where+ pretty (SomeRemoteError ex) = displayException ex++{-------------------------------------------------------------------------------+ Paths+-------------------------------------------------------------------------------}++remoteRepoPath :: RepoLayout -> RemoteFile fs typ -> Formats fs RepoPath+remoteRepoPath RepoLayout{..} = go+ where+ go :: RemoteFile fs typ -> Formats fs RepoPath+ go RemoteTimestamp = FsUn $ repoLayoutTimestamp+ go (RemoteRoot _) = FsUn $ repoLayoutRoot+ go (RemoteSnapshot _) = FsUn $ repoLayoutSnapshot+ go (RemoteMirrors _) = FsUn $ repoLayoutMirrors+ go (RemotePkgTarGz pId _) = FsGz $ repoLayoutPkgTarGz pId+ go (RemoteIndex _ lens) = formatsMap goIndex lens++ goIndex :: Format f -> a -> RepoPath+ goIndex FUn _ = repoLayoutIndexTar+ goIndex FGz _ = repoLayoutIndexTarGz++remoteRepoPath' :: RepoLayout -> RemoteFile fs typ -> HasFormat fs f -> RepoPath+remoteRepoPath' repoLayout file format =+ formatsLookup format $ remoteRepoPath repoLayout file++{-------------------------------------------------------------------------------+ Utility+-------------------------------------------------------------------------------}++-- | Is a particular remote file cached?+data IsCached :: * -> * where+ -- | This remote file should be cached, and we ask for it by name+ CacheAs :: CachedFile -> IsCached Metadata++ -- | We don't cache this remote file+ --+ -- This doesn't mean a Repository should not feel free to cache the file+ -- if desired, but it does mean the generic algorithms will never ask for+ -- this file from the cache.+ DontCache :: IsCached Binary++ -- | The index is somewhat special: it should be cached, but we never+ -- ask for it directly.+ --+ -- Instead, we will ask the Repository for files _from_ the index, which it+ -- can serve however it likes. For instance, some repositories might keep+ -- the index in uncompressed form, others in compressed form; some might+ -- keep an index tarball index for quick access, others may scan the tarball+ -- linearly, etc.+ CacheIndex :: IsCached Binary++deriving instance Eq (IsCached typ)+deriving instance Show (IsCached typ)++-- | Which remote files should we cache locally?+mustCache :: RemoteFile fs typ -> IsCached typ+mustCache RemoteTimestamp = CacheAs CachedTimestamp+mustCache (RemoteRoot _) = CacheAs CachedRoot+mustCache (RemoteSnapshot _) = CacheAs CachedSnapshot+mustCache (RemoteMirrors _) = CacheAs CachedMirrors+mustCache (RemoteIndex {}) = CacheIndex+mustCache (RemotePkgTarGz _ _) = DontCache++instance Pretty LogMessage where+ pretty LogRootUpdated =+ "Root info updated"+ pretty (LogVerificationError err) =+ "Verification error: " ++ pretty err+ pretty (LogDownloading file) =+ "Downloading " ++ pretty file+ pretty (LogUpdating file) =+ "Updating " ++ pretty file+ pretty (LogSelectedMirror mirror) =+ "Selected mirror " ++ mirror+ pretty (LogCannotUpdate file ex) =+ "Cannot update " ++ pretty file ++ " (" ++ pretty ex ++ ")"+ pretty (LogMirrorFailed mirror ex) =+ "Exception " ++ displayException ex ++ " when using mirror " ++ mirror++instance Pretty UpdateFailure where+ pretty UpdateImpossibleUnsupported =+ "server does not provide incremental downloads"+ pretty UpdateImpossibleNoLocalCopy =+ "no local copy"+ pretty UpdateFailedTwice =+ "update failed twice"+ pretty (UpdateFailed ex) =+ displayException ex
+ hackage-security/hackage-security/src/Hackage/Security/Client/Repository/Cache.hs view
@@ -0,0 +1,204 @@+{-# LANGUAGE BangPatterns #-}+-- | The files we cache from the repository+--+-- Both the Local and the Remote repositories make use of this module.+module Hackage.Security.Client.Repository.Cache (+ Cache(..)+ , getCached+ , getCachedRoot+ , getCachedIndex+ , clearCache+ , withIndex+ , getIndexIdx+ , cacheRemoteFile+ , lockCache+ ) where++import Control.Exception+import Control.Monad+import Codec.Archive.Tar (Entries(..))+import Codec.Archive.Tar.Index (TarIndex, IndexBuilder, TarEntryOffset)+import qualified Codec.Archive.Tar as Tar+import qualified Codec.Archive.Tar.Index as TarIndex+import qualified Codec.Compression.GZip as GZip+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as BS.L++import Hackage.Security.Client.Repository+import Hackage.Security.Client.Formats+import Hackage.Security.TUF+import Hackage.Security.Util.Checked+import Hackage.Security.Util.IO+import Hackage.Security.Util.Path++-- | Location and layout of the local cache+data Cache = Cache {+ cacheRoot :: Path Absolute+ , cacheLayout :: CacheLayout+ }++-- | Cache a previously downloaded remote file+cacheRemoteFile :: forall down typ f. DownloadedFile down+ => Cache -> down typ -> Format f -> IsCached typ -> IO ()+cacheRemoteFile cache downloaded f isCached = do+ go f isCached+ case isCached of+ CacheIndex -> rebuildTarIndex cache+ _otherwise -> return ()+ where+ go :: Format f -> IsCached typ -> IO ()+ go _ DontCache = return ()+ go FUn (CacheAs file) = copyTo (cachedFilePath cache file)+ go FGz CacheIndex = copyTo (cachedIndexPath cache FGz) >> unzipIndex+ go _ _ = error "cacheRemoteFile: unexpected case" -- TODO: enforce in types?++ copyTo :: Path Absolute -> IO ()+ copyTo fp = do+ createDirectoryIfMissing True (takeDirectory fp)+ downloadedCopyTo downloaded fp++ -- Whether or not we downloaded the compressed index incrementally, we can+ -- always update the uncompressed index incrementally.+ -- NOTE: This assumes we already updated the compressed file.+ unzipIndex :: typ ~ Binary => IO ()+ unzipIndex = do+ createDirectoryIfMissing True (takeDirectory indexUn)+ compressed <- readLazyByteString indexGz+ let uncompressed = GZip.decompress compressed+ withFile indexUn ReadWriteMode $ \h -> do+ currentSize <- hFileSize h+ let seekTo = 0 `max` (currentSize - tarTrailer)+ hSeek h AbsoluteSeek seekTo+ BS.L.hPut h $ BS.L.drop (fromInteger seekTo) uncompressed+ where+ indexGz = cachedIndexPath cache FGz+ indexUn = cachedIndexPath cache FUn++ tarTrailer :: Integer+ tarTrailer = 1024++-- | Rebuild the tarball index+--+-- Attempts to add to the existing index, if one exists.+--+-- TODO: Use throwChecked rather than throwUnchecked, and deal with the fallout.+-- See <https://github.com/well-typed/hackage-security/issues/84>.+rebuildTarIndex :: Cache -> IO ()+rebuildTarIndex cache = do+ (builder, offset) <- initBuilder <$> tryReadIndex (cachedIndexIdxPath cache)+ withFile (cachedIndexPath cache FUn) ReadMode $ \hTar -> do+ TarIndex.hSeekEntryOffset hTar offset+ newEntries <- Tar.read <$> BS.L.hGetContents hTar+ case addEntries builder newEntries of+ Left ex -> throwUnchecked ex+ Right idx -> withFile (cachedIndexIdxPath cache) WriteMode $ \hIdx -> do+ hSetBuffering hIdx (BlockBuffering Nothing)+ BS.hPut hIdx $ TarIndex.serialise idx+ where+ -- The initial index builder+ -- If we don't have an index (or it's broken), we start from scratch+ initBuilder :: Either e TarIndex -> (IndexBuilder, TarEntryOffset)+ initBuilder (Left _) = ( TarIndex.empty, 0 )+ initBuilder (Right idx) = ( TarIndex.unfinalise idx+ , TarIndex.indexEndEntryOffset idx+ )++-- | Get a cached file (if available)+getCached :: Cache -> CachedFile -> IO (Maybe (Path Absolute))+getCached cache cachedFile = do+ exists <- doesFileExist localPath+ if exists then return $ Just localPath+ else return $ Nothing+ where+ localPath = cachedFilePath cache cachedFile++-- | Get the cached index (if available)+getCachedIndex :: Cache -> Format f -> IO (Maybe (Path Absolute))+getCachedIndex cache format = do+ exists <- doesFileExist localPath+ if exists then return $ Just localPath+ else return $ Nothing+ where+ localPath = cachedIndexPath cache format++-- | Get the cached root+--+-- Calling 'getCachedRoot' without root info available is a programmer error+-- and will result in an unchecked exception. See 'requiresBootstrap'.+getCachedRoot :: Cache -> IO (Path Absolute)+getCachedRoot cache = do+ mPath <- getCached cache CachedRoot+ case mPath of+ Just p -> return p+ Nothing -> internalError "Client missing root info"++getIndexIdx :: Cache -> IO TarIndex+getIndexIdx cache = do+ mIndex <- tryReadIndex $ cachedIndexIdxPath cache+ case mIndex of+ Left _ -> throwIO $ userError "Could not read index. Did you call 'checkForUpdates'?"+ Right idx -> return idx++withIndex :: Cache -> (Handle -> IO a) -> IO a+withIndex cache = withFile (cachedIndexPath cache FUn) ReadMode++-- | Delete a previously downloaded remote file+clearCache :: Cache -> IO ()+clearCache cache = void . handleDoesNotExist $ do+ removeFile $ cachedFilePath cache CachedTimestamp+ removeFile $ cachedFilePath cache CachedSnapshot++-- | Lock the cache+--+-- This avoids two concurrent processes updating the cache at the same time,+-- provided they both take the lock.+lockCache :: Cache -> IO () -> IO ()+lockCache Cache{..} = withDirLock cacheRoot++{-------------------------------------------------------------------------------+ Auxiliary: tar+-------------------------------------------------------------------------------}++-- | Variation on 'TarIndex.build' that takes in the initial 'IndexBuilder'+addEntries :: IndexBuilder -> Entries e -> Either e TarIndex+addEntries = go+ where+ go !builder (Next e es) = go (TarIndex.addNextEntry e builder) es+ go !builder Done = Right $! TarIndex.finalise builder+ go !_ (Fail err) = Left err++-- TODO: How come 'deserialise' uses _strict_ ByteStrings?+tryReadIndex :: Path Absolute -> IO (Either (Maybe IOException) TarIndex)+tryReadIndex fp =+ aux <$> try (TarIndex.deserialise <$> readStrictByteString fp)+ where+ aux :: Either e (Maybe (a, leftover)) -> Either (Maybe e) a+ aux (Left e) = Left (Just e)+ aux (Right Nothing) = Left Nothing+ aux (Right (Just (a, _))) = Right a++{-------------------------------------------------------------------------------+ Auxiliary: paths+-------------------------------------------------------------------------------}++cachedFilePath :: Cache -> CachedFile -> Path Absolute+cachedFilePath Cache{cacheLayout=CacheLayout{..}, ..} file =+ anchorCachePath cacheRoot $ go file+ where+ go :: CachedFile -> CachePath+ go CachedRoot = cacheLayoutRoot+ go CachedTimestamp = cacheLayoutTimestamp+ go CachedSnapshot = cacheLayoutSnapshot+ go CachedMirrors = cacheLayoutMirrors++cachedIndexPath :: Cache -> Format f -> Path Absolute+cachedIndexPath Cache{..} format =+ anchorCachePath cacheRoot $ go format+ where+ go :: Format f -> CachePath+ go FUn = cacheLayoutIndexTar cacheLayout+ go FGz = cacheLayoutIndexTarGz cacheLayout++cachedIndexIdxPath :: Cache -> Path Absolute+cachedIndexIdxPath Cache{..} =+ anchorCachePath cacheRoot $ cacheLayoutIndexIdx cacheLayout
+ hackage-security/hackage-security/src/Hackage/Security/Client/Repository/HttpLib.hs view
@@ -0,0 +1,142 @@+{-# LANGUAGE CPP #-}+-- | Abstracting over HTTP libraries+module Hackage.Security.Client.Repository.HttpLib (+ HttpLib(..)+ , HttpRequestHeader(..)+ , HttpResponseHeader(..)+ , HttpStatus(..)+ , ProxyConfig(..)+ -- ** Body reader+ , BodyReader+ , bodyReaderFromBS+ ) where++import Data.IORef+import Network.URI hiding (uriPath, path)+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as BS.L++import Hackage.Security.Util.Checked+import Hackage.Security.Client.Repository (SomeRemoteError)++{-------------------------------------------------------------------------------+ Abstraction over HTTP clients (such as HTTP, http-conduit, etc.)+-------------------------------------------------------------------------------}++-- | Abstraction over HTTP clients+--+-- This avoids insisting on a particular implementation (such as the HTTP+-- package) and allows for other implementations (such as a conduit based one).+--+-- NOTE: Library-specific exceptions MUST be wrapped in 'SomeRemoteError'.+data HttpLib = HttpLib {+ -- | Download a file+ httpGet :: forall a. Throws SomeRemoteError+ => [HttpRequestHeader]+ -> URI+ -> ([HttpResponseHeader] -> BodyReader -> IO a)+ -> IO a++ -- | Download a byte range+ --+ -- Range is starting and (exclusive) end offset in bytes.+ --+ -- HTTP servers are normally expected to respond to a range request with+ -- a "206 Partial Content" response. However, servers can respond with a+ -- "200 OK" response, sending the entire file instead (for instance, this+ -- may happen for servers that don't actually support range rqeuests, but+ -- for which we optimistically assumed they did). Implementations of+ -- 'HttpLib' may accept such a response and inform the @hackage-security@+ -- library that the whole file is being returned; the security library can+ -- then decide to execute the 'BodyReader' anyway (downloading the entire+ -- file) or abort the request and try something else. For this reason+ -- the security library must be informed whether the server returned the+ -- full file or the requested range.+ , httpGetRange :: forall a. Throws SomeRemoteError+ => [HttpRequestHeader]+ -> URI+ -> (Int, Int)+ -> (HttpStatus -> [HttpResponseHeader] -> BodyReader -> IO a)+ -> IO a+ }++-- | Additional request headers+--+-- Since different libraries represent headers differently, here we just+-- abstract over the few request headers that we might want to set+data HttpRequestHeader =+ -- | Set @Cache-Control: max-age=0@+ HttpRequestMaxAge0++ -- | Set @Cache-Control: no-transform@+ | HttpRequestNoTransform+ deriving (Eq, Ord, Show)++-- | HTTP status code+data HttpStatus =+ -- | 200 OK+ HttpStatus200OK++ -- | 206 Partial Content+ | HttpStatus206PartialContent++-- | Response headers+--+-- Since different libraries represent headers differently, here we just+-- abstract over the few response headers that we might want to know about.+data HttpResponseHeader =+ -- | Server accepts byte-range requests (@Accept-Ranges: bytes@)+ HttpResponseAcceptRangesBytes+ deriving (Eq, Ord, Show)++-- | Proxy configuration+--+-- Although actually setting the proxy is the purview of the initialization+-- function for individual 'HttpLib' implementations and therefore outside+-- the scope of this module, we offer this 'ProxyConfiguration' type here as a+-- way to uniformly configure proxies across all 'HttpLib's.+data ProxyConfig a =+ -- | Don't use a proxy+ ProxyConfigNone++ -- | Use this specific proxy+ --+ -- Individual HTTP backends use their own types for specifying proxies.+ | ProxyConfigUse a++ -- | Use automatic proxy settings+ --+ -- What precisely automatic means is 'HttpLib' specific, though+ -- typically it will involve looking at the @HTTP_PROXY@ environment+ -- variable or the (Windows) registry.+ | ProxyConfigAuto++{-------------------------------------------------------------------------------+ Body readers+-------------------------------------------------------------------------------}++-- | An @IO@ action that represents an incoming response body coming from the+-- server.+--+-- The action gets a single chunk of data from the response body, or an empty+-- bytestring if no more data is available.+--+-- This definition is copied from the @http-client@ package.+type BodyReader = IO BS.ByteString++-- | Construct a 'Body' reader from a lazy bytestring+--+-- This is appropriate if the lazy bytestring is constructed, say, by calling+-- 'hGetContents' on a network socket, and the chunks of the bytestring+-- correspond to the chunks as they are returned from the OS network layer.+--+-- If the lazy bytestring needs to be re-chunked this function is NOT suitable.+bodyReaderFromBS :: BS.L.ByteString -> IO BodyReader+bodyReaderFromBS lazyBS = do+ chunks <- newIORef $ BS.L.toChunks lazyBS+ -- NOTE: Lazy bytestrings invariant: no empty chunks+ let br = do bss <- readIORef chunks+ case bss of+ [] -> return BS.empty+ (bs:bss') -> writeIORef chunks bss' >> return bs+ return br
+ hackage-security/hackage-security/src/Hackage/Security/Client/Repository/Local.hs view
@@ -0,0 +1,99 @@+-- | Local repository+module Hackage.Security.Client.Repository.Local (+ LocalRepo+ , LocalFile -- opaque+ , withRepository+ ) where++import Hackage.Security.Client.Formats+import Hackage.Security.Client.Repository+import Hackage.Security.Client.Repository.Cache+import Hackage.Security.Client.Verify+import Hackage.Security.TUF+import Hackage.Security.Trusted+import Hackage.Security.Util.IO+import Hackage.Security.Util.Path+import Hackage.Security.Util.Pretty+import Hackage.Security.Util.Some++-- | Location of the repository+--+-- Note that we regard the local repository as immutable; we cache files just+-- like we do for remote repositories.+type LocalRepo = Path Absolute++-- | Initialize the repository (and cleanup resources afterwards)+--+-- Like a remote repository, a local repository takes a RepoLayout as argument;+-- but where the remote repository interprets this RepoLayout relative to a URL,+-- the local repository interprets it relative to a local directory.+--+-- It uses the same cache as the remote repository.+withRepository+ :: LocalRepo -- ^ Location of local repository+ -> Cache -- ^ Location of local cache+ -> RepoLayout -- ^ Repository layout+ -> IndexLayout -- ^ Index layout+ -> (LogMessage -> IO ()) -- ^ Logger+ -> (Repository LocalFile -> IO a) -- ^ Callback+ -> IO a+withRepository repo+ cache+ repLayout+ repIndexLayout+ logger+ callback+ =+ callback Repository {+ repGetRemote = getRemote repLayout repo cache+ , repGetCached = getCached cache+ , repGetCachedRoot = getCachedRoot cache+ , repClearCache = clearCache cache+ , repWithIndex = withIndex cache+ , repGetIndexIdx = getIndexIdx cache+ , repLockCache = lockCache cache+ , repWithMirror = mirrorsUnsupported+ , repLog = logger+ , repLayout = repLayout+ , repIndexLayout = repIndexLayout+ , repDescription = "Local repository at " ++ pretty repo+ }++-- | Get a file from the server+getRemote :: RepoLayout -> LocalRepo -> Cache+ -> AttemptNr+ -> RemoteFile fs typ+ -> Verify (Some (HasFormat fs), LocalFile typ)+getRemote repoLayout repo cache _attemptNr remoteFile = do+ case remoteFileDefaultFormat remoteFile of+ Some format -> do+ let remotePath' = remoteRepoPath' repoLayout remoteFile format+ remotePath = anchorRepoPathLocally repo remotePath'+ localFile = LocalFile remotePath+ ifVerified $+ cacheRemoteFile cache+ localFile+ (hasFormatGet format)+ (mustCache remoteFile)+ return (Some format, localFile)++{-------------------------------------------------------------------------------+ Files in the local repository+-------------------------------------------------------------------------------}++newtype LocalFile a = LocalFile (Path Absolute)++instance DownloadedFile LocalFile where+ downloadedVerify = verifyLocalFile+ downloadedRead = \(LocalFile local) -> readLazyByteString local+ downloadedCopyTo = \(LocalFile local) -> copyFile local++verifyLocalFile :: LocalFile typ -> Trusted FileInfo -> IO Bool+verifyLocalFile (LocalFile fp) trustedInfo = do+ -- Verify the file size before comparing the entire file info+ sz <- FileLength <$> getFileSize fp+ if sz /= fileInfoLength+ then return False+ else knownFileInfoEqual info <$> computeFileInfo fp+ where+ info@FileInfo{..} = trusted trustedInfo
+ hackage-security/hackage-security/src/Hackage/Security/Client/Repository/Remote.hs view
@@ -0,0 +1,717 @@+{-# LANGUAGE CPP #-}+-- | An implementation of Repository that talks to repositories over HTTP.+--+-- This implementation is itself parameterized over a 'HttpClient', so that it+-- it not tied to a specific library; for instance, 'HttpClient' can be+-- implemented with the @HTTP@ library, the @http-client@ libary, or others.+--+-- It would also be possible to give _other_ Repository implementations that+-- talk to repositories over HTTP, if you want to make other design decisions+-- than we did here, in particular:+--+-- * We attempt to do incremental downloads of the index when possible.+-- * We reuse the "Repository.Local" to deal with the local cache.+-- * We download @timestamp.json@ and @snapshot.json@ together. This is+-- implemented here because:+-- - One level down (HttpClient) we have no access to the local cache+-- - One level up (Repository API) would require _all_ Repositories to+-- implement this optimization.+module Hackage.Security.Client.Repository.Remote (+ -- * Top-level API+ withRepository+ , RepoOpts(..)+ , defaultRepoOpts+ , RemoteTemp+ -- * File sizes+ , FileSize(..)+ , fileSizeWithinBounds+ ) where++import Control.Concurrent+import Control.Exception+import Control.Monad.Cont+import Control.Monad.Except+import Data.List (nub, intercalate)+import Data.Typeable+import Network.URI hiding (uriPath, path)+import System.IO ()+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as BS.L++import Hackage.Security.Client.Formats+import Hackage.Security.Client.Repository+import Hackage.Security.Client.Repository.Cache (Cache)+import Hackage.Security.Client.Repository.HttpLib+import Hackage.Security.Client.Verify+import Hackage.Security.Trusted+import Hackage.Security.TUF+import Hackage.Security.Util.Checked+import Hackage.Security.Util.IO+import Hackage.Security.Util.Path+import Hackage.Security.Util.Pretty+import Hackage.Security.Util.Some+import qualified Hackage.Security.Client.Repository.Cache as Cache++{-------------------------------------------------------------------------------+ Server capabilities+-------------------------------------------------------------------------------}++-- | Server capabilities+--+-- As the library interacts with the server and receives replies, we may+-- discover more information about the server's capabilities; for instance,+-- we may discover that it supports incremental downloads.+newtype ServerCapabilities = SC (MVar ServerCapabilities_)++-- | Internal type recording the various server capabilities we support+data ServerCapabilities_ = ServerCapabilities {+ -- | Does the server support range requests?+ serverAcceptRangesBytes :: Bool+ }++newServerCapabilities :: IO ServerCapabilities+newServerCapabilities = SC <$> newMVar ServerCapabilities {+ serverAcceptRangesBytes = False+ }++updateServerCapabilities :: ServerCapabilities -> [HttpResponseHeader] -> IO ()+updateServerCapabilities (SC mv) responseHeaders = modifyMVar_ mv $ \caps ->+ return $ caps {+ serverAcceptRangesBytes = serverAcceptRangesBytes caps+ || HttpResponseAcceptRangesBytes `elem` responseHeaders+ }++checkServerCapability :: MonadIO m+ => ServerCapabilities -> (ServerCapabilities_ -> a) -> m a+checkServerCapability (SC mv) f = liftIO $ withMVar mv $ return . f++{-------------------------------------------------------------------------------+ File size+-------------------------------------------------------------------------------}++data FileSize =+ -- | For most files we download we know the exact size beforehand+ -- (because this information comes from the snapshot or delegated info)+ FileSizeExact Int54++ -- | For some files we might not know the size beforehand, but we might+ -- be able to provide an upper bound (timestamp, root info)+ | FileSizeBound Int54+ deriving Show++fileSizeWithinBounds :: Int54 -> FileSize -> Bool+fileSizeWithinBounds sz (FileSizeExact sz') = sz <= sz'+fileSizeWithinBounds sz (FileSizeBound sz') = sz <= sz'++{-------------------------------------------------------------------------------+ Top-level API+-------------------------------------------------------------------------------}++-- | Repository options with a reasonable default+--+-- Clients should use 'defaultRepositoryOpts' and override required settings.+data RepoOpts = RepoOpts {+ -- | Allow additional mirrors?+ --+ -- If this is set to True (default), in addition to the (out-of-band)+ -- specified mirrors we will also use mirrors reported by those+ -- out-of-band mirrors (that is, @mirrors.json@).+ repoAllowAdditionalMirrors :: Bool+ }++-- | Default repository options+defaultRepoOpts :: RepoOpts+defaultRepoOpts = RepoOpts {+ repoAllowAdditionalMirrors = True+ }++-- | Initialize the repository (and cleanup resources afterwards)+--+-- We allow to specify multiple mirrors to initialize the repository. These+-- are mirrors that can be found "out of band" (out of the scope of the TUF+-- protocol), for example in a @cabal.config@ file. The TUF protocol itself+-- will specify that any of these mirrors can serve a @mirrors.json@ file+-- that itself contains mirrors; we consider these as _additional_ mirrors+-- to the ones that are passed here.+--+-- NOTE: The list of mirrors should be non-empty (and should typically include+-- the primary server).+--+-- TODO: In the future we could allow finer control over precisely which+-- mirrors we use (which combination of the mirrors that are passed as arguments+-- here and the mirrors that we get from @mirrors.json@) as well as indicating+-- mirror preferences.+withRepository+ :: HttpLib -- ^ Implementation of the HTTP protocol+ -> [URI] -- ^ "Out of band" list of mirrors+ -> RepoOpts -- ^ Repository options+ -> Cache -- ^ Location of local cache+ -> RepoLayout -- ^ Repository layout+ -> IndexLayout -- ^ Index layout+ -> (LogMessage -> IO ()) -- ^ Logger+ -> (Repository RemoteTemp -> IO a) -- ^ Callback+ -> IO a+withRepository httpLib+ outOfBandMirrors+ repoOpts+ cache+ repLayout+ repIndexLayout+ logger+ callback+ = do+ selectedMirror <- newMVar Nothing+ caps <- newServerCapabilities+ let remoteConfig mirror = RemoteConfig {+ cfgLayout = repLayout+ , cfgHttpLib = httpLib+ , cfgBase = mirror+ , cfgCache = cache+ , cfgCaps = caps+ , cfgLogger = liftIO . logger+ , cfgOpts = repoOpts+ }+ callback Repository {+ repGetRemote = getRemote remoteConfig selectedMirror+ , repGetCached = Cache.getCached cache+ , repGetCachedRoot = Cache.getCachedRoot cache+ , repClearCache = Cache.clearCache cache+ , repWithIndex = Cache.withIndex cache+ , repGetIndexIdx = Cache.getIndexIdx cache+ , repLockCache = Cache.lockCache cache+ , repWithMirror = withMirror httpLib+ selectedMirror+ logger+ outOfBandMirrors+ repoOpts+ , repLog = logger+ , repLayout = repLayout+ , repIndexLayout = repIndexLayout+ , repDescription = "Remote repository at " ++ show outOfBandMirrors+ }++{-------------------------------------------------------------------------------+ Implementations of the various methods of Repository+-------------------------------------------------------------------------------}++-- | We select a mirror in 'withMirror' (the implementation of 'repWithMirror').+-- Outside the scope of 'withMirror' no mirror is selected, and a call to+-- 'getRemote' will throw an exception. If this exception is ever thrown its+-- a bug: calls to 'getRemote' ('repGetRemote') should _always_ be in the+-- scope of 'repWithMirror'.+type SelectedMirror = MVar (Maybe URI)++-- | Get the selected mirror+--+-- Throws an exception if no mirror was selected (this would be a bug in the+-- client code).+--+-- NOTE: Cannot use 'withMVar' here, because the callback would be inside the+-- scope of the withMVar, and there might be further calls to 'withRemote' made+-- by the callback argument to 'withRemote', leading to deadlock.+getSelectedMirror :: SelectedMirror -> IO URI+getSelectedMirror selectedMirror = do+ mBaseURI <- readMVar selectedMirror+ case mBaseURI of+ Nothing -> internalError "Internal error: no mirror selected"+ Just baseURI -> return baseURI++-- | Get a file from the server+getRemote :: Throws SomeRemoteError+ => (URI -> RemoteConfig)+ -> SelectedMirror+ -> AttemptNr+ -> RemoteFile fs typ+ -> Verify (Some (HasFormat fs), RemoteTemp typ)+getRemote remoteConfig selectedMirror attemptNr remoteFile = do+ baseURI <- liftIO $ getSelectedMirror selectedMirror+ let cfg = remoteConfig baseURI+ downloadMethod <- liftIO $ pickDownloadMethod cfg attemptNr remoteFile+ getFile cfg attemptNr remoteFile downloadMethod++-- | HTTP options+--+-- We want to make sure caches don't transform files in any way (as this will+-- mess things up with respect to hashes etc). Additionally, after a validation+-- error we want to make sure caches get files upstream in case the validation+-- error was because the cache updated files out of order.+httpRequestHeaders :: RemoteConfig -> AttemptNr -> [HttpRequestHeader]+httpRequestHeaders RemoteConfig{..} attemptNr =+ if attemptNr == 0 then defaultHeaders+ else HttpRequestMaxAge0 : defaultHeaders+ where+ -- Headers we provide for _every_ attempt, first or not+ defaultHeaders :: [HttpRequestHeader]+ defaultHeaders = [HttpRequestNoTransform]++-- | Mirror selection+withMirror :: forall a.+ HttpLib -- ^ HTTP client+ -> SelectedMirror -- ^ MVar indicating currently mirror+ -> (LogMessage -> IO ()) -- ^ Logger+ -> [URI] -- ^ Out-of-band mirrors+ -> RepoOpts -- ^ Repository options+ -> Maybe [Mirror] -- ^ TUF mirrors+ -> IO a -- ^ Callback+ -> IO a+withMirror HttpLib{..}+ selectedMirror+ logger+ oobMirrors+ repoOpts+ tufMirrors+ callback+ =+ go orderedMirrors+ where+ go :: [URI] -> IO a+ -- Empty list of mirrors is a bug+ go [] = internalError "No mirrors configured"+ -- If we only have a single mirror left, let exceptions be thrown up+ go [m] = do+ logger $ LogSelectedMirror (show m)+ select m $ callback+ -- Otherwise, catch exceptions and if any were thrown, try with different+ -- mirror+ go (m:ms) = do+ logger $ LogSelectedMirror (show m)+ catchChecked (select m callback) $ \ex -> do+ logger $ LogMirrorFailed (show m) ex+ go ms++ -- TODO: We will want to make the construction of this list configurable.+ orderedMirrors :: [URI]+ orderedMirrors = nub $ concat [+ oobMirrors+ , if repoAllowAdditionalMirrors repoOpts+ then maybe [] (map mirrorUrlBase) tufMirrors+ else []+ ]++ select :: URI -> IO a -> IO a+ select uri =+ bracket_ (modifyMVar_ selectedMirror $ \_ -> return $ Just uri)+ (modifyMVar_ selectedMirror $ \_ -> return Nothing)++{-------------------------------------------------------------------------------+ Download methods+-------------------------------------------------------------------------------}++-- | Download method (downloading or updating)+data DownloadMethod :: * -> * -> * where+ -- | Download this file (we never attempt to update this type of file)+ NeverUpdated :: {+ neverUpdatedFormat :: HasFormat fs f+ } -> DownloadMethod fs typ++ -- | Download this file (we cannot update this file right now)+ CannotUpdate :: {+ cannotUpdateFormat :: HasFormat fs f+ , cannotUpdateReason :: UpdateFailure+ } -> DownloadMethod fs Binary++ -- | Attempt an (incremental) update of this file+ Update :: {+ updateFormat :: HasFormat fs f+ , updateInfo :: Trusted FileInfo+ , updateLocal :: Path Absolute+ , updateTail :: Int54+ } -> DownloadMethod fs Binary++pickDownloadMethod :: forall fs typ. RemoteConfig+ -> AttemptNr+ -> RemoteFile fs typ+ -> IO (DownloadMethod fs typ)+pickDownloadMethod RemoteConfig{..} attemptNr remoteFile =+ case remoteFile of+ RemoteTimestamp -> return $ NeverUpdated (HFZ FUn)+ (RemoteRoot _) -> return $ NeverUpdated (HFZ FUn)+ (RemoteSnapshot _) -> return $ NeverUpdated (HFZ FUn)+ (RemoteMirrors _) -> return $ NeverUpdated (HFZ FUn)+ (RemotePkgTarGz _ _) -> return $ NeverUpdated (HFZ FGz)+ (RemoteIndex hasGz formats) -> multipleExitPoints $ do+ -- Server must support @Range@ with a byte-range+ rangeSupport <- checkServerCapability cfgCaps serverAcceptRangesBytes+ unless rangeSupport $ exit $ CannotUpdate hasGz UpdateImpossibleUnsupported++ -- We must already have a local file to be updated+ mCachedIndex <- lift $ Cache.getCachedIndex cfgCache (hasFormatGet hasGz)+ cachedIndex <- case mCachedIndex of+ Nothing -> exit $ CannotUpdate hasGz UpdateImpossibleNoLocalCopy+ Just fp -> return fp++ -- We attempt an incremental update a maximum of 2 times+ -- See 'UpdateFailedTwice' for details.+ when (attemptNr >= 2) $ exit $ CannotUpdate hasGz UpdateFailedTwice++ -- If all these checks pass try to do an incremental update.+ return Update {+ updateFormat = hasGz+ , updateInfo = formatsLookup hasGz formats+ , updateLocal = cachedIndex+ , updateTail = 65536 -- max gzip block size+ }++-- | Download the specified file using the given download method+getFile :: forall fs typ. Throws SomeRemoteError+ => RemoteConfig -- ^ Internal configuration+ -> AttemptNr -- ^ Did a security check previously fail?+ -> RemoteFile fs typ -- ^ File to get+ -> DownloadMethod fs typ -- ^ Selected format+ -> Verify (Some (HasFormat fs), RemoteTemp typ)+getFile cfg@RemoteConfig{..} attemptNr remoteFile method =+ go method+ where+ go :: Throws SomeRemoteError+ => DownloadMethod fs typ -> Verify (Some (HasFormat fs), RemoteTemp typ)+ go NeverUpdated{..} = do+ cfgLogger $ LogDownloading remoteFile+ download neverUpdatedFormat+ go CannotUpdate{..} = do+ cfgLogger $ LogCannotUpdate remoteFile cannotUpdateReason+ cfgLogger $ LogDownloading remoteFile+ download cannotUpdateFormat+ go Update{..} = do+ cfgLogger $ LogUpdating remoteFile+ update updateFormat updateInfo updateLocal updateTail++ headers :: [HttpRequestHeader]+ headers = httpRequestHeaders cfg attemptNr++ -- Get any file from the server, without using incremental updates+ download :: Throws SomeRemoteError => HasFormat fs f+ -> Verify (Some (HasFormat fs), RemoteTemp typ)+ download format = do+ (tempPath, h) <- openTempFile (Cache.cacheRoot cfgCache) (uriTemplate uri)+ liftIO $ do+ httpGet headers uri $ \responseHeaders bodyReader -> do+ updateServerCapabilities cfgCaps responseHeaders+ execBodyReader targetPath sz h bodyReader+ hClose h+ cacheIfVerified format $ DownloadedWhole tempPath+ where+ targetPath = TargetPathRepo $ remoteRepoPath' cfgLayout remoteFile format+ uri = formatsLookup format $ remoteFileURI cfgLayout cfgBase remoteFile+ sz = formatsLookup format $ remoteFileSize remoteFile++ -- Get a file incrementally+ update :: (typ ~ Binary)+ => HasFormat fs f -- ^ Selected format+ -> Trusted FileInfo -- ^ Expected info+ -> Path Absolute -- ^ Location of cached file (after callback)+ -> Int54 -- ^ How much of the tail to overwrite+ -> Verify (Some (HasFormat fs), RemoteTemp typ)+ update format info cachedFile fileTail = do+ currentSz <- liftIO $ getFileSize cachedFile+ let fileSz = fileLength' info+ range = (0 `max` (currentSz - fileTail), fileSz)+ range' = (fromIntegral (fst range), fromIntegral (snd range))+ cacheRoot = Cache.cacheRoot cfgCache+ (tempPath, h) <- openTempFile cacheRoot (uriTemplate uri)+ statusCode <- liftIO $+ httpGetRange headers uri range' $ \statusCode responseHeaders bodyReader -> do+ updateServerCapabilities cfgCaps responseHeaders+ let expectedSize =+ case statusCode of+ HttpStatus206PartialContent ->+ FileSizeExact (snd range - fst range)+ HttpStatus200OK ->+ FileSizeExact fileSz+ execBodyReader targetPath expectedSize h bodyReader+ hClose h+ return statusCode+ let downloaded =+ case statusCode of+ HttpStatus206PartialContent ->+ DownloadedDelta {+ deltaTemp = tempPath+ , deltaExisting = cachedFile+ , deltaSeek = fst range+ }+ HttpStatus200OK ->+ DownloadedWhole tempPath+ cacheIfVerified format downloaded+ where+ targetPath = TargetPathRepo repoPath+ uri = modifyUriPath cfgBase (`anchorRepoPathRemotely` repoPath)+ repoPath = remoteRepoPath' cfgLayout remoteFile format++ cacheIfVerified :: HasFormat fs f -> RemoteTemp typ+ -> Verify (Some (HasFormat fs), RemoteTemp typ)+ cacheIfVerified format remoteTemp = do+ ifVerified $+ Cache.cacheRemoteFile cfgCache+ remoteTemp+ (hasFormatGet format)+ (mustCache remoteFile)+ return (Some format, remoteTemp)++ HttpLib{..} = cfgHttpLib++{-------------------------------------------------------------------------------+ Execute body reader+-------------------------------------------------------------------------------}++-- | Execute a body reader+--+-- TODO: Deal with minimum download rate.+execBodyReader :: Throws SomeRemoteError+ => TargetPath -- ^ File source (for error msgs only)+ -> FileSize -- ^ Maximum file size+ -> Handle -- ^ Handle to write data too+ -> BodyReader -- ^ The action to give us blocks from the file+ -> IO ()+execBodyReader file mlen h br = go 0+ where+ go :: Int54 -> IO ()+ go sz = do+ unless (sz `fileSizeWithinBounds` mlen) $+ throwChecked $ SomeRemoteError $ FileTooLarge file mlen+ bs <- br+ if BS.null bs+ then return ()+ else BS.hPut h bs >> go (sz + fromIntegral (BS.length bs))++-- | The file we requested from the server was larger than expected+-- (potential endless data attack)+data FileTooLarge = FileTooLarge {+ fileTooLargePath :: TargetPath+ , fileTooLargeExpected :: FileSize+ }+ deriving (Typeable)++instance Pretty FileTooLarge where+ pretty FileTooLarge{..} = concat [+ "file returned by server too large: "+ , pretty fileTooLargePath+ , " (expected " ++ expected fileTooLargeExpected ++ " bytes)"+ ]+ where+ expected :: FileSize -> String+ expected (FileSizeExact n) = "exactly " ++ show n+ expected (FileSizeBound n) = "at most " ++ show n++#if MIN_VERSION_base(4,8,0)+deriving instance Show FileTooLarge+instance Exception FileTooLarge where displayException = pretty+#else+instance Exception FileTooLarge+instance Show FileTooLarge where show = pretty+#endif++{-------------------------------------------------------------------------------+ Information about remote files+-------------------------------------------------------------------------------}++remoteFileURI :: RepoLayout -> URI -> RemoteFile fs typ -> Formats fs URI+remoteFileURI repoLayout baseURI = fmap aux . remoteRepoPath repoLayout+ where+ aux :: RepoPath -> URI+ aux repoPath = modifyUriPath baseURI (`anchorRepoPathRemotely` repoPath)++-- | Extracting or estimating file sizes+remoteFileSize :: RemoteFile fs typ -> Formats fs FileSize+remoteFileSize (RemoteTimestamp) =+ FsUn $ FileSizeBound fileSizeBoundTimestamp+remoteFileSize (RemoteRoot mLen) =+ FsUn $ maybe (FileSizeBound fileSizeBoundRoot)+ (FileSizeExact . fileLength')+ mLen+remoteFileSize (RemoteSnapshot len) =+ FsUn $ FileSizeExact (fileLength' len)+remoteFileSize (RemoteMirrors len) =+ FsUn $ FileSizeExact (fileLength' len)+remoteFileSize (RemoteIndex _ lens) =+ fmap (FileSizeExact . fileLength') lens+remoteFileSize (RemotePkgTarGz _pkgId len) =+ FsGz $ FileSizeExact (fileLength' len)++-- | Bound on the size of the timestamp+--+-- This is intended as a permissive rather than tight bound.+--+-- The timestamp signed with a single key is 420 bytes; the signature makes up+-- just under 200 bytes of that. So even if the timestamp is signed with 10+-- keys it would still only be 2420 bytes. Doubling this amount, an upper bound+-- of 4kB should definitely be sufficient.+fileSizeBoundTimestamp :: Int54+fileSizeBoundTimestamp = 4096++-- | Bound on the size of the root+--+-- This is intended as a permissive rather than tight bound.+--+-- The variable parts of the root metadata are+--+-- * Signatures, each of which are about 200 bytes+-- * A key environment (mapping from key IDs to public keys), each is of+-- which is also about 200 bytes+-- * Mirrors, root, snapshot, targets, and timestamp role specifications.+-- These contains key IDs, each of which is about 80 bytes.+--+-- A skeleton root metadata is about 580 bytes. Allowing for+--+-- * 100 signatures+-- * 100 mirror keys, 1000 root keys, 100 snapshot keys, 1000 target keys,+-- 100 timestamp keys+-- * the corresponding 2300 entries in the key environment+--+-- We end up with a bound of about 665,000 bytes. Doubling this amount, an+-- upper bound of 2MB should definitely be sufficient.+fileSizeBoundRoot :: Int54+fileSizeBoundRoot = 2 * 1024 * 2014++{-------------------------------------------------------------------------------+ Configuration+-------------------------------------------------------------------------------}++-- | Remote repository configuration+--+-- This is purely for internal convenience.+data RemoteConfig = RemoteConfig {+ cfgLayout :: RepoLayout+ , cfgHttpLib :: HttpLib+ , cfgBase :: URI+ , cfgCache :: Cache+ , cfgCaps :: ServerCapabilities+ , cfgLogger :: forall m. MonadIO m => LogMessage -> m ()+ , cfgOpts :: RepoOpts+ }++{-------------------------------------------------------------------------------+ Auxiliary+-------------------------------------------------------------------------------}++-- | Template for the local file we use to download a URI to+uriTemplate :: URI -> String+uriTemplate = takeFileName . uriPath++fileLength' :: Trusted FileInfo -> Int54+fileLength' = fileLength . fileInfoLength . trusted++{-------------------------------------------------------------------------------+ Files downloaded from the remote repository+-------------------------------------------------------------------------------}++data RemoteTemp :: * -> * where+ DownloadedWhole :: {+ wholeTemp :: Path Absolute+ } -> RemoteTemp a++ -- | If we download only the delta, we record both the path to where the+ -- "old" file is stored and the path to the temp file containing the delta.+ -- Then:+ --+ -- * When we verify the file, we need both of these paths if we compute+ -- the hash from scratch, or only the path to the delta if we attempt+ -- to compute the hash incrementally (TODO: incremental verification+ -- not currently implemented).+ -- * When we copy a file over, we are additionally given a destination+ -- path. In this case, we expect that destination path to be equal to+ -- the path to the old file (and assert this to be the case).+ DownloadedDelta :: {+ deltaTemp :: Path Absolute+ , deltaExisting :: Path Absolute+ , deltaSeek :: Int54 -- ^ How much of the existing file to keep+ } -> RemoteTemp Binary++instance Pretty (RemoteTemp typ) where+ pretty DownloadedWhole{..} = intercalate " " $ [+ "DownloadedWhole"+ , pretty wholeTemp+ ]+ pretty DownloadedDelta{..} = intercalate " " $ [+ "DownloadedDelta"+ , pretty deltaTemp+ , pretty deltaExisting+ , show deltaSeek+ ]++instance DownloadedFile RemoteTemp where+ downloadedVerify = verifyRemoteFile+ downloadedRead = readLazyByteString . wholeTemp+ downloadedCopyTo = \f dest ->+ case f of+ DownloadedWhole{..} ->+ renameFile wholeTemp dest+ DownloadedDelta{..} -> do+ unless (deltaExisting == dest) $+ throwIO $ userError "Assertion failure: deltaExisting /= dest"+ -- We need ReadWriteMode in order to be able to seek+ withFile deltaExisting ReadWriteMode $ \h -> do+ hSeek h AbsoluteSeek (fromIntegral deltaSeek)+ BS.L.hPut h =<< readLazyByteString deltaTemp++-- | Verify a file downloaded from the remote repository+--+-- TODO: This currently still computes the hash for the whole file. If we cached+-- the state of the hash generator we could compute the hash incrementally.+-- However, profiling suggests that this would only be a minor improvement.+verifyRemoteFile :: RemoteTemp typ -> Trusted FileInfo -> IO Bool+verifyRemoteFile remoteTemp trustedInfo = do+ sz <- FileLength <$> remoteSize remoteTemp+ if sz /= fileInfoLength+ then return False+ else withRemoteBS remoteTemp $ knownFileInfoEqual info . fileInfo+ where+ remoteSize :: RemoteTemp typ -> IO Int54+ remoteSize DownloadedWhole{..} = getFileSize wholeTemp+ remoteSize DownloadedDelta{..} = do+ deltaSize <- getFileSize deltaTemp+ return $ deltaSeek + deltaSize++ -- It is important that we close the file handles when we're done+ -- (esp. since we may not read the whole file)+ withRemoteBS :: RemoteTemp typ -> (BS.L.ByteString -> Bool) -> IO Bool+ withRemoteBS DownloadedWhole{..} callback = do+ withFile wholeTemp ReadMode $ \h -> do+ bs <- BS.L.hGetContents h+ evaluate $ callback bs+ withRemoteBS DownloadedDelta{..} callback =+ withFile deltaExisting ReadMode $ \hExisting ->+ withFile deltaTemp ReadMode $ \hTemp -> do+ existing <- BS.L.hGetContents hExisting+ temp <- BS.L.hGetContents hTemp+ evaluate $ callback $ BS.L.concat [+ BS.L.take (fromIntegral deltaSeek) existing+ , temp+ ]++ info@FileInfo{..} = trusted trustedInfo++{-------------------------------------------------------------------------------+ Auxiliary: multiple exit points+-------------------------------------------------------------------------------}++-- | Multiple exit points+--+-- We can simulate the imperative code+--+-- > if (cond1)+-- > return exp1;+-- > if (cond2)+-- > return exp2;+-- > if (cond3)+-- > return exp3;+-- > return exp4;+--+-- as+--+-- > multipleExitPoints $ do+-- > when (cond1) $+-- > exit exp1+-- > when (cond) $+-- > exit exp2+-- > when (cond)+-- > exit exp3+-- > return exp4+multipleExitPoints :: Monad m => ExceptT a m a -> m a+multipleExitPoints = liftM aux . runExceptT+ where+ aux :: Either a a -> a+ aux (Left a) = a+ aux (Right a) = a++-- | Function exit point (see 'multipleExitPoints')+exit :: Monad m => e -> ExceptT e m a+exit = throwError
+ hackage-security/hackage-security/src/Hackage/Security/Client/Verify.hs view
@@ -0,0 +1,103 @@+module Hackage.Security.Client.Verify (+ -- * Verification monad+ Verify -- opaque+ , runVerify+ , acquire+ , ifVerified+ -- * Specific resources+ , openTempFile+ -- * Re-exports+ , liftIO+ ) where++import Control.Exception+import Control.Monad.Reader+import Data.IORef++import Hackage.Security.Util.IO+import Hackage.Security.Util.Path++{-------------------------------------------------------------------------------+ Verification monad+-------------------------------------------------------------------------------}++type Finaliser = IO ()+type Cleanup = IO ()++-- | Verification monad+--+-- The verification monad is similar to 'ResourceT' in intent, in that we can+-- register handlers to be run to release resources. Unlike 'ResourceT',+-- however, we maintain _two_ handlers: a cleanup handler which is run whether+-- or not verification succeeds, and a finalisation handler which is run only if+-- verification succeeds.+--+-- * Cleanup handlers are registered using 'acquire', and are guaranteed to run+-- just before the computation terminates (after the finalisation handler).+-- * The finalisation handlers are run only when verification succeeds, and can+-- be registered with 'ifVerified'. Finalisation can be used for instance to+-- update the local cache (which should only happen if verification is+-- successful).+newtype Verify a = Verify {+ unVerify :: ReaderT (IORef Cleanup, IORef Finaliser) IO a+ }+ deriving (Functor, Applicative, Monad, MonadIO)++-- | Run an action in the 'Verify' monad+runVerify :: (Finaliser -> Finaliser) -> Verify a -> IO a+runVerify modifyFinaliser v = do+ rCleanup <- newIORef $ return ()+ rFinaliser <- newIORef $ return ()+ mask $ \restore -> do+ ma <- try $ restore $ runReaderT (unVerify v) (rCleanup, rFinaliser)+ case ma of+ Left ex -> do join $ readIORef rCleanup+ throwIO (ex :: SomeException)+ Right a -> do modifyFinaliser $ join $ readIORef rFinaliser+ join $ readIORef rCleanup+ return a++-- | Acquire a resource and register the corresponding cleanup handler+--+-- NOTE: Resource acquisition happens with exceptions masked. If it is important+-- that the resource acquistion can be timed out (or receive other kinds of+-- asynchronous exceptions), you will need to use an interruptible operation.+-- See <http://www.well-typed.com/blog/2014/08/asynchronous-exceptions/> for+-- details.+acquire :: IO a -> (a -> IO ()) -> Verify a+acquire get release = Verify $ do+ (rCleanup, _rFinaliser) <- ask+ liftIO $ mask_ $ do+ a <- liftIO get+ modifyIORef rCleanup (>> release a)+ return a++-- | Register an action to be run only if verification succeeds+ifVerified :: IO () -> Verify ()+ifVerified handler = Verify $ do+ (_rCleanup, rFinaliser) <- ask+ liftIO $ modifyIORef rFinaliser (>> handler)++{-------------------------------------------------------------------------------+ Specific resources+-------------------------------------------------------------------------------}++-- | Create a short-lived temporary file+--+-- Creates the directory where the temp file should live if it does not exist.+openTempFile :: FsRoot root+ => Path root -- ^ Temp directory+ -> String -- ^ Template+ -> Verify (Path Absolute, Handle)+openTempFile tmpDir template =+ acquire createTempFile closeAndDelete+ where+ createTempFile :: IO (Path Absolute, Handle)+ createTempFile = do+ createDirectoryIfMissing True tmpDir+ openTempFile' tmpDir template++ closeAndDelete :: (Path Absolute, Handle) -> IO ()+ closeAndDelete (fp, h) = do+ hClose h+ void $ handleDoesNotExist $ removeFile fp
+ hackage-security/hackage-security/src/Hackage/Security/JSON.hs view
@@ -0,0 +1,323 @@+-- | Hackage-specific wrappers around the Util.JSON module+{-# LANGUAGE CPP #-}+module Hackage.Security.JSON (+ -- * Deserialization errors+ DeserializationError(..)+ , validate+ , verifyType+ -- * MonadKeys+ , MonadKeys(..)+ , addKeys+ , withKeys+ , lookupKey+ , readKeyAsId+ -- * Reader monads+ , ReadJSON_Keys_Layout+ , ReadJSON_Keys_NoLayout+ , ReadJSON_NoKeys_NoLayout+ , runReadJSON_Keys_Layout+ , runReadJSON_Keys_NoLayout+ , runReadJSON_NoKeys_NoLayout+ -- ** Utility+ , parseJSON_Keys_Layout+ , parseJSON_Keys_NoLayout+ , parseJSON_NoKeys_NoLayout+ , readJSON_Keys_Layout+ , readJSON_Keys_NoLayout+ , readJSON_NoKeys_NoLayout+ -- * Writing+ , WriteJSON+ , runWriteJSON+ -- ** Utility+ , renderJSON+ , renderJSON_NoLayout+ , writeJSON+ , writeJSON_NoLayout+ , writeKeyAsId+ -- * Re-exports+ , module Hackage.Security.Util.JSON+ ) where++import Control.Arrow (first, second)+import Control.Exception+import Control.Monad.Except+import Control.Monad.Reader+import Data.Functor.Identity+import Data.Typeable (Typeable)+import qualified Data.ByteString.Lazy as BS.L++import Hackage.Security.Key+import Hackage.Security.Key.Env (KeyEnv)+import Hackage.Security.TUF.Layout.Repo+import Hackage.Security.Util.JSON+import Hackage.Security.Util.Path+import Hackage.Security.Util.Pretty+import Hackage.Security.Util.Some+import Text.JSON.Canonical+import qualified Hackage.Security.Key.Env as KeyEnv++{-------------------------------------------------------------------------------+ Deserialization errors+-------------------------------------------------------------------------------}++data DeserializationError =+ -- | Malformed JSON has syntax errors in the JSON itself+ -- (i.e., we cannot even parse it to a JSValue)+ DeserializationErrorMalformed String++ -- | Invalid JSON has valid syntax but invalid structure+ --+ -- The string gives a hint about what we expected instead+ | DeserializationErrorSchema String++ -- | The JSON file contains a key ID of an unknown key+ | DeserializationErrorUnknownKey KeyId++ -- | Some verification step failed+ | DeserializationErrorValidation String++ -- | Wrong file type+ --+ -- Records actual and expected types.+ | DeserializationErrorFileType String String+ deriving (Typeable)++#if MIN_VERSION_base(4,8,0)+deriving instance Show DeserializationError+instance Exception DeserializationError where displayException = pretty+#else+instance Show DeserializationError where show = pretty+instance Exception DeserializationError+#endif++instance Pretty DeserializationError where+ pretty (DeserializationErrorMalformed str) =+ "Malformed: " ++ str+ pretty (DeserializationErrorSchema str) =+ "Schema error: " ++ str+ pretty (DeserializationErrorUnknownKey kId) =+ "Unknown key: " ++ keyIdString kId+ pretty (DeserializationErrorValidation str) =+ "Invalid: " ++ str+ pretty (DeserializationErrorFileType actualType expectedType) =+ "Expected file of type " ++ show expectedType+ ++ " but got file of type " ++ show actualType++validate :: MonadError DeserializationError m => String -> Bool -> m ()+validate _ True = return ()+validate msg False = throwError $ DeserializationErrorValidation msg++verifyType :: (ReportSchemaErrors m, MonadError DeserializationError m)+ => JSValue -> String -> m ()+verifyType enc expectedType = do+ actualType <- fromJSField enc "_type"+ unless (actualType == expectedType) $+ throwError $ DeserializationErrorFileType actualType expectedType++{-------------------------------------------------------------------------------+ Access to keys+-------------------------------------------------------------------------------}++-- | MonadReader-like monad, specialized to key environments+class (ReportSchemaErrors m, MonadError DeserializationError m) => MonadKeys m where+ localKeys :: (KeyEnv -> KeyEnv) -> m a -> m a+ askKeys :: m KeyEnv++readKeyAsId :: MonadKeys m => JSValue -> m (Some PublicKey)+readKeyAsId (JSString kId) = lookupKey (KeyId kId)+readKeyAsId val = expected' "key ID" val++addKeys :: MonadKeys m => KeyEnv -> m a -> m a+addKeys keys = localKeys (KeyEnv.union keys)++withKeys :: MonadKeys m => KeyEnv -> m a -> m a+withKeys keys = localKeys (const keys)++lookupKey :: MonadKeys m => KeyId -> m (Some PublicKey)+lookupKey kId = do+ keyEnv <- askKeys+ case KeyEnv.lookup kId keyEnv of+ Just key -> return key+ Nothing -> throwError $ DeserializationErrorUnknownKey kId++{-------------------------------------------------------------------------------+ Reading+-------------------------------------------------------------------------------}++newtype ReadJSON_Keys_Layout a = ReadJSON_Keys_Layout {+ unReadJSON_Keys_Layout :: ExceptT DeserializationError (Reader (RepoLayout, KeyEnv)) a+ }+ deriving ( Functor+ , Applicative+ , Monad+ , MonadError DeserializationError+ )++newtype ReadJSON_Keys_NoLayout a = ReadJSON_Keys_NoLayout {+ unReadJSON_Keys_NoLayout :: ExceptT DeserializationError (Reader KeyEnv) a+ }+ deriving ( Functor+ , Applicative+ , Monad+ , MonadError DeserializationError+ )++newtype ReadJSON_NoKeys_NoLayout a = ReadJSON_NoKeys_NoLayout {+ unReadJSON_NoKeys_NoLayout :: Except DeserializationError a+ }+ deriving ( Functor+ , Applicative+ , Monad+ , MonadError DeserializationError+ )++instance ReportSchemaErrors ReadJSON_Keys_Layout where+ expected str mgot = throwError $ expectedError str mgot+instance ReportSchemaErrors ReadJSON_Keys_NoLayout where+ expected str mgot = throwError $ expectedError str mgot+instance ReportSchemaErrors ReadJSON_NoKeys_NoLayout where+ expected str mgot = throwError $ expectedError str mgot++expectedError :: Expected -> Maybe Got -> DeserializationError+expectedError str mgot = DeserializationErrorSchema msg+ where+ msg = case mgot of+ Nothing -> "Expected " ++ str+ Just got -> "Expected " ++ str ++ " but got " ++ got++instance MonadReader RepoLayout ReadJSON_Keys_Layout where+ ask = ReadJSON_Keys_Layout $ fst `liftM` ask+ local f act = ReadJSON_Keys_Layout $ local (first f) act'+ where+ act' = unReadJSON_Keys_Layout act++instance MonadKeys ReadJSON_Keys_Layout where+ askKeys = ReadJSON_Keys_Layout $ snd `liftM` ask+ localKeys f act = ReadJSON_Keys_Layout $ local (second f) act'+ where+ act' = unReadJSON_Keys_Layout act++instance MonadKeys ReadJSON_Keys_NoLayout where+ askKeys = ReadJSON_Keys_NoLayout $ ask+ localKeys f act = ReadJSON_Keys_NoLayout $ local f act'+ where+ act' = unReadJSON_Keys_NoLayout act++runReadJSON_Keys_Layout :: KeyEnv+ -> RepoLayout+ -> ReadJSON_Keys_Layout a+ -> Either DeserializationError a+runReadJSON_Keys_Layout keyEnv repoLayout act =+ runReader (runExceptT (unReadJSON_Keys_Layout act)) (repoLayout, keyEnv)++runReadJSON_Keys_NoLayout :: KeyEnv+ -> ReadJSON_Keys_NoLayout a+ -> Either DeserializationError a+runReadJSON_Keys_NoLayout keyEnv act =+ runReader (runExceptT (unReadJSON_Keys_NoLayout act)) keyEnv++runReadJSON_NoKeys_NoLayout :: ReadJSON_NoKeys_NoLayout a+ -> Either DeserializationError a+runReadJSON_NoKeys_NoLayout act =+ runExcept (unReadJSON_NoKeys_NoLayout act)++{-------------------------------------------------------------------------------+ Utility+-------------------------------------------------------------------------------}++parseJSON_Keys_Layout :: FromJSON ReadJSON_Keys_Layout a+ => KeyEnv+ -> RepoLayout+ -> BS.L.ByteString+ -> Either DeserializationError a+parseJSON_Keys_Layout keyEnv repoLayout bs =+ case parseCanonicalJSON bs of+ Left err -> Left (DeserializationErrorMalformed err)+ Right val -> runReadJSON_Keys_Layout keyEnv repoLayout (fromJSON val)++parseJSON_Keys_NoLayout :: FromJSON ReadJSON_Keys_NoLayout a+ => KeyEnv+ -> BS.L.ByteString+ -> Either DeserializationError a+parseJSON_Keys_NoLayout keyEnv bs =+ case parseCanonicalJSON bs of+ Left err -> Left (DeserializationErrorMalformed err)+ Right val -> runReadJSON_Keys_NoLayout keyEnv (fromJSON val)++parseJSON_NoKeys_NoLayout :: FromJSON ReadJSON_NoKeys_NoLayout a+ => BS.L.ByteString+ -> Either DeserializationError a+parseJSON_NoKeys_NoLayout bs =+ case parseCanonicalJSON bs of+ Left err -> Left (DeserializationErrorMalformed err)+ Right val -> runReadJSON_NoKeys_NoLayout (fromJSON val)++readJSON_Keys_Layout :: ( FsRoot root+ , FromJSON ReadJSON_Keys_Layout a+ )+ => KeyEnv+ -> RepoLayout+ -> Path root+ -> IO (Either DeserializationError a)+readJSON_Keys_Layout keyEnv repoLayout fp = do+ withFile fp ReadMode $ \h -> do+ bs <- BS.L.hGetContents h+ evaluate $ parseJSON_Keys_Layout keyEnv repoLayout bs++readJSON_Keys_NoLayout :: ( FsRoot root+ , FromJSON ReadJSON_Keys_NoLayout a+ )+ => KeyEnv+ -> Path root+ -> IO (Either DeserializationError a)+readJSON_Keys_NoLayout keyEnv fp = do+ withFile fp ReadMode $ \h -> do+ bs <- BS.L.hGetContents h+ evaluate $ parseJSON_Keys_NoLayout keyEnv bs++readJSON_NoKeys_NoLayout :: ( FsRoot root+ , FromJSON ReadJSON_NoKeys_NoLayout a+ )+ => Path root+ -> IO (Either DeserializationError a)+readJSON_NoKeys_NoLayout fp = do+ withFile fp ReadMode $ \h -> do+ bs <- BS.L.hGetContents h+ evaluate $ parseJSON_NoKeys_NoLayout bs++{-------------------------------------------------------------------------------+ Writing+-------------------------------------------------------------------------------}++newtype WriteJSON a = WriteJSON {+ unWriteJSON :: Reader RepoLayout a+ }+ deriving ( Functor+ , Applicative+ , Monad+ , MonadReader RepoLayout+ )++runWriteJSON :: RepoLayout -> WriteJSON a -> a+runWriteJSON repoLayout act = runReader (unWriteJSON act) repoLayout++{-------------------------------------------------------------------------------+ Writing: Utility+-------------------------------------------------------------------------------}++-- | Render to canonical JSON format+renderJSON :: ToJSON WriteJSON a => RepoLayout -> a -> BS.L.ByteString+renderJSON repoLayout = renderCanonicalJSON . runWriteJSON repoLayout . toJSON++-- | Variation on 'renderJSON' for files that don't require the repo layout+renderJSON_NoLayout :: ToJSON Identity a => a -> BS.L.ByteString+renderJSON_NoLayout = renderCanonicalJSON . runIdentity . toJSON++writeJSON :: ToJSON WriteJSON a => RepoLayout -> Path Absolute -> a -> IO ()+writeJSON repoLayout fp = writeLazyByteString fp . renderJSON repoLayout++writeJSON_NoLayout :: ToJSON Identity a => Path Absolute -> a -> IO ()+writeJSON_NoLayout fp = writeLazyByteString fp . renderJSON_NoLayout++writeKeyAsId :: Some PublicKey -> JSValue+writeKeyAsId = JSString . keyIdString . someKeyId
+ hackage-security/hackage-security/src/Hackage/Security/Key.hs view
@@ -0,0 +1,298 @@+{-# LANGUAGE CPP #-}+module Hackage.Security.Key (+ -- * Key types+ Ed25519+ -- * Types abstracting over key types+ , Key(..)+ , PublicKey(..)+ , PrivateKey(..)+ -- * Key types in isolation+ , KeyType(..)+ -- * Hiding key types+ , somePublicKey+ , somePublicKeyType+ , someKeyId+ -- * Operations on keys+ , publicKey+ , privateKey+ , createKey+ , createKey'+ -- * Key IDs+ , KeyId(..)+ , HasKeyId(..)+ -- * Signing+ , sign+ , verify+ ) where++import Control.Monad+import Data.Functor.Identity+import Data.Typeable (Typeable)+import Text.JSON.Canonical+import qualified Crypto.Hash as CH+import qualified Crypto.Sign.Ed25519 as Ed25519+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as BS.L++#if !MIN_VERSION_base(4,7,0)+import qualified Data.Typeable as Typeable+#endif++import Hackage.Security.Util.JSON+import Hackage.Security.Util.Some+import Hackage.Security.Util.TypedEmbedded+import qualified Hackage.Security.Util.Base64 as B64++{-------------------------------------------------------------------------------+ Generalization over key types+-------------------------------------------------------------------------------}++data Ed25519++data Key a where+ KeyEd25519 :: Ed25519.PublicKey -> Ed25519.SecretKey -> Key Ed25519+ deriving (Typeable)++data PublicKey a where+ PublicKeyEd25519 :: Ed25519.PublicKey -> PublicKey Ed25519+ deriving (Typeable)++data PrivateKey a where+ PrivateKeyEd25519 :: Ed25519.SecretKey -> PrivateKey Ed25519+ deriving (Typeable)++deriving instance Show (Key typ)+deriving instance Show (PublicKey typ)+deriving instance Show (PrivateKey typ)++deriving instance Eq (Key typ)+deriving instance Eq (PublicKey typ)+deriving instance Eq (PrivateKey typ)++instance SomeShow Key where someShow = DictShow+instance SomeShow PublicKey where someShow = DictShow+instance SomeShow PrivateKey where someShow = DictShow++instance SomeEq Key where someEq = DictEq+instance SomeEq PublicKey where someEq = DictEq+instance SomeEq PrivateKey where someEq = DictEq++publicKey :: Key a -> PublicKey a+publicKey (KeyEd25519 pub _pri) = PublicKeyEd25519 pub++privateKey :: Key a -> PrivateKey a+privateKey (KeyEd25519 _pub pri) = PrivateKeyEd25519 pri++{-------------------------------------------------------------------------------+ Sometimes it's useful to talk about the type of a key independent of the key+-------------------------------------------------------------------------------}++data KeyType typ where+ KeyTypeEd25519 :: KeyType Ed25519++deriving instance Show (KeyType typ)+deriving instance Eq (KeyType typ)++instance SomeShow KeyType where someShow = DictShow+instance SomeEq KeyType where someEq = DictEq++instance Unify KeyType where+ unify KeyTypeEd25519 KeyTypeEd25519 = Just Refl++type instance TypeOf Key = KeyType+type instance TypeOf PublicKey = KeyType+type instance TypeOf PrivateKey = KeyType++instance Typed Key where+ typeOf (KeyEd25519 _ _) = KeyTypeEd25519++instance Typed PublicKey where+ typeOf (PublicKeyEd25519 _) = KeyTypeEd25519++instance Typed PrivateKey where+ typeOf (PrivateKeyEd25519 _) = KeyTypeEd25519++{-------------------------------------------------------------------------------+ We don't always know the key type+-------------------------------------------------------------------------------}++somePublicKey :: Some Key -> Some PublicKey+somePublicKey (Some key) = Some (publicKey key)++somePublicKeyType :: Some PublicKey -> Some KeyType+somePublicKeyType (Some pub) = Some (typeOf pub)++someKeyId :: HasKeyId key => Some key -> KeyId+someKeyId (Some a) = keyId a++{-------------------------------------------------------------------------------+ Creating keys+-------------------------------------------------------------------------------}++createKey :: KeyType key -> IO (Key key)+createKey KeyTypeEd25519 = uncurry KeyEd25519 <$> Ed25519.createKeypair++createKey' :: KeyType key -> IO (Some Key)+createKey' = liftM Some . createKey++{-------------------------------------------------------------------------------+ Key IDs+-------------------------------------------------------------------------------}++-- | The key ID of a key, by definition, is the hexdigest of the SHA-256 hash of+-- the canonical JSON form of the key where the private object key is excluded.+--+-- NOTE: The FromJSON and ToJSON instances for KeyId are ntentially omitted. Use+-- writeKeyAsId instead.+newtype KeyId = KeyId { keyIdString :: String }+ deriving (Show, Eq, Ord)++instance Monad m => ToObjectKey m KeyId where+ toObjectKey = return . keyIdString++instance Monad m => FromObjectKey m KeyId where+ fromObjectKey = return . KeyId++-- | Compute the key ID of a key+class HasKeyId key where+ keyId :: key typ -> KeyId++instance HasKeyId PublicKey where+ keyId = KeyId+ . show+ . (CH.hashlazy :: BS.L.ByteString -> CH.Digest CH.SHA256)+ . renderCanonicalJSON+ . runIdentity+ . toJSON++instance HasKeyId Key where+ keyId = keyId . publicKey++{-------------------------------------------------------------------------------+ Signing+-------------------------------------------------------------------------------}++-- | Sign a bytestring and return the signature+--+-- TODO: It is unfortunate that we have to convert to a strict bytestring for+-- ed25519+sign :: PrivateKey typ -> BS.L.ByteString -> BS.ByteString+sign (PrivateKeyEd25519 pri) =+ Ed25519.unSignature . dsign pri . BS.concat . BS.L.toChunks+ where+#if MIN_VERSION_ed25519(0,0,4)+ dsign = Ed25519.dsign+#else+ dsign = Ed25519.sign'+#endif++verify :: PublicKey typ -> BS.L.ByteString -> BS.ByteString -> Bool+verify (PublicKeyEd25519 pub) inp sig =+ dverify pub (BS.concat $ BS.L.toChunks inp) (Ed25519.Signature sig)+ where+#if MIN_VERSION_ed25519(0,0,4)+ dverify = Ed25519.dverify+#else+ dverify = Ed25519.verify'+#endif++{-------------------------------------------------------------------------------+ JSON encoding and decoding+-------------------------------------------------------------------------------}++instance Monad m => ToJSON m (Key typ) where+ toJSON key = case key of+ KeyEd25519 pub pri ->+ enc "ed25519" (Ed25519.unPublicKey pub) (Ed25519.unSecretKey pri)+ where+ enc :: String -> BS.ByteString -> BS.ByteString -> m JSValue+ enc tag pub pri = mkObject [+ ("keytype", return $ JSString tag)+ , ("keyval", mkObject [+ ("public", toJSON (B64.fromByteString pub))+ , ("private", toJSON (B64.fromByteString pri))+ ])+ ]++instance ReportSchemaErrors m => FromJSON m (Some Key) where+ fromJSON enc = do+ (tag, pub, pri) <- dec enc+ case tag of+ "ed25519" -> return . Some $+ KeyEd25519 (Ed25519.PublicKey pub) (Ed25519.SecretKey pri)+ _otherwise ->+ expected "valid key type" (Just tag)+ where+ dec :: JSValue -> m (String, BS.ByteString, BS.ByteString)+ dec obj = do+ tag <- fromJSField obj "keytype"+ val <- fromJSField obj "keyval"+ pub <- fromJSField val "public"+ pri <- fromJSField val "private"+ return (tag, B64.toByteString pub, B64.toByteString pri)++instance Monad m => ToJSON m (PublicKey typ) where+ toJSON key = case key of+ PublicKeyEd25519 pub ->+ enc "ed25519" (Ed25519.unPublicKey pub)+ where+ enc :: String -> BS.ByteString -> m JSValue+ enc tag pub = mkObject [+ ("keytype", return $ JSString tag)+ , ("keyval", mkObject [+ ("public", toJSON (B64.fromByteString pub))+ ])+ ]++instance Monad m => ToJSON m (Some Key) where toJSON (Some a) = toJSON a+instance Monad m => ToJSON m (Some PublicKey) where toJSON (Some a) = toJSON a+instance Monad m => ToJSON m (Some KeyType) where toJSON (Some a) = toJSON a++instance ReportSchemaErrors m => FromJSON m (Some PublicKey) where+ fromJSON enc = do+ (tag, pub) <- dec enc+ case tag of+ "ed25519" -> return . Some $+ PublicKeyEd25519 (Ed25519.PublicKey pub)+ _otherwise ->+ expected "valid key type" (Just tag)+ where+ dec :: JSValue -> m (String, BS.ByteString)+ dec obj = do+ tag <- fromJSField obj "keytype"+ val <- fromJSField obj "keyval"+ pub <- fromJSField val "public"+ return (tag, B64.toByteString pub)++instance Monad m => ToJSON m (KeyType typ) where+ toJSON KeyTypeEd25519 = return $ JSString "ed25519"++instance ReportSchemaErrors m => FromJSON m (Some KeyType) where+ fromJSON enc = do+ tag <- fromJSON enc+ case tag of+ "ed25519" -> return . Some $ KeyTypeEd25519+ _otherwise -> expected "valid key type" (Just tag)++{-------------------------------------------------------------------------------+ Orphans++ Pre-7.8 (base 4.7) we cannot have Typeable instance for higher-kinded types.+ Instead, here we provide some instance for specific instantiations.+-------------------------------------------------------------------------------}++#if !MIN_VERSION_base(4,7,0)+tyConKey, tyConPublicKey, tyConPrivateKey :: Typeable.TyCon+tyConKey = Typeable.mkTyCon3 "hackage-security" "Hackage.Security.Key" "Key"+tyConPublicKey = Typeable.mkTyCon3 "hackage-security" "Hackage.Security.Key" "PublicKey"+tyConPrivateKey = Typeable.mkTyCon3 "hackage-security" "Hackage.Security.Key" "PrivateKey"++instance Typeable (Some Key) where+ typeOf _ = Typeable.mkTyConApp tyConSome [Typeable.mkTyConApp tyConKey []]++instance Typeable (Some PublicKey) where+ typeOf _ = Typeable.mkTyConApp tyConSome [Typeable.mkTyConApp tyConPublicKey []]++instance Typeable (Some PrivateKey) where+ typeOf _ = Typeable.mkTyConApp tyConSome [Typeable.mkTyConApp tyConPrivateKey []]+#endif
+ hackage-security/hackage-security/src/Hackage/Security/Key/Env.hs view
@@ -0,0 +1,90 @@+module Hackage.Security.Key.Env (+ KeyEnv -- opaque+ , keyEnvMap+ -- * Convenience constructors+ , fromPublicKeys+ , fromKeys+ -- * The usual accessors+ , empty+ , null+ , insert+ , lookup+ , union+ ) where++import Prelude hiding (lookup, null)+import Control.Monad+import Data.Map (Map)+import qualified Data.Map as Map++import Hackage.Security.Key+import Hackage.Security.Util.JSON+import Hackage.Security.Util.Some++{-------------------------------------------------------------------------------+ Main datatype+-------------------------------------------------------------------------------}++-- | A key environment is a mapping from key IDs to the corresponding keys.+--+-- It should satisfy the invariant that these key IDs actually match the keys;+-- see 'checkKeyEnvInvariant'.+newtype KeyEnv = KeyEnv {+ keyEnvMap :: Map KeyId (Some PublicKey)+ }+ deriving (Show)++-- | Verify that each key ID is mapped to a key with that ID+checkKeyEnvInvariant :: KeyEnv -> Bool+checkKeyEnvInvariant = all (uncurry go) . Map.toList . keyEnvMap+ where+ go :: KeyId -> Some PublicKey -> Bool+ go kId key = kId == someKeyId key++{-------------------------------------------------------------------------------+ Convenience constructors+-------------------------------------------------------------------------------}++fromPublicKeys :: [Some PublicKey] -> KeyEnv+fromPublicKeys = KeyEnv . Map.fromList . map aux+ where+ aux :: Some PublicKey -> (KeyId, Some PublicKey)+ aux pub = (someKeyId pub, pub)++fromKeys :: [Some Key] -> KeyEnv+fromKeys = fromPublicKeys . map somePublicKey++{-------------------------------------------------------------------------------+ The usual accessors+-------------------------------------------------------------------------------}++empty :: KeyEnv+empty = KeyEnv Map.empty++null :: KeyEnv -> Bool+null (KeyEnv env) = Map.null env++insert :: Some PublicKey -> KeyEnv -> KeyEnv+insert key (KeyEnv env) = KeyEnv $ Map.insert (someKeyId key) key env++lookup :: KeyId -> KeyEnv -> Maybe (Some PublicKey)+lookup kId (KeyEnv env) = Map.lookup kId env++union :: KeyEnv -> KeyEnv -> KeyEnv+union (KeyEnv env) (KeyEnv env') = KeyEnv (env `Map.union` env')++{-------------------------------------------------------------------------------+ JSON+-------------------------------------------------------------------------------}++instance Monad m => ToJSON m KeyEnv where+ toJSON (KeyEnv keyEnv) = toJSON keyEnv++instance ReportSchemaErrors m => FromJSON m KeyEnv where+ fromJSON enc = do+ keyEnv <- KeyEnv <$> fromJSON enc+ -- We should really use 'validate', but that causes module import cycles.+ -- Sigh.+ unless (checkKeyEnvInvariant keyEnv) $+ expected "valid key environment" Nothing+ return keyEnv
+ hackage-security/hackage-security/src/Hackage/Security/Server.hs view
@@ -0,0 +1,29 @@+-- | Main entry point into the Hackage Security framework for clients+module Hackage.Security.Server (+ -- * Re-exports+ module Hackage.Security.JSON+ , module Hackage.Security.Key+ , module Hackage.Security.TUF+ ) where++import Hackage.Security.JSON (+ ToJSON(..)+ , FromJSON(..)+ , DeserializationError(..)+ , ReadJSON_Keys_Layout+ , ReadJSON_Keys_NoLayout+ , ReadJSON_NoKeys_NoLayout+ , parseJSON_Keys_Layout+ , parseJSON_Keys_NoLayout+ , parseJSON_NoKeys_NoLayout+ , readJSON_Keys_Layout+ , readJSON_Keys_NoLayout+ , readJSON_NoKeys_NoLayout+ , WriteJSON+ , renderJSON+ , renderJSON_NoLayout+ , writeJSON+ , writeJSON_NoLayout+ )+import Hackage.Security.Key+import Hackage.Security.TUF
+ hackage-security/hackage-security/src/Hackage/Security/TUF.hs view
@@ -0,0 +1,41 @@+-- | Export all the TUF datatypes+module Hackage.Security.TUF (+ module Hackage.Security.TUF.Common+ , module Hackage.Security.TUF.FileInfo+ , module Hackage.Security.TUF.FileMap+ , module Hackage.Security.TUF.Header+ , module Hackage.Security.TUF.Layout.Cache+ , module Hackage.Security.TUF.Layout.Index+ , module Hackage.Security.TUF.Layout.Repo+ , module Hackage.Security.TUF.Mirrors+ , module Hackage.Security.TUF.Paths+-- , module Hackage.Security.TUF.Patterns+ , module Hackage.Security.TUF.Root+ , module Hackage.Security.TUF.Signed+ , module Hackage.Security.TUF.Snapshot+ , module Hackage.Security.TUF.Targets+ , module Hackage.Security.TUF.Timestamp+ ) where++import Hackage.Security.TUF.Common+import Hackage.Security.TUF.FileInfo+import Hackage.Security.TUF.Header+import Hackage.Security.TUF.Layout.Cache+import Hackage.Security.TUF.Layout.Index+import Hackage.Security.TUF.Layout.Repo+import Hackage.Security.TUF.Mirrors+-- import Hackage.Security.TUF.Patterns+import Hackage.Security.TUF.Paths+import Hackage.Security.TUF.Root+import Hackage.Security.TUF.Signed+import Hackage.Security.TUF.Snapshot+import Hackage.Security.TUF.Targets+import Hackage.Security.TUF.Timestamp++-- FileMap is intended for qualified imports, so we only export a subset+import Hackage.Security.TUF.FileMap (+ FileMap+ , TargetPath(..)+ , FileChange(..)+ , fileMapChanges+ )
+ hackage-security/hackage-security/src/Hackage/Security/TUF/Common.hs view
@@ -0,0 +1,53 @@+-- | Simple type wrappers+module Hackage.Security.TUF.Common (+ -- * Types+ FileLength(..)+ , Hash(..)+ , KeyThreshold(..)+ ) where++import Hackage.Security.JSON++{-------------------------------------------------------------------------------+ Simple types+-------------------------------------------------------------------------------}++-- | File length+--+-- Having verified file length information means we can protect against+-- endless data attacks and similar.+newtype FileLength = FileLength { fileLength :: Int54 }+ deriving (Eq, Ord, Show)++-- | Key threshold+--+-- The key threshold is the minimum number of keys a document must be signed+-- with. Key thresholds are specified in 'RoleSpec' or 'DelegationsSpec'.+newtype KeyThreshold = KeyThreshold Int54+ deriving (Eq, Ord, Show)++-- | File hash+newtype Hash = Hash String+ deriving (Eq, Ord, Show)++{-------------------------------------------------------------------------------+ JSON+-------------------------------------------------------------------------------}++instance Monad m => ToJSON m KeyThreshold where+ toJSON (KeyThreshold i) = toJSON i++instance Monad m => ToJSON m FileLength where+ toJSON (FileLength i) = toJSON i++instance Monad m => ToJSON m Hash where+ toJSON (Hash str) = toJSON str++instance ReportSchemaErrors m => FromJSON m KeyThreshold where+ fromJSON enc = KeyThreshold <$> fromJSON enc++instance ReportSchemaErrors m => FromJSON m FileLength where+ fromJSON enc = FileLength <$> fromJSON enc++instance ReportSchemaErrors m => FromJSON m Hash where+ fromJSON enc = Hash <$> fromJSON enc
+ hackage-security/hackage-security/src/Hackage/Security/TUF/FileInfo.hs view
@@ -0,0 +1,103 @@+-- | Information about files+module Hackage.Security.TUF.FileInfo (+ FileInfo(..)+ , HashFn(..)+ , Hash(..)+ -- * Utility+ , fileInfo+ , computeFileInfo+ , knownFileInfoEqual+ , fileInfoSHA256+ -- ** Re-exports+ , Int54+ ) where++import Prelude hiding (lookup)+import Data.Map (Map)+import qualified Crypto.Hash as CH+import qualified Data.Map as Map+import qualified Data.ByteString.Lazy as BS.L++import Hackage.Security.JSON+import Hackage.Security.TUF.Common+import Hackage.Security.Util.Path++{-------------------------------------------------------------------------------+ Datatypes+-------------------------------------------------------------------------------}++data HashFn = HashFnSHA256+ deriving (Show, Eq, Ord)++-- | File information+--+-- This intentionally does not have an 'Eq' instance; see 'knownFileInfoEqual'+-- and 'verifyFileInfo' instead.+--+-- NOTE: Throughout we compute file information always over the raw bytes.+-- For example, when @timestamp.json@ lists the hash of @snapshot.json@, this+-- hash is computed over the actual @snapshot.json@ file (as opposed to the+-- canonical form of the embedded JSON). This brings it in line with the hash+-- computed over target files, where that is the only choice available.+data FileInfo = FileInfo {+ fileInfoLength :: FileLength+ , fileInfoHashes :: Map HashFn Hash+ }+ deriving (Show)++{-------------------------------------------------------------------------------+ Utility+-------------------------------------------------------------------------------}++-- | Compute 'FileInfo'+--+-- TODO: Currently this will load the entire input bytestring into memory.+-- We need to make this incremental, by computing the length and all hashes+-- in a single traversal over the input.+fileInfo :: BS.L.ByteString -> FileInfo+fileInfo bs = FileInfo {+ fileInfoLength = FileLength . fromIntegral $ BS.L.length bs+ , fileInfoHashes = Map.fromList [+ (HashFnSHA256, Hash $ show (CH.hashlazy bs :: CH.Digest CH.SHA256))+ ]+ }++-- | Compute 'FileInfo'+computeFileInfo :: FsRoot root => Path root -> IO FileInfo+computeFileInfo fp = fileInfo <$> readLazyByteString fp++-- | Compare known file info+--+-- This should be used only when the FileInfo is already known. If we want to+-- compare known FileInfo against a file on disk we should delay until we know+-- have confirmed that the file lengths don't match (see 'verifyFileInfo').+knownFileInfoEqual :: FileInfo -> FileInfo -> Bool+knownFileInfoEqual a b = (==) (fileInfoLength a, fileInfoHashes a)+ (fileInfoLength b, fileInfoHashes b)++-- | Extract SHA256 hash from 'FileInfo' (if present)+fileInfoSHA256 :: FileInfo -> Maybe Hash+fileInfoSHA256 FileInfo{..} = Map.lookup HashFnSHA256 fileInfoHashes++{-------------------------------------------------------------------------------+ JSON+-------------------------------------------------------------------------------}++instance Monad m => ToObjectKey m HashFn where+ toObjectKey HashFnSHA256 = return "sha256"++instance ReportSchemaErrors m => FromObjectKey m HashFn where+ fromObjectKey "sha256" = return HashFnSHA256+ fromObjectKey str = expected "valid hash function" (Just str)++instance Monad m => ToJSON m FileInfo where+ toJSON FileInfo{..} = mkObject [+ ("length", toJSON fileInfoLength)+ , ("hashes", toJSON fileInfoHashes)+ ]++instance ReportSchemaErrors m => FromJSON m FileInfo where+ fromJSON enc = do+ fileInfoLength <- fromJSField enc "length"+ fileInfoHashes <- fromJSField enc "hashes"+ return FileInfo{..}
+ hackage-security/hackage-security/src/Hackage/Security/TUF/FileMap.hs view
@@ -0,0 +1,134 @@+-- | Information about files+--+-- Intended to be double imported+--+-- > import Hackage.Security.TUF.FileMap (FileMap)+-- > import qualified Hackage.Security.TUF.FileMap as FileMap+module Hackage.Security.TUF.FileMap (+ FileMap -- opaque+ , TargetPath(..)+ -- * Standard accessors+ , empty+ , lookup+ , (!)+ , insert+ , fromList+ -- * Convenience accessors+ , lookupM+ -- * Comparing file maps+ , FileChange(..)+ , fileMapChanges+ ) where++import Prelude hiding (lookup)+import Control.Arrow (second)+import Data.Map (Map)+import qualified Data.Map as Map++import Hackage.Security.JSON+import Hackage.Security.TUF.FileInfo+import Hackage.Security.TUF.Paths+import Hackage.Security.Util.Path+import Hackage.Security.Util.Pretty++{-------------------------------------------------------------------------------+ Datatypes+-------------------------------------------------------------------------------}++-- | Mapping from paths to file info+--+-- File maps are used in target files; the paths are relative to the location+-- of the target files containing the file map.+newtype FileMap = FileMap { fileMap :: Map TargetPath FileInfo }+ deriving (Show)++-- | Entries in 'FileMap' either talk about the repository or the index+data TargetPath =+ TargetPathRepo RepoPath+ | TargetPathIndex IndexPath+ deriving (Show, Eq, Ord)++instance Pretty TargetPath where+ pretty (TargetPathRepo path) = pretty path+ pretty (TargetPathIndex path) = pretty path++{-------------------------------------------------------------------------------+ Standard accessors+-------------------------------------------------------------------------------}++empty :: FileMap+empty = FileMap Map.empty++lookup :: TargetPath -> FileMap -> Maybe FileInfo+lookup fp = Map.lookup fp . fileMap++(!) :: FileMap -> TargetPath -> FileInfo+fm ! fp = fileMap fm Map.! fp++insert :: TargetPath -> FileInfo -> FileMap -> FileMap+insert fp nfo = FileMap . Map.insert fp nfo . fileMap++fromList :: [(TargetPath, FileInfo)] -> FileMap+fromList = FileMap . Map.fromList++{-------------------------------------------------------------------------------+ Convenience accessors+-------------------------------------------------------------------------------}++lookupM :: Monad m => FileMap -> TargetPath -> m FileInfo+lookupM m fp =+ case lookup fp m of+ Nothing -> fail $ "No entry for " ++ pretty fp ++ " in filemap"+ Just nfo -> return nfo++{-------------------------------------------------------------------------------+ Comparing filemaps+-------------------------------------------------------------------------------}++data FileChange =+ -- | File got added or modified; we record the new file info+ FileChanged FileInfo++ -- | File got deleted+ | FileDeleted+ deriving (Show)++fileMapChanges :: FileMap -- ^ Old+ -> FileMap -- ^ New+ -> Map TargetPath FileChange+fileMapChanges (FileMap a) (FileMap b) =+ Map.fromList $ go (Map.toList a) (Map.toList b)+ where+ -- Assumes the old and new lists are sorted alphabetically+ -- (Map.toList guarantees this)+ go :: [(TargetPath, FileInfo)]+ -> [(TargetPath, FileInfo)]+ -> [(TargetPath, FileChange)]+ go [] new = map (second FileChanged) new+ go old [] = map (second (const FileDeleted)) old+ go old@((fp, nfo):old') new@((fp', nfo'):new')+ | fp < fp' = (fp , FileDeleted ) : go old' new+ | fp > fp' = (fp', FileChanged nfo') : go old new'+ | knownFileInfoEqual nfo nfo' = (fp , FileChanged nfo') : go old' new'+ | otherwise = go old' new'++{-------------------------------------------------------------------------------+ JSON+-------------------------------------------------------------------------------}++instance Monad m => ToJSON m FileMap where+ toJSON (FileMap metaFiles) = toJSON metaFiles++instance ReportSchemaErrors m => FromJSON m FileMap where+ fromJSON enc = FileMap <$> fromJSON enc++instance Monad m => ToObjectKey m TargetPath where+ toObjectKey = return . pretty++instance ReportSchemaErrors m => FromObjectKey m TargetPath where+ fromObjectKey ('<':'r':'e':'p':'o':'>':'/':path) =+ return . TargetPathRepo . rootPath . fromUnrootedFilePath $ path+ fromObjectKey ('<':'i':'n':'d':'e':'x':'>':'/':path) =+ return . TargetPathIndex . rootPath . fromUnrootedFilePath $ path+ fromObjectKey str =+ expected "target path" (Just str)
+ hackage-security/hackage-security/src/Hackage/Security/TUF/Header.hs view
@@ -0,0 +1,119 @@+-- | Header used by all TUF types+module Hackage.Security.TUF.Header (+ HasHeader(..)+ , FileVersion(..)+ , FileExpires(..)+ , Header(..)+ -- ** Utility+ , expiresInDays+ , expiresNever+ , isExpired+ , versionInitial+ , versionIncrement+ ) where++import Data.Time+import Data.Typeable (Typeable)++import Hackage.Security.JSON+import Hackage.Security.Util.Lens++{-------------------------------------------------------------------------------+ TUF header+-------------------------------------------------------------------------------}++class HasHeader a where+ -- | File expiry date+ fileExpires :: Lens' a FileExpires++ -- | File version (monotonically increasing counter)+ fileVersion :: Lens' a FileVersion++-- | File version+--+-- The file version is a flat integer which must monotonically increase on+-- every file update.+--+-- 'Show' and 'Read' instance are defined in terms of the underlying 'Int'+-- (this is use for example by hackage during the backup process).+newtype FileVersion = FileVersion Int54+ deriving (Eq, Ord, Typeable)++instance Show FileVersion where+ show (FileVersion v) = show v++instance Read FileVersion where+ readsPrec p = map (\(v, xs) -> (FileVersion v, xs)) . readsPrec p++-- | File expiry date+--+-- A 'Nothing' value here means no expiry. That makes it possible to set some+-- files to never expire. (Note that not having the Maybe in the type here still+-- allows that, because you could set an expiry date 2000 years into the future.+-- By having the Maybe here we avoid the _need_ for such encoding issues.)+newtype FileExpires = FileExpires (Maybe UTCTime)+ deriving (Eq, Ord, Show, Typeable)++-- | Occassionally it is useful to read only a header from a file.+--+-- 'HeaderOnly' intentionally only has a 'FromJSON' instance (no 'ToJSON').+data Header = Header {+ headerExpires :: FileExpires+ , headerVersion :: FileVersion+ }++instance HasHeader Header where+ fileExpires f x = (\y -> x { headerExpires = y }) <$> f (headerExpires x)+ fileVersion f x = (\y -> x { headerVersion = y }) <$> f (headerVersion x)++{-------------------------------------------------------------------------------+ Utility+-------------------------------------------------------------------------------}++expiresNever :: FileExpires+expiresNever = FileExpires Nothing++expiresInDays :: UTCTime -> Integer -> FileExpires+expiresInDays now n =+ FileExpires . Just $ addUTCTime (fromInteger n * oneDay) now++isExpired :: UTCTime -> FileExpires -> Bool+isExpired _ (FileExpires Nothing) = False+isExpired now (FileExpires (Just e)) = e < now++versionInitial :: FileVersion+versionInitial = FileVersion 1++versionIncrement :: FileVersion -> FileVersion+versionIncrement (FileVersion i) = FileVersion (i + 1)++{-------------------------------------------------------------------------------+ JSON+-------------------------------------------------------------------------------}++instance Monad m => ToJSON m FileVersion where+ toJSON (FileVersion i) = toJSON i++instance Monad m => ToJSON m FileExpires where+ toJSON (FileExpires (Just e)) = toJSON e+ toJSON (FileExpires Nothing) = return JSNull++instance ReportSchemaErrors m => FromJSON m FileVersion where+ fromJSON enc = FileVersion <$> fromJSON enc++instance ReportSchemaErrors m => FromJSON m FileExpires where+ fromJSON JSNull = return $ FileExpires Nothing+ fromJSON enc = FileExpires . Just <$> fromJSON enc++instance ReportSchemaErrors m => FromJSON m Header where+ fromJSON enc = do+ headerExpires <- fromJSField enc "expires"+ headerVersion <- fromJSField enc "version"+ return Header{..}++{-------------------------------------------------------------------------------+ Auxiliary+-------------------------------------------------------------------------------}++oneDay :: NominalDiffTime+oneDay = 24 * 60 * 60
+ hackage-security/hackage-security/src/Hackage/Security/TUF/Layout/Cache.hs view
@@ -0,0 +1,64 @@+module Hackage.Security.TUF.Layout.Cache (+ -- * Cache layout+ CacheLayout(..)+ , cabalCacheLayout+ ) where++import Hackage.Security.TUF.Paths+import Hackage.Security.Util.Path++{-------------------------------------------------------------------------------+ Cache layout+-------------------------------------------------------------------------------}++-- | Location of the various files we cache+--+-- Although the generic TUF algorithms do not care how we organize the cache,+-- we nonetheless specity this here because as long as there are tools which+-- access files in the cache directly we need to define the cache layout.+-- See also comments for 'defaultCacheLayout'.+data CacheLayout = CacheLayout {+ -- | TUF root metadata+ cacheLayoutRoot :: CachePath++ -- | TUF timestamp+ , cacheLayoutTimestamp :: CachePath++ -- | TUF snapshot+ , cacheLayoutSnapshot :: CachePath++ -- | TUF mirrors list+ , cacheLayoutMirrors :: CachePath++ -- | Uncompressed index tarball+ , cacheLayoutIndexTar :: CachePath++ -- | Index to the uncompressed index tarball+ , cacheLayoutIndexIdx :: CachePath++ -- | Compressed index tarball+ --+ -- We cache both the compressed and the uncompressed tarballs, because+ -- incremental updates happen through the compressed tarball, but reads+ -- happen through the uncompressed one (with the help of the tarball index).+ , cacheLayoutIndexTarGz :: CachePath+ }++-- | The cache layout cabal-install uses+--+-- We cache the index as @<cache>/00-index.tar@; this is important because+-- `cabal-install` expects to find it there (and does not currently go through+-- the hackage-security library to get files from the index).+cabalCacheLayout :: CacheLayout+cabalCacheLayout = CacheLayout {+ cacheLayoutRoot = rp $ fragment "root.json"+ , cacheLayoutTimestamp = rp $ fragment "timestamp.json"+ , cacheLayoutSnapshot = rp $ fragment "snapshot.json"+ , cacheLayoutMirrors = rp $ fragment "mirrors.json"+ , cacheLayoutIndexTar = rp $ fragment "00-index.tar"+ , cacheLayoutIndexIdx = rp $ fragment "00-index.tar.idx"+ , cacheLayoutIndexTarGz = rp $ fragment "00-index.tar.gz"+ }+ where+ rp :: Path Unrooted -> CachePath+ rp = rootPath
+ hackage-security/hackage-security/src/Hackage/Security/TUF/Layout/Index.hs view
@@ -0,0 +1,116 @@+module Hackage.Security.TUF.Layout.Index (+ -- * Repository layout+ IndexLayout(..)+ , IndexFile(..)+ , hackageIndexLayout+ -- ** Utility+ , indexLayoutPkgMetadata+ , indexLayoutPkgCabal+ , indexLayoutPkgPrefs+ ) where++import qualified System.FilePath as FP++import Distribution.Package+import Distribution.Text++import Hackage.Security.TUF.Paths+import Hackage.Security.TUF.Signed+import Hackage.Security.TUF.Targets+import Hackage.Security.Util.Path+import Hackage.Security.Util.Pretty+import Hackage.Security.Util.Some++{-------------------------------------------------------------------------------+ Index layout+-------------------------------------------------------------------------------}++-- | Layout of the files within the index tarball+data IndexLayout = IndexLayout {+ -- | Translate an 'IndexFile' to a path+ indexFileToPath :: forall dec. IndexFile dec -> IndexPath++ -- | Parse an 'FilePath'+ , indexFileFromPath :: IndexPath -> Maybe (Some IndexFile)+ }++-- | Files that we might request from the index+--+-- The type index tells us the type of the decoded file, if any. For files for+-- which the library does not support decoding this will be @()@.+-- NOTE: Clients should NOT rely on this type index being @()@, or they might+-- break if we add support for parsing additional file formats in the future.+--+-- TODO: If we wanted to support legacy Hackage, we should also have a case for+-- the global preferred-versions file. But supporting legacy Hackage will+-- probably require more work anyway..+data IndexFile :: * -> * where+ -- | Package-specific metadata (@targets.json@)+ IndexPkgMetadata :: PackageIdentifier -> IndexFile (Signed Targets)++ -- | Cabal file for a package+ IndexPkgCabal :: PackageIdentifier -> IndexFile ()++ -- | Preferred versions a package+ IndexPkgPrefs :: PackageName -> IndexFile ()++deriving instance Show (IndexFile dec)++instance Pretty (IndexFile dec) where+ pretty (IndexPkgMetadata pkgId) = "metadata for " ++ display pkgId+ pretty (IndexPkgCabal pkgId) = ".cabal for " ++ display pkgId+ pretty (IndexPkgPrefs pkgNm) = "preferred-versions for " ++ display pkgNm++instance SomeShow IndexFile where someShow = DictShow+instance SomePretty IndexFile where somePretty = DictPretty++-- | The layout of the index as maintained on Hackage+hackageIndexLayout :: IndexLayout+hackageIndexLayout = IndexLayout {+ indexFileToPath = toPath+ , indexFileFromPath = fromPath . toUnrootedFilePath . unrootPath+ }+ where+ toPath :: IndexFile dec -> IndexPath+ toPath (IndexPkgCabal pkgId) = fromFragments [+ display (packageName pkgId)+ , display (packageVersion pkgId)+ , display (packageName pkgId) ++ ".cabal"+ ]+ toPath (IndexPkgMetadata pkgId) = fromFragments [+ display (packageName pkgId)+ , display (packageVersion pkgId)+ , "package.json"+ ]+ toPath (IndexPkgPrefs pkgNm) = fromFragments [+ display pkgNm+ , "preferred-versions"+ ]++ fromFragments :: [String] -> IndexPath+ fromFragments = rootPath . joinFragments++ fromPath :: FilePath -> Maybe (Some IndexFile)+ fromPath fp = case FP.splitPath fp of+ [pkg, version, file] -> do+ pkgId <- simpleParse (init pkg ++ "-" ++ init version)+ case FP.takeExtension file of+ ".cabal" -> return $ Some $ IndexPkgCabal pkgId+ ".json" -> return $ Some $ IndexPkgMetadata pkgId+ _otherwise -> Nothing+ [pkg, "preferred-versions"] ->+ Some . IndexPkgPrefs <$> simpleParse (init pkg)+ _otherwise -> Nothing++{-------------------------------------------------------------------------------+ Utility+-------------------------------------------------------------------------------}++indexLayoutPkgMetadata :: IndexLayout -> PackageIdentifier -> IndexPath+indexLayoutPkgMetadata IndexLayout{..} = indexFileToPath . IndexPkgMetadata++indexLayoutPkgCabal :: IndexLayout -> PackageIdentifier -> IndexPath+indexLayoutPkgCabal IndexLayout{..} = indexFileToPath . IndexPkgCabal++indexLayoutPkgPrefs :: IndexLayout -> PackageName -> IndexPath+indexLayoutPkgPrefs IndexLayout{..} = indexFileToPath . IndexPkgPrefs
+ hackage-security/hackage-security/src/Hackage/Security/TUF/Layout/Repo.hs view
@@ -0,0 +1,79 @@+module Hackage.Security.TUF.Layout.Repo (+ -- * Repository layout+ RepoLayout(..)+ , hackageRepoLayout+ , cabalLocalRepoLayout+ ) where++import Distribution.Package+import Distribution.Text++import Hackage.Security.TUF.Paths+import Hackage.Security.Util.Path++{-------------------------------------------------------------------------------+ Repository layout+-------------------------------------------------------------------------------}++-- | Layout of a repository+data RepoLayout = RepoLayout {+ -- | TUF root metadata+ repoLayoutRoot :: RepoPath++ -- | TUF timestamp+ , repoLayoutTimestamp :: RepoPath++ -- | TUF snapshot+ , repoLayoutSnapshot :: RepoPath++ -- | TUF mirrors list+ , repoLayoutMirrors :: RepoPath++ -- | Compressed index tarball+ , repoLayoutIndexTarGz :: RepoPath++ -- | Uncompressed index tarball+ , repoLayoutIndexTar :: RepoPath++ -- | Path to the package tarball+ , repoLayoutPkgTarGz :: PackageIdentifier -> RepoPath+ }++-- | The layout used on Hackage+hackageRepoLayout :: RepoLayout+hackageRepoLayout = RepoLayout {+ repoLayoutRoot = rp $ fragment "root.json"+ , repoLayoutTimestamp = rp $ fragment "timestamp.json"+ , repoLayoutSnapshot = rp $ fragment "snapshot.json"+ , repoLayoutMirrors = rp $ fragment "mirrors.json"+ , repoLayoutIndexTarGz = rp $ fragment "01-index.tar.gz"+ , repoLayoutIndexTar = rp $ fragment "01-index.tar"+ , repoLayoutPkgTarGz = \pkgId -> rp $ fragment "package" </> pkgFile pkgId+ }+ where+ pkgFile :: PackageIdentifier -> Path Unrooted+ pkgFile pkgId = fragment (display pkgId) <.> "tar.gz"++ rp :: Path Unrooted -> RepoPath+ rp = rootPath++-- | Layout used by cabal for ("legacy") local repos+--+-- Obviously, such repos do not normally contain any of the TUF files, so their+-- location is more or less arbitrary here.+cabalLocalRepoLayout :: RepoLayout+cabalLocalRepoLayout = hackageRepoLayout {+ repoLayoutPkgTarGz = \pkgId -> rp $ pkgLoc pkgId </> pkgFile pkgId+ }+ where+ pkgLoc :: PackageIdentifier -> Path Unrooted+ pkgLoc pkgId = joinFragments [+ display (packageName pkgId)+ , display (packageVersion pkgId)+ ]++ pkgFile :: PackageIdentifier -> Path Unrooted+ pkgFile pkgId = fragment (display pkgId) <.> "tar.gz"++ rp :: Path Unrooted -> RepoPath+ rp = rootPath
+ hackage-security/hackage-security/src/Hackage/Security/TUF/Mirrors.hs view
@@ -0,0 +1,103 @@+{-# LANGUAGE UndecidableInstances #-}+module Hackage.Security.TUF.Mirrors (+ -- * TUF types+ Mirrors(..)+ , Mirror(..)+ , MirrorContent(..)+ -- ** Utility+ , MirrorDescription+ , describeMirror+ ) where++import Control.Monad.Except+import Network.URI++import Hackage.Security.JSON+import Hackage.Security.TUF.Header+import Hackage.Security.TUF.Signed++{-------------------------------------------------------------------------------+ Datatypes+-------------------------------------------------------------------------------}++data Mirrors = Mirrors {+ mirrorsVersion :: FileVersion+ , mirrorsExpires :: FileExpires+ , mirrorsMirrors :: [Mirror]+ }++-- | Definition of a mirror+--+-- NOTE: Unlike the TUF specification, we require that all mirrors must have+-- the same format. That is, we omit @metapath@ and @targetspath@.+data Mirror = Mirror {+ mirrorUrlBase :: URI+ , mirrorContent :: MirrorContent+ }+ deriving Show++-- | Full versus partial mirrors+--+-- The TUF spec explicitly allows for partial mirrors, with the mirrors file+-- specifying (through patterns) what is available from partial mirrors.+--+-- For now we only support full mirrors; if we wanted to add partial mirrors,+-- we would add a second @MirrorPartial@ constructor here with arguments+-- corresponding to TUF's @metacontent@ and @targetscontent@ fields.+data MirrorContent =+ MirrorFull+ deriving Show++instance HasHeader Mirrors where+ fileVersion f x = (\y -> x { mirrorsVersion = y }) <$> f (mirrorsVersion x)+ fileExpires f x = (\y -> x { mirrorsExpires = y }) <$> f (mirrorsExpires x)++{-------------------------------------------------------------------------------+ Utility+-------------------------------------------------------------------------------}++type MirrorDescription = String++-- | Give a human-readable description of a particular mirror+--+-- (for use in error messages)+describeMirror :: Mirror -> MirrorDescription+describeMirror = show . mirrorUrlBase++{-------------------------------------------------------------------------------+ JSON+-------------------------------------------------------------------------------}++instance Monad m => ToJSON m Mirror where+ toJSON Mirror{..} = mkObject $ concat [+ [ ("urlbase", toJSON mirrorUrlBase) ]+ , case mirrorContent of+ MirrorFull -> []+ ]++instance Monad m => ToJSON m Mirrors where+ toJSON Mirrors{..} = mkObject [+ ("_type" , return $ JSString "Mirrorlist")+ , ("version" , toJSON mirrorsVersion)+ , ("expires" , toJSON mirrorsExpires)+ , ("mirrors" , toJSON mirrorsMirrors)+ ]++instance ReportSchemaErrors m => FromJSON m Mirror where+ fromJSON enc = do+ mirrorUrlBase <- fromJSField enc "urlbase"+ let mirrorContent = MirrorFull+ return Mirror{..}++instance ( MonadError DeserializationError m+ , ReportSchemaErrors m+ ) => FromJSON m Mirrors where+ fromJSON enc = do+ verifyType enc "Mirrorlist"+ mirrorsVersion <- fromJSField enc "version"+ mirrorsExpires <- fromJSField enc "expires"+ mirrorsMirrors <- fromJSField enc "mirrors"+ return Mirrors{..}++instance MonadKeys m => FromJSON m (Signed Mirrors) where+ fromJSON = signedFromJSON
+ hackage-security/hackage-security/src/Hackage/Security/TUF/Paths.hs view
@@ -0,0 +1,72 @@+-- | Paths used in the TUF data structures+module Hackage.Security.TUF.Paths (+ -- * Repository+ RepoRoot+ , RepoPath+ , anchorRepoPathLocally+ , anchorRepoPathRemotely+ -- * Index+ , IndexRoot+ , IndexPath+ -- * Cache+ , CacheRoot+ , CachePath+ , anchorCachePath+ ) where++import Hackage.Security.Util.Path+import Hackage.Security.Util.Pretty++{-------------------------------------------------------------------------------+ Repo+-------------------------------------------------------------------------------}++-- | The root of the repository+--+-- Repository roots can be anchored at a remote URL or a local directory.+--+-- Note that even for remote repos 'RepoRoot' is (potentially) different from+-- 'Web' -- for a repository located at, say, @http://hackage.haskell.org@+-- they happen to coincide, but for one location at+-- @http://example.com/some/subdirectory@ they do not.+data RepoRoot++-- | Paths relative to the root of the repository+type RepoPath = Path RepoRoot++instance Pretty (Path RepoRoot) where+ pretty (Path fp) = "<repo>/" ++ fp++anchorRepoPathLocally :: FsRoot root => Path root -> RepoPath -> Path root+anchorRepoPathLocally localRoot repoPath = localRoot </> unrootPath repoPath++anchorRepoPathRemotely :: Path Web -> RepoPath -> Path Web+anchorRepoPathRemotely remoteRoot repoPath = remoteRoot </> unrootPath repoPath++{-------------------------------------------------------------------------------+ Index+-------------------------------------------------------------------------------}++-- | The root of the index tarball+data IndexRoot++-- | Paths relative to the root of the index tarball+type IndexPath = Path IndexRoot++instance Pretty (Path IndexRoot) where+ pretty (Path fp) = "<index>/" ++ fp++{-------------------------------------------------------------------------------+ Cache+-------------------------------------------------------------------------------}++-- | The cache directory+data CacheRoot+type CachePath = Path CacheRoot++instance Pretty (Path CacheRoot) where+ pretty (Path fp) = "<cache>/" ++ fp++-- | Anchor a cache path to the location of the cache+anchorCachePath :: FsRoot root => Path root -> CachePath -> Path root+anchorCachePath cacheRoot cachePath = cacheRoot </> unrootPath cachePath
+ hackage-security/hackage-security/src/Hackage/Security/TUF/Patterns.hs view
@@ -0,0 +1,346 @@+-- | Patterns and replacements+--+-- NOTE: This module was developed to prepare for proper delegation (#39).+-- It is currently unusued.+{-# LANGUAGE CPP #-}+#if __GLASGOW_HASKELL__ >= 800+{-# LANGUAGE TemplateHaskellQuotes #-}+#else+{-# LANGUAGE TemplateHaskell #-}+#endif+module Hackage.Security.TUF.Patterns (+ -- * Patterns and replacements+ FileName+ , Directory+ , Extension+ , BaseName+ , Pattern(..)+ , Replacement(..)+ , Delegation(..)+ -- ** Utility+ , identityReplacement+ , matchDelegation+ -- ** Parsing and quasi-quoting+ , parseDelegation+ , qqd+ ) where++import Control.Monad.Except+import Language.Haskell.TH (Q, Exp)+import System.FilePath+import qualified Language.Haskell.TH.Syntax as TH++import Hackage.Security.JSON+import Hackage.Security.Util.Some+import Hackage.Security.Util.Stack+import Hackage.Security.Util.TypedEmbedded++{-------------------------------------------------------------------------------+ Patterns and replacements+-------------------------------------------------------------------------------}++type FileName = String+type Directory = String+type Extension = String+type BaseName = String++-- | Structured patterns over paths+--+-- The type argument indicates what kind of function we expect when the+-- pattern matches. For example, we have the pattern @"*/*.txt"@:+--+-- > PathPatternDirAny (PathPatternFileExt ".txt")+-- > :: PathPattern (Directory :- BaseName :- ())+--+-- TODOs (see README.md):+--+-- * Update this to work with 'Path' rather than 'FilePath'/'String'+-- * Add different kinds of wildcards+-- * Add path roots+--+-- Currently this is a proof of concept more than anything else; the right+-- structure is here, but it needs updating. However, until we add author+-- signing (or out-of-tarball targets) we don't actually use this yet.+--+-- NOTE: Haddock lacks GADT support so constructors have only regular comments.+data Pattern a where+ -- Match against a specific filename+ PatFileConst :: FileName -> Pattern ()++ -- Match against a filename with the given extension+ PatFileExt :: Extension -> Pattern (BaseName :- ())++ -- Match against any filename+ PatFileAny :: Pattern (FileName :- ())++ -- Match against a specific directory+ PatDirConst :: Directory -> Pattern a -> Pattern a++ -- Match against any directory+ PatDirAny :: Pattern a -> Pattern (Directory :- a)++-- | Replacement patterns+--+-- These constructors match the ones in 'Pattern': wildcards must be used+-- in the same order as they appear in the pattern, but they don't all have to+-- be used (that's why the base constructors are polymorphic in the stack tail).+data Replacement a where+ RepFileConst :: FileName -> Replacement a+ RepFileExt :: Extension -> Replacement (BaseName :- a)+ RepFileAny :: Replacement (FileName :- a)+ RepDirConst :: Directory -> Replacement a -> Replacement a+ RepDirAny :: Replacement a -> Replacement (Directory :- a)++deriving instance Eq (Pattern typ)+deriving instance Show (Pattern typ)++deriving instance Eq (Replacement typ)+deriving instance Show (Replacement typ)++-- | The identity replacement replaces a matched pattern with itself+identityReplacement :: Pattern typ -> Replacement typ+identityReplacement = go+ where+ go :: Pattern typ -> Replacement typ+ go (PatFileConst fn) = RepFileConst fn+ go (PatFileExt e) = RepFileExt e+ go PatFileAny = RepFileAny+ go (PatDirConst d p) = RepDirConst d (go p)+ go (PatDirAny p) = RepDirAny (go p)++-- | A delegation+--+-- A delegation is a pair of a pattern and a replacement.+--+-- See 'match' for an example.+data Delegation = forall a. Delegation (Pattern a) (Replacement a)++deriving instance Show Delegation++{-------------------------------------------------------------------------------+ Matching+-------------------------------------------------------------------------------}++matchPattern :: String -> Pattern a -> Maybe a+matchPattern = go . splitDirectories+ where+ go :: [String] -> Pattern a -> Maybe a+ go [] _ = Nothing+ go [f] (PatFileConst f') = do guard (f == f')+ return ()+ go [f] (PatFileExt e') = do let (bn, _:e) = splitExtension f+ guard $ e == e'+ return (bn :- ())+ go [_] _ = Nothing+ go (d:p) (PatDirConst d' p') = do guard (d == d')+ go p p'+ go (d:p) (PatDirAny p') = (d :-) <$> go p p'+ go (_:_) _ = Nothing++constructReplacement :: Replacement a -> a -> String+constructReplacement = \repl a -> joinPath $ go repl a+ where+ go :: Replacement a -> a -> [String]+ go (RepFileConst c) _ = [c]+ go (RepFileExt e) (bn :- _) = [bn <.> e]+ go RepFileAny (fn :- _) = [fn]+ go (RepDirConst d p) a = d : go p a+ go (RepDirAny p) (d :- a) = d : go p a++matchDelegation :: Delegation -> String -> Maybe String+matchDelegation (Delegation pat repl) str =+ constructReplacement repl <$> matchPattern str pat++{-------------------------------------------------------------------------------+ Typechecking patterns and replacements+-------------------------------------------------------------------------------}++-- | Types for pattern and replacements+--+-- We intentially are not very precise here, saying @String@ (instead of+-- @FileName@, @BaseName@, or @Directory@, say) so that we can, for example,+-- use a matched filename in a pattern as a directory in a replacement.+data PatternType a where+ PatTypeNil :: PatternType ()+ PatTypeStr :: PatternType a -> PatternType (String :- a)++instance Unify PatternType where+ unify PatTypeNil PatTypeNil = Just Refl+ unify (PatTypeStr p) (PatTypeStr p') = case unify p p' of+ Just Refl -> Just Refl+ Nothing -> Nothing+ unify _ _ = Nothing++type instance TypeOf Pattern = PatternType+type instance TypeOf Replacement = PatternType++instance Typed Pattern where+ typeOf (PatFileConst _) = PatTypeNil+ typeOf (PatFileExt _) = PatTypeStr PatTypeNil+ typeOf (PatFileAny ) = PatTypeStr PatTypeNil+ typeOf (PatDirConst _ p) = typeOf p+ typeOf (PatDirAny p) = PatTypeStr (typeOf p)++instance AsType Replacement where+ asType = go+ where+ go :: Replacement typ -> PatternType typ' -> Maybe (Replacement typ')+ go (RepFileConst c) _ = return $ RepFileConst c+ go (RepFileExt _) PatTypeNil = Nothing+ go (RepFileExt e) (PatTypeStr _) = return $ RepFileExt e+ go RepFileAny PatTypeNil = Nothing+ go RepFileAny (PatTypeStr _) = return $ RepFileAny+ go (RepDirConst c p) tp = RepDirConst c <$> go p tp+ go (RepDirAny _) PatTypeNil = Nothing+ go (RepDirAny p) (PatTypeStr tp) = RepDirAny <$> go p tp++{-------------------------------------------------------------------------------+ Pretty-printing and parsing patterns and replacements+-------------------------------------------------------------------------------}++prettyPattern :: Pattern typ -> String+prettyPattern (PatFileConst f) = f+prettyPattern (PatFileExt e) = "*" <.> e+prettyPattern PatFileAny = "*"+prettyPattern (PatDirConst d p) = d </> prettyPattern p+prettyPattern (PatDirAny p) = "*" </> prettyPattern p++prettyReplacement :: Replacement typ -> String+prettyReplacement (RepFileConst f) = f+prettyReplacement (RepFileExt e) = "*" <.> e+prettyReplacement RepFileAny = "*"+prettyReplacement (RepDirConst d p) = d </> prettyReplacement p+prettyReplacement (RepDirAny p) = "*" </> prettyReplacement p++-- | Parse a pattern+parsePattern :: String -> Maybe (Some Pattern)+parsePattern = go . splitDirectories+ where+ go :: [String] -> Maybe (Some Pattern)+ go [] = Nothing+ go ["*"] = return . Some $ PatFileAny+ go [p] = if '*' `notElem` p+ then return . Some $ PatFileConst p+ else case splitExtension p of+ ("*", _:ext) -> return . Some $ PatFileExt ext+ _otherwise -> Nothing+ go (p:ps) = do Some p' <- go ps+ if '*' `notElem` p+ then return . Some $ PatDirConst p p'+ else case p of+ "*" -> return . Some $ PatDirAny p'+ _otherwise -> Nothing++-- | Parse a replacement+--+-- We cheat and use the parser for patterns and then translate using the+-- identity replacement.+parseReplacement :: String -> Maybe (Some Replacement)+parseReplacement = fmap aux . parsePattern+ where+ aux :: Some Pattern -> Some Replacement+ aux (Some pat) = Some (identityReplacement pat)++parseDelegation :: String -> String -> Either String Delegation+parseDelegation pat repl =+ case (parsePattern pat, parseReplacement repl) of+ (Just (Some pat'), Just (Some repl')) ->+ case repl' `asType` typeOf pat' of+ Just repl'' -> Right $ Delegation pat' repl''+ Nothing -> Left "Replacement does not match pattern type"+ _otherwise ->+ Left "Cannot parse delegation"++{-------------------------------------------------------------------------------+ Quasi-quotation++ We cannot (easily) use dataToExpQ because of the use of GADTs, so we manually+ give Lift instances.+-------------------------------------------------------------------------------}++-- | Quasi-quoter for delegations to make them easier to write in code+--+-- This allows to write delegations as+--+-- > $(qqd "targets/*/*/*.cabal" "targets/*/*/revisions.json")+--+-- (The alternative syntax which actually uses a quasi-quoter doesn't work very+-- well because the '/*' bits confuse CPP: "unterminated comment")+qqd :: String -> String -> Q Exp+qqd pat repl =+ case parseDelegation pat repl of+ Left err -> fail $ "Invalid delegation: " ++ err+ Right del -> TH.lift del++instance TH.Lift (Pattern a) where+ lift (PatFileConst fn) = [| PatFileConst fn |]+ lift (PatFileExt e) = [| PatFileExt e |]+ lift PatFileAny = [| PatFileAny |]+ lift (PatDirConst d p) = [| PatDirConst d p |]+ lift (PatDirAny p) = [| PatDirAny p |]++instance TH.Lift (Replacement a) where+ lift (RepFileConst fn) = [| RepFileConst fn |]+ lift (RepFileExt e ) = [| RepFileExt e |]+ lift RepFileAny = [| RepFileAny |]+ lift (RepDirConst d r) = [| RepDirConst d r |]+ lift (RepDirAny r) = [| RepDirAny r |]++instance TH.Lift Delegation where+ lift (Delegation pat repl) = [| Delegation pat repl |]++{-------------------------------------------------------------------------------+ JSON+-------------------------------------------------------------------------------}++instance Monad m => ToJSON m (Pattern typ) where+ toJSON = return . JSString . prettyPattern+instance Monad m => ToJSON m (Replacement typ) where+ toJSON = return . JSString . prettyReplacement++instance Monad m => ToJSON m (Some Pattern) where+ toJSON (Some p) = toJSON p+instance Monad m => ToJSON m (Some Replacement) where+ toJSON (Some r) = toJSON r++instance ReportSchemaErrors m => FromJSON m (Some Pattern) where+ fromJSON enc = do+ str <- fromJSON enc+ case parsePattern str of+ Nothing -> expected "valid pattern" (Just str)+ Just p -> return p++instance ReportSchemaErrors m => FromJSON m (Some Replacement) where+ fromJSON enc = do+ str <- fromJSON enc+ case parseReplacement str of+ Nothing -> expected "valid replacement" (Just str)+ Just r -> return r++{-------------------------------------------------------------------------------+ Debugging: examples+-------------------------------------------------------------------------------}++_ex1 :: Maybe String+_ex1 = matchDelegation del "A/x/y/z.foo"+ where+ del = Delegation+ ( PatDirConst "A"+ $ PatDirAny+ $ PatDirAny+ $ PatFileExt "foo"+ )+ ( RepDirConst "B"+ $ RepDirAny+ $ RepDirConst "C"+ $ RepDirAny+ $ RepFileExt "bar"+ )++_ex2 :: Maybe String+_ex2 = matchDelegation del "A/x/y/z.foo"+ where+ Right del = parseDelegation "A/*/*/*.foo" "B/*/C/*/*.bar"++_ex3 :: Either String Delegation+_ex3 = parseDelegation "foo" "*/bar"
+ hackage-security/hackage-security/src/Hackage/Security/TUF/Root.hs view
@@ -0,0 +1,117 @@+-- | The root filetype+module Hackage.Security.TUF.Root (+ -- * Datatypes+ Root(..)+ , RootRoles(..)+ , RoleSpec(..)+ ) where++import Hackage.Security.JSON+import Hackage.Security.Key+import Hackage.Security.Key.Env (KeyEnv)+import Hackage.Security.TUF.Common+import Hackage.Security.TUF.Header+import Hackage.Security.TUF.Mirrors+import Hackage.Security.TUF.Signed+import Hackage.Security.TUF.Snapshot+import Hackage.Security.TUF.Targets+import Hackage.Security.TUF.Timestamp+import Hackage.Security.Util.Some++{-------------------------------------------------------------------------------+ Datatypes+-------------------------------------------------------------------------------}++-- | The root metadata+--+-- NOTE: We must have the invariant that ALL keys (apart from delegation keys)+-- must be listed in 'rootKeys'. (Delegation keys satisfy a similar invariant,+-- see Targets.)+data Root = Root {+ rootVersion :: FileVersion+ , rootExpires :: FileExpires+ , rootKeys :: KeyEnv+ , rootRoles :: RootRoles+ }++data RootRoles = RootRoles {+ rootRolesRoot :: RoleSpec Root+ , rootRolesSnapshot :: RoleSpec Snapshot+ , rootRolesTargets :: RoleSpec Targets+ , rootRolesTimestamp :: RoleSpec Timestamp+ , rootRolesMirrors :: RoleSpec Mirrors+ }++-- | Role specification+--+-- The phantom type indicates what kind of type this role is meant to verify.+data RoleSpec a = RoleSpec {+ roleSpecKeys :: [Some PublicKey]+ , roleSpecThreshold :: KeyThreshold+ }+ deriving (Show)++instance HasHeader Root where+ fileVersion f x = (\y -> x { rootVersion = y }) <$> f (rootVersion x)+ fileExpires f x = (\y -> x { rootExpires = y }) <$> f (rootExpires x)++{-------------------------------------------------------------------------------+ JSON encoding+-------------------------------------------------------------------------------}++instance Monad m => ToJSON m RootRoles where+ toJSON RootRoles{..} = mkObject [+ ("root" , toJSON rootRolesRoot)+ , ("snapshot" , toJSON rootRolesSnapshot)+ , ("targets" , toJSON rootRolesTargets)+ , ("timestamp" , toJSON rootRolesTimestamp)+ , ("mirrors" , toJSON rootRolesMirrors)+ ]++instance MonadKeys m => FromJSON m RootRoles where+ fromJSON enc = do+ rootRolesRoot <- fromJSField enc "root"+ rootRolesSnapshot <- fromJSField enc "snapshot"+ rootRolesTargets <- fromJSField enc "targets"+ rootRolesTimestamp <- fromJSField enc "timestamp"+ rootRolesMirrors <- fromJSField enc "mirrors"+ return RootRoles{..}++instance Monad m => ToJSON m Root where+ toJSON Root{..} = mkObject [+ ("_type" , return $ JSString "Root")+ , ("version" , toJSON rootVersion)+ , ("expires" , toJSON rootExpires)+ , ("keys" , toJSON rootKeys)+ , ("roles" , toJSON rootRoles)+ ]++instance Monad m => ToJSON m (RoleSpec a) where+ toJSON RoleSpec{..} = mkObject [+ ("keyids" , return . JSArray . map writeKeyAsId $ roleSpecKeys)+ , ("threshold" , toJSON roleSpecThreshold)+ ]++-- | We give an instance for Signed Root rather than Root because the key+-- environment from the root data is necessary to resolve the explicit sharing+-- in the signatures.+instance MonadKeys m => FromJSON m (Signed Root) where+ fromJSON envelope = do+ enc <- fromJSField envelope "signed"+ rootKeys <- fromJSField enc "keys"+ withKeys rootKeys $ do+ verifyType enc "Root"+ rootVersion <- fromJSField enc "version"+ rootExpires <- fromJSField enc "expires"+ rootRoles <- fromJSField enc "roles"+ let signed = Root{..}++ signatures <- fromJSField envelope "signatures"+ validate "signatures" $ verifySignatures enc signatures+ return Signed{..}++instance MonadKeys m => FromJSON m (RoleSpec a) where+ fromJSON enc = do+ roleSpecKeys <- mapM readKeyAsId =<< fromJSField enc "keyids"+ roleSpecThreshold <- fromJSField enc "threshold"+ return RoleSpec{..}
+ hackage-security/hackage-security/src/Hackage/Security/TUF/Signed.hs view
@@ -0,0 +1,238 @@+-- | Wrapper around an arbitrary datatype that adds signatures+--+-- Note that in the spec there is explicit sharing of keys through key IDs;+-- we translate this to implicit sharing in our Haskell datatypes, with the+-- translation done in the JSON serialization/deserialization.+module Hackage.Security.TUF.Signed (+ -- * TUF types+ Signed(..)+ , Signatures(..)+ , Signature(..)+ -- * Construction and verification+ , unsigned+ , withSignatures+ , withSignatures'+ , signRendered+ , verifySignature+ -- * JSON aids+ , signedFromJSON+ , verifySignatures+ -- * Avoid interpreting signatures+ , UninterpretedSignatures(..)+ , PreSignature(..)+ -- ** Utility+ , fromPreSignature+ , fromPreSignatures+ , toPreSignature+ , toPreSignatures+ ) where++import Control.Monad+import Data.Functor.Identity+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as BS.L+import qualified Data.Set as Set++import Hackage.Security.JSON+import Hackage.Security.Key+import Hackage.Security.TUF.Layout.Repo+import Hackage.Security.Util.Some+import Text.JSON.Canonical+import qualified Hackage.Security.Util.Base64 as B64++{-------------------------------------------------------------------------------+ Signed objects+-------------------------------------------------------------------------------}++data Signed a = Signed {+ signed :: a+ , signatures :: Signatures+ }++-- | A list of signatures+--+-- Invariant: each signature must be made with a different key.+-- We enforce this invariant for incoming untrusted data ('fromPreSignatures')+-- but not for lists of signatures that we create in code.+newtype Signatures = Signatures [Signature]++data Signature = Signature {+ signature :: BS.ByteString+ , signatureKey :: Some PublicKey+ }++-- | Create a new document without any signatures+unsigned :: a -> Signed a+unsigned a = Signed { signed = a, signatures = Signatures [] }++-- | Sign a document+withSignatures :: ToJSON WriteJSON a => RepoLayout -> [Some Key] -> a -> Signed a+withSignatures repoLayout keys doc = Signed {+ signed = doc+ , signatures = signRendered keys $ renderJSON repoLayout doc+ }++-- | Variation on 'withSignatures' that doesn't need the repo layout+withSignatures' :: ToJSON Identity a => [Some Key] -> a -> Signed a+withSignatures' keys doc = Signed {+ signed = doc+ , signatures = signRendered keys $ renderJSON_NoLayout doc+ }++-- | Construct signatures for already rendered value+signRendered :: [Some Key] -> BS.L.ByteString -> Signatures+signRendered keys rendered = Signatures $ map go keys+ where+ go :: Some Key -> Signature+ go (Some key) = Signature {+ signature = sign (privateKey key) rendered+ , signatureKey = Some $ publicKey key+ }++verifySignature :: BS.L.ByteString -> Signature -> Bool+verifySignature inp Signature{signature = sig, signatureKey = Some pub} =+ verify pub inp sig++instance (Monad m, ToJSON m a) => ToJSON m (Signed a) where+ toJSON Signed{..} = mkObject [+ ("signed" , toJSON signed)+ , ("signatures" , toJSON signatures)+ ]++instance Monad m => ToJSON m Signatures where+ toJSON = toJSON . toPreSignatures++instance MonadKeys m => FromJSON m Signatures where+ fromJSON = fromPreSignatures <=< fromJSON++{-------------------------------------------------------------------------------+ JSON aids+-------------------------------------------------------------------------------}++-- | General FromJSON instance for signed datatypes+--+-- We don't give a general FromJSON instance for Signed because for some+-- datatypes we need to do something special (datatypes where we need to+-- read key environments); for instance, see the "Signed Root" instance.+signedFromJSON :: (MonadKeys m, FromJSON m a) => JSValue -> m (Signed a)+signedFromJSON envelope = do+ enc <- fromJSField envelope "signed"+ signed <- fromJSON enc+ signatures <- fromJSField envelope "signatures"+ validate "signatures" $ verifySignatures enc signatures+ return Signed{..}++-- | Signature verification+--+-- NOTES:+-- 1. By definition, the signature must be verified against the canonical+-- JSON format. This means we _must_ parse and then pretty print (as+-- we do here) because the document as stored may or may not be in+-- canonical format.+-- 2. However, it is important that we NOT translate from the JSValue+-- to whatever internal datatype we are using and then back to JSValue,+-- because that may not roundtrip: we must allow for additional fields+-- in the JSValue that we ignore (and would therefore lose when we+-- attempt to roundtrip).+-- 3. We verify that all signatures are valid, but we cannot verify (here)+-- that these signatures are signed with the right key, or that we+-- have a sufficient number of signatures. This will be the+-- responsibility of the calling code.+verifySignatures :: JSValue -> Signatures -> Bool+verifySignatures parsed (Signatures sigs) =+ all (verifySignature $ renderCanonicalJSON parsed) sigs++{-------------------------------------------------------------------------------+ Uninterpreted signatures+-------------------------------------------------------------------------------}++-- | File with uninterpreted signatures+--+-- Sometimes we want to be able to read a file without interpreting the+-- signatures (that is, resolving the key IDs) or doing any kind of checks on+-- them. One advantage of this is that this allows us to read many file types+-- without any key environment at all, which is sometimes useful.+data UninterpretedSignatures a = UninterpretedSignatures {+ uninterpretedSigned :: a+ , uninterpretedSignatures :: [PreSignature]+ }+ deriving (Show)++-- | A signature with a key ID (rather than an actual key)+--+-- This corresponds precisely to the TUF representation of a signature.+data PreSignature = PreSignature {+ presignature :: BS.ByteString+ , presigMethod :: Some KeyType+ , presigKeyId :: KeyId+ }+ deriving (Show)++-- | Convert a pre-signature to a signature+--+-- Verifies that the key type matches the advertised method.+fromPreSignature :: MonadKeys m => PreSignature -> m Signature+fromPreSignature PreSignature{..} = do+ key <- lookupKey presigKeyId+ validate "key type" $ typecheckSome key presigMethod+ return Signature {+ signature = presignature+ , signatureKey = key+ }++-- | Convert signature to pre-signature+toPreSignature :: Signature -> PreSignature+toPreSignature Signature{..} = PreSignature {+ presignature = signature+ , presigMethod = somePublicKeyType signatureKey+ , presigKeyId = someKeyId signatureKey+ }++-- | Convert a list of 'PreSignature's to a list of 'Signature's+--+-- This verifies the invariant that all signatures are made with different keys.+-- We do this on the presignatures rather than the signatures so that we can do+-- the check on key IDs, rather than keys (the latter don't have an Ord+-- instance).+fromPreSignatures :: MonadKeys m => [PreSignature] -> m Signatures+fromPreSignatures sigs = do+ validate "all signatures made with different keys" $+ Set.size (Set.fromList (map presigKeyId sigs)) == length sigs+ Signatures <$> mapM fromPreSignature sigs++-- | Convert list of pre-signatures to a list of signatures+toPreSignatures :: Signatures -> [PreSignature]+toPreSignatures (Signatures sigs) = map toPreSignature sigs++instance ReportSchemaErrors m => FromJSON m PreSignature where+ fromJSON enc = do+ kId <- fromJSField enc "keyid"+ method <- fromJSField enc "method"+ sig <- fromJSField enc "sig"+ return PreSignature {+ presignature = B64.toByteString sig+ , presigMethod = method+ , presigKeyId = KeyId kId+ }++instance Monad m => ToJSON m PreSignature where+ toJSON PreSignature{..} = mkObject [+ ("keyid" , return $ JSString . keyIdString $ presigKeyId)+ , ("method" , toJSON $ presigMethod)+ , ("sig" , toJSON $ B64.fromByteString presignature)+ ]++instance ( ReportSchemaErrors m+ , FromJSON m a+ ) => FromJSON m (UninterpretedSignatures a) where+ fromJSON envelope = do+ enc <- fromJSField envelope "signed"+ uninterpretedSigned <- fromJSON enc+ uninterpretedSignatures <- fromJSField envelope "signatures"+ return UninterpretedSignatures{..}++instance (Monad m, ToJSON m a) => ToJSON m (UninterpretedSignatures a) where+ toJSON UninterpretedSignatures{..} = mkObject [+ ("signed" , toJSON uninterpretedSigned)+ , ("signatures" , toJSON uninterpretedSignatures)+ ]
+ hackage-security/hackage-security/src/Hackage/Security/TUF/Snapshot.hs view
@@ -0,0 +1,100 @@+{-# LANGUAGE UndecidableInstances #-}+module Hackage.Security.TUF.Snapshot (+ Snapshot(..)+ ) where++import Control.Monad.Except+import Control.Monad.Reader++import Hackage.Security.JSON+import Hackage.Security.TUF.Header+import Hackage.Security.TUF.FileInfo+import Hackage.Security.TUF.FileMap+import Hackage.Security.TUF.Layout.Repo+import Hackage.Security.TUF.Signed+import qualified Hackage.Security.TUF.FileMap as FileMap++{-------------------------------------------------------------------------------+ Datatypes+-------------------------------------------------------------------------------}++data Snapshot = Snapshot {+ snapshotVersion :: FileVersion+ , snapshotExpires :: FileExpires++ -- | File info for the root metadata+ --+ -- We list this explicitly in the snapshot so that we can check if we need+ -- to update the root metadata without first having to download the entire+ -- index tarball.+ , snapshotInfoRoot :: FileInfo++ -- | File info for the mirror metadata+ , snapshotInfoMirrors :: FileInfo++ -- | Compressed index tarball+ , snapshotInfoTarGz :: FileInfo++ -- | Uncompressed index tarball+ --+ -- Repositories are not required to provide this.+ , snapshotInfoTar :: Maybe FileInfo+ }++instance HasHeader Snapshot where+ fileVersion f x = (\y -> x { snapshotVersion = y }) <$> f (snapshotVersion x)+ fileExpires f x = (\y -> x { snapshotExpires = y }) <$> f (snapshotExpires x)++{-------------------------------------------------------------------------------+ JSON+-------------------------------------------------------------------------------}++instance MonadReader RepoLayout m => ToJSON m Snapshot where+ toJSON Snapshot{..} = do+ repoLayout <- ask+ mkObject [+ ("_type" , return $ JSString "Snapshot")+ , ("version" , toJSON snapshotVersion)+ , ("expires" , toJSON snapshotExpires)+ , ("meta" , toJSON (snapshotMeta repoLayout))+ ]+ where+ snapshotMeta repoLayout = FileMap.fromList $ [+ (pathRoot repoLayout , snapshotInfoRoot)+ , (pathMirrors repoLayout , snapshotInfoMirrors)+ , (pathIndexTarGz repoLayout , snapshotInfoTarGz)+ ] +++ [ (pathIndexTar repoLayout , infoTar) | Just infoTar <- [snapshotInfoTar] ]++instance ( MonadReader RepoLayout m+ , MonadError DeserializationError m+ , ReportSchemaErrors m+ ) => FromJSON m Snapshot where+ fromJSON enc = do+ verifyType enc "Snapshot"+ repoLayout <- ask+ snapshotVersion <- fromJSField enc "version"+ snapshotExpires <- fromJSField enc "expires"+ snapshotMeta <- fromJSField enc "meta"+ snapshotInfoRoot <- FileMap.lookupM snapshotMeta (pathRoot repoLayout)+ snapshotInfoMirrors <- FileMap.lookupM snapshotMeta (pathMirrors repoLayout)+ snapshotInfoTarGz <- FileMap.lookupM snapshotMeta (pathIndexTarGz repoLayout)+ let snapshotInfoTar = FileMap.lookup (pathIndexTar repoLayout) snapshotMeta+ return Snapshot{..}++instance (MonadKeys m, MonadReader RepoLayout m) => FromJSON m (Signed Snapshot) where+ fromJSON = signedFromJSON++{-------------------------------------------------------------------------------+ Paths used in the snapshot++ NOTE: Since the snapshot lives in the top-level directory of the repository,+ we can safely reinterpret "relative to the repo root" as "relative to the+ snapshot"; hence, this use of 'castRoot' is okay.+-------------------------------------------------------------------------------}++pathRoot, pathMirrors, pathIndexTarGz, pathIndexTar :: RepoLayout -> TargetPath+pathRoot = TargetPathRepo . repoLayoutRoot+pathMirrors = TargetPathRepo . repoLayoutMirrors+pathIndexTarGz = TargetPathRepo . repoLayoutIndexTarGz+pathIndexTar = TargetPathRepo . repoLayoutIndexTar
+ hackage-security/hackage-security/src/Hackage/Security/TUF/Targets.hs view
@@ -0,0 +1,130 @@+module Hackage.Security.TUF.Targets (+ -- * TUF types+ Targets(..)+ , Delegations(..)+ , DelegationSpec(..)+ , Delegation(..)+ -- ** Util+ , targetsLookup+ ) where++import Hackage.Security.JSON+import Hackage.Security.Key+import Hackage.Security.Key.Env (KeyEnv)+import Hackage.Security.TUF.Common+import Hackage.Security.TUF.FileInfo+import Hackage.Security.TUF.FileMap (FileMap, TargetPath)+import Hackage.Security.TUF.Header+import Hackage.Security.TUF.Patterns+import Hackage.Security.TUF.Signed+import Hackage.Security.Util.Some+import qualified Hackage.Security.TUF.FileMap as FileMap++{-------------------------------------------------------------------------------+ TUF types+-------------------------------------------------------------------------------}++-- | Target metadata+--+-- Most target files do not need expiry dates because they are not subject to+-- change (and hence attacks like freeze attacks are not a concern).+data Targets = Targets {+ targetsVersion :: FileVersion+ , targetsExpires :: FileExpires+ , targetsTargets :: FileMap+ , targetsDelegations :: Maybe Delegations+ }+ deriving (Show)++-- | Delegations+--+-- Much like the Root datatype, this must have an invariant that ALL used keys+-- (apart from the global keys, which are in the root key environment) must+-- be listed in 'delegationsKeys'.+data Delegations = Delegations {+ delegationsKeys :: KeyEnv+ , delegationsRoles :: [DelegationSpec]+ }+ deriving (Show)++-- | Delegation specification+--+-- NOTE: This is a close analogue of 'RoleSpec'.+data DelegationSpec = DelegationSpec {+ delegationSpecKeys :: [Some PublicKey]+ , delegationSpecThreshold :: KeyThreshold+ , delegation :: Delegation+ }+ deriving (Show)++instance HasHeader Targets where+ fileVersion f x = (\y -> x { targetsVersion = y }) <$> f (targetsVersion x)+ fileExpires f x = (\y -> x { targetsExpires = y }) <$> f (targetsExpires x)++{-------------------------------------------------------------------------------+ Utility+-------------------------------------------------------------------------------}++targetsLookup :: TargetPath -> Targets -> Maybe FileInfo+targetsLookup fp Targets{..} = FileMap.lookup fp targetsTargets++{-------------------------------------------------------------------------------+ JSON+-------------------------------------------------------------------------------}++instance Monad m => ToJSON m DelegationSpec where+ toJSON DelegationSpec{delegation = Delegation fp name, ..} = mkObject [+ ("name" , toJSON name)+ , ("keyids" , return . JSArray . map writeKeyAsId $ delegationSpecKeys)+ , ("threshold" , toJSON delegationSpecThreshold)+ , ("path" , toJSON fp)+ ]++instance MonadKeys m => FromJSON m DelegationSpec where+ fromJSON enc = do+ delegationName <- fromJSField enc "name"+ delegationSpecKeys <- mapM readKeyAsId =<< fromJSField enc "keyids"+ delegationSpecThreshold <- fromJSField enc "threshold"+ delegationPath <- fromJSField enc "path"+ case parseDelegation delegationName delegationPath of+ Left err -> expected ("valid name/path combination: " ++ err) Nothing+ Right delegation -> return DelegationSpec{..}++-- NOTE: Unlike the Root object, the keys that are used to sign the delegations+-- are NOT listed inside the delegations, so the same "bootstrapping" problems+-- do not arise here.+instance Monad m => ToJSON m Delegations where+ toJSON Delegations{..} = mkObject [+ ("keys" , toJSON delegationsKeys)+ , ("roles" , toJSON delegationsRoles)+ ]++instance MonadKeys m => FromJSON m Delegations where+ fromJSON enc = do+ delegationsKeys <- fromJSField enc "keys"+ delegationsRoles <- fromJSField enc "roles"+ return Delegations{..}++instance Monad m => ToJSON m Targets where+ toJSON Targets{..} = mkObject $ mconcat [+ [ ("_type" , return $ JSString "Targets")+ , ("version" , toJSON targetsVersion)+ , ("expires" , toJSON targetsExpires)+ , ("targets" , toJSON targetsTargets)+ ]+ , [ ("delegations" , toJSON d) | Just d <- [ targetsDelegations ] ]+ ]++instance MonadKeys m => FromJSON m Targets where+ fromJSON enc = do+ verifyType enc "Targets"+ targetsVersion <- fromJSField enc "version"+ targetsExpires <- fromJSField enc "expires"+ targetsTargets <- fromJSField enc "targets"+ targetsDelegations <- fromJSOptField enc "delegations"+ return Targets{..}++-- TODO: This is okay right now because targets do not introduce additional+-- keys, but will no longer be okay once we have author keys.+instance MonadKeys m => FromJSON m (Signed Targets) where+ fromJSON = signedFromJSON
+ hackage-security/hackage-security/src/Hackage/Security/TUF/Timestamp.hs view
@@ -0,0 +1,74 @@+{-# LANGUAGE UndecidableInstances #-}+module Hackage.Security.TUF.Timestamp (+ Timestamp(..)+ ) where++import Control.Monad.Except+import Control.Monad.Reader++import Hackage.Security.JSON+import Hackage.Security.TUF.FileInfo+import Hackage.Security.TUF.FileMap+import Hackage.Security.TUF.Header+import Hackage.Security.TUF.Layout.Repo+import Hackage.Security.TUF.Signed+import qualified Hackage.Security.TUF.FileMap as FileMap++{-------------------------------------------------------------------------------+ Datatypes+-------------------------------------------------------------------------------}++data Timestamp = Timestamp {+ timestampVersion :: FileVersion+ , timestampExpires :: FileExpires+ , timestampInfoSnapshot :: FileInfo+ }++instance HasHeader Timestamp where+ fileVersion f x = (\y -> x { timestampVersion = y }) <$> f (timestampVersion x)+ fileExpires f x = (\y -> x { timestampExpires = y }) <$> f (timestampExpires x)++{-------------------------------------------------------------------------------+ JSON+-------------------------------------------------------------------------------}++instance MonadReader RepoLayout m => ToJSON m Timestamp where+ toJSON Timestamp{..} = do+ repoLayout <- ask+ mkObject [+ ("_type" , return $ JSString "Timestamp")+ , ("version" , toJSON timestampVersion)+ , ("expires" , toJSON timestampExpires)+ , ("meta" , toJSON (timestampMeta repoLayout))+ ]+ where+ timestampMeta repoLayout = FileMap.fromList [+ (pathSnapshot repoLayout, timestampInfoSnapshot)+ ]++instance ( MonadReader RepoLayout m+ , MonadError DeserializationError m+ , ReportSchemaErrors m+ ) => FromJSON m Timestamp where+ fromJSON enc = do+ verifyType enc "Timestamp"+ repoLayout <- ask+ timestampVersion <- fromJSField enc "version"+ timestampExpires <- fromJSField enc "expires"+ timestampMeta <- fromJSField enc "meta"+ timestampInfoSnapshot <- FileMap.lookupM timestampMeta (pathSnapshot repoLayout)+ return Timestamp{..}++instance (MonadKeys m, MonadReader RepoLayout m) => FromJSON m (Signed Timestamp) where+ fromJSON = signedFromJSON++{-------------------------------------------------------------------------------+ Paths used in the timestamp++ NOTE: Since the timestamp lives in the top-level directory of the repository,+ we can safely reinterpret "relative to the repo root" as "relative to the+ timestamp"; hence, this use of 'castRoot' is okay.+-------------------------------------------------------------------------------}++pathSnapshot :: RepoLayout -> TargetPath+pathSnapshot = TargetPathRepo . repoLayoutSnapshot
+ hackage-security/hackage-security/src/Hackage/Security/Trusted.hs view
@@ -0,0 +1,58 @@+{-# LANGUAGE CPP #-}+#if __GLASGOW_HASKELL__ >= 710+{-# LANGUAGE StaticPointers #-}+#endif+module Hackage.Security.Trusted (+ module Hackage.Security.Trusted.TCB+ -- * Derived functions+ , (<$$>)+ -- ** Role verification+ , VerifyRole(..)+ -- ** File info verification+ , trustedFileInfoEqual+ ) where++import Data.Function (on)+import Data.Time+import Hackage.Security.TUF+import Hackage.Security.Trusted.TCB hiding (DeclareTrusted)++{-------------------------------------------------------------------------------+ Combinators on trusted values+-------------------------------------------------------------------------------}++-- | Apply a static function to a trusted argument+(<$$>) :: StaticPtr (a -> b) -> Trusted a -> Trusted b+(<$$>) = trustApply . trustStatic++{-------------------------------------------------------------------------------+ Role verification+-------------------------------------------------------------------------------}++class VerifyRole a where+ verifyRole :: Trusted Root -- ^ Root data+ -> TargetPath -- ^ Source (for error messages)+ -> Maybe FileVersion -- ^ Previous version (if available)+ -> Maybe UTCTime -- ^ Time now (if checking expiry)+ -> Signed a -- ^ Mirrors to verify+ -> Either VerificationError (SignaturesVerified a)++instance VerifyRole Root where+ verifyRole = verifyRole' . (static (rootRolesRoot . rootRoles) <$$>)++instance VerifyRole Timestamp where+ verifyRole = verifyRole' . (static (rootRolesTimestamp . rootRoles) <$$>)++instance VerifyRole Snapshot where+ verifyRole = verifyRole' . (static (rootRolesSnapshot . rootRoles) <$$>)++instance VerifyRole Mirrors where+ verifyRole = verifyRole' . (static (rootRolesMirrors . rootRoles) <$$>)++{-------------------------------------------------------------------------------+ File info verification+-------------------------------------------------------------------------------}++-- | Variation on 'knownFileInfoEqual' for 'Trusted' 'FileInfo'+trustedFileInfoEqual :: Trusted FileInfo -> Trusted FileInfo -> Bool+trustedFileInfoEqual = knownFileInfoEqual `on` trusted
+ hackage-security/hackage-security/src/Hackage/Security/Trusted/TCB.hs view
@@ -0,0 +1,320 @@+{-# LANGUAGE CPP #-}+module Hackage.Security.Trusted.TCB (+ -- * Trusted values+ Trusted(DeclareTrusted)+ , trusted+ , trustStatic+ , trustVerified+ , trustApply+ , trustElems+ -- * Verification errors+ , VerificationError(..)+ , RootUpdated(..)+ , VerificationHistory+ -- * Role verification+ , SignaturesVerified -- opaque+ , signaturesVerified+ , verifyRole'+ , verifyFingerprints+#if __GLASGOW_HASKELL__ >= 710+ -- * Re-exports+ , StaticPtr+#else+ -- * Fake static pointers+ , StaticPtr+ , static+#endif+ ) where++import Control.Exception+import Control.Monad.Except+import Data.Typeable+import Data.Time+import Hackage.Security.TUF+import Hackage.Security.JSON+import Hackage.Security.Key+import Hackage.Security.Util.Pretty+import qualified Hackage.Security.Util.Lens as Lens++#if __GLASGOW_HASKELL__ >= 710+import GHC.StaticPtr+#else+-- Fake static pointers for ghc < 7.10. This means Trusted offers no+-- additional type safety, but that's okay: we can still verify the code+-- with ghc 7.10 and get the additional checks.+newtype StaticPtr a = StaticPtr { deRefStaticPtr :: a }++static :: a -> StaticPtr a+static = StaticPtr+#endif++-- | Trusted values+--+-- Trusted values originate in only two ways:+--+-- * Anything that is statically known is trusted ('trustStatic')+-- * If we have "dynamic" data we can trust it once we have verified the+-- the signatures (trustSigned).+--+-- NOTE: Trusted is NOT a functor. If it was we could define+--+-- > trustAnything :: a -> Trusted a+-- > trustAnything a = fmap (const a) (trustStatic (static ()))+--+-- Consequently, it is neither a monad nor a comonad. However, we _can_ apply+-- trusted functions to trusted arguments ('trustApply').+--+-- The 'DeclareTrusted' constructor is exported, but any use of it should be+-- verified.+newtype Trusted a = DeclareTrusted { trusted :: a }+ deriving (Eq, Show)++trustStatic :: StaticPtr a -> Trusted a+trustStatic = DeclareTrusted . deRefStaticPtr++trustVerified :: SignaturesVerified a -> Trusted a+trustVerified = DeclareTrusted . signaturesVerified++-- | Equivalent of '<*>'+--+-- Trusted isn't quite applicative (no pure, not a functor), but it is+-- somehow Applicative-like: we have the equivalent of '<*>'+trustApply :: Trusted (a -> b) -> Trusted a -> Trusted b+trustApply (DeclareTrusted f) (DeclareTrusted x) = DeclareTrusted (f x)++-- | Trust all elements of some trusted (traversable) container+--+-- If we have, say, a trusted list of values, we should be able to get a list+-- of trusted values out of it.+--+-- > trustElems :: Trusted [a] -> [Trusted a]+--+-- NOTE. It might appear that the more natural primitive to offer is a+-- 'sequenceA'-like operator such as+--+-- > trustSeq :: Applicative f => Trusted (f a) -> f (Trusted a)+--+-- However, this is unsound. To see this, consider that @((->) a)@ is+-- 'Applicative' (it's the reader monad); hence, we can instantiate 'trustSeq'+-- at+--+-- > trustSeq :: Trusted (a -> a) -> a -> Trusted a+--+-- and by passing @trustStatic (static id)@ make 'Trusted' a functor, which we+-- certainly don't want to do (see comments for 'Trusted').+--+-- So why is it okay when we insist on 'Traversable' rather than 'Applicative'?+-- To see this, it's instructive to consider how we might make a @((->) a)@ an+-- instance of 'Traversable'. If we define the domain of enumerable types as+--+-- > class Eq a => Enumerable a where+-- > enumerate :: [a]+--+-- then we can make @((->) r)@ traversable by+--+-- > instance Enumerable r => Traversable ((->) r) where+-- > sequenceA f = rebuild <$> sequenceA ((\r -> (r,) <$> f r) <$> enumerate)+-- > where+-- > rebuild :: [(r, a)] -> r -> a+-- > rebuild fun arg = fromJust (lookup arg fun)+--+-- The idea is that if the domain of a function is enumerable, we can apply the+-- function to each possible input, collect the outputs, and construct a new+-- function by pairing the inputs with the outputs. I.e., if we had something of+-- type+--+-- > a -> IO b+--+-- and @a@ is enumerable, we just run the @IO@ action on each possible @a@ and+-- collect all @b@s to get a pure function @a -> b@. Of course, you probably+-- don't want to be doing that, but the point is that as far as the type system+-- is concerned you could.+--+-- In the context of 'Trusted', this means that we can derive+--+-- > enumPure :: Enumerable a => a -> Trusted a+--+-- but in a way this this makes sense anyway. If a domain is enumerable, it+-- would not be unreasonable to change @Enumerable@ to+--+-- > class Eq a => Enumerable a where+-- > enumerate :: [StaticPtr a]+--+-- so we could define @enumPure@ as+--+-- > enumPure :: Enumerable a => a -> Trusted a+-- > enumPure x = trustStatic+-- > $ fromJust (find ((== x) . deRefStaticPtr) enumerate)+--+-- In other words, we just enumerate the entire domain as trusted values+-- (because we defined them locally) and then return the one that matched the+-- untrusted value.+--+-- The conclusion from all of this is that the types of untrusted input (like+-- the types of the TUF files we download from the server) should probably not+-- be considered enumerable.+trustElems :: Traversable f => Trusted (f a) -> f (Trusted a)+trustElems (DeclareTrusted fa) = DeclareTrusted `fmap` fa++{-------------------------------------------------------------------------------+ Role verification+-------------------------------------------------------------------------------}++newtype SignaturesVerified a = SignaturesVerified { signaturesVerified :: a }++-- | Errors thrown during role validation+data VerificationError =+ -- | Not enough signatures signed with the appropriate keys+ VerificationErrorSignatures TargetPath++ -- | The file is expired+ | VerificationErrorExpired TargetPath++ -- | The file version is less than the previous version+ | VerificationErrorVersion TargetPath++ -- | File information mismatch+ | VerificationErrorFileInfo TargetPath++ -- | We tried to lookup file information about a particular target file,+ -- but the information wasn't in the corresponding @targets.json@ file.+ | VerificationErrorUnknownTarget TargetPath++ -- | The metadata for the specified target is missing a SHA256+ | VerificationErrorMissingSHA256 TargetPath++ -- | Some verification errors materialize as deserialization errors+ --+ -- For example: if we try to deserialize a timestamp file but the timestamp+ -- key has been rolled over, deserialization of the file will fail with+ -- 'DeserializationErrorUnknownKey'.+ | VerificationErrorDeserialization TargetPath DeserializationError++ -- | The spec stipulates that if a verification error occurs during+ -- the check for updates, we must download new root information and+ -- start over. However, we limit how often we attempt this.+ --+ -- We record all verification errors that occurred before we gave up.+ | VerificationErrorLoop VerificationHistory+ deriving (Typeable)++-- | Root metadata updated (as part of the normal update process)+data RootUpdated = RootUpdated+ deriving (Typeable)++type VerificationHistory = [Either RootUpdated VerificationError]++#if MIN_VERSION_base(4,8,0)+deriving instance Show VerificationError+deriving instance Show RootUpdated+instance Exception VerificationError where displayException = pretty+instance Exception RootUpdated where displayException = pretty+#else+instance Exception VerificationError+instance Show VerificationError where show = pretty+instance Show RootUpdated where show = pretty+instance Exception RootUpdated+#endif++instance Pretty VerificationError where+ pretty (VerificationErrorSignatures file) =+ pretty file ++ " does not have enough signatures signed with the appropriate keys"+ pretty (VerificationErrorExpired file) =+ pretty file ++ " is expired"+ pretty (VerificationErrorVersion file) =+ "Version of " ++ pretty file ++ " is less than the previous version"+ pretty (VerificationErrorFileInfo file) =+ "Invalid hash for " ++ pretty file+ pretty (VerificationErrorUnknownTarget file) =+ pretty file ++ " not found in corresponding target metadata"+ pretty (VerificationErrorMissingSHA256 file) =+ "Missing SHA256 hash for " ++ pretty file+ pretty (VerificationErrorDeserialization file err) =+ "Could not deserialize " ++ pretty file ++ ": " ++ pretty err+ pretty (VerificationErrorLoop es) =+ "Verification loop. Errors in order:\n"+ ++ unlines (map ((" " ++) . either pretty pretty) es)++instance Pretty RootUpdated where+ pretty RootUpdated = "Root information updated"++-- | Role verification+--+-- NOTE: We throw an error when the version number _decreases_, but allow it+-- to be the same. This is sufficient: the file number is there so that+-- attackers cannot replay old files. It cannot protect against freeze attacks+-- (that's what the expiry date is for), so "replaying" the same file is not+-- a problem. If an attacker changes the contents of the file but not the+-- version number we have an inconsistent situation, but this is not something+-- we need to worry about: in this case the attacker will need to resign the+-- file or otherwise the signature won't match, and if the attacker has+-- compromised the key then he might just as well increase the version number+-- and resign.+--+-- NOTE 2: We are not actually verifying the signatures _themselves_ here+-- (we did that when we parsed the JSON). We are merely verifying the provenance+-- of the keys.+verifyRole' :: forall a. HasHeader a+ => Trusted (RoleSpec a) -- ^ For signature validation+ -> TargetPath -- ^ File source (for error messages)+ -> Maybe FileVersion -- ^ Previous version (if available)+ -> Maybe UTCTime -- ^ Time now (if checking expiry)+ -> Signed a -> Either VerificationError (SignaturesVerified a)+verifyRole' (trusted -> RoleSpec{roleSpecThreshold = KeyThreshold threshold, ..})+ targetPath+ mPrev+ mNow+ Signed{signatures = Signatures sigs, ..} =+ runExcept go+ where+ go :: Except VerificationError (SignaturesVerified a)+ go = do+ -- Verify expiry date+ case mNow of+ Just now ->+ when (isExpired now (Lens.get fileExpires signed)) $+ throwError $ VerificationErrorExpired targetPath+ _otherwise ->+ return ()++ -- Verify timestamp+ case mPrev of+ Nothing -> return ()+ Just prev ->+ when (Lens.get fileVersion signed < prev) $+ throwError $ VerificationErrorVersion targetPath++ -- Verify signatures+ -- NOTE: We only need to verify the keys that were used; if the signature+ -- was invalid we would already have thrown an error constructing Signed.+ -- (Similarly, if two signatures were made by the same key, the FromJSON+ -- instance for Signatures would have thrown an error.)+ unless (length (filter isRoleSpecKey sigs) >= fromIntegral threshold) $+ throwError $ VerificationErrorSignatures targetPath++ -- Everything is A-OK!+ return $ SignaturesVerified signed++ isRoleSpecKey :: Signature -> Bool+ isRoleSpecKey Signature{..} = signatureKey `elem` roleSpecKeys++-- | Variation on 'verifyRole' that uses key IDs rather than keys+--+-- This is used during the bootstrap process.+--+-- See <http://en.wikipedia.org/wiki/Public_key_fingerprint>.+verifyFingerprints :: [KeyId]+ -> KeyThreshold+ -> TargetPath -- ^ For error messages+ -> Signed Root+ -> Either VerificationError (SignaturesVerified Root)+verifyFingerprints fingerprints+ (KeyThreshold threshold)+ targetPath+ Signed{signatures = Signatures sigs, ..} =+ if length (filter isTrustedKey sigs) >= fromIntegral threshold+ then Right $ SignaturesVerified signed+ else Left $ VerificationErrorSignatures targetPath+ where+ isTrustedKey :: Signature -> Bool+ isTrustedKey Signature{..} = someKeyId signatureKey `elem` fingerprints
+ hackage-security/hackage-security/src/Hackage/Security/Util/Base64.hs view
@@ -0,0 +1,31 @@+module Hackage.Security.Util.Base64 (+ Base64 -- opaque+ , fromByteString+ , toByteString+ ) where++import Data.ByteString (ByteString)+import qualified Data.ByteString.Char8 as C8 -- only called on B64-enc strings+import qualified Data.ByteString.Base64 as B64++import Hackage.Security.Util.JSON++-- | Simple wrapper around bytestring with ToJSON and FromJSON instances that+-- use base64 encoding.+newtype Base64 = Base64 ByteString++fromByteString :: ByteString -> Base64+fromByteString = Base64++toByteString :: Base64 -> ByteString+toByteString (Base64 bs) = bs++instance Monad m => ToJSON m Base64 where+ toJSON (Base64 bs) = toJSON (C8.unpack (B64.encode bs))++instance ReportSchemaErrors m => FromJSON m Base64 where+ fromJSON val = do+ str <- fromJSON val+ case B64.decode (C8.pack str) of+ Left _err -> expected "base-64 encoded string" Nothing+ Right bs -> return $ Base64 bs
+ hackage-security/hackage-security/src/Hackage/Security/Util/Checked.hs view
@@ -0,0 +1,102 @@+{-# LANGUAGE CPP #-}+{-# OPTIONS_GHC -fno-warn-unused-binds #-}++#if __GLASGOW_HASKELL__ >= 708+{-# LANGUAGE RoleAnnotations #-}+{-# LANGUAGE IncoherentInstances #-}+#endif++-- | Checked exceptions+module Hackage.Security.Util.Checked (+ Throws+ , unthrow+ -- ** Base exceptions+ , throwChecked+ , catchChecked+ , handleChecked+ , tryChecked+ , checkIO+ , throwUnchecked+ , internalError+ ) where++import Control.Exception (Exception, IOException)+import qualified Control.Exception as Base++#if __GLASGOW_HASKELL__ >= 708+import GHC.Prim (coerce)+#else+import Unsafe.Coerce (unsafeCoerce)+#endif++{-------------------------------------------------------------------------------+ Basic infrastructure+-------------------------------------------------------------------------------}++-- | Checked exceptions+class Throws e where++#if __GLASGOW_HASKELL__ >= 708+type role Throws representational+#endif++unthrow :: forall a e proxy . proxy e -> (Throws e => a) -> a+unthrow _ x = unWrap (coerceWrap (Wrap x :: Wrap e a))++{-------------------------------------------------------------------------------+ Base exceptions+-------------------------------------------------------------------------------}++-- | Throw a checked exception+throwChecked :: (Exception e, Throws e) => e -> IO a+throwChecked = Base.throwIO++-- | Catch a checked exception+catchChecked :: forall a e. Exception e+ => (Throws e => IO a) -> (e -> IO a) -> IO a+catchChecked act = Base.catch (unthrow (Proxy :: Proxy e) act)++-- | 'catchChecked' with the arguments reversed+handleChecked :: Exception e => (e -> IO a) -> (Throws e => IO a) -> IO a+handleChecked act handler = catchChecked handler act++-- | Like 'try', but for checked exceptions+tryChecked :: Exception e => (Throws e => IO a) -> IO (Either e a)+tryChecked act = catchChecked (Right <$> act) (return . Left)++-- | Rethrow IO exceptions as checked exceptions+checkIO :: Throws IOException => IO a -> IO a+checkIO = Base.handle $ \(ex :: IOException) -> throwChecked ex++-- | Throw an unchecked exception+--+-- This is just an alias for 'throw', but makes it evident that this is a very+-- intentional use of an unchecked exception.+throwUnchecked :: Exception e => e -> IO a+throwUnchecked = Base.throwIO++-- | Variation on 'throwUnchecked' for internal errors+internalError :: String -> IO a+internalError = throwUnchecked . userError++{-------------------------------------------------------------------------------+ Auxiliary definitions (not exported)+-------------------------------------------------------------------------------}++-- | Wrap an action that may throw a checked exception+--+-- This is used internally in 'unthrow' to avoid impredicative+-- instantiation of the type of 'coerce'/'unsafeCoerce'.+newtype Wrap e a = Wrap { unWrap :: Throws e => a }++coerceWrap :: Wrap e a -> Wrap (Catch e) a+#if __GLASGOW_HASKELL__ >= 708+coerceWrap = coerce+#else+coerceWrap = unsafeCoerce+#endif++data Proxy a = Proxy++newtype Catch a = Catch a+instance Throws (Catch e) where
+ hackage-security/hackage-security/src/Hackage/Security/Util/IO.hs view
@@ -0,0 +1,61 @@+module Hackage.Security.Util.IO (+ -- * Miscelleneous+ getFileSize+ , handleDoesNotExist+ , withDirLock+ -- * Debugging+ , timedIO+ ) where++import Control.Exception+import Data.Time+import System.IO hiding (openTempFile, withFile)+import System.IO.Error++import Hackage.Security.Util.Path++{-------------------------------------------------------------------------------+ Miscelleneous+-------------------------------------------------------------------------------}++getFileSize :: (Num a, FsRoot root) => Path root -> IO a+getFileSize fp = fromInteger <$> withFile fp ReadMode hFileSize++handleDoesNotExist :: IO a -> IO (Maybe a)+handleDoesNotExist act =+ handle aux (Just <$> act)+ where+ aux e =+ if isDoesNotExistError e+ then return Nothing+ else throwIO e++-- | Attempt to create a filesystem lock in the specified directory+--+-- Given a file @/path/to@, we do this by attempting to create the directory+-- @//path/to/hackage-security-lock@, and deleting the directory again+-- afterwards. Creating a directory that already exists will throw an exception+-- on most OSs (certainly Linux, OSX and Windows) and is a reasonably common way+-- to implement a lock file.+withDirLock :: Path Absolute -> IO a -> IO a+withDirLock dir = bracket_ takeLock releaseLock+ where+ lock :: Path Absolute+ lock = dir </> fragment "hackage-security-lock"++ takeLock, releaseLock :: IO ()+ takeLock = createDirectory lock+ releaseLock = removeDirectory lock++{-------------------------------------------------------------------------------+ Debugging+-------------------------------------------------------------------------------}++timedIO :: String -> IO a -> IO a+timedIO label act = do+ before <- getCurrentTime+ result <- act+ after <- getCurrentTime+ hPutStrLn stderr $ label ++ ": " ++ show (after `diffUTCTime` before)+ hFlush stderr+ return result
+ hackage-security/hackage-security/src/Hackage/Security/Util/JSON.hs view
@@ -0,0 +1,212 @@+{-# LANGUAGE CPP #-}+#if __GLASGOW_HASKELL__ < 710+{-# LANGUAGE OverlappingInstances #-}+#endif+-- |+module Hackage.Security.Util.JSON (+ -- * Type classes+ ToJSON(..)+ , FromJSON(..)+ , ToObjectKey(..)+ , FromObjectKey(..)+ , ReportSchemaErrors(..)+ , Expected+ , Got+ , expected'+ -- * Utility+ , fromJSObject+ , fromJSField+ , fromJSOptField+ , mkObject+ -- * Re-exports+ , JSValue(..)+ , Int54+ ) where++import Control.Monad (liftM)+import Data.Map (Map)+import Data.Time+import Text.JSON.Canonical+import Network.URI+import qualified Data.Map as Map++#if !MIN_VERSION_time(1,5,0)+import System.Locale (defaultTimeLocale)+#endif++import Hackage.Security.Util.Path++{-------------------------------------------------------------------------------+ ToJSON and FromJSON classes++ We parameterize over the monad here to avoid mutual module dependencies.+-------------------------------------------------------------------------------}++class ToJSON m a where+ toJSON :: a -> m JSValue++class FromJSON m a where+ fromJSON :: JSValue -> m a++-- | Used in the 'ToJSON' instance for 'Map'+class ToObjectKey m a where+ toObjectKey :: a -> m String++-- | Used in the 'FromJSON' instance for 'Map'+class FromObjectKey m a where+ fromObjectKey :: String -> m a++-- | Monads in which we can report schema errors+class (Applicative m, Monad m) => ReportSchemaErrors m where+ expected :: Expected -> Maybe Got -> m a++type Expected = String+type Got = String++expected' :: ReportSchemaErrors m => Expected -> JSValue -> m a+expected' descr val = expected descr (Just (describeValue val))+ where+ describeValue :: JSValue -> String+ describeValue (JSNull ) = "null"+ describeValue (JSBool _) = "bool"+ describeValue (JSNum _) = "num"+ describeValue (JSString _) = "string"+ describeValue (JSArray _) = "array"+ describeValue (JSObject _) = "object"++unknownField :: ReportSchemaErrors m => String -> m a+unknownField field = expected ("field " ++ show field) Nothing++{-------------------------------------------------------------------------------+ ToObjectKey and FromObjectKey instances+-------------------------------------------------------------------------------}++instance Monad m => ToObjectKey m String where+ toObjectKey = return++instance Monad m => FromObjectKey m String where+ fromObjectKey = return++instance Monad m => ToObjectKey m (Path root) where+ toObjectKey (Path fp) = return fp++instance Monad m => FromObjectKey m (Path root) where+ fromObjectKey = liftM Path . fromObjectKey++{-------------------------------------------------------------------------------+ ToJSON and FromJSON instances+-------------------------------------------------------------------------------}++instance Monad m => ToJSON m JSValue where+ toJSON = return++instance Monad m => FromJSON m JSValue where+ fromJSON = return++instance Monad m => ToJSON m String where+ toJSON = return . JSString++instance ReportSchemaErrors m => FromJSON m String where+ fromJSON (JSString str) = return str+ fromJSON val = expected' "string" val++instance Monad m => ToJSON m Int54 where+ toJSON = return . JSNum++instance ReportSchemaErrors m => FromJSON m Int54 where+ fromJSON (JSNum i) = return i+ fromJSON val = expected' "int" val++instance+#if __GLASGOW_HASKELL__ >= 710+ {-# OVERLAPPABLE #-}+#endif+ (Monad m, ToJSON m a) => ToJSON m [a] where+ toJSON = liftM JSArray . mapM toJSON++instance+#if __GLASGOW_HASKELL__ >= 710+ {-# OVERLAPPABLE #-}+#endif+ (ReportSchemaErrors m, FromJSON m a) => FromJSON m [a] where+ fromJSON (JSArray as) = mapM fromJSON as+ fromJSON val = expected' "array" val++instance Monad m => ToJSON m UTCTime where+ toJSON = return . JSString . formatTime defaultTimeLocale "%FT%TZ"++instance ReportSchemaErrors m => FromJSON m UTCTime where+ fromJSON enc = do+ str <- fromJSON enc+ case parseTimeM False defaultTimeLocale "%FT%TZ" str of+ Just time -> return time+ Nothing -> expected "valid date-time string" (Just str)+#if !MIN_VERSION_time(1,5,0)+ where+ parseTimeM _trim = parseTime+#endif++instance ( Monad m+ , ToObjectKey m k+ , ToJSON m a+ ) => ToJSON m (Map k a) where+ toJSON = liftM JSObject . mapM aux . Map.toList+ where+ aux :: (k, a) -> m (String, JSValue)+ aux (k, a) = do k' <- toObjectKey k; a' <- toJSON a; return (k', a')++instance ( ReportSchemaErrors m+ , Ord k+ , FromObjectKey m k+ , FromJSON m a+ ) => FromJSON m (Map k a) where+ fromJSON enc = do+ obj <- fromJSObject enc+ Map.fromList <$> mapM aux obj+ where+ aux :: (String, JSValue) -> m (k, a)+ aux (k, a) = (,) <$> fromObjectKey k <*> fromJSON a++instance Monad m => ToJSON m URI where+ toJSON = toJSON . show++instance ReportSchemaErrors m => FromJSON m URI where+ fromJSON enc = do+ str <- fromJSON enc+ case parseURI str of+ Nothing -> expected "valid URI" (Just str)+ Just uri -> return uri++{-------------------------------------------------------------------------------+ Utility+-------------------------------------------------------------------------------}++fromJSObject :: ReportSchemaErrors m => JSValue -> m [(String, JSValue)]+fromJSObject (JSObject obj) = return obj+fromJSObject val = expected' "object" val++-- | Extract a field from a JSON object+fromJSField :: (ReportSchemaErrors m, FromJSON m a)+ => JSValue -> String -> m a+fromJSField val nm = do+ obj <- fromJSObject val+ case lookup nm obj of+ Just fld -> fromJSON fld+ Nothing -> unknownField nm++fromJSOptField :: (ReportSchemaErrors m, FromJSON m a)+ => JSValue -> String -> m (Maybe a)+fromJSOptField val nm = do+ obj <- fromJSObject val+ case lookup nm obj of+ Just fld -> Just <$> fromJSON fld+ Nothing -> return Nothing++mkObject :: forall m. Monad m => [(String, m JSValue)] -> m JSValue+mkObject = liftM JSObject . sequenceFields+ where+ sequenceFields :: [(String, m JSValue)] -> m [(String, JSValue)]+ sequenceFields [] = return []+ sequenceFields ((fld,val):flds) = do val' <- val+ flds' <- sequenceFields flds+ return ((fld,val'):flds')
+ hackage-security/hackage-security/src/Hackage/Security/Util/Lens.hs view
@@ -0,0 +1,48 @@+-- | Some very simple lens definitions (to avoid further dependencies)+--+-- Intended to be double-imported+-- > import Hackage.Security.Util.Lens (Lens)+-- > import qualified Hackage.Security.Util.Lens as Lens+module Hackage.Security.Util.Lens (+ -- * Generic definitions+ Lens+ , Lens'+ , get+ , modify+ , set+ -- * Specific lenses+ , lookupM+ ) where++import Control.Applicative+import Data.Functor.Identity++{-------------------------------------------------------------------------------+ General definitions+-------------------------------------------------------------------------------}++-- | Polymorphic lens+type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t++-- | Monomorphic lens+type Lens' s a = Lens s s a a++get :: Lens' s a -> s -> a+get l = getConst . l Const++modify :: Lens s t a b -> (a -> b) -> s -> t+modify l f = runIdentity . l (Identity . f)++set :: Lens s t a b -> b -> s -> t+set l = modify l . const++{-------------------------------------------------------------------------------+ Specific lenses+-------------------------------------------------------------------------------}++lookupM :: forall a b. (Eq a, Monoid b) => a -> Lens' [(a, b)] b+lookupM a f = go+ where+ go [] = (\b' -> [(a, b')] ) <$> f mempty+ go ((a', b):xs) | a == a' = (\b' -> (a, b'):xs ) <$> f b+ | otherwise = (\xs' -> (a', b):xs') <$> go xs
+ hackage-security/hackage-security/src/Hackage/Security/Util/Path.hs view
@@ -0,0 +1,459 @@+-- | A more type-safe version of file paths+--+-- This module is intended to replace imports of System.FilePath, and+-- additionally exports thin wrappers around common IO functions. To facilitate+-- importing this module unqualified we also re-export some definitions from+-- System.IO (importing both would likely lead to name clashes).+--+-- Note that his module does not import any other modules from Hackage.Security;+-- everywhere else we use Path instead of FilePath directly.+{-# LANGUAGE CPP #-}+module Hackage.Security.Util.Path (+ -- * Paths+ Path(..)+ , castRoot+ -- * FilePath-like operations on paths with arbitrary roots+ , takeDirectory+ , takeFileName+ , (<.>)+ , splitExtension+ -- * Unrooted paths+ , Unrooted+ , (</>)+ , rootPath+ , unrootPath+ , toUnrootedFilePath+ , fromUnrootedFilePath+ , fragment+ , joinFragments+ , isPathPrefixOf+ -- * File-system paths+ , Relative+ , Absolute+ , HomeDir+ , FsRoot(..)+ , FsPath(..)+ -- ** Conversions+ , toFilePath+ , fromFilePath+ , makeAbsolute+ , fromAbsoluteFilePath+ -- ** Wrappers around System.IO+ , withFile+ , openTempFile'+ -- ** Wrappers around Data.ByteString+ , readLazyByteString+ , readStrictByteString+ , writeLazyByteString+ , writeStrictByteString+ -- ** Wrappers around System.Directory+ , copyFile+ , createDirectory+ , createDirectoryIfMissing+ , removeDirectory+ , doesFileExist+ , doesDirectoryExist+ , removeFile+ , getTemporaryDirectory+ , getDirectoryContents+ , getRecursiveContents+ , renameFile+ , getCurrentDirectory+ -- * Wrappers around Codec.Archive.Tar+ , Tar+ , tarIndexLookup+ , tarAppend+ -- * Wrappers around Network.URI+ , Web+ , toURIPath+ , fromURIPath+ , uriPath+ , modifyUriPath+ -- * Re-exports+ , IOMode(..)+ , BufferMode(..)+ , Handle+ , SeekMode(..)+ , IO.hSetBuffering+ , IO.hClose+ , IO.hFileSize+ , IO.hSeek+ ) where++import Control.Monad+import Data.List (isPrefixOf)+import System.IO (IOMode(..), BufferMode(..), Handle, SeekMode(..))+import System.IO.Unsafe (unsafeInterleaveIO)+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as BS.L+import qualified System.FilePath as FP+import qualified System.IO as IO+import qualified System.Directory as Dir+import qualified Codec.Archive.Tar as Tar+import qualified Codec.Archive.Tar.Index as TarIndex+import qualified Network.URI as URI++import Hackage.Security.Util.Pretty++{-------------------------------------------------------------------------------+ Paths+-------------------------------------------------------------------------------}++-- | Paths+--+-- A 'Path' is simply a 'FilePath' with a type-level tag indicating where this+-- path is rooted (relative to the current directory, absolute path, relative to+-- a web domain, whatever). Most operations on 'Path' are just lifted versions+-- of the operations on the underlying 'FilePath'. The tag however allows us to+-- give a lot of operations a more meaningful type. For instance, it does not+-- make sense to append two absolute paths together; instead, we can only append+-- an unrooted path to another path. It also means we avoid bugs where we use+-- one kind of path where we expect another.+newtype Path a = Path { unPath :: FilePath }+ deriving (Show, Eq, Ord)++-- | Reinterpret the root of a path+--+-- This literally just changes the type-level tag; use with caution!+castRoot :: Path root -> Path root'+castRoot (Path fp) = Path fp++{-------------------------------------------------------------------------------+ FilePath-like operations on paths with an arbitrary root+-------------------------------------------------------------------------------}++takeDirectory :: Path a -> Path a+takeDirectory = liftFP FP.takeDirectory++takeFileName :: Path a -> String+takeFileName = liftFromFP FP.takeFileName++(<.>) :: Path a -> String -> Path a+fp <.> ext = liftFP (FP.<.> ext) fp++splitExtension :: Path a -> (Path a, String)+splitExtension (Path fp) = (Path fp', ext)+ where+ (fp', ext) = FP.splitExtension fp++{-------------------------------------------------------------------------------+ Unrooted paths+-------------------------------------------------------------------------------}++-- | Type-level tag for unrooted paths+--+-- Unrooted paths need a root before they can be interpreted.+data Unrooted++instance Pretty (Path Unrooted) where+ pretty (Path fp) = fp++(</>) :: Path a -> Path Unrooted -> Path a+(</>) = liftFP2 (FP.</>)++-- | Reinterpret an unrooted path+--+-- This is an alias for 'castRoot'; see comments there.+rootPath :: Path Unrooted -> Path root+rootPath (Path fp) = Path fp++-- | Forget a path's root+--+-- This is an alias for 'castRoot'; see comments there.+unrootPath :: Path root -> Path Unrooted+unrootPath (Path fp) = Path fp++toUnrootedFilePath :: Path Unrooted -> FilePath+toUnrootedFilePath = unPath++fromUnrootedFilePath :: FilePath -> Path Unrooted+fromUnrootedFilePath = Path++-- | A path fragment (like a single directory or filename)+fragment :: String -> Path Unrooted+fragment = fromUnrootedFilePath++joinFragments :: [String] -> Path Unrooted+joinFragments = liftToFP FP.joinPath++isPathPrefixOf :: Path Unrooted -> Path Unrooted -> Bool+isPathPrefixOf = liftFromFP2 isPrefixOf++{-------------------------------------------------------------------------------+ File-system paths+-------------------------------------------------------------------------------}++data Relative+data Absolute+data HomeDir++instance Pretty (Path Absolute) where+ pretty (Path fp) = fp++instance Pretty (Path Relative) where+ pretty (Path fp) = "./" ++ fp++instance Pretty (Path HomeDir) where+ pretty (Path fp) = "~/" ++ fp++-- | A file system root can be interpreted as an (absolute) FilePath+class FsRoot root where+ toAbsoluteFilePath :: Path root -> IO FilePath++instance FsRoot Relative where+ toAbsoluteFilePath (Path fp) = go fp+ where+ go :: FilePath -> IO FilePath+#if MIN_VERSION_directory(1,2,2)+ go = Dir.makeAbsolute+#else+ -- copied implementation from the directory package+ go = (FP.normalise <$>) . absolutize+ absolutize path -- avoid the call to `getCurrentDirectory` if we can+ | FP.isRelative path = (FP.</> path) . FP.addTrailingPathSeparator <$>+ Dir.getCurrentDirectory+ | otherwise = return path+#endif++instance FsRoot Absolute where+ toAbsoluteFilePath (Path fp) = return fp++instance FsRoot HomeDir where+ toAbsoluteFilePath (Path fp) = do+ home <- Dir.getHomeDirectory+ return $ home FP.</> fp++-- | Abstract over a file system root+--+-- see 'fromFilePath'+data FsPath = forall root. FsRoot root => FsPath (Path root)++{-------------------------------------------------------------------------------+ Conversions+-------------------------------------------------------------------------------}++toFilePath :: Path Absolute -> FilePath+toFilePath (Path fp) = fp++fromFilePath :: FilePath -> FsPath+fromFilePath fp+ | FP.isAbsolute fp = FsPath (Path fp :: Path Absolute)+ | Just fp' <- atHome fp = FsPath (Path fp' :: Path HomeDir)+ | otherwise = FsPath (Path fp :: Path Relative)+ where+ -- TODO: I don't know if there a standard way that Windows users refer to+ -- their home directory. For now, we'll only interpret '~'. Everybody else+ -- can specify an absolute path if this doesn't work.+ atHome :: FilePath -> Maybe FilePath+ atHome "~" = Just ""+ atHome ('~':sep:fp') | FP.isPathSeparator sep = Just fp'+ atHome _otherwise = Nothing++makeAbsolute :: FsPath -> IO (Path Absolute)+makeAbsolute (FsPath p) = Path <$> toAbsoluteFilePath p++fromAbsoluteFilePath :: FilePath -> Path Absolute+fromAbsoluteFilePath fp+ | FP.isAbsolute fp = Path fp+ | otherwise = error "fromAbsoluteFilePath: not an absolute path"++{-------------------------------------------------------------------------------+ Wrappers around System.IO+-------------------------------------------------------------------------------}++-- | Wrapper around 'withFile'+withFile :: FsRoot root => Path root -> IOMode -> (Handle -> IO r) -> IO r+withFile path mode callback = do+ filePath <- toAbsoluteFilePath path+ IO.withFile filePath mode callback++-- | Wrapper around 'openBinaryTempFileWithDefaultPermissions'+--+-- NOTE: The caller is responsible for cleaning up the temporary file.+openTempFile' :: FsRoot root => Path root -> String -> IO (Path Absolute, Handle)+openTempFile' path template = do+ filePath <- toAbsoluteFilePath path+ (tempFilePath, h) <- IO.openBinaryTempFileWithDefaultPermissions filePath template+ return (fromAbsoluteFilePath tempFilePath, h)++{-------------------------------------------------------------------------------+ Wrappers around Data.ByteString.*+-------------------------------------------------------------------------------}++readLazyByteString :: FsRoot root => Path root -> IO BS.L.ByteString+readLazyByteString path = do+ filePath <- toAbsoluteFilePath path+ BS.L.readFile filePath++readStrictByteString :: FsRoot root => Path root -> IO BS.ByteString+readStrictByteString path = do+ filePath <- toAbsoluteFilePath path+ BS.readFile filePath++writeLazyByteString :: FsRoot root => Path root -> BS.L.ByteString -> IO ()+writeLazyByteString path bs = do+ filePath <- toAbsoluteFilePath path+ BS.L.writeFile filePath bs++writeStrictByteString :: FsRoot root => Path root -> BS.ByteString -> IO ()+writeStrictByteString path bs = do+ filePath <- toAbsoluteFilePath path+ BS.writeFile filePath bs++{-------------------------------------------------------------------------------+ Wrappers around System.Directory+-------------------------------------------------------------------------------}++copyFile :: (FsRoot root, FsRoot root') => Path root -> Path root' -> IO ()+copyFile src dst = do+ src' <- toAbsoluteFilePath src+ dst' <- toAbsoluteFilePath dst+ Dir.copyFile src' dst'++createDirectory :: FsRoot root => Path root -> IO ()+createDirectory path = Dir.createDirectory =<< toAbsoluteFilePath path++createDirectoryIfMissing :: FsRoot root => Bool -> Path root -> IO ()+createDirectoryIfMissing createParents path = do+ filePath <- toAbsoluteFilePath path+ Dir.createDirectoryIfMissing createParents filePath++removeDirectory :: FsRoot root => Path root -> IO ()+removeDirectory path = Dir.removeDirectory =<< toAbsoluteFilePath path++doesFileExist :: FsRoot root => Path root -> IO Bool+doesFileExist path = do+ filePath <- toAbsoluteFilePath path+ Dir.doesFileExist filePath++doesDirectoryExist :: FsRoot root => Path root -> IO Bool+doesDirectoryExist path = do+ filePath <- toAbsoluteFilePath path+ Dir.doesDirectoryExist filePath++removeFile :: FsRoot root => Path root -> IO ()+removeFile path = do+ filePath <- toAbsoluteFilePath path+ Dir.removeFile filePath++getTemporaryDirectory :: IO (Path Absolute)+getTemporaryDirectory = fromAbsoluteFilePath <$> Dir.getTemporaryDirectory++-- | Return the immediate children of a directory+--+-- Filters out @"."@ and @".."@.+getDirectoryContents :: FsRoot root => Path root -> IO [Path Unrooted]+getDirectoryContents path = do+ filePath <- toAbsoluteFilePath path+ fragments <$> Dir.getDirectoryContents filePath+ where+ fragments :: [String] -> [Path Unrooted]+ fragments = map fromUnrootedFilePath . filter (not . skip)++ skip :: String -> Bool+ skip "." = True+ skip ".." = True+ skip _ = False++-- | Recursive traverse a directory structure+--+-- Returns a set of paths relative to the directory specified. The list is+-- lazily constructed, so that directories are only read when required.+-- (This is also essential to ensure that this function does not build the+-- entire result in memory before returning, potentially running out of heap.)+getRecursiveContents :: FsRoot root => Path root -> IO [Path Unrooted]+getRecursiveContents root = go emptyPath+ where+ go :: Path Unrooted -> IO [Path Unrooted]+ go subdir = unsafeInterleaveIO $ do+ entries <- getDirectoryContents (root </> subdir)+ liftM concat $ forM entries $ \entry -> do+ let path = subdir </> entry+ isDirectory <- doesDirectoryExist (root </> path)+ if isDirectory then go path+ else return [path]++ emptyPath :: Path Unrooted+ emptyPath = Path (FP.joinPath [])++renameFile :: (FsRoot root, FsRoot root')+ => Path root -- ^ Old+ -> Path root' -- ^ New+ -> IO ()+renameFile old new = do+ old' <- toAbsoluteFilePath old+ new' <- toAbsoluteFilePath new+ Dir.renameFile old' new'++getCurrentDirectory :: IO (Path Absolute)+getCurrentDirectory = do+ cwd <- Dir.getCurrentDirectory+ makeAbsolute $ fromFilePath cwd++{-------------------------------------------------------------------------------+ Wrappers around Codec.Archive.Tar.*+-------------------------------------------------------------------------------}++data Tar++instance Pretty (Path Tar) where+ pretty (Path fp) = "<tarball>/" ++ fp++tarIndexLookup :: TarIndex.TarIndex -> Path Tar -> Maybe TarIndex.TarIndexEntry+tarIndexLookup index path = TarIndex.lookup index path'+ where+ path' :: FilePath+ path' = toUnrootedFilePath $ unrootPath path++tarAppend :: (FsRoot root, FsRoot root')+ => Path root -- ^ Path of the @.tar@ file+ -> Path root' -- ^ Base directory+ -> [Path Tar] -- ^ Files to add, relative to the base dir+ -> IO ()+tarAppend tarFile baseDir contents = do+ tarFile' <- toAbsoluteFilePath tarFile+ baseDir' <- toAbsoluteFilePath baseDir+ Tar.append tarFile' baseDir' contents'+ where+ contents' :: [FilePath]+ contents' = map (toUnrootedFilePath . unrootPath) contents++{-------------------------------------------------------------------------------+ Wrappers around Network.URI+-------------------------------------------------------------------------------}++data Web++toURIPath :: FilePath -> Path Web+toURIPath = rootPath . fromUnrootedFilePath++fromURIPath :: Path Web -> FilePath+fromURIPath = toUnrootedFilePath . unrootPath++uriPath :: URI.URI -> Path Web+uriPath = toURIPath . URI.uriPath++modifyUriPath :: URI.URI -> (Path Web -> Path Web) -> URI.URI+modifyUriPath uri f = uri { URI.uriPath = f' (URI.uriPath uri) }+ where+ f' :: FilePath -> FilePath+ f' = fromURIPath . f . toURIPath++{-------------------------------------------------------------------------------+ Auxiliary+-------------------------------------------------------------------------------}++liftFP :: (FilePath -> FilePath) -> Path a -> Path b+liftFP f (Path fp) = Path (f fp)++liftFP2 :: (FilePath -> FilePath -> FilePath) -> Path a -> Path b -> Path c+liftFP2 f (Path fp) (Path fp') = Path (f fp fp')++liftFromFP :: (FilePath -> x) -> Path a -> x+liftFromFP f (Path fp) = f fp++liftFromFP2 :: (FilePath -> FilePath -> x) -> Path a -> Path b -> x+liftFromFP2 f (Path fp) (Path fp') = f fp fp'++liftToFP :: (x -> FilePath) -> x -> Path a+liftToFP f x = Path (f x)
+ hackage-security/hackage-security/src/Hackage/Security/Util/Pretty.hs view
@@ -0,0 +1,8 @@+-- | Producing human-reaadable strings+module Hackage.Security.Util.Pretty (+ Pretty(..)+ ) where++-- | Produce a human-readable string+class Pretty a where+ pretty :: a -> String
+ hackage-security/hackage-security/src/Hackage/Security/Util/Some.hs view
@@ -0,0 +1,102 @@+{-# LANGUAGE CPP #-}+-- | Hiding existentials+module Hackage.Security.Util.Some (+ Some(..)+ -- ** Equality+ , DictEq(..)+ , SomeEq(..)+ -- ** Serialization+ , DictShow(..)+ , SomeShow(..)+ -- ** Pretty-printing+ , DictPretty(..)+ , SomePretty(..)+ -- ** Type checking+ , typecheckSome+#if !MIN_VERSION_base(4,7,0)+ -- ** Compatibility with base < 4.7+ , tyConSome+#endif+ ) where++#if MIN_VERSION_base(4,7,0)+import Data.Typeable (Typeable)+#else+import qualified Data.Typeable as Typeable+#endif++import Hackage.Security.Util.TypedEmbedded+import Hackage.Security.Util.Pretty++data Some f = forall a. Some (f a)++#if MIN_VERSION_base(4,7,0)+deriving instance Typeable Some+#else+tyConSome :: Typeable.TyCon+tyConSome = Typeable.mkTyCon3 "hackage-security" "Hackage.Security.Util.Some" "Some"+#endif++{-------------------------------------------------------------------------------+ Equality on Some types++ Note that we cannot really do something similar for ordering; what value+ should we return for++ > Some (f x) `compare` Some (f x')++ where @x :: a@, @x' :: a'@ and @a /= a'@? These are incomparable.+-------------------------------------------------------------------------------}++data DictEq a where+ DictEq :: Eq a => DictEq a++-- | Type @f@ satisfies @SomeEq f@ if @f a@ satisfies @Eq@ independent of @a@+class SomeEq f where+ someEq :: DictEq (f a)++instance (Typed f, SomeEq f) => Eq (Some f) where+ Some (x :: f a) == Some (y :: f a') =+ case unify (typeOf x) (typeOf y) of+ Nothing -> False+ Just Refl -> case someEq :: DictEq (f a) of DictEq -> x == y++{-------------------------------------------------------------------------------+ Showing Some types+-------------------------------------------------------------------------------}++data DictShow a where+ DictShow :: Show a => DictShow a++-- | Type @f@ satisfies @SomeShow f@ if @f a@ satisfies @Show@ independent of @a@+class SomeShow f where+ someShow :: DictShow (f a)++instance SomeShow f => Show (Some f) where+ show (Some (x :: f a)) =+ case someShow :: DictShow (f a) of DictShow -> show x++{-------------------------------------------------------------------------------+ Pretty-printing Some types+-------------------------------------------------------------------------------}++data DictPretty a where+ DictPretty :: Pretty a => DictPretty a++-- | Type @f@ satisfies @SomeShow f@ if @f a@ satisfies @Show@ independent of @a@+class SomePretty f where+ somePretty :: DictPretty (f a)++instance SomePretty f => Pretty (Some f) where+ pretty (Some (x :: f a)) =+ case somePretty :: DictPretty (f a) of DictPretty -> pretty x++{-------------------------------------------------------------------------------+ Typechecking Some types+-------------------------------------------------------------------------------}++typecheckSome :: Typed f => Some f -> Some (TypeOf f) -> Bool+typecheckSome (Some x) (Some typ) =+ case unify (typeOf x) typ of+ Just Refl -> True+ Nothing -> False
+ hackage-security/hackage-security/src/Hackage/Security/Util/Stack.hs view
@@ -0,0 +1,8 @@+-- | Heterogenous lists+module Hackage.Security.Util.Stack (+ (:-)(..)+ ) where++data h :- t = h :- t+ deriving (Eq, Show)+infixr 5 :-
+ hackage-security/hackage-security/src/Hackage/Security/Util/TypedEmbedded.hs view
@@ -0,0 +1,38 @@+-- | Embedded languages with meta level types+module Hackage.Security.Util.TypedEmbedded (+ (:=:)(Refl)+ , TypeOf+ , Unify(..)+ , Typed(..)+ , AsType(..)+ ) where++-- | Type equality proofs+--+-- This is a direct copy of "type-equality:Data.Type.Equality"; if we don't+-- mind the dependency we can use that package directly.+data a :=: b where+ Refl :: a :=: a++type family TypeOf (f :: * -> *) :: * -> *++-- | Equality check that gives us a type-level equality proof.+class Unify f where+ unify :: f typ -> f typ' -> Maybe (typ :=: typ')++-- | Embedded languages with type inference+class Unify (TypeOf f) => Typed f where+ typeOf :: f typ -> TypeOf f typ++-- | Cast from one type to another+--+-- By default (for language with type inference) we just compare the types+-- returned by 'typeOf'; however, in languages in which terms can have more+-- than one type this may not be the correct definition (indeed, for such+-- languages we cannot give an instance of 'Typed').+class AsType f where+ asType :: f typ -> TypeOf f typ' -> Maybe (f typ')+ default asType :: Typed f => f typ -> TypeOf f typ' -> Maybe (f typ')+ asType x typ = case unify (typeOf x) typ of+ Just Refl -> Just x+ Nothing -> Nothing
+ hackage-security/hackage-security/src/Prelude.hs view
@@ -0,0 +1,32 @@+-- | Smooth over differences between various ghc versions by making older+-- preludes look like 4.8.0+{-# LANGUAGE PackageImports #-}+{-# LANGUAGE CPP #-}+module Prelude (+ module P+#if !MIN_VERSION_base(4,8,0)+ , Applicative(..)+ , Monoid(..)+ , (<$>)+ , (<$)+ , Traversable(traverse)+ , displayException+#endif+ ) where++#if MIN_VERSION_base(4,8,0)+import "base" Prelude as P+#else+#if MIN_VERSION_base(4,6,0)+import "base" Prelude as P+#else+import "base" Prelude as P hiding (catch)+#endif+import Control.Applicative+import Control.Exception (Exception)+import Data.Monoid+import Data.Traversable (Traversable(traverse))++displayException :: Exception e => e -> String+displayException = show+#endif
+ hackage-security/hackage-security/src/Text/JSON/Canonical.hs view
@@ -0,0 +1,272 @@+{-# LANGUAGE CPP #-}+--------------------------------------------------------------------+-- |+-- Module : Text.JSON.Parsec+-- Copyright : (c) Galois, Inc. 2007-2009, Duncan Coutts 2015+--+--+-- Minimal implementation of Canonical JSON.+--+-- <http://wiki.laptop.org/go/Canonical_JSON>+--+-- A "canonical JSON" format is provided in order to provide meaningful and+-- repeatable hashes of JSON-encoded data. Canonical JSON is parsable with any+-- full JSON parser, but security-conscious applications will want to verify+-- that input is in canonical form before authenticating any hash or signature+-- on that input.+--+-- This implementation is derived from the json parser from the json package,+-- with simplifications to meet the Canonical JSON grammar.+--+-- TODO: Known bugs/limitations:+--+-- * Decoding/encoding Unicode code-points beyond @U+00ff@ is currently broken+--+module Text.JSON.Canonical+ ( JSValue(..)+ , Int54+ , parseCanonicalJSON+ , renderCanonicalJSON+ ) where++import Text.ParserCombinators.Parsec+ ( CharParser, (<|>), (<?>), many, between, sepBy+ , satisfy, char, string, digit, spaces+ , parse )+import Control.Arrow (first)+import Data.Bits (Bits)+#if MIN_VERSION_base(4,7,0)+import Data.Bits (FiniteBits)+#endif+import Data.Char (isDigit, digitToInt)+import Data.Data (Data)+import Data.Function (on)+import Data.Int (Int64)+import Data.Ix (Ix)+import Data.List (foldl', sortBy)+import Data.Typeable (Typeable)+import Foreign.Storable (Storable)+import Text.Printf (PrintfArg)+import qualified Data.ByteString.Lazy.Char8 as BS++data JSValue+ = JSNull+ | JSBool !Bool+ | JSNum !Int54+ | JSString String+ | JSArray [JSValue]+ | JSObject [(String, JSValue)]+ deriving (Show, Read, Eq, Ord)++-- | 54-bit integer values+--+-- JavaScript can only safely represent numbers between @-(2^53 - 1)@ and+-- @2^53 - 1@.+--+-- TODO: Although we introduce the type here, we don't actually do any bounds+-- checking and just inherit all type class instance from Int64. We should+-- probably define `fromInteger` to do bounds checking, give different instances+-- for type classes such as `Bounded` and `FiniteBits`, etc.+newtype Int54 = Int54 { int54ToInt64 :: Int64 }+ deriving ( Bounded+ , Enum+ , Eq+ , Integral+ , Data+ , Num+ , Ord+ , Real+ , Ix+#if MIN_VERSION_base(4,7,0)+ , FiniteBits+#endif+ , Bits+ , Storable+ , PrintfArg+ , Typeable+ )++instance Show Int54 where+ show = show . int54ToInt64++instance Read Int54 where+ readsPrec p = map (first Int54) . readsPrec p++------------------------------------------------------------------------------++-- | Encode as \"Canonical\" JSON.+--+-- NB: Canonical JSON's string escaping rules deviate from RFC 7159+-- JSON which requires+--+-- "All Unicode characters may be placed within the quotation+-- marks, except for the characters that must be escaped: quotation+-- mark, reverse solidus, and the control characters (@U+0000@+-- through @U+001F@)."+--+-- Whereas the current specification of Canonical JSON explicitly+-- requires to violate this by only escaping the quotation mark and+-- the reverse solidus. This, however, contradicts Canonical JSON's+-- statement that "Canonical JSON is parsable with any full JSON+-- parser"+--+-- Consequently, Canonical JSON is not a proper subset of RFC 7159.+renderCanonicalJSON :: JSValue -> BS.ByteString+renderCanonicalJSON v = BS.pack (s_value v [])++s_value :: JSValue -> ShowS+s_value JSNull = showString "null"+s_value (JSBool False) = showString "false"+s_value (JSBool True) = showString "true"+s_value (JSNum n) = shows n+s_value (JSString s) = s_string s+s_value (JSArray vs) = s_array vs+s_value (JSObject fs) = s_object (sortBy (compare `on` fst) fs)++s_string :: String -> ShowS+s_string s = showChar '"' . showl s+ where showl [] = showChar '"'+ showl (c:cs) = s_char c . showl cs++ s_char '"' = showChar '\\' . showChar '"'+ s_char '\\' = showChar '\\' . showChar '\\'+ s_char c = showChar c++s_array :: [JSValue] -> ShowS+s_array [] = showString "[]"+s_array (v0:vs0) = showChar '[' . s_value v0 . showl vs0+ where showl [] = showChar ']'+ showl (v:vs) = showChar ',' . s_value v . showl vs++s_object :: [(String, JSValue)] -> ShowS+s_object [] = showString "{}"+s_object ((k0,v0):kvs0) = showChar '{' . s_string k0+ . showChar ':' . s_value v0+ . showl kvs0+ where showl [] = showChar '}'+ showl ((k,v):kvs) = showChar ',' . s_string k+ . showChar ':' . s_value v+ . showl kvs++------------------------------------------------------------------------------++parseCanonicalJSON :: BS.ByteString -> Either String JSValue+parseCanonicalJSON = either (Left . show) Right+ . parse p_value ""+ . BS.unpack++p_value :: CharParser () JSValue+p_value = spaces *> p_jvalue++tok :: CharParser () a -> CharParser () a+tok p = p <* spaces++{-+value:+ string+ number+ object+ array+ true+ false+ null+-}+p_jvalue :: CharParser () JSValue+p_jvalue = (JSNull <$ p_null)+ <|> (JSBool <$> p_boolean)+ <|> (JSArray <$> p_array)+ <|> (JSString <$> p_string)+ <|> (JSObject <$> p_object)+ <|> (JSNum <$> p_number)+ <?> "JSON value"++p_null :: CharParser () ()+p_null = tok (string "null") >> return ()++p_boolean :: CharParser () Bool+p_boolean = tok+ ( (True <$ string "true")+ <|> (False <$ string "false")+ )+{-+array:+ []+ [ elements ]+elements:+ value+ value , elements+-}+p_array :: CharParser () [JSValue]+p_array = between (tok (char '[')) (tok (char ']'))+ $ p_jvalue `sepBy` tok (char ',')++{-+string:+ ""+ " chars "+chars:+ char+ char chars+char:+ any byte except hex 22 (") or hex 5C (\)+ \\+ \"+-}+p_string :: CharParser () String+p_string = between (tok (char '"')) (tok (char '"')) (many p_char)+ where p_char = (char '\\' >> p_esc)+ <|> (satisfy (\x -> x /= '"' && x /= '\\'))++ p_esc = ('"' <$ char '"')+ <|> ('\\' <$ char '\\')+ <?> "escape character"+{-+object:+ {}+ { members }+members:+ pair+ pair , members+pair:+ string : value+-}+p_object :: CharParser () [(String,JSValue)]+p_object = between (tok (char '{')) (tok (char '}'))+ $ p_field `sepBy` tok (char ',')+ where p_field = (,) <$> (p_string <* tok (char ':')) <*> p_jvalue++{-+number:+ int+int:+ digit+ digit1-9 digits+ - digit1-9+ - digit1-9 digits+digits:+ digit+ digit digits+-}++-- | Parse an int+--+-- TODO: Currently this allows for a maximum of 15 digits (i.e. a maximum value+-- of @999,999,999,999,999@) as a crude approximation of the 'Int54' range.+p_number :: CharParser () Int54+p_number = tok+ ( (char '-' *> (negate <$> pnat))+ <|> pnat+ <|> zero+ )+ where pnat = (\d ds -> strToInt (d:ds)) <$> digit19 <*> manyN 14 digit+ digit19 = satisfy (\c -> isDigit c && c /= '0') <?> "digit"+ strToInt = foldl' (\x d -> 10*x + digitToInt54 d) 0+ zero = 0 <$ char '0'++digitToInt54 :: Char -> Int54+digitToInt54 = fromIntegral . digitToInt++manyN :: Int -> CharParser () a -> CharParser () [a]+manyN 0 _ = pure []+manyN n p = ((:) <$> p <*> manyN (n-1) p)+ <|> pure []
+ hackage-security/hackage-security/tests/TestSuite.hs view
@@ -0,0 +1,434 @@+module Main (main) where++-- stdlib+import Control.Exception+import Control.Monad+import Data.Maybe (fromJust)+import Data.Time+import Network.URI (URI, parseURI)+import Test.Tasty+import Test.Tasty.HUnit+import System.IO.Temp (withSystemTempDirectory)++-- hackage-security+import Hackage.Security.Client+import Hackage.Security.Client.Repository+import Hackage.Security.JSON (DeserializationError(..))+import Hackage.Security.Util.Checked+import Hackage.Security.Util.Path+import Hackage.Security.Util.Pretty+import qualified Hackage.Security.Client.Repository.Remote as Remote+import qualified Hackage.Security.Client.Repository.Cache as Cache++-- TestSuite+import TestSuite.HttpMem+import TestSuite.InMemCache+import TestSuite.InMemRepo+import TestSuite.InMemRepository+import TestSuite.PrivateKeys+import TestSuite.Util.StrictMVar++{-------------------------------------------------------------------------------+ TestSuite driver+-------------------------------------------------------------------------------}++main :: IO ()+main = defaultMain tests++tests :: TestTree+tests = testGroup "hackage-security" [+ testGroup "InMem" [+ testCase "testInMemInitialHasForUpdates" testInMemInitialHasUpdates+ , testCase "testInMemNoUpdates" testInMemNoUpdates+ , testCase "testInMemUpdatesAfterCron" testInMemUpdatesAfterCron+ , testCase "testInMemKeyRollover" testInMemKeyRollover+ , testCase "testInMemOutdatedTimestamp" testInMemOutdatedTimestamp+ ]+ , testGroup "HttpMem" [+ testCase "testHttpMemInitialHasForUpdates" testHttpMemInitialHasUpdates+ , testCase "testHttpMemNoUpdates" testHttpMemNoUpdates+ , testCase "testHttpMemUpdatesAfterCron" testHttpMemUpdatesAfterCron+ , testCase "testHttpMemKeyRollover" testHttpMemKeyRollover+ , testCase "testHttpMemOutdatedTimestamp" testHttpMemOutdatedTimestamp+ ]+ ]++{-------------------------------------------------------------------------------+ In-memory tests++ These tests test the core TUF infrastructure, but any specific Repository+ implementation; instead, they use one specifically designed for testing+ (almost a Repository mock-up).+-------------------------------------------------------------------------------}++-- | Initial check for updates: empty cache+testInMemInitialHasUpdates :: Assertion+testInMemInitialHasUpdates = inMemTest $ \_inMemRepo logMsgs repo -> do+ withAssertLog "A" logMsgs [] $+ assertEqual "A.1" HasUpdates =<< checkForUpdates repo =<< checkExpiry++-- | Check that if we run updates again, with no changes on the server,+-- we get NoUpdates+testInMemNoUpdates :: Assertion+testInMemNoUpdates = inMemTest $ \_inMemRepo logMsgs repo -> do+ withAssertLog "A" logMsgs [] $ do+ assertEqual "A.1" HasUpdates =<< checkForUpdates repo =<< checkExpiry+ withAssertLog "B" logMsgs [] $ do+ assertEqual "B.2" NoUpdates =<< checkForUpdates repo =<< checkExpiry++-- | Test that we have updates reported after the timestamp is resigned+testInMemUpdatesAfterCron :: Assertion+testInMemUpdatesAfterCron = inMemTest $ \inMemRepo logMsgs repo -> do+ withAssertLog "A" logMsgs [] $ do+ assertEqual "A.1" HasUpdates =<< checkForUpdates repo =<< checkExpiry+ withAssertLog "B" logMsgs [] $ do+ assertEqual "B.2" NoUpdates =<< checkForUpdates repo =<< checkExpiry++ inMemRepoCron inMemRepo =<< getCurrentTime++ withAssertLog "C" logMsgs [] $ do+ assertEqual "C.1" HasUpdates =<< checkForUpdates repo =<< checkExpiry+ withAssertLog "D" logMsgs [] $ do+ assertEqual "D.2" NoUpdates =<< checkForUpdates repo =<< checkExpiry++-- | Test what happens when the timestamp/snapshot keys rollover+testInMemKeyRollover :: Assertion+testInMemKeyRollover = inMemTest $ \inMemRepo logMsgs repo -> do+ withAssertLog "A" logMsgs [] $ do+ assertEqual "A.1" HasUpdates =<< checkForUpdates repo =<< checkExpiry+ withAssertLog "B" logMsgs [] $ do+ assertEqual "B.2" NoUpdates =<< checkForUpdates repo =<< checkExpiry++ inMemRepoKeyRollover inMemRepo =<< getCurrentTime++ let msgs = [verificationError $ unknownKeyError timestampPath]+ withAssertLog "C" logMsgs msgs $ do+ assertEqual "C.1" HasUpdates =<< checkForUpdates repo =<< checkExpiry+ withAssertLog "D" logMsgs [] $ do+ assertEqual "D.1" NoUpdates =<< checkForUpdates repo =<< checkExpiry++-- | Test what happens when server has an outdated timestamp+-- (after a successful initial update)+testInMemOutdatedTimestamp :: Assertion+testInMemOutdatedTimestamp = inMemTest $ \_inMemRepo logMsgs repo -> do+ withAssertLog "A" logMsgs [] $ do+ assertEqual "A.1" HasUpdates =<< checkForUpdates repo =<< checkExpiry+ withAssertLog "B" logMsgs [] $ do+ assertEqual "B.2" NoUpdates =<< checkForUpdates repo =<< checkExpiry++ now <- getCurrentTime+ let (FileExpires fourDaysLater) = expiresInDays now 4++ let msgs = replicate 5 (inHistory (Right (expired timestampPath)))+ catchVerificationLoop msgs $ do+ withAssertLog "C" logMsgs [] $ do+ assertEqual "C.1" HasUpdates =<< checkForUpdates repo fourDaysLater++{-------------------------------------------------------------------------------+ Same tests, but going through the "real" Remote repository and Cache, though+ still using an in-memory repository (with a HttpLib bridge)++ These are almost hte same as the in-memory tests, but the log messages we+ expect are slightly different because the Remote repository indicates what+ is is downloading, and why.+-------------------------------------------------------------------------------}++-- | Initial check for updates: empty cache+testHttpMemInitialHasUpdates :: Assertion+testHttpMemInitialHasUpdates = httpMemTest $ \_inMemRepo logMsgs repo -> do+ withAssertLog "A" logMsgs msgsInitialUpdate $+ assertEqual "A.1" HasUpdates =<< checkForUpdates repo =<< checkExpiry++-- | Check that if we run updates again, with no changes on the server,+-- we get NoUpdates+testHttpMemNoUpdates :: Assertion+testHttpMemNoUpdates = httpMemTest $ \_inMemRepo logMsgs repo -> do+ withAssertLog "A" logMsgs msgsInitialUpdate $ do+ assertEqual "A.1" HasUpdates =<< checkForUpdates repo =<< checkExpiry+ withAssertLog "B" logMsgs msgsNoUpdates $ do+ assertEqual "B.2" NoUpdates =<< checkForUpdates repo =<< checkExpiry++-- | Test that we have updates reported after the timestamp is resigned+testHttpMemUpdatesAfterCron :: Assertion+testHttpMemUpdatesAfterCron = httpMemTest $ \inMemRepo logMsgs repo -> do+ withAssertLog "A" logMsgs msgsInitialUpdate $ do+ assertEqual "A.1" HasUpdates =<< checkForUpdates repo =<< checkExpiry+ withAssertLog "B" logMsgs msgsNoUpdates $ do+ assertEqual "B.2" NoUpdates =<< checkForUpdates repo =<< checkExpiry++ inMemRepoCron inMemRepo =<< getCurrentTime++ withAssertLog "C" logMsgs msgsResigned $ do+ assertEqual "C.1" HasUpdates =<< checkForUpdates repo =<< checkExpiry+ withAssertLog "D" logMsgs msgsNoUpdates $ do+ assertEqual "D.2" NoUpdates =<< checkForUpdates repo =<< checkExpiry++-- | Test what happens when the timestamp/snapshot keys rollover+testHttpMemKeyRollover :: Assertion+testHttpMemKeyRollover = httpMemTest $ \inMemRepo logMsgs repo -> do+ withAssertLog "A" logMsgs msgsInitialUpdate $ do+ assertEqual "A.1" HasUpdates =<< checkForUpdates repo =<< checkExpiry+ withAssertLog "B" logMsgs msgsNoUpdates $ do+ assertEqual "B.2" NoUpdates =<< checkForUpdates repo =<< checkExpiry++ inMemRepoKeyRollover inMemRepo =<< getCurrentTime++ withAssertLog "C" logMsgs msgsKeyRollover $ do+ assertEqual "C.1" HasUpdates =<< checkForUpdates repo =<< checkExpiry+ withAssertLog "D" logMsgs msgsNoUpdates $ do+ assertEqual "D.1" NoUpdates =<< checkForUpdates repo =<< checkExpiry++-- | Test what happens when server has an outdated timestamp+-- (after a successful initial update)+testHttpMemOutdatedTimestamp :: Assertion+testHttpMemOutdatedTimestamp = httpMemTest $ \_inMemRepo logMsgs repo -> do+ withAssertLog "A" logMsgs msgsInitialUpdate $ do+ assertEqual "A.1" HasUpdates =<< checkForUpdates repo =<< checkExpiry+ withAssertLog "B" logMsgs msgsNoUpdates $ do+ assertEqual "B.2" NoUpdates =<< checkForUpdates repo =<< checkExpiry++ now <- getCurrentTime+ let (FileExpires fourDaysLater) = expiresInDays now 4++ let msgs = replicate 5 (inHistory (Right (expired timestampPath)))+ catchVerificationLoop msgs $ do+ withAssertLog "C" logMsgs [] $ do+ assertEqual "C.1" HasUpdates =<< checkForUpdates repo fourDaysLater++{-------------------------------------------------------------------------------+ Log messages we expect when using the Remote repository+-------------------------------------------------------------------------------}++-- | The log messages we expect on the initial check for updates+msgsInitialUpdate :: [LogMessage -> Bool]+msgsInitialUpdate = [+ selectedMirror inMemURI+ , downloading isTimestamp+ , downloading isSnapshot+ , downloading isMirrors+ , noLocalCopy+ , downloading isIndex+ ]++-- | Log messages when we do a check for updates and there are no changes+msgsNoUpdates :: [LogMessage -> Bool]+msgsNoUpdates = [+ selectedMirror inMemURI+ , downloading isTimestamp+ ]++-- | Log messages we expect when the timestamp and snapshot have been resigned+msgsResigned :: [LogMessage -> Bool]+msgsResigned = [+ selectedMirror inMemURI+ , downloading isTimestamp+ , downloading isSnapshot+ ]++-- | Log messages we expect when the timestamp key has been rolled over+msgsKeyRollover :: [LogMessage -> Bool]+msgsKeyRollover = [+ selectedMirror inMemURI+ , downloading isTimestamp+ , verificationError $ unknownKeyError timestampPath+ , downloading isRoot+ , downloading isTimestamp+ , downloading isSnapshot+ -- Since we delete the timestamp and snapshot on a root info change,+ -- we will then conclude that we need to update the mirrors and the index.+ , downloading isMirrors+ , updating isIndex+ ]++{-------------------------------------------------------------------------------+ Classifying log messages+-------------------------------------------------------------------------------}++downloading :: (forall fs typ. RemoteFile fs typ -> Bool) -> LogMessage -> Bool+downloading isFile (LogDownloading file) = isFile file+downloading _ _ = False++noLocalCopy :: LogMessage -> Bool+noLocalCopy (LogCannotUpdate (RemoteIndex _ _) UpdateImpossibleNoLocalCopy) = True+noLocalCopy _ = False++selectedMirror :: URI -> LogMessage -> Bool+selectedMirror mirror (LogSelectedMirror mirror') = mirror' == show mirror+selectedMirror _ _ = False++updating :: (forall fs typ. RemoteFile fs typ -> Bool) -> LogMessage -> Bool+updating isFile (LogUpdating file) = isFile file+updating _ _ = False++expired :: TargetPath -> VerificationError -> Bool+expired f (VerificationErrorExpired f') = f == f'+expired _ _ = False++unknownKeyError :: TargetPath -> VerificationError -> Bool+unknownKeyError f (VerificationErrorDeserialization f' (DeserializationErrorUnknownKey _keyId)) =+ f == f'+unknownKeyError _ _ = False++verificationError :: (VerificationError -> Bool) -> LogMessage -> Bool+verificationError isErr (LogVerificationError err) = isErr err+verificationError _ _ = False++inHistory :: Either RootUpdated (VerificationError -> Bool) -> HistoryMsg -> Bool+inHistory (Right isErr) (Right err) = isErr err+inHistory (Left _) (Left _) = True+inHistory _ _ = False++type HistoryMsg = Either RootUpdated VerificationError++catchVerificationLoop :: ([HistoryMsg -> Bool]) -> Assertion -> Assertion+catchVerificationLoop history = handleJust isLoop handler+ where+ isLoop :: VerificationError -> Maybe VerificationHistory+ isLoop (VerificationErrorLoop history') = Just history'+ isLoop _ = Nothing++ handler :: VerificationHistory -> Assertion+ handler history' =+ unless (length history == length history' && and (zipWith ($) history history')) $+ assertFailure $ "Unexpected verification history:"+ ++ unlines (map pretty' history')++ pretty' :: HistoryMsg -> String+ pretty' (Left RootUpdated) = "root updated"+ pretty' (Right err) = pretty err++{-------------------------------------------------------------------------------+ Classifying files+-------------------------------------------------------------------------------}++isRoot :: RemoteFile fs typ -> Bool+isRoot (RemoteRoot _) = True+isRoot _ = False++isIndex :: RemoteFile fs typ -> Bool+isIndex (RemoteIndex _ _) = True+isIndex _ = False++isMirrors :: RemoteFile fs typ -> Bool+isMirrors (RemoteMirrors _) = True+isMirrors _ = False++isSnapshot :: RemoteFile fs typ -> Bool+isSnapshot (RemoteSnapshot _) = True+isSnapshot _ = False++isTimestamp :: RemoteFile fs typ -> Bool+isTimestamp RemoteTimestamp = True+isTimestamp _ = False++timestampPath :: TargetPath+timestampPath = TargetPathRepo $ repoLayoutTimestamp hackageRepoLayout++{-------------------------------------------------------------------------------+ Auxiliary+-------------------------------------------------------------------------------}++-- | Check the contents of the log+assertLog :: String -> [LogMessage -> Bool] -> [LogMessage] -> Assertion+assertLog label expected actual = go expected actual+ where+ go :: [LogMessage -> Bool] -> [LogMessage] -> Assertion+ go [] [] = return ()+ go [] as = unexpected as+ go (_:_) [] = assertFailure $ label ++ ": expected log message"+ go (e:es) (a:as) = if e a then go es as else unexpected [a]++ unexpected :: [LogMessage] -> Assertion+ unexpected msgs = assertFailure $ label ++ ": "+ ++ "unexpected log messages:\n"+ ++ unlines (map pretty msgs)+ ++ "\nfull set of log messages was:\n"+ ++ unlines (map pretty actual)++-- | Run the actions and check its log messages+withAssertLog :: String+ -> MVar [LogMessage]+ -> [LogMessage -> Bool]+ -> Assertion -> Assertion+withAssertLog label mv expected action = do+ oldMsgs <- modifyMVar mv $ \old -> return ([], old)+ action+ newMsgs <- modifyMVar mv $ \new -> return (oldMsgs, new)+ assertLog label expected newMsgs++-- | Unit test using the in-memory repository/cache+inMemTest :: ( ( Throws SomeRemoteError+ , Throws VerificationError+ ) => InMemRepo -> MVar [LogMessage] -> Repository InMemFile -> Assertion+ )+ -> Assertion+inMemTest test = uncheckClientErrors $ do+ now <- getCurrentTime+ keys <- createPrivateKeys+ let root = initRoot now layout keys+ withSystemTempDirectory "hackage-security-test" $ \tempDir' -> do+ tempDir <- makeAbsolute $ fromFilePath tempDir'+ inMemRepo <- newInMemRepo layout root now keys+ inMemCache <- newInMemCache tempDir layout+ logMsgs <- newMVar []++ let logger msg = modifyMVar_ logMsgs $ \msgs -> return $ msgs ++ [msg]+ repository <- newInMemRepository layout hackageIndexLayout inMemRepo inMemCache logger++ bootstrap repository (map someKeyId (privateRoot keys)) (KeyThreshold 2)+ test inMemRepo logMsgs repository+ where+ layout :: RepoLayout+ layout = hackageRepoLayout++-- | Unit test using the Remote repository but with the in-mem repo+httpMemTest :: ( ( Throws SomeRemoteError+ , Throws VerificationError+ ) => InMemRepo -> MVar [LogMessage] -> Repository Remote.RemoteTemp -> Assertion+ )+ -> Assertion+httpMemTest test = uncheckClientErrors $ do+ now <- getCurrentTime+ keys <- createPrivateKeys+ let root = initRoot now layout keys+ withSystemTempDirectory "hackage-security-test" $ \tempDir' -> do+ tempDir <- makeAbsolute $ fromFilePath tempDir'+ inMemRepo <- newInMemRepo layout root now keys+ logMsgs <- newMVar []++ let logger msg = modifyMVar_ logMsgs $ \msgs -> return $ msgs ++ [msg]+ httpLib = httpMem inMemRepo+ cache = Cache.Cache {+ cacheRoot = tempDir </> fragment "cache"+ , cacheLayout = cabalCacheLayout+ }++ Remote.withRepository httpLib+ [inMemURI]+ Remote.defaultRepoOpts+ cache+ hackageRepoLayout+ hackageIndexLayout+ logger+ $ \repository -> do+ withAssertLog "bootstrap" logMsgs bootstrapMsgs $+ bootstrap repository (map someKeyId (privateRoot keys)) (KeyThreshold 2)+ test inMemRepo logMsgs repository+ where+ bootstrapMsgs :: [LogMessage -> Bool]+ bootstrapMsgs = [ selectedMirror inMemURI+ , downloading isRoot+ ]++ layout :: RepoLayout+ layout = hackageRepoLayout++-- | Base URI for the in-memory repository+--+-- This could really be anything at all+inMemURI :: URI+inMemURI = fromJust (parseURI "inmem://")++-- | Return @Just@ the current time+checkExpiry :: IO (Maybe UTCTime)+checkExpiry = Just `fmap` getCurrentTime
+ hackage-security/hackage-security/tests/TestSuite/HttpMem.hs view
@@ -0,0 +1,70 @@+-- | HttpLib bridge to the in-memory repository+module TestSuite.HttpMem (+ httpMem+ ) where++-- stdlib+import Network.URI (URI)+import qualified Data.ByteString.Lazy as BS.L++-- hackage-security+import Hackage.Security.Client+import Hackage.Security.Client.Repository.HttpLib+import Hackage.Security.Util.Checked+import Hackage.Security.Util.Path+import Hackage.Security.Util.Some++-- TestSuite+import TestSuite.InMemRepo++httpMem :: InMemRepo -> HttpLib+httpMem inMemRepo = HttpLib {+ httpGet = get inMemRepo+ , httpGetRange = getRange inMemRepo+ }++{-------------------------------------------------------------------------------+ Individual methods+-------------------------------------------------------------------------------}++-- | Download a file+--+-- Since we don't (yet?) make any attempt to simulate a cache, we ignore+-- caching headers.+get :: forall a. Throws SomeRemoteError+ => InMemRepo+ -> [HttpRequestHeader]+ -> URI+ -> ([HttpResponseHeader] -> BodyReader -> IO a)+ -> IO a+get InMemRepo{..} _requestHeaders uri callback = do+ Some inMemFile <- inMemRepoGetPath $ castRoot (uriPath uri)+ br <- bodyReaderFromBS $ inMemFileRender inMemFile+ callback [HttpResponseAcceptRangesBytes] br++-- | Download a byte range+--+-- Range is starting and (exclusive) end offset in bytes.+--+-- We ignore requests for compression; different servers deal with compression+-- for byte range requests differently; in particular, Apache returns the range+-- of the _compressed_ file, which is pretty useless for our purposes. For now+-- we ignore this issue completely here.+getRange :: forall a. Throws SomeRemoteError+ => InMemRepo+ -> [HttpRequestHeader]+ -> URI+ -> (Int, Int)+ -> (HttpStatus -> [HttpResponseHeader] -> BodyReader -> IO a)+ -> IO a+getRange InMemRepo{..} _requestHeaders uri (fr, to) callback = do+ Some inMemFile <- inMemRepoGetPath $ castRoot (uriPath uri)+ br <- bodyReaderFromBS $ substr (inMemFileRender inMemFile)++ let responseHeaders = concat [+ [ HttpResponseAcceptRangesBytes ]+ ]+ callback HttpStatus206PartialContent responseHeaders br+ where+ substr :: BS.L.ByteString -> BS.L.ByteString+ substr = BS.L.take (fromIntegral (to - fr)) . BS.L.drop (fromIntegral fr)
+ hackage-security/hackage-security/tests/TestSuite/InMemCache.hs view
@@ -0,0 +1,149 @@+module TestSuite.InMemCache (+ InMemCache(..)+ , newInMemCache+ ) where++-- base+import Control.Exception+import qualified Codec.Compression.GZip as GZip+import qualified Data.ByteString.Lazy as BS.L++-- hackage-security+import Hackage.Security.Client+import Hackage.Security.Client.Formats+import Hackage.Security.Client.Repository+import Hackage.Security.JSON+import Hackage.Security.Util.Path++-- TestSuite+import TestSuite.Util.StrictMVar+import TestSuite.InMemRepo++data InMemCache = InMemCache {+ inMemCacheGet :: CachedFile -> IO (Maybe (Path Absolute))+ , inMemCacheGetRoot :: IO (Path Absolute)+ , inMemCacheClear :: IO ()+ , inMemCachePut :: forall f typ. InMemFile typ -> Format f -> IsCached typ -> IO ()+ }++newInMemCache :: Path Absolute -> RepoLayout -> IO InMemCache+newInMemCache tempDir layout = do+ state <- newMVar $ initLocalState layout+ return InMemCache {+ inMemCacheGet = get state tempDir+ , inMemCacheGetRoot = getRoot state tempDir+ , inMemCacheClear = clear state+ , inMemCachePut = put state+ }++{-------------------------------------------------------------------------------+ "Local" state (the files we "cached")+-------------------------------------------------------------------------------}++data LocalState = LocalState {+ cacheRepoLayout :: !RepoLayout+ , cachedRoot :: !(Maybe (Signed Root))+ , cachedMirrors :: !(Maybe (Signed Mirrors))+ , cachedTimestamp :: !(Maybe (Signed Timestamp))+ , cachedSnapshot :: !(Maybe (Signed Snapshot))++ -- We cache only the uncompressed index++ -- (we can unambiguously construct the @.tar@ from the @.tar.gz@,+ -- but not the other way around)+ , cachedIndex :: Maybe BS.L.ByteString+ }++cachedRoot' :: LocalState -> Signed Root+cachedRoot' LocalState{..} = needRoot cachedRoot++needRoot :: Maybe a -> a+needRoot Nothing = error "InMemCache: no root info (did you bootstrap?)"+needRoot (Just root) = root++initLocalState :: RepoLayout -> LocalState+initLocalState layout = LocalState {+ cacheRepoLayout = layout+ , cachedRoot = Nothing+ , cachedMirrors = Nothing+ , cachedTimestamp = Nothing+ , cachedSnapshot = Nothing+ , cachedIndex = Nothing+ }++{-------------------------------------------------------------------------------+ Individual methods+-------------------------------------------------------------------------------}++-- | Get a cached file (if available)+get :: MVar LocalState -> Path Absolute -> CachedFile -> IO (Maybe (Path Absolute))+get state cacheTempDir cachedFile =+ case cachedFile of+ CachedRoot -> serve "root.json" $ render cachedRoot+ CachedMirrors -> serve "mirrors.json" $ render cachedMirrors+ CachedTimestamp -> serve "timestamp.json" $ render cachedTimestamp+ CachedSnapshot -> serve "snapshot.json" $ render cachedSnapshot+ where+ render :: forall b. ToJSON WriteJSON b+ => (LocalState -> Maybe b)+ -> (LocalState -> Maybe BS.L.ByteString)+ render f st = renderJSON (cacheRepoLayout st) `fmap` (f st)++ serve :: String+ -> (LocalState -> Maybe BS.L.ByteString)+ -> IO (Maybe (Path Absolute))+ serve template f =+ withMVar state $ \st ->+ case f st of+ Nothing -> return Nothing+ Just bs -> do (tempFile, h) <- openTempFile' cacheTempDir template+ BS.L.hPut h bs+ hClose h+ return $ Just tempFile++-- | Get the cached root+getRoot :: MVar LocalState -> Path Absolute -> IO (Path Absolute)+getRoot state cacheTempDir =+ needRoot `fmap` get state cacheTempDir CachedRoot++-- | Clear all cached data+clear :: MVar LocalState -> IO ()+clear state = modifyMVar_ state $ \st -> return st {+ cachedMirrors = Nothing+ , cachedTimestamp = Nothing+ , cachedSnapshot = Nothing+ , cachedIndex = Nothing+ }++-- | Cache a previously downloaded remote file+put :: MVar LocalState -> InMemFile typ -> Format f -> IsCached typ -> IO ()+put state = put' state . inMemFileRender++put' :: MVar LocalState -> BS.L.ByteString -> Format f -> IsCached typ -> IO ()+put' state bs = go+ where+ go :: Format f -> IsCached typ -> IO ()+ go _ DontCache = return ()+ go FUn (CacheAs f) = go' f+ go FGz (CacheAs _) = error "put: the impossible happened"+ go FUn CacheIndex = modifyMVar_ state $ \st -> return st {+ cachedIndex = Just bs+ }+ go FGz CacheIndex = modifyMVar_ state $ \st -> return st {+ cachedIndex = Just (GZip.decompress bs)+ }++ go' :: CachedFile -> IO ()+ go' CachedRoot = go'' $ \x st -> st { cachedRoot = Just x }+ go' CachedTimestamp = go'' $ \x st -> st { cachedTimestamp = Just x }+ go' CachedSnapshot = go'' $ \x st -> st { cachedSnapshot = Just x }+ go' CachedMirrors = go'' $ \x st -> st { cachedMirrors = Just x }++ go'' :: forall a. FromJSON ReadJSON_Keys_Layout a+ => (a -> LocalState -> LocalState) -> IO ()+ go'' f = do+ modifyMVar_ state $ \st@LocalState{..} -> do+ let keyEnv = rootKeys . signed . cachedRoot' $ st+ case parseJSON_Keys_Layout keyEnv cacheRepoLayout bs of+ Left err -> throwIO err+ Right parsed -> return $ f parsed st
+ hackage-security/hackage-security/tests/TestSuite/InMemRepo.hs view
@@ -0,0 +1,262 @@+module TestSuite.InMemRepo (+ InMemRepo(..)+ , newInMemRepo+ , initRoot+ , InMemFile(..)+ , inMemFileRender+ ) where++-- stdlib+import Control.Exception+import Data.Time+import qualified Codec.Archive.Tar as Tar+import qualified Codec.Compression.GZip as GZip+import qualified Data.ByteString.Lazy as BS.L++-- Cabal+import Distribution.Text++-- hackage-security+import Hackage.Security.Client+import Hackage.Security.Client.Formats+import Hackage.Security.Client.Repository+import Hackage.Security.Client.Verify+import Hackage.Security.JSON+import Hackage.Security.Util.Path+import Hackage.Security.Util.Some++-- TestSuite+import TestSuite.PrivateKeys+import TestSuite.Util.StrictMVar++{-------------------------------------------------------------------------------+ "Files" from the in-memory repository+-------------------------------------------------------------------------------}++data InMemFile :: * -> * where+ InMemMetadata :: ToJSON WriteJSON a => RepoLayout -> a -> InMemFile Metadata+ InMemBinary :: BS.L.ByteString -> InMemFile Binary++inMemFileRender :: InMemFile typ -> BS.L.ByteString+inMemFileRender (InMemMetadata layout file) = renderJSON layout file+inMemFileRender (InMemBinary bs) = bs++instance DownloadedFile InMemFile where+ downloadedRead file =+ return $ inMemFileRender file++ downloadedVerify file info =+ return $ knownFileInfoEqual (fileInfo (inMemFileRender file))+ (trusted info)++ downloadedCopyTo file dest =+ writeLazyByteString dest (inMemFileRender file)++{-------------------------------------------------------------------------------+ In-memory repository+-------------------------------------------------------------------------------}++data InMemRepo = InMemRepo {+ -- | Get a file from the repository+ inMemRepoGet :: forall fs typ.+ RemoteFile fs typ+ -> Verify (Some (HasFormat fs), InMemFile typ)++ -- | Get a file, based on a path (uses hackageRepoLayout)+ , inMemRepoGetPath :: RepoPath -> IO (Some InMemFile)++ -- | Run the "cron job" on the server+ --+ -- That is, resign the timestamp and the snapshot+ , inMemRepoCron :: UTCTime -> IO ()++ -- | Rollover the timestamp and snapshot keys+ , inMemRepoKeyRollover :: UTCTime -> IO ()+ }++newInMemRepo :: RepoLayout+ -> Signed Root+ -> UTCTime+ -> PrivateKeys+ -> IO InMemRepo+newInMemRepo layout root now keys = do+ state <- newMVar $ initRemoteState now layout keys root+ return InMemRepo {+ inMemRepoGet = get state+ , inMemRepoGetPath = getPath state+ , inMemRepoCron = cron state+ , inMemRepoKeyRollover = keyRollover state+ }++{-------------------------------------------------------------------------------+ "Remote" state (as it is "on the server")+-------------------------------------------------------------------------------}++data RemoteState = RemoteState {+ remoteKeys :: !PrivateKeys+ , remoteLayout :: !RepoLayout+ , remoteRoot :: !(Signed Root)+ , remoteTimestamp :: !(Signed Timestamp)+ , remoteSnapshot :: !(Signed Snapshot)+ , remoteMirrors :: !(Signed Mirrors)+ , remoteTar :: !BS.L.ByteString+ , remoteTarGz :: !BS.L.ByteString+ }++initRoot :: UTCTime -> RepoLayout -> PrivateKeys -> Signed Root+initRoot now layout keys = withSignatures layout (privateRoot keys) Root {+ rootVersion = FileVersion 1+ , rootExpires = expiresInDays now (365 * 10)+ , rootKeys = privateKeysEnv keys+ , rootRoles = privateKeysRoles keys+ }++initRemoteState :: UTCTime+ -> RepoLayout+ -> PrivateKeys+ -> Signed Root+ -> RemoteState+initRemoteState now layout keys signedRoot = RemoteState {+ remoteKeys = keys+ , remoteLayout = layout+ , remoteRoot = signedRoot+ , remoteTimestamp = signedTimestamp+ , remoteSnapshot = signedSnapshot+ , remoteMirrors = signedMirrors+ , remoteTar = initTar+ , remoteTarGz = initTarGz+ }+ where+ signedTimestamp = withSignatures layout [privateTimestamp keys] initTimestamp+ signedSnapshot = withSignatures layout [privateSnapshot keys] initSnapshot+ signedMirrors = withSignatures layout [privateMirrors keys] initMirrors++ initMirrors :: Mirrors+ initMirrors = Mirrors {+ mirrorsVersion = FileVersion 1+ , mirrorsExpires = expiresNever+ , mirrorsMirrors = []+ }++ initSnapshot :: Snapshot+ initSnapshot = Snapshot {+ snapshotVersion = FileVersion 1+ , snapshotExpires = expiresInDays now 3+ , snapshotInfoRoot = fileInfo $ renderJSON layout signedRoot+ , snapshotInfoMirrors = fileInfo $ renderJSON layout signedMirrors+ , snapshotInfoTarGz = fileInfo $ initTarGz+ , snapshotInfoTar = Just $ fileInfo initTar+ }++ initTimestamp :: Timestamp+ initTimestamp = Timestamp {+ timestampVersion = FileVersion 1+ , timestampExpires = expiresInDays now 3+ , timestampInfoSnapshot = fileInfo $ renderJSON layout signedSnapshot+ }++ initTar :: BS.L.ByteString+ initTar = Tar.write []++ initTarGz :: BS.L.ByteString+ initTarGz = GZip.compress initTar++{-------------------------------------------------------------------------------+ InMemRepo methods+-------------------------------------------------------------------------------}++-- | Get a file from the server+get :: MVar RemoteState -> RemoteFile fs typ -> Verify (Some (HasFormat fs), InMemFile typ)+get state remoteFile = do+ RemoteState{..} <- liftIO $ readMVar state+ case remoteFile of+ RemoteTimestamp -> return (Some (HFZ FUn), InMemMetadata remoteLayout remoteTimestamp)+ RemoteSnapshot _ -> return (Some (HFZ FUn), InMemMetadata remoteLayout remoteSnapshot)+ RemoteMirrors _ -> return (Some (HFZ FUn), InMemMetadata remoteLayout remoteMirrors)+ RemoteRoot _ -> return (Some (HFZ FUn), InMemMetadata remoteLayout remoteRoot)+ RemoteIndex hasGz _ -> return (Some hasGz, InMemBinary remoteTarGz)+ RemotePkgTarGz pkgId _ -> error $ "withRemote: RemotePkgTarGz " ++ display pkgId++getPath :: MVar RemoteState -> RepoPath -> IO (Some InMemFile)+getPath state repoPath = do+ RemoteState{..} <- readMVar state+ case toUnrootedFilePath (unrootPath repoPath) of+ "root.json" -> return $ Some (InMemMetadata remoteLayout remoteRoot)+ "timestamp.json" -> return $ Some (InMemMetadata remoteLayout remoteTimestamp)+ "snapshot.json" -> return $ Some (InMemMetadata remoteLayout remoteSnapshot)+ "mirrors.json" -> return $ Some (InMemMetadata remoteLayout remoteMirrors)+ "01-index.tar.gz" -> return $ Some (InMemBinary remoteTarGz)+ "01-index.tar" -> return $ Some (InMemBinary remoteTar)+ otherPath -> throwIO . userError $ "getPath: Unknown path " ++ otherPath+ where++cron :: MVar RemoteState -> UTCTime -> IO ()+cron state now = modifyMVar_ state $ \st@RemoteState{..} -> do+ let snapshot, snapshot' :: Snapshot+ snapshot = signed remoteSnapshot+ snapshot' = snapshot {+ snapshotVersion = versionIncrement $ snapshotVersion snapshot+ , snapshotExpires = expiresInDays now 3+ }++ timestamp, timestamp' :: Timestamp+ timestamp = signed remoteTimestamp+ timestamp' = Timestamp {+ timestampVersion = versionIncrement $ timestampVersion timestamp+ , timestampExpires = expiresInDays now 3+ , timestampInfoSnapshot = fileInfo $ renderJSON remoteLayout signedSnapshot+ }++ signedTimestamp = withSignatures remoteLayout [privateTimestamp remoteKeys] timestamp'+ signedSnapshot = withSignatures remoteLayout [privateSnapshot remoteKeys] snapshot'++ return st {+ remoteTimestamp = signedTimestamp+ , remoteSnapshot = signedSnapshot+ }++keyRollover :: MVar RemoteState -> UTCTime -> IO ()+keyRollover state now = modifyMVar_ state $ \st@RemoteState{..} -> do+ newKeySnapshot <- createKey' KeyTypeEd25519+ newKeyTimestamp <- createKey' KeyTypeEd25519++ let remoteKeys' :: PrivateKeys+ remoteKeys' = remoteKeys {+ privateSnapshot = newKeySnapshot+ , privateTimestamp = newKeyTimestamp+ }++ root, root' :: Root+ root = signed remoteRoot+ root' = Root {+ rootVersion = versionIncrement $ rootVersion root+ , rootExpires = expiresInDays now (365 * 10)+ , rootKeys = privateKeysEnv remoteKeys'+ , rootRoles = privateKeysRoles remoteKeys'+ }++ snapshot, snapshot' :: Snapshot+ snapshot = signed remoteSnapshot+ snapshot' = snapshot {+ snapshotVersion = versionIncrement $ snapshotVersion snapshot+ , snapshotExpires = expiresInDays now 3+ , snapshotInfoRoot = fileInfo $ renderJSON remoteLayout signedRoot+ }++ timestamp, timestamp' :: Timestamp+ timestamp = signed remoteTimestamp+ timestamp' = Timestamp {+ timestampVersion = versionIncrement $ timestampVersion timestamp+ , timestampExpires = expiresInDays now 3+ , timestampInfoSnapshot = fileInfo $ renderJSON remoteLayout signedSnapshot+ }++ signedRoot = withSignatures remoteLayout (privateRoot remoteKeys') root'+ signedTimestamp = withSignatures remoteLayout [privateTimestamp remoteKeys'] timestamp'+ signedSnapshot = withSignatures remoteLayout [privateSnapshot remoteKeys'] snapshot'++ return st {+ remoteRoot = signedRoot+ , remoteTimestamp = signedTimestamp+ , remoteSnapshot = signedSnapshot+ }
+ hackage-security/hackage-security/tests/TestSuite/InMemRepository.hs view
@@ -0,0 +1,63 @@+module TestSuite.InMemRepository (+ newInMemRepository+ ) where++-- stdlib+import Control.Concurrent++-- hackage-security+import Hackage.Security.Client+import Hackage.Security.Client.Formats+import Hackage.Security.Client.Repository+import Hackage.Security.Client.Verify+import Hackage.Security.Util.Checked+import Hackage.Security.Util.Some++-- TestSuite+import TestSuite.InMemCache+import TestSuite.InMemRepo++newInMemRepository :: RepoLayout+ -> IndexLayout+ -> InMemRepo+ -> InMemCache+ -> (LogMessage -> IO ())+ -> IO (Repository InMemFile)+newInMemRepository layout indexLayout repo cache logger = do+ cacheLock <- newMVar ()+ return $ Repository {+ repGetRemote = getRemote repo cache+ , repGetCached = inMemCacheGet cache+ , repGetCachedRoot = inMemCacheGetRoot cache+ , repClearCache = inMemCacheClear cache+ , repLockCache = withMVar cacheLock . const+ , repWithIndex = error "newInMemRepository: repWithIndex TODO"+ , repGetIndexIdx = error "newInMemRepository: repGetIndexIdx TODO"+ , repWithMirror = withMirror+ , repLog = logger+ , repLayout = layout+ , repIndexLayout = indexLayout+ , repDescription = "In memory repository"+ }++{-------------------------------------------------------------------------------+ Repository methods+-------------------------------------------------------------------------------}++-- | Get a file from the server+getRemote :: forall fs typ. Throws SomeRemoteError+ => InMemRepo+ -> InMemCache+ -> AttemptNr+ -> RemoteFile fs typ+ -> Verify (Some (HasFormat fs), InMemFile typ)+getRemote InMemRepo{..} InMemCache{..} _isRetry remoteFile = do+ (Some format, inMemFile) <- inMemRepoGet remoteFile+ ifVerified $ inMemCachePut inMemFile (hasFormatGet format) (mustCache remoteFile)+ return (Some format, inMemFile)++-- | Mirror selection+withMirror :: forall a. Maybe [Mirror] -> IO a -> IO a+withMirror Nothing callback = callback+withMirror (Just []) callback = callback+withMirror _ _ = error "Mirror selection not implemented"
+ hackage-security/hackage-security/tests/TestSuite/PrivateKeys.hs view
@@ -0,0 +1,69 @@+module TestSuite.PrivateKeys (+ PrivateKeys(..)+ , createPrivateKeys+ , privateKeysEnv+ , privateKeysRoles+ ) where++-- stdlib+import Control.Monad++-- hackage-security+import Hackage.Security.Client+import Hackage.Security.Key.Env (KeyEnv)+import Hackage.Security.Util.Some+import qualified Hackage.Security.Key.Env as KeyEnv++{-------------------------------------------------------------------------------+ All private keys+-------------------------------------------------------------------------------}++data PrivateKeys = PrivateKeys {+ privateRoot :: [Some Key]+ , privateTarget :: [Some Key]+ , privateSnapshot :: Some Key+ , privateTimestamp :: Some Key+ , privateMirrors :: Some Key+ }++createPrivateKeys :: IO PrivateKeys+createPrivateKeys = do+ privateRoot <- replicateM 3 $ createKey' KeyTypeEd25519+ privateTarget <- replicateM 3 $ createKey' KeyTypeEd25519+ privateSnapshot <- createKey' KeyTypeEd25519+ privateTimestamp <- createKey' KeyTypeEd25519+ privateMirrors <- createKey' KeyTypeEd25519+ return PrivateKeys{..}++privateKeysEnv :: PrivateKeys -> KeyEnv+privateKeysEnv PrivateKeys{..} = KeyEnv.fromKeys $ concat [+ privateRoot+ , privateTarget+ , [privateSnapshot]+ , [privateTimestamp]+ , [privateMirrors]+ ]++privateKeysRoles :: PrivateKeys -> RootRoles+privateKeysRoles PrivateKeys{..} = RootRoles {+ rootRolesRoot = RoleSpec {+ roleSpecKeys = map somePublicKey privateRoot+ , roleSpecThreshold = KeyThreshold 2+ }+ , rootRolesSnapshot = RoleSpec {+ roleSpecKeys = [somePublicKey privateSnapshot]+ , roleSpecThreshold = KeyThreshold 1+ }+ , rootRolesTargets = RoleSpec {+ roleSpecKeys = map somePublicKey privateTarget+ , roleSpecThreshold = KeyThreshold 2+ }+ , rootRolesTimestamp = RoleSpec {+ roleSpecKeys = [somePublicKey privateTimestamp]+ , roleSpecThreshold = KeyThreshold 1+ }+ , rootRolesMirrors = RoleSpec {+ roleSpecKeys = [somePublicKey privateMirrors]+ , roleSpecThreshold = KeyThreshold 1+ }+ }
+ hackage-security/hackage-security/tests/TestSuite/Util/StrictMVar.hs view
@@ -0,0 +1,27 @@+module TestSuite.Util.StrictMVar (+ MVar -- opaque+ , newMVar+ , CC.withMVar+ , modifyMVar+ , modifyMVar_+ , CC.readMVar+ ) where++import Control.Concurrent (MVar)+import Control.Exception+import qualified Control.Concurrent as CC++newMVar :: a -> IO (MVar a)+newMVar x = CC.newMVar =<< evaluate x++modifyMVar :: MVar a -> (a -> IO (a, b)) -> IO b+modifyMVar mv f = CC.modifyMVar mv $ \old -> do+ (new, ret) <- f old+ new' <- evaluate new+ return (new', ret)++modifyMVar_ :: MVar a -> (a -> IO a) -> IO ()+modifyMVar_ mv f = modifyMVar mv (returnUnit . f)+ where+ returnUnit :: IO a -> IO (a, ())+ returnUnit = fmap $ \a -> (a, ())
+ hackage-security/precompute-fileinfo/LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2015, Well-Typed LLP++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Well-Typed LLP nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ hackage-security/precompute-fileinfo/Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ hackage-security/precompute-fileinfo/precompute-fileinfo.cabal view
@@ -0,0 +1,27 @@+name: precompute-fileinfo+version: 0.1.0.0+synopsis: Precompute fileinfo for faster Hackage migration+license: BSD3+license-file: LICENSE+author: Edsko de Vries+maintainer: edsko@well-typed.com+copyright: Copyright 2015 Well-Typed LLP+category: Distribution+build-type: Simple+cabal-version: >=1.10++executable precompute-fileinfo+ main-is: Main.hs+ build-depends: base >= 4.4,+ bytestring >= 0.9,+ containers >= 0.4,+ deepseq >= 1.3,+ filepath >= 1.2,+ optparse-applicative >= 0.11,+ SHA >= 1.6,+ tar >= 0.4,+ zlib >= 0.5+ hs-source-dirs: src+ default-language: Haskell2010+ default-extensions: RecordWildCards+ ghc-options: -Wall
+ hackage-security/precompute-fileinfo/src/Main.hs view
@@ -0,0 +1,196 @@+module Main where++import Control.Applicative+import Control.Concurrent+import Control.DeepSeq+import Control.Exception+import Control.Monad+import Data.Map.Strict (Map)+import Data.Monoid+import Options.Applicative+import System.FilePath+import System.IO+import qualified Codec.Archive.Tar as Tar+import qualified Codec.Archive.Tar.Entry as Tar+import qualified Codec.Compression.GZip as GZip+import qualified Data.ByteString.Lazy as BS.L+import qualified Data.Digest.Pure.SHA as SHA+import qualified Data.Map as Map++{-------------------------------------------------------------------------------+ Main application driver+-------------------------------------------------------------------------------}++main :: IO ()+main = do+ options <- getOptions+ tarEntries <- readTGz $ optionsArchive options+ state <- initState options++ precomputeEntries state tarEntries `finally` writeState options state++{-------------------------------------------------------------------------------+ Command line options+-------------------------------------------------------------------------------}++data Options = Options {+ optionsInput :: Maybe FilePath+ , optionsOutput :: FilePath+ , optionsArchive :: FilePath+ }++parseOptions :: Parser Options+parseOptions = Options+ <$> (optional . strOption $ mconcat [+ short 'i'+ , long "input"+ , help "Input filename (if appending to existing map)"+ ])+ <*> (strOption $ mconcat [+ short 'o'+ , long "output"+ , help "Output filename"+ ])+ <*> argument str (metavar "ARCHIVE")++getOptions :: IO Options+getOptions = execParser opts+ where+ opts = info (helper <*> parseOptions) $ mconcat [+ fullDesc+ , progDesc "Precompute file info from a Hackage --hardlink-blobs backup"+ ]++{-------------------------------------------------------------------------------+ Core functionality+-------------------------------------------------------------------------------}++precomputeEntries :: Exception e => State -> Tar.Entries e -> IO ()+precomputeEntries state = go+ where+ go (Tar.Fail e) = throwIO e+ go Tar.Done = return ()+ go (Tar.Next e es) = precomputeEntry state e >> go es++precomputeEntry :: State -> Tar.Entry -> IO ()+precomputeEntry state entry =+ case Tar.entryContent entry of+ Tar.SymbolicLink linkTarget ->+ precomputeSymLink state+ (Tar.entryPath entry)+ (Tar.fromLinkTarget linkTarget)+ _otherwise ->+ return ()++precomputeSymLink :: State -> FilePath -> FilePath -> IO ()+precomputeSymLink state src dst =+ case splitPath src of+ [_date, "core/", "package/", pkg, _pkgTarGz] -> do+ let blobPath = normalizePath (stateBackupDir state </> takeDirectory src </> dst)+ precomputePkg state ("package " ++ init pkg) blobPath+ [_date, "candidates/", "package/", pkg, _pkgTarGz] -> do+ let blobPath = normalizePath (stateBackupDir state </> takeDirectory src </> dst)+ precomputePkg state ("candidate " ++ init pkg) blobPath+ _otherwise -> do+ return ()++precomputePkg :: State -- ^ State to add the computed hash to+ -> String -- ^ Package name (for reporting only)+ -> FilePath -- ^ Location of the blob ID (ending in @../<md5>@)+ -> IO ()+precomputePkg state pkg dst = do+ putStr pkg+ let md5 = takeFileName dst+ alreadyKnown <- isKnownHash state md5+ if alreadyKnown+ then putStrLn " (skipped)"+ else do+ (sha256, len) <- withFile dst ReadMode $ \h -> do+ len <- hFileSize h+ sha256 <- SHA.showDigest . SHA.sha256 <$> BS.L.hGetContents h+ evaluate $ rnf (sha256, len)+ return (sha256, len)+ recordHash state md5 sha256 len+ putStrLn " OK"++{-------------------------------------------------------------------------------+ State+-------------------------------------------------------------------------------}++type MD5 = String+type SHA256 = String+type Length = Integer++data State = State {+ -- | The directory where the backup is stored+ --+ -- This is necessary so we can resolve relative paths inside the tarball+ stateBackupDir :: FilePath++ -- | Mutable variable where we store the hashes computed so far+ --+ -- We use this so that even on CTRL-C we can still output a partial map+ , stateVar :: MVar (Map MD5 (SHA256, Length))+ }++initState :: Options -> IO State+initState Options{..} = do+ initMap <- case optionsInput of+ Nothing -> return Map.empty+ Just fn -> readMap fn+ stateVar <- newMVar initMap+ return State{..}+ where+ stateBackupDir = takeDirectory optionsArchive++writeState :: Options -> State -> IO ()+writeState Options{..} State{..} = do+ putStrLn $ "Writing " ++ optionsOutput+ writeMap optionsOutput =<< readMVar stateVar++recordHash :: State -> MD5 -> SHA256 -> Length -> IO ()+recordHash State{..} md5 sha256 len =+ modifyMVar_ stateVar $ return . Map.insert md5 (sha256, len)++isKnownHash :: State -> MD5 -> IO Bool+isKnownHash State{..} md5 = withMVar stateVar $ return . Map.member md5++writeMap :: FilePath -> Map MD5 (SHA256, Length) -> IO ()+writeMap fp hashes = withFile fp WriteMode $ \h ->+ mapM_ (uncurry (writeEntry h)) $ Map.toList hashes+ where+ writeEntry :: Handle -> MD5 -> (SHA256, Length) -> IO ()+ writeEntry h md5 (sha256, len) =+ hPutStrLn h $ unwords [md5, sha256, show len]++-- | Read an existing hashmap+--+-- The result is guaranteed to be in normal form.+readMap :: FilePath -> IO (Map MD5 (SHA256, Length))+readMap fp =+ withFile fp ReadMode $ \h -> do+ hashes <- Map.fromList . map parseEntry . lines <$> hGetContents h+ evaluate $ rnf hashes+ return hashes+ where+ parseEntry :: String -> (MD5, (SHA256, Length))+ parseEntry line = let [md5, sha256, len] = words line+ in (md5, (sha256, read len))++{-------------------------------------------------------------------------------+ Auxiliary+-------------------------------------------------------------------------------}++-- | Change @a/b/c/d/e/../../../../f@ to @a/f@+normalizePath :: FilePath -> FilePath+normalizePath = joinPath . go . splitPath . normalise+ where+ go :: [String] -> [String]+ go [] = []+ go (d : ds) =+ case go ds of+ "../" : ds'' | d /= "../" -> ds''+ ds' -> d : ds'++readTGz :: FilePath -> IO (Tar.Entries Tar.FormatError)+readTGz = liftM (Tar.read . GZip.decompress) . BS.L.readFile
+ hackage-security/stack.yaml view
@@ -0,0 +1,13 @@+resolver: lts-3.4+packages:+- hackage-security+- precompute-fileinfo+- hackage-security-http-client+- example-client+- hackage-security-curl+- hackage-root-tool+- hackage-repo-tool+- hackage-security-HTTP+extra-deps:+- ed25519-0.0.2.0+- zlib-0.6.1.1
+ hackage-security/testscripts/squid.conf view
@@ -0,0 +1,4 @@+# Don't specify a cache dir; use in-memory cache+http_port 3128+access_log stdio:/dev/stdout combined +http_access allow all
+ hackage-security/testscripts/test-outdated-index.log view
@@ -0,0 +1,440 @@++ BINDIR=../sandbox/7.8.3/bin++ EXAMPLE_CLIENT=../sandbox/7.8.3/bin/example-client++ SECURITY_UTILITY=../sandbox/7.8.3/bin/hackage-security++ REPO=http://127.0.0.1/~e/local-repo++ PROXY=http://localhost:3128++ LOCAL_REPO=../unversioned/local-repo++ KEYS=../unversioned/keys++ LOCAL_CACHE=./tmp++ rm -rf ../unversioned/local-repo/index++ ../sandbox/7.8.3/bin/hackage-security -v bootstrap --repo ../unversioned/local-repo --keys ../unversioned/keys+Info: Writing <repo>/root.json+Info: Writing <repo>/mirrors.json+Info: Writing <repo>/generics-sop/0.1.0.0/package.json+Info: Writing <repo>/generics-sop/0.1.0.0/generics-sop.cabal (extracted from <repo>/package/generics-sop-0.1.0.0.tar.gz)+Info: Writing <repo>/generics-sop/0.1.0.1/package.json+Info: Writing <repo>/generics-sop/0.1.0.1/generics-sop.cabal (extracted from <repo>/package/generics-sop-0.1.0.1.tar.gz)+Info: Writing <repo>/generics-sop/0.1.0.2/package.json+Info: Writing <repo>/generics-sop/0.1.0.2/generics-sop.cabal (extracted from <repo>/package/generics-sop-0.1.0.2.tar.gz)+Info: Writing <repo>/generics-sop/0.1.0.3/package.json+Info: Writing <repo>/generics-sop/0.1.0.3/generics-sop.cabal (extracted from <repo>/package/generics-sop-0.1.0.3.tar.gz)+Info: Writing <repo>/generics-sop/0.1.0.4/package.json+Info: Writing <repo>/generics-sop/0.1.0.4/generics-sop.cabal (extracted from <repo>/package/generics-sop-0.1.0.4.tar.gz)+Info: Writing <repo>/generics-sop/0.1.1.1/package.json+Info: Writing <repo>/generics-sop/0.1.1.1/generics-sop.cabal (extracted from <repo>/package/generics-sop-0.1.1.1.tar.gz)+Info: Writing <repo>/generics-sop/0.1.1.2/package.json+Warning: Failed to extract .cabal from package generics-sop-0.1.1.2: <repo>/package/generics-sop-0.1.1.2.tar.gz: Codec.Compression.Zlib: compressed data stream format error (incorrect header check)+Info: Writing <repo>/pretty-show/1.4.1/package.json+Info: Writing <repo>/pretty-show/1.4.1/pretty-show.cabal (extracted from <repo>/package/pretty-show-1.4.1.tar.gz)+Info: Writing <repo>/pretty-show/1.5/package.json+Info: Writing <repo>/pretty-show/1.5/pretty-show.cabal (extracted from <repo>/package/pretty-show-1.5.tar.gz)+Info: Writing <repo>/pretty-show/1.6/package.json+Info: Writing <repo>/pretty-show/1.6/pretty-show.cabal (extracted from <repo>/package/pretty-show-1.6.tar.gz)+Info: Writing <repo>/pretty-sop/0.1.0.0/package.json+Info: Writing <repo>/pretty-sop/0.1.0.0/pretty-sop.cabal (extracted from <repo>/package/pretty-sop-0.1.0.0.tar.gz)+Info: Writing <repo>/pretty-sop/0.1.0.1/package.json+Info: Writing <repo>/pretty-sop/0.1.0.1/pretty-sop.cabal (extracted from <repo>/package/pretty-sop-0.1.0.1.tar.gz)+Info: Writing <repo>/tagged/0.6.1/package.json+Info: Writing <repo>/tagged/0.6.1/tagged.cabal (extracted from <repo>/package/tagged-0.6.1.tar.gz)+Info: Writing <repo>/tagged/0.7.1/package.json+Info: Writing <repo>/tagged/0.7.1/tagged.cabal (extracted from <repo>/package/tagged-0.7.1.tar.gz)+Info: Writing <repo>/tagged/0.7.2/package.json+Info: Writing <repo>/tagged/0.7.2/tagged.cabal (extracted from <repo>/package/tagged-0.7.2.tar.gz)+Info: Writing <repo>/tagged/0.7.3/package.json+Info: Writing <repo>/tagged/0.7.3/tagged.cabal (extracted from <repo>/package/tagged-0.7.3.tar.gz)+Info: Writing <repo>/tagged/0.8.0.1/package.json+Info: Writing <repo>/tagged/0.8.0.1/tagged.cabal (extracted from <repo>/package/tagged-0.8.0.1.tar.gz)+Info: Writing <repo>/00-index.tar+Info: Writing <repo>/00-index.tar.gz+Info: Writing <repo>/snapshot.json+Info: Writing <repo>/timestamp.json++ unset HTTP_PROXY++ unset http_proxy++ rm -rf ./tmp++ ../sandbox/7.8.3/bin/example-client --repo http://127.0.0.1/~e/local-repo --cache ./tmp bootstrap 0+# Selected mirror http://127.0.0.1/~e/local-repo+# Downloading root+Sending:+GET /~e/local-repo/root.json HTTP/1.1 +Host: 127.0.0.1 +Cache-Control: no-transform +Content-Length: 0 +User-Agent: haskell-HTTP/4000.2.20 + ++Creating new connection to 127.0.0.1+Received:+HTTP/1.1 200 OK +Date: Mon, 13 Jul 2015 15:16:39 GMT +Server: Apache/2.4.10 (Unix) +Last-Modified: Mon, 13 Jul 2015 15:16:39 GMT +ETag: W/"cfb-51ac3355103c0" +Accept-Ranges: bytes +Content-Length: 3323 +Cache-Control: max-age=300, public, no-transform +Content-Type: application/json + ++OK++ ../sandbox/7.8.3/bin/example-client --repo http://127.0.0.1/~e/local-repo --cache ./tmp check+# Selected mirror http://127.0.0.1/~e/local-repo+# Downloading timestamp+Sending:+GET /~e/local-repo/timestamp.json HTTP/1.1 +Host: 127.0.0.1 +Cache-Control: no-transform +Content-Length: 0 +User-Agent: haskell-HTTP/4000.2.20 + ++Creating new connection to 127.0.0.1+Received:+HTTP/1.1 200 OK +Date: Mon, 13 Jul 2015 15:16:39 GMT +Server: Apache/2.4.10 (Unix) +Last-Modified: Mon, 13 Jul 2015 15:16:39 GMT +ETag: W/"1a3-51ac3355103c0" +Accept-Ranges: bytes +Content-Length: 419 +Cache-Control: max-age=300, public, no-transform +Content-Type: application/json + ++# Downloading snapshot+Sending:+GET /~e/local-repo/snapshot.json HTTP/1.1 +Host: 127.0.0.1 +Cache-Control: no-transform +Content-Length: 0 +User-Agent: haskell-HTTP/4000.2.20 + ++Recovering connection to 127.0.0.1+Received:+HTTP/1.1 200 OK +Date: Mon, 13 Jul 2015 15:16:39 GMT +Server: Apache/2.4.10 (Unix) +Last-Modified: Mon, 13 Jul 2015 15:16:39 GMT +ETag: W/"319-51ac3355103c0" +Accept-Ranges: bytes +Content-Length: 793 +Cache-Control: max-age=300, public, no-transform +Content-Type: application/json + ++# Downloading mirrors+Sending:+GET /~e/local-repo/mirrors.json HTTP/1.1 +Host: 127.0.0.1 +Cache-Control: no-transform +Content-Length: 0 +User-Agent: haskell-HTTP/4000.2.20 + ++Recovering connection to 127.0.0.1+Received:+HTTP/1.1 200 OK +Date: Mon, 13 Jul 2015 15:16:39 GMT +Server: Apache/2.4.10 (Unix) +Last-Modified: Mon, 13 Jul 2015 15:16:39 GMT +ETag: W/"2ad-51ac3355103c0" +Accept-Ranges: bytes +Content-Length: 685 +Cache-Control: max-age=300, public, no-transform +Content-Type: application/json + ++# Updating index failed (no local copy)+# Downloading index+Sending:+GET /~e/local-repo/00-index.tar.gz HTTP/1.1 +Host: 127.0.0.1 +Cache-Control: no-transform +Content-Length: 0 +User-Agent: haskell-HTTP/4000.2.20 + ++Recovering connection to 127.0.0.1+Received:+HTTP/1.1 200 OK +Date: Mon, 13 Jul 2015 15:16:39 GMT +Server: Apache/2.4.10 (Unix) +Last-Modified: Mon, 13 Jul 2015 15:16:39 GMT +ETag: W/"1241-51ac3355103c0" +Accept-Ranges: bytes +Content-Length: 4673 +Cache-Control: max-age=300, public, no-transform +Content-Type: application/x-gzip + ++HasUpdates++ export HTTP_PROXY=http://localhost:3128++ HTTP_PROXY=http://localhost:3128++ export http_proxy=http://localhost:3128++ http_proxy=http://localhost:3128++ curl -Lv http://127.0.0.1/~e/local-repo/00-index.tar+* Hostname was NOT found in DNS cache+ % Total % Received % Xferd Average Speed Time Time Time Current+ Dload Upload Total Spent Left Speed+ 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying 127.0.0.1...+* Connected to localhost (127.0.0.1) port 3128 (#0)+> GET http://127.0.0.1/~e/local-repo/00-index.tar HTTP/1.1 +> User-Agent: curl/7.37.1 +> Host: 127.0.0.1 +> Accept: */* +> Proxy-Connection: Keep-Alive +> +< HTTP/1.1 200 OK +< Date: Mon, 13 Jul 2015 15:16:39 GMT +* Server Apache/2.4.10 (Unix) is not blacklisted+< Server: Apache/2.4.10 (Unix) +< Last-Modified: Mon, 13 Jul 2015 15:16:39 GMT +< ETag: W/"10000-51ac3355103c0" +< Accept-Ranges: bytes +< Content-Length: 65536 +< Cache-Control: max-age=300, public, no-transform +< Content-Type: application/x-tar +< X-Cache: MISS from server.local +< Via: 1.1 server.local (squid/3.5.6) +< Connection: keep-alive +< +{ [data not shown]+ 100 65536 100 65536 0 0 7622k 0 --:--:-- --:--:-- --:--:-- 8000k+* Connection #0 to host localhost left intact++ sleep 5++ dd bs=1 count=16384 if=/dev/random of=../unversioned/local-repo/index/extra-file+16384+0 records in+16384+0 records out+16384 bytes transferred in 0.050569 secs (323993 bytes/sec)++ ../sandbox/7.8.3/bin/hackage-security -v update --repo ../unversioned/local-repo --keys ../unversioned/keys+Info: Skipping <repo>/generics-sop/0.1.0.0/package.json+Info: Skipping <repo>/generics-sop/0.1.0.0/generics-sop.cabal+Info: Skipping <repo>/generics-sop/0.1.0.1/package.json+Info: Skipping <repo>/generics-sop/0.1.0.1/generics-sop.cabal+Info: Skipping <repo>/generics-sop/0.1.0.2/package.json+Info: Skipping <repo>/generics-sop/0.1.0.2/generics-sop.cabal+Info: Skipping <repo>/generics-sop/0.1.0.3/package.json+Info: Skipping <repo>/generics-sop/0.1.0.3/generics-sop.cabal+Info: Skipping <repo>/generics-sop/0.1.0.4/package.json+Info: Skipping <repo>/generics-sop/0.1.0.4/generics-sop.cabal+Info: Skipping <repo>/generics-sop/0.1.1.1/package.json+Info: Skipping <repo>/generics-sop/0.1.1.1/generics-sop.cabal+Info: Skipping <repo>/generics-sop/0.1.1.2/package.json+Warning: Failed to extract .cabal from package generics-sop-0.1.1.2: <repo>/package/generics-sop-0.1.1.2.tar.gz: Codec.Compression.Zlib: compressed data stream format error (incorrect header check)+Info: Skipping <repo>/pretty-show/1.4.1/package.json+Info: Skipping <repo>/pretty-show/1.4.1/pretty-show.cabal+Info: Skipping <repo>/pretty-show/1.5/package.json+Info: Skipping <repo>/pretty-show/1.5/pretty-show.cabal+Info: Skipping <repo>/pretty-show/1.6/package.json+Info: Skipping <repo>/pretty-show/1.6/pretty-show.cabal+Info: Skipping <repo>/pretty-sop/0.1.0.0/package.json+Info: Skipping <repo>/pretty-sop/0.1.0.0/pretty-sop.cabal+Info: Skipping <repo>/pretty-sop/0.1.0.1/package.json+Info: Skipping <repo>/pretty-sop/0.1.0.1/pretty-sop.cabal+Info: Skipping <repo>/tagged/0.6.1/package.json+Info: Skipping <repo>/tagged/0.6.1/tagged.cabal+Info: Skipping <repo>/tagged/0.7.1/package.json+Info: Skipping <repo>/tagged/0.7.1/tagged.cabal+Info: Skipping <repo>/tagged/0.7.2/package.json+Info: Skipping <repo>/tagged/0.7.2/tagged.cabal+Info: Skipping <repo>/tagged/0.7.3/package.json+Info: Skipping <repo>/tagged/0.7.3/tagged.cabal+Info: Skipping <repo>/tagged/0.8.0.1/package.json+Info: Skipping <repo>/tagged/0.8.0.1/tagged.cabal+Info: Appending 1 file(s) to <repo>/00-index.tar+Info: Writing <repo>/00-index.tar.gz+Info: Updating <repo>/snapshot.json+Info: Updating <repo>/timestamp.json++ ../sandbox/7.8.3/bin/example-client --repo http://127.0.0.1/~e/local-repo --cache ./tmp check+# Selected mirror http://127.0.0.1/~e/local-repo+# Downloading timestamp+Sending:+GET http://127.0.0.1/~e/local-repo/timestamp.json HTTP/1.1 +Cache-Control: no-transform +Content-Length: 0 +User-Agent: haskell-HTTP/4000.2.20 +Host: 127.0.0.1 + ++proxy uri host: localhost, port: :3128+Creating new connection to localhost:3128+Received:+HTTP/1.1 200 OK +Date: Mon, 13 Jul 2015 15:16:45 GMT +Server: Apache/2.4.10 (Unix) +Last-Modified: Mon, 13 Jul 2015 15:16:44 GMT +ETag: "1a3-51ac3359d4f00" +Accept-Ranges: bytes +Content-Length: 419 +Cache-Control: max-age=300, public, no-transform +Content-Type: application/json +X-Cache: MISS from server.local +Via: 1.1 server.local (squid/3.5.6) +Connection: keep-alive + ++# Downloading snapshot+Sending:+GET http://127.0.0.1/~e/local-repo/snapshot.json HTTP/1.1 +Cache-Control: no-transform +Content-Length: 0 +User-Agent: haskell-HTTP/4000.2.20 +Host: 127.0.0.1 + ++proxy uri host: localhost, port: :3128+Recovering connection to localhost:3128+Received:+HTTP/1.1 200 OK +Date: Mon, 13 Jul 2015 15:16:45 GMT +Server: Apache/2.4.10 (Unix) +Last-Modified: Mon, 13 Jul 2015 15:16:44 GMT +ETag: "31a-51ac3359d4f00" +Accept-Ranges: bytes +Content-Length: 794 +Cache-Control: max-age=300, public, no-transform +Content-Type: application/json +X-Cache: MISS from server.local +Via: 1.1 server.local (squid/3.5.6) +Connection: keep-alive + ++# Updating index+Sending:+GET http://127.0.0.1/~e/local-repo/00-index.tar HTTP/1.1 +Range: bytes=64512-82431 +Cache-Control: no-transform +Content-Length: 0 +User-Agent: haskell-HTTP/4000.2.20 +Host: 127.0.0.1 + ++proxy uri host: localhost, port: :3128+Recovering connection to localhost:3128+Received:+HTTP/1.1 206 Partial Content +Date: Mon, 13 Jul 2015 15:16:39 GMT +Server: Apache/2.4.10 (Unix) +Last-Modified: Mon, 13 Jul 2015 15:16:39 GMT +ETag: W/"10000-51ac3355103c0" +Accept-Ranges: bytes +Cache-Control: max-age=300, public, no-transform +Content-Type: application/x-tar +Age: 6 +X-Cache: HIT from server.local +Via: 1.1 server.local (squid/3.5.6) +Connection: keep-alive +Content-Range: bytes 64512-65535/65536 +Content-Length: 1024 + ++# Verification error: Invalid hash for <repo>/00-index.tar+# Downloading root+Sending:+GET http://127.0.0.1/~e/local-repo/root.json HTTP/1.1 +Cache-Control: no-transform, max-age=0 +Content-Length: 0 +User-Agent: haskell-HTTP/4000.2.20 +Host: 127.0.0.1 + ++proxy uri host: localhost, port: :3128+Recovering connection to localhost:3128+Received:+HTTP/1.1 200 OK +Date: Mon, 13 Jul 2015 15:16:45 GMT +Server: Apache/2.4.10 (Unix) +Last-Modified: Mon, 13 Jul 2015 15:16:39 GMT +ETag: "cfb-51ac3355103c0" +Accept-Ranges: bytes +Content-Length: 3323 +Cache-Control: max-age=300, public, no-transform +Content-Type: application/json +X-Cache: MISS from server.local +Via: 1.1 server.local (squid/3.5.6) +Connection: keep-alive + ++# Downloading timestamp+Sending:+GET http://127.0.0.1/~e/local-repo/timestamp.json HTTP/1.1 +Cache-Control: no-transform, max-age=0 +Content-Length: 0 +User-Agent: haskell-HTTP/4000.2.20 +Host: 127.0.0.1 + ++proxy uri host: localhost, port: :3128+Recovering connection to localhost:3128+Received:+HTTP/1.1 200 OK +Last-Modified: Mon, 13 Jul 2015 15:16:44 GMT +Accept-Ranges: bytes +Content-Length: 419 +Content-Type: application/json +Date: Mon, 13 Jul 2015 15:16:45 GMT +Server: Apache/2.4.10 (Unix) +ETag: "1a3-51ac3359d4f00" +Cache-Control: max-age=300, public, no-transform +Age: 0 +X-Cache: HIT from server.local +Via: 1.1 server.local (squid/3.5.6) +Connection: keep-alive + ++# Downloading snapshot+Sending:+GET http://127.0.0.1/~e/local-repo/snapshot.json HTTP/1.1 +Cache-Control: no-transform, max-age=0 +Content-Length: 0 +User-Agent: haskell-HTTP/4000.2.20 +Host: 127.0.0.1 + ++proxy uri host: localhost, port: :3128+Recovering connection to localhost:3128+Received:+HTTP/1.1 200 OK +Last-Modified: Mon, 13 Jul 2015 15:16:44 GMT +Accept-Ranges: bytes +Content-Length: 794 +Content-Type: application/json +Date: Mon, 13 Jul 2015 15:16:45 GMT +Server: Apache/2.4.10 (Unix) +ETag: "31a-51ac3359d4f00" +Cache-Control: max-age=300, public, no-transform +Age: 0 +X-Cache: HIT from server.local +Via: 1.1 server.local (squid/3.5.6) +Connection: keep-alive + ++# Updating index+Sending:+GET http://127.0.0.1/~e/local-repo/00-index.tar HTTP/1.1 +Range: bytes=64512-82431 +Cache-Control: no-transform, max-age=0 +Content-Length: 0 +User-Agent: haskell-HTTP/4000.2.20 +Host: 127.0.0.1 + ++proxy uri host: localhost, port: :3128+Recovering connection to localhost:3128+Received:+HTTP/1.1 206 Partial Content +Date: Mon, 13 Jul 2015 15:16:45 GMT +Server: Apache/2.4.10 (Unix) +Last-Modified: Mon, 13 Jul 2015 15:16:44 GMT +ETag: "14200-51ac3359d4f00" +Accept-Ranges: bytes +Content-Length: 17920 +Cache-Control: max-age=300, public, no-transform +Content-Range: bytes 64512-82431/82432 +Content-Type: application/x-tar +X-Cache: MISS from server.local +Via: 1.1 server.local (squid/3.5.6) +Connection: keep-alive + ++HasUpdates
+ hackage-security/testscripts/test-outdated-index.sh view
@@ -0,0 +1,67 @@+#!/bin/bash++##+# Cache incoherence test 2: outdated index+#+# DESCRIPTION+#+# The general approach is the same as in test 1, except we now set things up+# so that the cache has an outdated index.+#+# EXPECTED OUTCOME+#+# We should recover from the cache incoherence problem and still download the+# index incrementally (rather than downloading the entire index from scratch).+# See example log file for a succcessful run.+#+# ASSUMPTIONS+#+# Since this relies on incremental downloads, it must currently be tested+# against Apache rather than Hackage. Make sure to have+#+# Header set Cache-Control "max-age=5, public, no-transform"+#+# in your .htaccess (and set AllowOverride: All for your domain, if necessary).+# As for test 1, you should also have a fresh instance of squid running.+##++set -x++BINDIR=../sandbox/7.8.3/bin+EXAMPLE_CLIENT=${BINDIR}/example-client+SECURITY_UTILITY=${BINDIR}/hackage-security+REPO=http://127.0.0.1/~e/local-repo+PROXY=http://localhost:3128+LOCAL_REPO=../unversioned/local-repo+KEYS=../unversioned/keys+LOCAL_CACHE=./tmp # NOTE: We will delete this directory++# Reset the repo+rm -rf ${LOCAL_REPO}/index+${SECURITY_UTILITY} -v bootstrap --repo ${LOCAL_REPO} --keys ${KEYS}++# Start with a fresh local cache, and do an update (directly, without squid) so+# that we have a local copy of the index that we can update incrementally later+unset HTTP_PROXY+unset http_proxy+rm -rf ${LOCAL_CACHE}+${EXAMPLE_CLIENT} --repo ${REPO} --cache ${LOCAL_CACHE} bootstrap 0+${EXAMPLE_CLIENT} --repo ${REPO} --cache ${LOCAL_CACHE} check++# Now start using squid+export HTTP_PROXY=${PROXY}+export http_proxy=${PROXY}++# Get the index from the server so that it's in squid's cache+curl -Lv ${REPO}/00-index.tar >/dev/null++# Add something to the index, and update snapshot/timestamp+# Sleep a bit first, so that the time on the extra file is definitely different+# to the time on the index+sleep 5+dd bs=1 count=16384 if=/dev/random of=${LOCAL_REPO}/index/extra-file+${SECURITY_UTILITY} -v update --repo ${LOCAL_REPO} --keys ${KEYS}++# Now do another update.+# The timestamp and snapshot will be out of sync with each other.+${EXAMPLE_CLIENT} --repo ${REPO} --cache ${LOCAL_CACHE} check
+ hackage-security/testscripts/test-outdated-timestamp.log view
@@ -0,0 +1,280 @@++ export HTTP_PROXY=http://localhost:3128++ HTTP_PROXY=http://localhost:3128++ export http_proxy=http://localhost:3128++ http_proxy=http://localhost:3128++ rm -rf ./tmp++ ../sandbox/7.8.3/bin/example-client --repo http://127.0.0.1:8080 --cache ./tmp bootstrap 0+# Selected mirror http://127.0.0.1:8080+# Downloading root+Sending:+GET http://127.0.0.1:8080/root.json HTTP/1.1 +Cache-Control: no-transform +Content-Length: 0 +User-Agent: haskell-HTTP/4000.2.20 +Host: 127.0.0.1:8080 + ++proxy uri host: localhost, port: :3128+Creating new connection to localhost:3128+Received:+HTTP/1.1 200 OK +Cache-Control: public, no-transform, max-age=60 +Content-Length: 3323 +Content-MD5: 914e6bed90bde8179af0b657937ca1d9 +Content-Type: text/json +Date: Mon, 13 Jul 2015 14:46:41 GMT +ETag: "914e6bed90bde8179af0b657937ca1d9" +Last-Modified: Thu, 11 Jun 2015 14:37:51 GMT +Server: Happstack/7.4.4 +X-Cache: MISS from server.local +Via: 1.1 server.local (squid/3.5.6) +Connection: keep-alive + ++OK++ curl -Lv http://127.0.0.1:8080/timestamp.json+* Hostname was NOT found in DNS cache+ % Total % Received % Xferd Average Speed Time Time Time Current+ Dload Upload Total Spent Left Speed+ 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying 127.0.0.1...+* Connected to localhost (127.0.0.1) port 3128 (#0)+> GET http://127.0.0.1:8080/timestamp.json HTTP/1.1 +> User-Agent: curl/7.37.1 +> Host: 127.0.0.1:8080 +> Accept: */* +> Proxy-Connection: Keep-Alive +> +< HTTP/1.1 200 OK +< Cache-Control: public, no-transform, max-age=60 +< Content-Length: 420 +< Content-MD5: fea6f07b4caa591625bcf4a2f27fe9af +< Content-Type: text/json +< Date: Mon, 13 Jul 2015 14:46:41 GMT +< ETag: "fea6f07b4caa591625bcf4a2f27fe9af" +< Last-Modified: Mon, 13 Jul 2015 14:45:38 GMT +* Server Happstack/7.4.4 is not blacklisted+< Server: Happstack/7.4.4 +< X-Cache: MISS from server.local +< Via: 1.1 server.local (squid/3.5.6) +< Connection: keep-alive +< +{ [data not shown]+ 100 420 100 420 0 0 52135 0 --:--:-- --:--:-- --:--:-- 60000+* Connection #0 to host localhost left intact++ killall -SIGHUP hackage-server++ sleep 5++ ../sandbox/7.8.3/bin/example-client --repo http://127.0.0.1:8080 --cache ./tmp check+# Selected mirror http://127.0.0.1:8080+# Downloading timestamp+Sending:+GET http://127.0.0.1:8080/timestamp.json HTTP/1.1 +Cache-Control: no-transform +Content-Length: 0 +User-Agent: haskell-HTTP/4000.2.20 +Host: 127.0.0.1:8080 + ++proxy uri host: localhost, port: :3128+Creating new connection to localhost:3128+Received:+HTTP/1.1 200 OK +Cache-Control: public, no-transform, max-age=60 +Content-Length: 420 +Content-MD5: fea6f07b4caa591625bcf4a2f27fe9af +Content-Type: text/json +Date: Mon, 13 Jul 2015 14:46:41 GMT +ETag: "fea6f07b4caa591625bcf4a2f27fe9af" +Last-Modified: Mon, 13 Jul 2015 14:45:38 GMT +Server: Happstack/7.4.4 +Age: 6 +X-Cache: HIT from server.local +Via: 1.1 server.local (squid/3.5.6) +Connection: keep-alive + ++# Downloading snapshot+Sending:+GET http://127.0.0.1:8080/snapshot.json HTTP/1.1 +Cache-Control: no-transform +Content-Length: 0 +User-Agent: haskell-HTTP/4000.2.20 +Host: 127.0.0.1:8080 + ++proxy uri host: localhost, port: :3128+Recovering connection to localhost:3128+Received:+HTTP/1.1 200 OK +Cache-Control: public, no-transform, max-age=60 +Content-Length: 791 +Content-MD5: 90a469a7bedd38afe12951dc774a555d +Content-Type: text/json +Date: Mon, 13 Jul 2015 14:46:46 GMT +ETag: "90a469a7bedd38afe12951dc774a555d" +Last-Modified: Mon, 13 Jul 2015 14:46:42 GMT +Server: Happstack/7.4.4 +X-Cache: MISS from server.local +Via: 1.1 server.local (squid/3.5.6) +Connection: keep-alive + ++# Verification error: Invalid hash for <repo>/snapshot.json+# Downloading root+Sending:+GET http://127.0.0.1:8080/root.json HTTP/1.1 +Cache-Control: no-transform, max-age=0 +Content-Length: 0 +User-Agent: haskell-HTTP/4000.2.20 +Host: 127.0.0.1:8080 + ++proxy uri host: localhost, port: :3128+Recovering connection to localhost:3128+Received:+HTTP/1.1 200 OK +Content-Length: 3323 +Content-MD5: 914e6bed90bde8179af0b657937ca1d9 +Content-Type: text/json +Last-Modified: Thu, 11 Jun 2015 14:37:51 GMT +Cache-Control: public, no-transform, max-age=60 +Date: Mon, 13 Jul 2015 14:46:46 GMT +ETag: "914e6bed90bde8179af0b657937ca1d9" +Server: Happstack/7.4.4 +Age: 1 +X-Cache: HIT from server.local +Via: 1.1 server.local (squid/3.5.6) +Connection: keep-alive + ++# Downloading timestamp+Sending:+GET http://127.0.0.1:8080/timestamp.json HTTP/1.1 +Cache-Control: no-transform, max-age=0 +Content-Length: 0 +User-Agent: haskell-HTTP/4000.2.20 +Host: 127.0.0.1:8080 + ++proxy uri host: localhost, port: :3128+Recovering connection to localhost:3128+Received:+HTTP/1.1 200 OK +Cache-Control: public, no-transform, max-age=60 +Content-Length: 420 +Content-MD5: 25aa54b986c278d7ec4a2d9514fe1316 +Content-Type: text/json +Date: Mon, 13 Jul 2015 14:46:46 GMT +ETag: "25aa54b986c278d7ec4a2d9514fe1316" +Last-Modified: Mon, 13 Jul 2015 14:46:42 GMT +Server: Happstack/7.4.4 +X-Cache: MISS from server.local +Via: 1.1 server.local (squid/3.5.6) +Connection: keep-alive + ++# Downloading snapshot+Sending:+GET http://127.0.0.1:8080/snapshot.json HTTP/1.1 +Cache-Control: no-transform, max-age=0 +Content-Length: 0 +User-Agent: haskell-HTTP/4000.2.20 +Host: 127.0.0.1:8080 + ++proxy uri host: localhost, port: :3128+Recovering connection to localhost:3128+Received:+HTTP/1.1 200 OK +Content-Length: 791 +Content-MD5: 90a469a7bedd38afe12951dc774a555d +Content-Type: text/json +Last-Modified: Mon, 13 Jul 2015 14:46:42 GMT +Cache-Control: public, no-transform, max-age=60 +Date: Mon, 13 Jul 2015 14:46:46 GMT +ETag: "90a469a7bedd38afe12951dc774a555d" +Server: Happstack/7.4.4 +Age: 1 +X-Cache: HIT from server.local +Via: 1.1 server.local (squid/3.5.6) +Connection: keep-alive + ++# Downloading mirrors+Sending:+GET http://127.0.0.1:8080/mirrors.json HTTP/1.1 +Cache-Control: no-transform, max-age=0 +Content-Length: 0 +User-Agent: haskell-HTTP/4000.2.20 +Host: 127.0.0.1:8080 + ++proxy uri host: localhost, port: :3128+Recovering connection to localhost:3128+Received:+HTTP/1.1 200 OK +Cache-Control: public, no-transform, max-age=60 +Content-Length: 685 +Content-MD5: e98e8b013404cf085be353a055b723be +Content-Type: text/json +Date: Mon, 13 Jul 2015 14:46:46 GMT +ETag: "e98e8b013404cf085be353a055b723be" +Last-Modified: Thu, 11 Jun 2015 14:37:59 GMT +Server: Happstack/7.4.4 +X-Cache: MISS from server.local +Via: 1.1 server.local (squid/3.5.6) +Connection: keep-alive + ++# Updating index failed (server does not provide incremental downloads)+# Downloading index+Sending:+GET http://127.0.0.1:8080/00-index.tar.gz HTTP/1.1 +Cache-Control: no-transform, max-age=0 +Content-Length: 0 +User-Agent: haskell-HTTP/4000.2.20 +Host: 127.0.0.1:8080 + ++proxy uri host: localhost, port: :3128+Recovering connection to localhost:3128+Received:+HTTP/1.1 301 Moved Permanently +Content-Type: text/plain; charset=UTF-8 +Date: Mon, 13 Jul 2015 14:46:46 GMT +Location: /packages/index.tar.gz +Server: Happstack/7.4.4 +X-Cache: MISS from server.local +Transfer-Encoding: chunked +Via: 1.1 server.local (squid/3.5.6) +Connection: keep-alive +Content-Length: 0 + ++301 - redirect+Redirecting to http://127.0.0.1:8080/packages/index.tar.gz ...+Sending:+GET http://127.0.0.1:8080/packages/index.tar.gz HTTP/1.1 +Cache-Control: no-transform, max-age=0 +Content-Length: 0 +User-Agent: haskell-HTTP/4000.2.20 +Host: 127.0.0.1:8080 + ++proxy uri host: localhost, port: :3128+Recovering connection to localhost:3128+Received:+HTTP/1.1 200 OK +Cache-Control: public, no-transform, max-age=300 +Content-Length: 29 +Content-MD5: 31f6566d35ccd604be46ed5b1f813cdf +Content-Type: application/x-gzip +Date: Mon, 13 Jul 2015 14:46:46 GMT +ETag: "31f6566d35ccd604be46ed5b1f813cdf" +Last-Modified: Mon, 13 Jul 2015 08:18:42 GMT +Server: Happstack/7.4.4 +X-Cache: MISS from server.local +Via: 1.1 server.local (squid/3.5.6) +Connection: keep-alive + ++HasUpdates
+ hackage-security/testscripts/test-outdated-timestamp.sh view
@@ -0,0 +1,60 @@+#!/bin/bash++##+# Cache incoherence test 1: outdated timestamp+#+# DESCRIPTION+#+# We request the timestamp from the server through the proxy, so that it's in+# the proxy's cache (assuming a `max-age` header was set on the file). We then+# ask Hackage to resign, and do a check-for-updates using `example-client`.+# This will cause the proxy to return the now-outdated `timestamp` but a newer+# snapshot.+#+# NOTE: We refer to the cache maintained by the `hackage-security` library+# (through `example-client`) as the "local cache", and the cache maintained by+# squid as the "proxy cache".+#+# EXPECTED OUTCOME+#+# The client should detect this and try the request again, asking the+# cache to fetch the file stream. See the example log file.+#+# ASSUMPTIONS+#+# We assume Hackage is running on the same machine as the test script (so that+# we can send it SIGHUP), and that we have a fresh (nothing cached yet) instance+# of squid. There is an example configuration file for squid in this directory;+# start with+#+# ~/homebrew/sbin/squid -f ./squid.conf -N -d 1+#+# (mutatis mutandis).+##++# Configuration+BINDIR=../sandbox/7.8.3/bin+EXAMPLE_CLIENT=${BINDIR}/example-client+REPO=http://127.0.0.1:8080+PROXY=http://localhost:3128+LOCAL_CACHE=./tmp # NOTE: We will delete this directory++set -x++# Enable the proxy+export HTTP_PROXY=${PROXY}+export http_proxy=${PROXY}++# Start with a fresh local cache (so that we definitely have updates)+rm -rf ${LOCAL_CACHE}+${EXAMPLE_CLIENT} --repo ${REPO} --cache ${LOCAL_CACHE} bootstrap 0++# Get the timestamp from the server (so that it's in proxy cache)+curl -Lv ${REPO}/timestamp.json >/dev/null++# Make the server resign the timestamp+killall -SIGHUP hackage-server+sleep 5++# Now do an update+${EXAMPLE_CLIENT} --repo ${REPO} --cache ${LOCAL_CACHE} check
+ hackage-security/testscripts/test-range-request.sh view
@@ -0,0 +1,13 @@+#!/bin/bash++BINDIR=../sandbox/7.8.3/bin+EXAMPLE_CLIENT=${BINDIR}/example-client+LOCAL_CACHE=../unversioned/cache/+#REPO=http://localhost:8080+REPO=http://localhost/~e/local-repo++rm -r ${LOCAL_CACHE}+${EXAMPLE_CLIENT} --repo ${REPO} --cache ${LOCAL_CACHE} bootstrap 0+dd if=/dev/zero of=${LOCAL_CACHE}/00-index.tar bs=1024 count=1+${EXAMPLE_CLIENT} --repo ${REPO} --cache ${LOCAL_CACHE} check+
hackport.cabal view
@@ -1,5 +1,5 @@ Name: hackport-Version: 0.4.7+Version: 0.5 License: GPL License-file: LICENSE Author: Henning Günther, Duncan Coutts, Lennart Kolmodin@@ -20,40 +20,80 @@ ghc-prof-options: -caf-all -auto-all -rtsopts Main-Is: Main.hs Default-Language: Haskell98- Hs-Source-Dirs: ., cabal, cabal/Cabal, cabal/cabal-install+ Hs-Source-Dirs:+ .,+ cabal,+ cabal/Cabal,+ cabal/cabal-install,+ hackage-security/hackage-security/src Build-Depends:+ array, base >= 2.0 && < 5, deepseq >= 1.3,+ extensible-exceptions, filepath,- parsec,+ HTTP >= 4000.0.3,+ MissingH, network >= 2.6, network-uri >= 2.6,+ parsec, pretty,- regex-compat,- MissingH, old-locale,- HTTP >= 4000.0.3,- zlib,+ regex-compat,+ split, tar,- xml >= 1.3.7,- array,- extensible-exceptions, time,+ zlib,+ xml >= 1.3.7, -- cabal depends binary, random, stm,- unix+ unix,+ -- cabal-install depends+ -- hackage-security depends+ base64-bytestring,+ cryptohash,+ ed25519,+ ghc-prim,+ hashable,+ mtl,+ template-haskell,+ transformers - -- extensions due to hackport- other-extensions:+ default-extensions:+ -- hackage-security+ DefaultSignatures, DeriveDataTypeable,- PatternGuards-- -- extensions due to bundled cabal-install+ EmptyDataDecls,+ ExistentialQuantification,+ FlexibleContexts,+ FlexibleInstances,+ GADTs,+ GeneralizedNewtypeDeriving,+ KindSignatures,+ MultiParamTypeClasses,+ PatternGuards,+ RankNTypes,+ RecordWildCards,+ ScopedTypeVariables,+ StandaloneDeriving,+ TypeFamilies,+ TypeOperators,+ ViewPatterns other-extensions:+ DeriveDataTypeable,+ PatternGuards,+ -- extensions due to bundled cabal-install CPP, ForeignFunctionInterface,- PatternGuards+ -- hackage-security extensions+ DefaultSignatures,+ GeneralizedNewtypeDeriving,+ GADTs,+ KindSignatures,+ RankNTypes,+ RecordWildCards,+ TypeOperators Build-Depends: base >= 3 && < 5,@@ -66,10 +106,7 @@ other-modules: AnsiColor Cabal2Ebuild- CacheFile- Diff Error- Hackage Main Overlays Paths_hackport@@ -87,40 +124,6 @@ Merge Util --Executable hackport-guess-ghc-version- ghc-options: -Wall- Main-Is: Main-GuessGHC.hs- Default-Language: Haskell98- Buildable: False- -- this was used as a test while developing the- -- ghc-guessfeature. now we can disable building- Build-Depends:- base >= 2.0 && < 5,- filepath,- parsec,- network >= 2.6, network-uri >= 2.6,- pretty,- regex-compat,- HTTP >= 4000.0.3,- zlib,- tar,- array,- -- array is inherited from cabal-install - -- tar >= 0.3.0.0 && < 0.4- extensible-exceptions-- Build-Depends:- base >= 3 && < 5,- directory,- containers,- process,- old-time,- bytestring-- other-modules:- Portage.GHCCore- Test-Suite test-resolve-category ghc-options: -Wall Type: exitcode-stdio-1.0@@ -138,6 +141,7 @@ HUnit, pretty, process,+ split, time, unix, xml