diff --git a/Main.hs b/Main.hs
deleted file mode 100644
--- a/Main.hs
+++ /dev/null
@@ -1,2 +0,0 @@
-module Main (main) where
-import MakeTravisYml (main)
diff --git a/cli/Main.hs b/cli/Main.hs
new file mode 100644
--- /dev/null
+++ b/cli/Main.hs
@@ -0,0 +1,2 @@
+module Main (main) where
+import HaskellCI (main)
diff --git a/fixtures/cabal.haskell-ci b/fixtures/cabal.haskell-ci
--- a/fixtures/cabal.haskell-ci
+++ b/fixtures/cabal.haskell-ci
@@ -1,8 +1,11 @@
 -- Most settings can be given as command line arguments
--- Config file is convinient if you maintain large amount of packages
+-- Config file is convenient if you maintain large amount of packages
 
 jobs: 2:2
 
+-- Add GHC head job
+ghc-head: True
+
 -- travis Caching
 -- cache: True
 
@@ -21,9 +24,8 @@
 -- no-tests-no-bench: True
 
 -- By default `installed` constraints are used for packages
--- in global db. Unconstrained step is a build without them.
--- Note: solver might or might not pick newer versions.
--- unconstrained-step: True
+-- in global db. We can modify which constraints are used.
+installed: +all -Cabal
 
 -- Options for local packages
 local-ghc-options: -Werror
@@ -48,6 +50,9 @@
 doctest-options: --fast
 -- doctest-version: ==0.13.*
 
+-- macOS job
+osx: 8.4.4
+
 -- Constraint sets
 -- Package will be build with different constraints.
 -- This is useful to check compatibility with older versions of dependencies.
@@ -55,3 +60,9 @@
 constraint-set deepseq-1.4
   ghc: (>= 7.8 && <7.10) || == 8.2.2
   constraints: deepseq ==1.4.*
+
+-- Include these fields "as is" in generated cabal.project
+raw-project
+  keep-going: False
+  package bytestring
+    tests: False
diff --git a/fixtures/cabal.project.copy-fields.all b/fixtures/cabal.project.copy-fields.all
new file mode 100644
--- /dev/null
+++ b/fixtures/cabal.project.copy-fields.all
@@ -0,0 +1,15 @@
+packages:
+  servant/
+  servant-client/
+  servant-docs/
+  servant-server/
+
+package servant
+  tests: False
+
+constraints: foundation >= 0.14
+
+allow-newer:
+  servant-js:servant
+allow-newer:
+  servant-js:servant-foreign
diff --git a/fixtures/cabal.project.copy-fields.all.stderr b/fixtures/cabal.project.copy-fields.all.stderr
new file mode 100644
--- /dev/null
+++ b/fixtures/cabal.project.copy-fields.all.stderr
@@ -0,0 +1,1 @@
+*INFO* Generating Travis-CI config for testing for GHC versions: 7.8.1 7.8.2 7.8.3 7.8.4 7.10.1 7.10.2 7.10.3 8.0.1 8.0.2 8.2.1 8.2.2 8.4.1 8.4.2 8.4.3 8.4.4 8.6.1 8.6.2 8.6.3
diff --git a/fixtures/cabal.project.copy-fields.all.travis.yml b/fixtures/cabal.project.copy-fields.all.travis.yml
new file mode 100644
--- /dev/null
+++ b/fixtures/cabal.project.copy-fields.all.travis.yml
@@ -0,0 +1,164 @@
+# This Travis job script has been generated by a script via
+#
+#   haskell-ci '-o' 'cabal.project.empty-line.travis.yml' '--copy-fields=all' 'cabal.project.empty-line'
+#
+# For more information, see https://github.com/haskell-CI/haskell-ci
+#
+# version: 0.2
+#
+language: c
+dist: xenial
+
+git:
+  submodules: false  # whether to recursively clone submodules
+
+cache:
+  directories:
+    - $HOME/.cabal/packages
+    - $HOME/.cabal/store
+
+before_cache:
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/build-reports.log
+  # remove files that are regenerated by 'cabal update'
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/00-index.*
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/*.json
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.cache
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar.idx
+
+  - rm -rfv $CABALHOME/packages/head.hackage
+
+matrix:
+  include:
+    - compiler: "ghc-8.6.3"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.6.3], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.6.2"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.6.2], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.6.1"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.6.1], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.4.4"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.4.4], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.4.3"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.4.3], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.4.2"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.4.2], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.4.1"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.4.1], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.2.2"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.2.2], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.2.1"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.2.1], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.0.2"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.0.2], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.0.1"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.0.1], sources: [hvr-ghc]}}
+    - compiler: "ghc-7.10.3"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.10.3], sources: [hvr-ghc]}}
+    - compiler: "ghc-7.10.2"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.10.2], sources: [hvr-ghc]}}
+    - compiler: "ghc-7.10.1"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.10.1], sources: [hvr-ghc]}}
+    - compiler: "ghc-7.8.4"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.8.4], sources: [hvr-ghc]}}
+    - compiler: "ghc-7.8.3"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.8.3], sources: [hvr-ghc]}}
+    - compiler: "ghc-7.8.2"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.8.2], sources: [hvr-ghc]}}
+    - compiler: "ghc-7.8.1"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.8.1], sources: [hvr-ghc]}}
+
+before_install:
+  - HC=/opt/ghc/bin/${CC}
+  - HCPKG=${HC/ghc/ghc-pkg}
+  - unset CC
+  - CABAL=/opt/ghc/bin/cabal
+  - CABALHOME=$HOME/.cabal
+  - export PATH="$CABALHOME/bin:$PATH"
+  - ROOTDIR=$(pwd)
+  - HCNUMVER=$(( $(${HC} --numeric-version|sed -E 's/([0-9]+)\.([0-9]+)\.([0-9]+).*/\1 * 10000 + \2 * 100 + \3/') ))
+  - echo $HCNUMVER
+
+install:
+  - ${CABAL} --version
+  - echo "$(${HC} --version) [$(${HC} --print-project-git-commit-id 2> /dev/null || echo '?')]"
+  - TEST=--enable-tests
+  - BENCH=--enable-benchmarks
+  - GHCHEAD=${GHCHEAD-false}
+  - travis_retry ${CABAL} update -v
+  - sed -i.bak 's/^jobs:/-- jobs:/' $CABALHOME/config
+  - rm -fv cabal.project cabal.project.local
+  - grep -Ev -- '^\s*--' $CABALHOME/config | grep -Ev '^\s*$'
+  - rm -f cabal.project
+  - touch cabal.project
+  - "printf 'packages: \"servant\"\\n' >> cabal.project"
+  - "printf 'packages: \"servant-client\"\\n' >> cabal.project"
+  - "printf 'packages: \"servant-docs\"\\n' >> cabal.project"
+  - "printf 'packages: \"servant-server\"\\n' >> cabal.project"
+  - echo 'package servant' >> cabal.project
+  - "echo '  tests: False' >> cabal.project"
+  - "echo 'constraints: foundation >= 0.14' >> cabal.project"
+  - "echo 'allow-newer: servant-js:servant' >> cabal.project"
+  - "echo 'allow-newer: servant-js:servant-foreign' >> cabal.project"
+  - "printf 'write-ghc-environment-files: always\\n' >> cabal.project"
+  - touch cabal.project.local
+  - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | grep -vE -- '^(servant|servant-client|servant-docs|servant-server)$' | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"
+  - cat cabal.project || true
+  - cat cabal.project.local || true
+  - if [ -f "servant/configure.ac" ]; then (cd "servant" && autoreconf -i); fi
+  - if [ -f "servant-client/configure.ac" ]; then (cd "servant-client" && autoreconf -i); fi
+  - if [ -f "servant-docs/configure.ac" ]; then (cd "servant-docs" && autoreconf -i); fi
+  - if [ -f "servant-server/configure.ac" ]; then (cd "servant-server" && autoreconf -i); fi
+  - rm -f cabal.project.freeze
+  - ${CABAL} new-freeze -w ${HC} ${TEST} ${BENCH} --project-file="cabal.project.copy-fields.all" --dry
+  - "cat \"cabal.project.copy-fields.all.freeze\" | sed -E 's/^(constraints: *| *)//' | sed 's/any.//'"
+  - rm  "cabal.project.copy-fields.all.freeze"
+  - ${CABAL} new-build -w ${HC} ${TEST} ${BENCH} --project-file="cabal.project.copy-fields.all" --dep -j2 all
+  - ${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks --project-file="cabal.project.copy-fields.all" --dep -j2 all
+  - rm -rf .ghc.environment.* "servant"/dist "servant-client"/dist "servant-docs"/dist "servant-server"/dist
+  - DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)
+
+# 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:
+  # test that source-distributions can be generated
+  - ${CABAL} new-sdist all
+  - mv dist-newstyle/sdist/*.tar.gz ${DISTDIR}/
+  - cd ${DISTDIR} || false
+  - find . -maxdepth 1 -name '*.tar.gz' -exec tar -xvf '{}' \;
+  - rm -f cabal.project
+  - touch cabal.project
+  - "printf 'packages: \"servant-*/*.cabal\"\\n' >> cabal.project"
+  - "printf 'packages: \"servant-client-*/*.cabal\"\\n' >> cabal.project"
+  - "printf 'packages: \"servant-docs-*/*.cabal\"\\n' >> cabal.project"
+  - "printf 'packages: \"servant-server-*/*.cabal\"\\n' >> cabal.project"
+  - echo 'package servant' >> cabal.project
+  - "echo '  tests: False' >> cabal.project"
+  - "echo 'constraints: foundation >= 0.14' >> cabal.project"
+  - "echo 'allow-newer: servant-js:servant' >> cabal.project"
+  - "echo 'allow-newer: servant-js:servant-foreign' >> cabal.project"
+  - "printf 'write-ghc-environment-files: always\\n' >> cabal.project"
+  - touch cabal.project.local
+  - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | grep -vE -- '^(servant|servant-client|servant-docs|servant-server)$' | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"
+  - cat cabal.project || true
+  - cat cabal.project.local || true
+  # this builds all libraries and executables (without tests/benchmarks)
+  - ${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks all
+
+  # build & run tests, build benchmarks
+  - ${CABAL} new-build -w ${HC} ${TEST} ${BENCH} all
+  - if [ "x$TEST" = "x--enable-tests" ]; then ${CABAL} new-test -w ${HC} ${TEST} ${BENCH} all; fi
+
+  # cabal check
+  - (cd servant-* && ${CABAL} check)
+  - (cd servant-client-* && ${CABAL} check)
+  - (cd servant-docs-* && ${CABAL} check)
+  - (cd servant-server-* && ${CABAL} check)
+
+  # haddock
+  - ${CABAL} new-haddock -w ${HC} ${TEST} ${BENCH} all
+
+  # Build without installed constraints for packages in global-db
+  - rm -f cabal.project.local; ${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks all;
+
+# REGENDATA ["-o","cabal.project.empty-line.travis.yml","--copy-fields=all","cabal.project.empty-line"]
+# EOF
diff --git a/fixtures/cabal.project.copy-fields.none b/fixtures/cabal.project.copy-fields.none
new file mode 100644
--- /dev/null
+++ b/fixtures/cabal.project.copy-fields.none
@@ -0,0 +1,15 @@
+packages:
+  servant/
+  servant-client/
+  servant-docs/
+  servant-server/
+
+package servant
+  tests: False
+
+constraints: foundation >= 0.14
+
+allow-newer:
+  servant-js:servant
+allow-newer:
+  servant-js:servant-foreign
diff --git a/fixtures/cabal.project.copy-fields.none.stderr b/fixtures/cabal.project.copy-fields.none.stderr
new file mode 100644
--- /dev/null
+++ b/fixtures/cabal.project.copy-fields.none.stderr
@@ -0,0 +1,1 @@
+*INFO* Generating Travis-CI config for testing for GHC versions: 7.8.1 7.8.2 7.8.3 7.8.4 7.10.1 7.10.2 7.10.3 8.0.1 8.0.2 8.2.1 8.2.2 8.4.1 8.4.2 8.4.3 8.4.4 8.6.1 8.6.2 8.6.3
diff --git a/fixtures/cabal.project.copy-fields.none.travis.yml b/fixtures/cabal.project.copy-fields.none.travis.yml
new file mode 100644
--- /dev/null
+++ b/fixtures/cabal.project.copy-fields.none.travis.yml
@@ -0,0 +1,154 @@
+# This Travis job script has been generated by a script via
+#
+#   haskell-ci '-o' 'cabal.project.empty-line.travis.yml' '--copy-fields=none' 'cabal.project.empty-line'
+#
+# For more information, see https://github.com/haskell-CI/haskell-ci
+#
+# version: 0.2
+#
+language: c
+dist: xenial
+
+git:
+  submodules: false  # whether to recursively clone submodules
+
+cache:
+  directories:
+    - $HOME/.cabal/packages
+    - $HOME/.cabal/store
+
+before_cache:
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/build-reports.log
+  # remove files that are regenerated by 'cabal update'
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/00-index.*
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/*.json
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.cache
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar.idx
+
+  - rm -rfv $CABALHOME/packages/head.hackage
+
+matrix:
+  include:
+    - compiler: "ghc-8.6.3"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.6.3], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.6.2"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.6.2], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.6.1"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.6.1], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.4.4"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.4.4], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.4.3"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.4.3], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.4.2"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.4.2], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.4.1"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.4.1], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.2.2"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.2.2], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.2.1"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.2.1], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.0.2"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.0.2], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.0.1"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.0.1], sources: [hvr-ghc]}}
+    - compiler: "ghc-7.10.3"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.10.3], sources: [hvr-ghc]}}
+    - compiler: "ghc-7.10.2"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.10.2], sources: [hvr-ghc]}}
+    - compiler: "ghc-7.10.1"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.10.1], sources: [hvr-ghc]}}
+    - compiler: "ghc-7.8.4"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.8.4], sources: [hvr-ghc]}}
+    - compiler: "ghc-7.8.3"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.8.3], sources: [hvr-ghc]}}
+    - compiler: "ghc-7.8.2"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.8.2], sources: [hvr-ghc]}}
+    - compiler: "ghc-7.8.1"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.8.1], sources: [hvr-ghc]}}
+
+before_install:
+  - HC=/opt/ghc/bin/${CC}
+  - HCPKG=${HC/ghc/ghc-pkg}
+  - unset CC
+  - CABAL=/opt/ghc/bin/cabal
+  - CABALHOME=$HOME/.cabal
+  - export PATH="$CABALHOME/bin:$PATH"
+  - ROOTDIR=$(pwd)
+  - HCNUMVER=$(( $(${HC} --numeric-version|sed -E 's/([0-9]+)\.([0-9]+)\.([0-9]+).*/\1 * 10000 + \2 * 100 + \3/') ))
+  - echo $HCNUMVER
+
+install:
+  - ${CABAL} --version
+  - echo "$(${HC} --version) [$(${HC} --print-project-git-commit-id 2> /dev/null || echo '?')]"
+  - TEST=--enable-tests
+  - BENCH=--enable-benchmarks
+  - GHCHEAD=${GHCHEAD-false}
+  - travis_retry ${CABAL} update -v
+  - sed -i.bak 's/^jobs:/-- jobs:/' $CABALHOME/config
+  - rm -fv cabal.project cabal.project.local
+  - grep -Ev -- '^\s*--' $CABALHOME/config | grep -Ev '^\s*$'
+  - rm -f cabal.project
+  - touch cabal.project
+  - "printf 'packages: \"servant\"\\n' >> cabal.project"
+  - "printf 'packages: \"servant-client\"\\n' >> cabal.project"
+  - "printf 'packages: \"servant-docs\"\\n' >> cabal.project"
+  - "printf 'packages: \"servant-server\"\\n' >> cabal.project"
+  - "printf 'write-ghc-environment-files: always\\n' >> cabal.project"
+  - touch cabal.project.local
+  - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | grep -vE -- '^(servant|servant-client|servant-docs|servant-server)$' | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"
+  - cat cabal.project || true
+  - cat cabal.project.local || true
+  - if [ -f "servant/configure.ac" ]; then (cd "servant" && autoreconf -i); fi
+  - if [ -f "servant-client/configure.ac" ]; then (cd "servant-client" && autoreconf -i); fi
+  - if [ -f "servant-docs/configure.ac" ]; then (cd "servant-docs" && autoreconf -i); fi
+  - if [ -f "servant-server/configure.ac" ]; then (cd "servant-server" && autoreconf -i); fi
+  - rm -f cabal.project.freeze
+  - ${CABAL} new-freeze -w ${HC} ${TEST} ${BENCH} --project-file="cabal.project.copy-fields.none" --dry
+  - "cat \"cabal.project.copy-fields.none.freeze\" | sed -E 's/^(constraints: *| *)//' | sed 's/any.//'"
+  - rm  "cabal.project.copy-fields.none.freeze"
+  - ${CABAL} new-build -w ${HC} ${TEST} ${BENCH} --project-file="cabal.project.copy-fields.none" --dep -j2 all
+  - ${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks --project-file="cabal.project.copy-fields.none" --dep -j2 all
+  - rm -rf .ghc.environment.* "servant"/dist "servant-client"/dist "servant-docs"/dist "servant-server"/dist
+  - DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)
+
+# 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:
+  # test that source-distributions can be generated
+  - ${CABAL} new-sdist all
+  - mv dist-newstyle/sdist/*.tar.gz ${DISTDIR}/
+  - cd ${DISTDIR} || false
+  - find . -maxdepth 1 -name '*.tar.gz' -exec tar -xvf '{}' \;
+  - rm -f cabal.project
+  - touch cabal.project
+  - "printf 'packages: \"servant-*/*.cabal\"\\n' >> cabal.project"
+  - "printf 'packages: \"servant-client-*/*.cabal\"\\n' >> cabal.project"
+  - "printf 'packages: \"servant-docs-*/*.cabal\"\\n' >> cabal.project"
+  - "printf 'packages: \"servant-server-*/*.cabal\"\\n' >> cabal.project"
+  - "printf 'write-ghc-environment-files: always\\n' >> cabal.project"
+  - touch cabal.project.local
+  - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | grep -vE -- '^(servant|servant-client|servant-docs|servant-server)$' | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"
+  - cat cabal.project || true
+  - cat cabal.project.local || true
+  # this builds all libraries and executables (without tests/benchmarks)
+  - ${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks all
+
+  # build & run tests, build benchmarks
+  - ${CABAL} new-build -w ${HC} ${TEST} ${BENCH} all
+  - if [ "x$TEST" = "x--enable-tests" ]; then ${CABAL} new-test -w ${HC} ${TEST} ${BENCH} all; fi
+
+  # cabal check
+  - (cd servant-* && ${CABAL} check)
+  - (cd servant-client-* && ${CABAL} check)
+  - (cd servant-docs-* && ${CABAL} check)
+  - (cd servant-server-* && ${CABAL} check)
+
+  # haddock
+  - ${CABAL} new-haddock -w ${HC} ${TEST} ${BENCH} all
+
+  # Build without installed constraints for packages in global-db
+  - rm -f cabal.project.local; ${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks all;
+
+# REGENDATA ["-o","cabal.project.empty-line.travis.yml","--copy-fields=none","cabal.project.empty-line"]
+# EOF
diff --git a/fixtures/cabal.project.copy-fields.some b/fixtures/cabal.project.copy-fields.some
new file mode 100644
--- /dev/null
+++ b/fixtures/cabal.project.copy-fields.some
@@ -0,0 +1,15 @@
+packages:
+  servant/
+  servant-client/
+  servant-docs/
+  servant-server/
+
+package servant
+  tests: False
+
+constraints: foundation >= 0.14
+
+allow-newer:
+  servant-js:servant
+allow-newer:
+  servant-js:servant-foreign
diff --git a/fixtures/cabal.project.copy-fields.some.stderr b/fixtures/cabal.project.copy-fields.some.stderr
new file mode 100644
--- /dev/null
+++ b/fixtures/cabal.project.copy-fields.some.stderr
@@ -0,0 +1,1 @@
+*INFO* Generating Travis-CI config for testing for GHC versions: 7.8.1 7.8.2 7.8.3 7.8.4 7.10.1 7.10.2 7.10.3 8.0.1 8.0.2 8.2.1 8.2.2 8.4.1 8.4.2 8.4.3 8.4.4 8.6.1 8.6.2 8.6.3
diff --git a/fixtures/cabal.project.copy-fields.some.travis.yml b/fixtures/cabal.project.copy-fields.some.travis.yml
new file mode 100644
--- /dev/null
+++ b/fixtures/cabal.project.copy-fields.some.travis.yml
@@ -0,0 +1,160 @@
+# This Travis job script has been generated by a script via
+#
+#   haskell-ci '-o' 'cabal.project.empty-line.travis.yml' '--copy-fields=some' 'cabal.project.empty-line'
+#
+# For more information, see https://github.com/haskell-CI/haskell-ci
+#
+# version: 0.2
+#
+language: c
+dist: xenial
+
+git:
+  submodules: false  # whether to recursively clone submodules
+
+cache:
+  directories:
+    - $HOME/.cabal/packages
+    - $HOME/.cabal/store
+
+before_cache:
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/build-reports.log
+  # remove files that are regenerated by 'cabal update'
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/00-index.*
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/*.json
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.cache
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar.idx
+
+  - rm -rfv $CABALHOME/packages/head.hackage
+
+matrix:
+  include:
+    - compiler: "ghc-8.6.3"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.6.3], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.6.2"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.6.2], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.6.1"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.6.1], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.4.4"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.4.4], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.4.3"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.4.3], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.4.2"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.4.2], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.4.1"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.4.1], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.2.2"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.2.2], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.2.1"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.2.1], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.0.2"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.0.2], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.0.1"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.0.1], sources: [hvr-ghc]}}
+    - compiler: "ghc-7.10.3"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.10.3], sources: [hvr-ghc]}}
+    - compiler: "ghc-7.10.2"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.10.2], sources: [hvr-ghc]}}
+    - compiler: "ghc-7.10.1"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.10.1], sources: [hvr-ghc]}}
+    - compiler: "ghc-7.8.4"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.8.4], sources: [hvr-ghc]}}
+    - compiler: "ghc-7.8.3"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.8.3], sources: [hvr-ghc]}}
+    - compiler: "ghc-7.8.2"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.8.2], sources: [hvr-ghc]}}
+    - compiler: "ghc-7.8.1"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.8.1], sources: [hvr-ghc]}}
+
+before_install:
+  - HC=/opt/ghc/bin/${CC}
+  - HCPKG=${HC/ghc/ghc-pkg}
+  - unset CC
+  - CABAL=/opt/ghc/bin/cabal
+  - CABALHOME=$HOME/.cabal
+  - export PATH="$CABALHOME/bin:$PATH"
+  - ROOTDIR=$(pwd)
+  - HCNUMVER=$(( $(${HC} --numeric-version|sed -E 's/([0-9]+)\.([0-9]+)\.([0-9]+).*/\1 * 10000 + \2 * 100 + \3/') ))
+  - echo $HCNUMVER
+
+install:
+  - ${CABAL} --version
+  - echo "$(${HC} --version) [$(${HC} --print-project-git-commit-id 2> /dev/null || echo '?')]"
+  - TEST=--enable-tests
+  - BENCH=--enable-benchmarks
+  - GHCHEAD=${GHCHEAD-false}
+  - travis_retry ${CABAL} update -v
+  - sed -i.bak 's/^jobs:/-- jobs:/' $CABALHOME/config
+  - rm -fv cabal.project cabal.project.local
+  - grep -Ev -- '^\s*--' $CABALHOME/config | grep -Ev '^\s*$'
+  - rm -f cabal.project
+  - touch cabal.project
+  - "printf 'packages: \"servant\"\\n' >> cabal.project"
+  - "printf 'packages: \"servant-client\"\\n' >> cabal.project"
+  - "printf 'packages: \"servant-docs\"\\n' >> cabal.project"
+  - "printf 'packages: \"servant-server\"\\n' >> cabal.project"
+  - "echo 'constraints: foundation >= 0.14' >> cabal.project"
+  - "echo 'allow-newer: servant-js:servant' >> cabal.project"
+  - "echo 'allow-newer: servant-js:servant-foreign' >> cabal.project"
+  - "printf 'write-ghc-environment-files: always\\n' >> cabal.project"
+  - touch cabal.project.local
+  - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | grep -vE -- '^(servant|servant-client|servant-docs|servant-server)$' | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"
+  - cat cabal.project || true
+  - cat cabal.project.local || true
+  - if [ -f "servant/configure.ac" ]; then (cd "servant" && autoreconf -i); fi
+  - if [ -f "servant-client/configure.ac" ]; then (cd "servant-client" && autoreconf -i); fi
+  - if [ -f "servant-docs/configure.ac" ]; then (cd "servant-docs" && autoreconf -i); fi
+  - if [ -f "servant-server/configure.ac" ]; then (cd "servant-server" && autoreconf -i); fi
+  - rm -f cabal.project.freeze
+  - ${CABAL} new-freeze -w ${HC} ${TEST} ${BENCH} --project-file="cabal.project.copy-fields.some" --dry
+  - "cat \"cabal.project.copy-fields.some.freeze\" | sed -E 's/^(constraints: *| *)//' | sed 's/any.//'"
+  - rm  "cabal.project.copy-fields.some.freeze"
+  - ${CABAL} new-build -w ${HC} ${TEST} ${BENCH} --project-file="cabal.project.copy-fields.some" --dep -j2 all
+  - ${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks --project-file="cabal.project.copy-fields.some" --dep -j2 all
+  - rm -rf .ghc.environment.* "servant"/dist "servant-client"/dist "servant-docs"/dist "servant-server"/dist
+  - DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)
+
+# 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:
+  # test that source-distributions can be generated
+  - ${CABAL} new-sdist all
+  - mv dist-newstyle/sdist/*.tar.gz ${DISTDIR}/
+  - cd ${DISTDIR} || false
+  - find . -maxdepth 1 -name '*.tar.gz' -exec tar -xvf '{}' \;
+  - rm -f cabal.project
+  - touch cabal.project
+  - "printf 'packages: \"servant-*/*.cabal\"\\n' >> cabal.project"
+  - "printf 'packages: \"servant-client-*/*.cabal\"\\n' >> cabal.project"
+  - "printf 'packages: \"servant-docs-*/*.cabal\"\\n' >> cabal.project"
+  - "printf 'packages: \"servant-server-*/*.cabal\"\\n' >> cabal.project"
+  - "echo 'constraints: foundation >= 0.14' >> cabal.project"
+  - "echo 'allow-newer: servant-js:servant' >> cabal.project"
+  - "echo 'allow-newer: servant-js:servant-foreign' >> cabal.project"
+  - "printf 'write-ghc-environment-files: always\\n' >> cabal.project"
+  - touch cabal.project.local
+  - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | grep -vE -- '^(servant|servant-client|servant-docs|servant-server)$' | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"
+  - cat cabal.project || true
+  - cat cabal.project.local || true
+  # this builds all libraries and executables (without tests/benchmarks)
+  - ${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks all
+
+  # build & run tests, build benchmarks
+  - ${CABAL} new-build -w ${HC} ${TEST} ${BENCH} all
+  - if [ "x$TEST" = "x--enable-tests" ]; then ${CABAL} new-test -w ${HC} ${TEST} ${BENCH} all; fi
+
+  # cabal check
+  - (cd servant-* && ${CABAL} check)
+  - (cd servant-client-* && ${CABAL} check)
+  - (cd servant-docs-* && ${CABAL} check)
+  - (cd servant-server-* && ${CABAL} check)
+
+  # haddock
+  - ${CABAL} new-haddock -w ${HC} ${TEST} ${BENCH} all
+
+  # Build without installed constraints for packages in global-db
+  - rm -f cabal.project.local; ${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks all;
+
+# REGENDATA ["-o","cabal.project.empty-line.travis.yml","--copy-fields=some","cabal.project.empty-line"]
+# EOF
diff --git a/fixtures/cabal.project.empty-line.stderr b/fixtures/cabal.project.empty-line.stderr
--- a/fixtures/cabal.project.empty-line.stderr
+++ b/fixtures/cabal.project.empty-line.stderr
@@ -1,1 +1,1 @@
-*INFO* Generating Travis-CI config for testing for GHC versions: head 7.8.1 7.8.2 7.8.3 7.8.4 7.10.1 7.10.2 7.10.3 8.0.1 8.0.2 8.2.1 8.2.2 8.4.1 8.4.2 8.4.3 8.6.1
+*INFO* Generating Travis-CI config for testing for GHC versions: head 7.8.1 7.8.2 7.8.3 7.8.4 7.10.1 7.10.2 7.10.3 8.0.1 8.0.2 8.2.1 8.2.2 8.4.1 8.4.2 8.4.3 8.4.4 8.6.1 8.6.2 8.6.3
diff --git a/fixtures/cabal.project.empty-line.travis.yml b/fixtures/cabal.project.empty-line.travis.yml
--- a/fixtures/cabal.project.empty-line.travis.yml
+++ b/fixtures/cabal.project.empty-line.travis.yml
@@ -1,11 +1,13 @@
 # This Travis job script has been generated by a script via
 #
-#   runghc make_travis_yml_2.hs '--ghc-head' 'cabal.project.empty-line'
+#   haskell-ci '-o' 'cabal.project.empty-line.travis.yml' '--ghc-head' 'cabal.project.empty-line'
 #
 # For more information, see https://github.com/haskell-CI/haskell-ci
 #
+# version: 0.2
+#
 language: c
-sudo: false
+dist: xenial
 
 git:
   submodules: false  # whether to recursively clone submodules
@@ -16,134 +18,124 @@
     - $HOME/.cabal/store
 
 before_cache:
-  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/build-reports.log
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/build-reports.log
   # remove files that are regenerated by 'cabal update'
-  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/00-index.*
-  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/*.json
-  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.cache
-  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.tar
-  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.tar.idx
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/00-index.*
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/*.json
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.cache
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar.idx
 
-  - rm -rfv $HOME/.cabal/packages/head.hackage
+  - rm -rfv $CABALHOME/packages/head.hackage
 
 matrix:
   include:
+    - compiler: "ghc-8.6.3"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.6.3], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.6.2"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.6.2], sources: [hvr-ghc]}}
     - compiler: "ghc-8.6.1"
-      env: GHCHEAD=true
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-head,ghc-8.6.1], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.6.1], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.4.4"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.4.4], sources: [hvr-ghc]}}
     - compiler: "ghc-8.4.3"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-8.4.3], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.4.3], sources: [hvr-ghc]}}
     - compiler: "ghc-8.4.2"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-8.4.2], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.4.2], sources: [hvr-ghc]}}
     - compiler: "ghc-8.4.1"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-8.4.1], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.4.1], sources: [hvr-ghc]}}
     - compiler: "ghc-8.2.2"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-8.2.2], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.2.2], sources: [hvr-ghc]}}
     - compiler: "ghc-8.2.1"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-8.2.1], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.2.1], sources: [hvr-ghc]}}
     - compiler: "ghc-8.0.2"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-8.0.2], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.0.2], sources: [hvr-ghc]}}
     - compiler: "ghc-8.0.1"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-8.0.1], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.0.1], sources: [hvr-ghc]}}
     - compiler: "ghc-7.10.3"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-7.10.3], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.10.3], sources: [hvr-ghc]}}
     - compiler: "ghc-7.10.2"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-7.10.2], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.10.2], sources: [hvr-ghc]}}
     - compiler: "ghc-7.10.1"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-7.10.1], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.10.1], sources: [hvr-ghc]}}
     - compiler: "ghc-7.8.4"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-7.8.4], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.8.4], sources: [hvr-ghc]}}
     - compiler: "ghc-7.8.3"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-7.8.3], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.8.3], sources: [hvr-ghc]}}
     - compiler: "ghc-7.8.2"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-7.8.2], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.8.2], sources: [hvr-ghc]}}
     - compiler: "ghc-7.8.1"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-7.8.1], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.8.1], sources: [hvr-ghc]}}
     - compiler: "ghc-head"
       env: GHCHEAD=true
       addons: {apt: {packages: [ghc-ppa-tools,cabal-install-head,ghc-head], sources: [hvr-ghc]}}
 
   allow_failures:
     - compiler: "ghc-head"
-    - compiler: "ghc-8.6.1"
 
 before_install:
-  - HC=${CC}
+  - HC=/opt/ghc/bin/${CC}
   - HCPKG=${HC/ghc/ghc-pkg}
   - unset CC
+  - CABAL=/opt/ghc/bin/cabal
+  - CABALHOME=$HOME/.cabal
+  - export PATH="$CABALHOME/bin:$PATH"
   - ROOTDIR=$(pwd)
-  - mkdir -p $HOME/.local/bin
-  - "PATH=/opt/ghc/bin:/opt/ghc-ppa-tools/bin:$HOME/local/bin:$PATH"
   - HCNUMVER=$(( $(${HC} --numeric-version|sed -E 's/([0-9]+)\.([0-9]+)\.([0-9]+).*/\1 * 10000 + \2 * 100 + \3/') ))
   - echo $HCNUMVER
 
 install:
-  - cabal --version
+  - ${CABAL} --version
   - echo "$(${HC} --version) [$(${HC} --print-project-git-commit-id 2> /dev/null || echo '?')]"
-  - BENCH=${BENCH---enable-benchmarks}
-  - TEST=${TEST---enable-tests}
-  - HADDOCK=${HADDOCK-true}
-  - UNCONSTRAINED=${UNCONSTRAINED-true}
-  - NOINSTALLEDCONSTRAINTS=${NOINSTALLEDCONSTRAINTS-false}
+  - TEST=--enable-tests
+  - BENCH=--enable-benchmarks
   - GHCHEAD=${GHCHEAD-false}
-  - travis_retry cabal update -v
-  - "sed -i.bak 's/^jobs:/-- jobs:/' ${HOME}/.cabal/config"
+  - travis_retry ${CABAL} update -v
+  - sed -i.bak 's/^jobs:/-- jobs:/' $CABALHOME/config
   - rm -fv cabal.project cabal.project.local
   # Overlay Hackage Package Index for GHC HEAD: https://github.com/hvr/head.hackage
   - |
     if $GHCHEAD; then
-      sed -i 's/-- allow-newer: .*/allow-newer: *:base/' ${HOME}/.cabal/config
-      for pkg in $($HCPKG list --simple-output); do pkg=$(echo $pkg | sed 's/-[^-]*$//'); sed -i "s/allow-newer: /allow-newer: *:$pkg, /" ${HOME}/.cabal/config; done
+      sed -i 's/-- allow-newer: .*/allow-newer: *:base/' $CABALHOME/config
+      for pkg in $($HCPKG list --simple-output); do pkg=$(echo $pkg | sed 's/-[^-]*$//'); sed -i "s/allow-newer: /allow-newer: *:$pkg, /" $CABALHOME/config; done
 
-      echo 'repository head.hackage'                                                        >> ${HOME}/.cabal/config
-      echo '   url: http://head.hackage.haskell.org/'                                       >> ${HOME}/.cabal/config
-      echo '   secure: True'                                                                >> ${HOME}/.cabal/config
-      echo '   root-keys: 07c59cb65787dedfaef5bd5f987ceb5f7e5ebf88b904bbd4c5cbdeb2ff71b740' >> ${HOME}/.cabal/config
-      echo '              2e8555dde16ebd8df076f1a8ef13b8f14c66bad8eafefd7d9e37d0ed711821fb' >> ${HOME}/.cabal/config
-      echo '              8f79fd2389ab2967354407ec852cbe73f2e8635793ac446d09461ffb99527f6e' >> ${HOME}/.cabal/config
-      echo '   key-threshold: 3'                                                            >> ${HOME}/.cabal.config
+      echo 'repository head.hackage'                                                        >> $CABALHOME/config
+      echo '   url: http://head.hackage.haskell.org/'                                       >> $CABALHOME/config
+      echo '   secure: True'                                                                >> $CABALHOME/config
+      echo '   root-keys: 07c59cb65787dedfaef5bd5f987ceb5f7e5ebf88b904bbd4c5cbdeb2ff71b740' >> $CABALHOME/config
+      echo '              2e8555dde16ebd8df076f1a8ef13b8f14c66bad8eafefd7d9e37d0ed711821fb' >> $CABALHOME/config
+      echo '              8f79fd2389ab2967354407ec852cbe73f2e8635793ac446d09461ffb99527f6e' >> $CABALHOME/config
+      echo '   key-threshold: 3'                                                            >> $CABALHOME.config
 
-      grep -Ev -- '^\s*--' ${HOME}/.cabal/config | grep -Ev '^\s*$'
+      grep -Ev -- '^\s*--' $CABALHOME/config | grep -Ev '^\s*$'
 
-      cabal new-update head.hackage -v
+      ${CABAL} new-update head.hackage -v
     fi
-  - grep -Ev -- '^\s*--' ${HOME}/.cabal/config | grep -Ev '^\s*$'
-  - "printf 'packages: \"servant\" \"servant-client\" \"servant-docs\" \"servant-server\"\\n' > cabal.project"
+  - grep -Ev -- '^\s*--' $CABALHOME/config | grep -Ev '^\s*$'
+  - rm -f cabal.project
+  - touch cabal.project
+  - "printf 'packages: \"servant\"\\n' >> cabal.project"
+  - "printf 'packages: \"servant-client\"\\n' >> cabal.project"
+  - "printf 'packages: \"servant-docs\"\\n' >> cabal.project"
+  - "printf 'packages: \"servant-server\"\\n' >> cabal.project"
   - "echo 'constraints: foundatiion >= 0.14' >> cabal.project"
-  - "echo 'allow-newer: servant-js:servant, servant-js:servant-foreign' >> cabal.project"
+  - "echo 'allow-newer: servant-js:servant' >> cabal.project"
+  - "echo 'allow-newer: servant-js:servant-foreign' >> cabal.project"
+  - "printf 'write-ghc-environment-files: always\\n' >> cabal.project"
   - touch cabal.project.local
-  - "if ! $NOINSTALLEDCONSTRAINTS; then for pkg in $($HCPKG list --simple-output); do echo $pkg  | grep -vw -- servant | grep -vw -- servant-client | grep -vw -- servant-docs | grep -vw -- servant-server | sed 's/^/constraints: /' | sed 's/-[^-]*$/ installed/' >> cabal.project.local; done; fi"
+  - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | grep -vE -- '^(servant|servant-client|servant-docs|servant-server)$' | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"
   - cat cabal.project || true
   - cat cabal.project.local || true
-  - if [ -f "servant/configure.ac" ]; then
-      (cd "servant" && autoreconf -i);
-    fi
-  - if [ -f "servant-client/configure.ac" ]; then
-      (cd "servant-client" && autoreconf -i);
-    fi
-  - if [ -f "servant-docs/configure.ac" ]; then
-      (cd "servant-docs" && autoreconf -i);
-    fi
-  - if [ -f "servant-server/configure.ac" ]; then
-      (cd "servant-server" && autoreconf -i);
-    fi
+  - if [ -f "servant/configure.ac" ]; then (cd "servant" && autoreconf -i); fi
+  - if [ -f "servant-client/configure.ac" ]; then (cd "servant-client" && autoreconf -i); fi
+  - if [ -f "servant-docs/configure.ac" ]; then (cd "servant-docs" && autoreconf -i); fi
+  - if [ -f "servant-server/configure.ac" ]; then (cd "servant-server" && autoreconf -i); fi
   - rm -f cabal.project.freeze
-  - cabal new-build -w ${HC} ${TEST} ${BENCH} --project-file="cabal.project.empty-line" --dep -j2 all
-  - cabal new-build -w ${HC} --disable-tests --disable-benchmarks --project-file="cabal.project.empty-line" --dep -j2 all
+  - ${CABAL} new-freeze -w ${HC} ${TEST} ${BENCH} --project-file="cabal.project.empty-line" --dry
+  - "cat \"cabal.project.empty-line.freeze\" | sed -E 's/^(constraints: *| *)//' | sed 's/any.//'"
+  - rm  "cabal.project.empty-line.freeze"
+  - ${CABAL} new-build -w ${HC} ${TEST} ${BENCH} --project-file="cabal.project.empty-line" --dep -j2 all
+  - ${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks --project-file="cabal.project.empty-line" --dep -j2 all
   - rm -rf .ghc.environment.* "servant"/dist "servant-client"/dist "servant-docs"/dist "servant-server"/dist
   - DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)
 
@@ -151,39 +143,42 @@
 # any command which exits with a non-zero exit code causes the build to fail.
 script:
   # test that source-distributions can be generated
-  - (cd "servant" && cabal sdist)
-  - (cd "servant-client" && cabal sdist)
-  - (cd "servant-docs" && cabal sdist)
-  - (cd "servant-server" && cabal sdist)
-  - mv "servant"/dist/servant-*.tar.gz "servant-client"/dist/servant-client-*.tar.gz "servant-docs"/dist/servant-docs-*.tar.gz "servant-server"/dist/servant-server-*.tar.gz ${DISTDIR}/
+  - ${CABAL} new-sdist all
+  - mv dist-newstyle/sdist/*.tar.gz ${DISTDIR}/
   - cd ${DISTDIR} || false
   - find . -maxdepth 1 -name '*.tar.gz' -exec tar -xvf '{}' \;
-  - "printf 'packages: servant-*/*.cabal servant-client-*/*.cabal servant-docs-*/*.cabal servant-server-*/*.cabal\\n' > cabal.project"
+  - rm -f cabal.project
+  - touch cabal.project
+  - "printf 'packages: \"servant-*/*.cabal\"\\n' >> cabal.project"
+  - "printf 'packages: \"servant-client-*/*.cabal\"\\n' >> cabal.project"
+  - "printf 'packages: \"servant-docs-*/*.cabal\"\\n' >> cabal.project"
+  - "printf 'packages: \"servant-server-*/*.cabal\"\\n' >> cabal.project"
   - "echo 'constraints: foundatiion >= 0.14' >> cabal.project"
-  - "echo 'allow-newer: servant-js:servant, servant-js:servant-foreign' >> cabal.project"
+  - "echo 'allow-newer: servant-js:servant' >> cabal.project"
+  - "echo 'allow-newer: servant-js:servant-foreign' >> cabal.project"
+  - "printf 'write-ghc-environment-files: always\\n' >> cabal.project"
   - touch cabal.project.local
-  - "if ! $NOINSTALLEDCONSTRAINTS; then for pkg in $($HCPKG list --simple-output); do echo $pkg  | grep -vw -- servant | grep -vw -- servant-client | grep -vw -- servant-docs | grep -vw -- servant-server | sed 's/^/constraints: /' | sed 's/-[^-]*$/ installed/' >> cabal.project.local; done; fi"
+  - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | grep -vE -- '^(servant|servant-client|servant-docs|servant-server)$' | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"
   - cat cabal.project || true
   - cat cabal.project.local || true
   # this builds all libraries and executables (without tests/benchmarks)
-  - cabal new-build -w ${HC} --disable-tests --disable-benchmarks all
+  - ${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks all
 
   # build & run tests, build benchmarks
-  - cabal new-build -w ${HC} ${TEST} ${BENCH} all
-  - if [ "x$TEST" = "x--enable-tests" ]; then cabal new-test -w ${HC} ${TEST} ${BENCH} all; fi
+  - ${CABAL} new-build -w ${HC} ${TEST} ${BENCH} all
+  - if [ "x$TEST" = "x--enable-tests" ]; then ${CABAL} new-test -w ${HC} ${TEST} ${BENCH} all; fi
 
   # cabal check
-  - (cd servant-* && cabal check)
-  - (cd servant-client-* && cabal check)
-  - (cd servant-docs-* && cabal check)
-  - (cd servant-server-* && cabal check)
+  - (cd servant-* && ${CABAL} check)
+  - (cd servant-client-* && ${CABAL} check)
+  - (cd servant-docs-* && ${CABAL} check)
+  - (cd servant-server-* && ${CABAL} check)
 
   # haddock
-  - rm -rf ./dist-newstyle
-  - if $HADDOCK; then cabal new-haddock -w ${HC} ${TEST} ${BENCH} all; else echo "Skipping haddock generation";fi
+  - ${CABAL} new-haddock -w ${HC} ${TEST} ${BENCH} all
 
   # Build without installed constraints for packages in global-db
-  - if $UNCONSTRAINED; then rm -f cabal.project.local; echo cabal new-build -w ${HC} --disable-tests --disable-benchmarks all; else echo "Not building without installed constraints"; fi
+  - rm -f cabal.project.local; ${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks all;
 
-# REGENDATA ["--ghc-head","cabal.project.empty-line"]
+# REGENDATA ["-o","cabal.project.empty-line.travis.yml","--ghc-head","cabal.project.empty-line"]
 # EOF
diff --git a/fixtures/cabal.project.messy.stderr b/fixtures/cabal.project.messy.stderr
--- a/fixtures/cabal.project.messy.stderr
+++ b/fixtures/cabal.project.messy.stderr
@@ -1,1 +1,1 @@
-*INFO* Generating Travis-CI config for testing for GHC versions: head 7.8.1 7.8.2 7.8.3 7.8.4 7.10.1 7.10.2 7.10.3 8.0.1 8.0.2 8.2.1 8.2.2 8.4.1 8.4.2 8.4.3 8.6.1
+*INFO* Generating Travis-CI config for testing for GHC versions: head 7.8.1 7.8.2 7.8.3 7.8.4 7.10.1 7.10.2 7.10.3 8.0.1 8.0.2 8.2.1 8.2.2 8.4.1 8.4.2 8.4.3 8.4.4 8.6.1 8.6.2 8.6.3
diff --git a/fixtures/cabal.project.messy.travis.yml b/fixtures/cabal.project.messy.travis.yml
--- a/fixtures/cabal.project.messy.travis.yml
+++ b/fixtures/cabal.project.messy.travis.yml
@@ -1,11 +1,13 @@
 # This Travis job script has been generated by a script via
 #
-#   runghc make_travis_yml_2.hs '--ghc-head' 'cabal.project.messy'
+#   haskell-ci '-o' 'cabal.project.messy.travis.yml' '--ghc-head' '--apt=fftw3-dev' '--installed=-all +deepseq' 'cabal.project.messy'
 #
 # For more information, see https://github.com/haskell-CI/haskell-ci
 #
+# version: 0.2
+#
 language: c
-sudo: false
+dist: xenial
 
 git:
   submodules: false  # whether to recursively clone submodules
@@ -16,132 +18,121 @@
     - $HOME/.cabal/store
 
 before_cache:
-  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/build-reports.log
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/build-reports.log
   # remove files that are regenerated by 'cabal update'
-  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/00-index.*
-  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/*.json
-  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.cache
-  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.tar
-  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.tar.idx
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/00-index.*
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/*.json
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.cache
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar.idx
 
-  - rm -rfv $HOME/.cabal/packages/head.hackage
+  - rm -rfv $CABALHOME/packages/head.hackage
 
 matrix:
   include:
+    - compiler: "ghc-8.6.3"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.6.3,fftw3-dev], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.6.2"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.6.2,fftw3-dev], sources: [hvr-ghc]}}
     - compiler: "ghc-8.6.1"
-      env: GHCHEAD=true
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-head,ghc-8.6.1], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.6.1,fftw3-dev], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.4.4"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.4.4,fftw3-dev], sources: [hvr-ghc]}}
     - compiler: "ghc-8.4.3"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-8.4.3], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.4.3,fftw3-dev], sources: [hvr-ghc]}}
     - compiler: "ghc-8.4.2"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-8.4.2], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.4.2,fftw3-dev], sources: [hvr-ghc]}}
     - compiler: "ghc-8.4.1"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-8.4.1], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.4.1,fftw3-dev], sources: [hvr-ghc]}}
     - compiler: "ghc-8.2.2"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-8.2.2], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.2.2,fftw3-dev], sources: [hvr-ghc]}}
     - compiler: "ghc-8.2.1"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-8.2.1], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.2.1,fftw3-dev], sources: [hvr-ghc]}}
     - compiler: "ghc-8.0.2"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-8.0.2], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.0.2,fftw3-dev], sources: [hvr-ghc]}}
     - compiler: "ghc-8.0.1"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-8.0.1], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.0.1,fftw3-dev], sources: [hvr-ghc]}}
     - compiler: "ghc-7.10.3"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-7.10.3], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.10.3,fftw3-dev], sources: [hvr-ghc]}}
     - compiler: "ghc-7.10.2"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-7.10.2], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.10.2,fftw3-dev], sources: [hvr-ghc]}}
     - compiler: "ghc-7.10.1"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-7.10.1], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.10.1,fftw3-dev], sources: [hvr-ghc]}}
     - compiler: "ghc-7.8.4"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-7.8.4], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.8.4,fftw3-dev], sources: [hvr-ghc]}}
     - compiler: "ghc-7.8.3"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-7.8.3], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.8.3,fftw3-dev], sources: [hvr-ghc]}}
     - compiler: "ghc-7.8.2"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-7.8.2], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.8.2,fftw3-dev], sources: [hvr-ghc]}}
     - compiler: "ghc-7.8.1"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-7.8.1], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.8.1,fftw3-dev], sources: [hvr-ghc]}}
     - compiler: "ghc-head"
       env: GHCHEAD=true
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-head,ghc-head], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-head,ghc-head,fftw3-dev], sources: [hvr-ghc]}}
 
   allow_failures:
     - compiler: "ghc-head"
-    - compiler: "ghc-8.6.1"
 
 before_install:
-  - HC=${CC}
+  - HC=/opt/ghc/bin/${CC}
   - HCPKG=${HC/ghc/ghc-pkg}
   - unset CC
+  - CABAL=/opt/ghc/bin/cabal
+  - CABALHOME=$HOME/.cabal
+  - export PATH="$CABALHOME/bin:$PATH"
   - ROOTDIR=$(pwd)
-  - mkdir -p $HOME/.local/bin
-  - "PATH=/opt/ghc/bin:/opt/ghc-ppa-tools/bin:$HOME/local/bin:$PATH"
   - HCNUMVER=$(( $(${HC} --numeric-version|sed -E 's/([0-9]+)\.([0-9]+)\.([0-9]+).*/\1 * 10000 + \2 * 100 + \3/') ))
   - echo $HCNUMVER
 
 install:
-  - cabal --version
+  - ${CABAL} --version
   - echo "$(${HC} --version) [$(${HC} --print-project-git-commit-id 2> /dev/null || echo '?')]"
-  - BENCH=${BENCH---enable-benchmarks}
-  - TEST=${TEST---enable-tests}
-  - HADDOCK=${HADDOCK-true}
-  - UNCONSTRAINED=${UNCONSTRAINED-true}
-  - NOINSTALLEDCONSTRAINTS=${NOINSTALLEDCONSTRAINTS-false}
+  - TEST=--enable-tests
+  - BENCH=--enable-benchmarks
   - GHCHEAD=${GHCHEAD-false}
-  - travis_retry cabal update -v
-  - "sed -i.bak 's/^jobs:/-- jobs:/' ${HOME}/.cabal/config"
+  - travis_retry ${CABAL} update -v
+  - sed -i.bak 's/^jobs:/-- jobs:/' $CABALHOME/config
   - rm -fv cabal.project cabal.project.local
   # Overlay Hackage Package Index for GHC HEAD: https://github.com/hvr/head.hackage
   - |
     if $GHCHEAD; then
-      sed -i 's/-- allow-newer: .*/allow-newer: *:base/' ${HOME}/.cabal/config
-      for pkg in $($HCPKG list --simple-output); do pkg=$(echo $pkg | sed 's/-[^-]*$//'); sed -i "s/allow-newer: /allow-newer: *:$pkg, /" ${HOME}/.cabal/config; done
+      sed -i 's/-- allow-newer: .*/allow-newer: *:base/' $CABALHOME/config
+      for pkg in $($HCPKG list --simple-output); do pkg=$(echo $pkg | sed 's/-[^-]*$//'); sed -i "s/allow-newer: /allow-newer: *:$pkg, /" $CABALHOME/config; done
 
-      echo 'repository head.hackage'                                                        >> ${HOME}/.cabal/config
-      echo '   url: http://head.hackage.haskell.org/'                                       >> ${HOME}/.cabal/config
-      echo '   secure: True'                                                                >> ${HOME}/.cabal/config
-      echo '   root-keys: 07c59cb65787dedfaef5bd5f987ceb5f7e5ebf88b904bbd4c5cbdeb2ff71b740' >> ${HOME}/.cabal/config
-      echo '              2e8555dde16ebd8df076f1a8ef13b8f14c66bad8eafefd7d9e37d0ed711821fb' >> ${HOME}/.cabal/config
-      echo '              8f79fd2389ab2967354407ec852cbe73f2e8635793ac446d09461ffb99527f6e' >> ${HOME}/.cabal/config
-      echo '   key-threshold: 3'                                                            >> ${HOME}/.cabal.config
+      echo 'repository head.hackage'                                                        >> $CABALHOME/config
+      echo '   url: http://head.hackage.haskell.org/'                                       >> $CABALHOME/config
+      echo '   secure: True'                                                                >> $CABALHOME/config
+      echo '   root-keys: 07c59cb65787dedfaef5bd5f987ceb5f7e5ebf88b904bbd4c5cbdeb2ff71b740' >> $CABALHOME/config
+      echo '              2e8555dde16ebd8df076f1a8ef13b8f14c66bad8eafefd7d9e37d0ed711821fb' >> $CABALHOME/config
+      echo '              8f79fd2389ab2967354407ec852cbe73f2e8635793ac446d09461ffb99527f6e' >> $CABALHOME/config
+      echo '   key-threshold: 3'                                                            >> $CABALHOME.config
 
-      grep -Ev -- '^\s*--' ${HOME}/.cabal/config | grep -Ev '^\s*$'
+      grep -Ev -- '^\s*--' $CABALHOME/config | grep -Ev '^\s*$'
 
-      cabal new-update head.hackage -v
+      ${CABAL} new-update head.hackage -v
     fi
-  - grep -Ev -- '^\s*--' ${HOME}/.cabal/config | grep -Ev '^\s*$'
-  - "printf 'packages: \"servant\" \"servant-client\" \"servant-docs\" \"servant-server\"\\n' > cabal.project"
+  - grep -Ev -- '^\s*--' $CABALHOME/config | grep -Ev '^\s*$'
+  - rm -f cabal.project
+  - touch cabal.project
+  - "printf 'packages: \"servant\"\\n' >> cabal.project"
+  - "printf 'packages: \"servant-client\"\\n' >> cabal.project"
+  - "printf 'packages: \"servant-docs\"\\n' >> cabal.project"
+  - "printf 'packages: \"servant-server\"\\n' >> cabal.project"
+  - "printf 'write-ghc-environment-files: always\\n' >> cabal.project"
   - touch cabal.project.local
-  - "if ! $NOINSTALLEDCONSTRAINTS; then for pkg in $($HCPKG list --simple-output); do echo $pkg  | grep -vw -- servant | grep -vw -- servant-client | grep -vw -- servant-docs | grep -vw -- servant-server | sed 's/^/constraints: /' | sed 's/-[^-]*$/ installed/' >> cabal.project.local; done; fi"
+  - "for pkg in deepseq; do echo \"constraints: $pkg installed\" >> cabal.project >> cabal.project.local; done"
   - cat cabal.project || true
   - cat cabal.project.local || true
-  - if [ -f "servant/configure.ac" ]; then
-      (cd "servant" && autoreconf -i);
-    fi
-  - if [ -f "servant-client/configure.ac" ]; then
-      (cd "servant-client" && autoreconf -i);
-    fi
-  - if [ -f "servant-docs/configure.ac" ]; then
-      (cd "servant-docs" && autoreconf -i);
-    fi
-  - if [ -f "servant-server/configure.ac" ]; then
-      (cd "servant-server" && autoreconf -i);
-    fi
+  - if [ -f "servant/configure.ac" ]; then (cd "servant" && autoreconf -i); fi
+  - if [ -f "servant-client/configure.ac" ]; then (cd "servant-client" && autoreconf -i); fi
+  - if [ -f "servant-docs/configure.ac" ]; then (cd "servant-docs" && autoreconf -i); fi
+  - if [ -f "servant-server/configure.ac" ]; then (cd "servant-server" && autoreconf -i); fi
   - rm -f cabal.project.freeze
-  - cabal new-build -w ${HC} ${TEST} ${BENCH} --project-file="cabal.project.messy" --dep -j2 all
-  - cabal new-build -w ${HC} --disable-tests --disable-benchmarks --project-file="cabal.project.messy" --dep -j2 all
+  - ${CABAL} new-freeze -w ${HC} ${TEST} ${BENCH} --project-file="cabal.project.messy" --dry
+  - "cat \"cabal.project.messy.freeze\" | sed -E 's/^(constraints: *| *)//' | sed 's/any.//'"
+  - rm  "cabal.project.messy.freeze"
+  - ${CABAL} new-build -w ${HC} ${TEST} ${BENCH} --project-file="cabal.project.messy" --dep -j2 all
+  - ${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks --project-file="cabal.project.messy" --dep -j2 all
   - rm -rf .ghc.environment.* "servant"/dist "servant-client"/dist "servant-docs"/dist "servant-server"/dist
   - DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)
 
@@ -149,37 +140,39 @@
 # any command which exits with a non-zero exit code causes the build to fail.
 script:
   # test that source-distributions can be generated
-  - (cd "servant" && cabal sdist)
-  - (cd "servant-client" && cabal sdist)
-  - (cd "servant-docs" && cabal sdist)
-  - (cd "servant-server" && cabal sdist)
-  - mv "servant"/dist/servant-*.tar.gz "servant-client"/dist/servant-client-*.tar.gz "servant-docs"/dist/servant-docs-*.tar.gz "servant-server"/dist/servant-server-*.tar.gz ${DISTDIR}/
+  - ${CABAL} new-sdist all
+  - mv dist-newstyle/sdist/*.tar.gz ${DISTDIR}/
   - cd ${DISTDIR} || false
   - find . -maxdepth 1 -name '*.tar.gz' -exec tar -xvf '{}' \;
-  - "printf 'packages: servant-*/*.cabal servant-client-*/*.cabal servant-docs-*/*.cabal servant-server-*/*.cabal\\n' > cabal.project"
+  - rm -f cabal.project
+  - touch cabal.project
+  - "printf 'packages: \"servant-*/*.cabal\"\\n' >> cabal.project"
+  - "printf 'packages: \"servant-client-*/*.cabal\"\\n' >> cabal.project"
+  - "printf 'packages: \"servant-docs-*/*.cabal\"\\n' >> cabal.project"
+  - "printf 'packages: \"servant-server-*/*.cabal\"\\n' >> cabal.project"
+  - "printf 'write-ghc-environment-files: always\\n' >> cabal.project"
   - touch cabal.project.local
-  - "if ! $NOINSTALLEDCONSTRAINTS; then for pkg in $($HCPKG list --simple-output); do echo $pkg  | grep -vw -- servant | grep -vw -- servant-client | grep -vw -- servant-docs | grep -vw -- servant-server | sed 's/^/constraints: /' | sed 's/-[^-]*$/ installed/' >> cabal.project.local; done; fi"
+  - "for pkg in deepseq; do echo \"constraints: $pkg installed\" >> cabal.project >> cabal.project.local; done"
   - cat cabal.project || true
   - cat cabal.project.local || true
   # this builds all libraries and executables (without tests/benchmarks)
-  - cabal new-build -w ${HC} --disable-tests --disable-benchmarks all
+  - ${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks all
 
   # build & run tests, build benchmarks
-  - cabal new-build -w ${HC} ${TEST} ${BENCH} all
-  - if [ "x$TEST" = "x--enable-tests" ]; then cabal new-test -w ${HC} ${TEST} ${BENCH} all; fi
+  - ${CABAL} new-build -w ${HC} ${TEST} ${BENCH} all
+  - if [ "x$TEST" = "x--enable-tests" ]; then ${CABAL} new-test -w ${HC} ${TEST} ${BENCH} all; fi
 
   # cabal check
-  - (cd servant-* && cabal check)
-  - (cd servant-client-* && cabal check)
-  - (cd servant-docs-* && cabal check)
-  - (cd servant-server-* && cabal check)
+  - (cd servant-* && ${CABAL} check)
+  - (cd servant-client-* && ${CABAL} check)
+  - (cd servant-docs-* && ${CABAL} check)
+  - (cd servant-server-* && ${CABAL} check)
 
   # haddock
-  - rm -rf ./dist-newstyle
-  - if $HADDOCK; then cabal new-haddock -w ${HC} ${TEST} ${BENCH} all; else echo "Skipping haddock generation";fi
+  - ${CABAL} new-haddock -w ${HC} ${TEST} ${BENCH} all
 
   # Build without installed constraints for packages in global-db
-  - if $UNCONSTRAINED; then rm -f cabal.project.local; echo cabal new-build -w ${HC} --disable-tests --disable-benchmarks all; else echo "Not building without installed constraints"; fi
+  - rm -f cabal.project.local; ${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks all;
 
-# REGENDATA ["--ghc-head","cabal.project.messy"]
+# REGENDATA ["-o","cabal.project.messy.travis.yml","--ghc-head","--apt=fftw3-dev","--installed=-all +deepseq","cabal.project.messy"]
 # EOF
diff --git a/fixtures/haskell-ci.cabal b/fixtures/haskell-ci.cabal
--- a/fixtures/haskell-ci.cabal
+++ b/fixtures/haskell-ci.cabal
@@ -1,16 +1,15 @@
-cabal-version:       1.18
-name:                haskell-ci
-version:             0.1.0.0
-
-synopsis:            Cabal package script generator for Travis-CI
+cabal-version:      2.2
+name:               haskell-ci
+version:            0.2
+synopsis:           Cabal package script generator for Travis-CI
 description:
-  Script generator (@make-travis-yml@) for [Travis-CI](https://travis-ci.org/) for continuous-integration testing of Haskell Cabal packages.
+  Script generator (@haskell-ci@) for [Travis-CI](https://travis-ci.org/) for continuous-integration testing of Haskell Cabal packages.
   .
   Included features (not limited to):
   .
   * Multiple GHC support
   * Dependency caching
-  * cabal.project support (see [Nix-style local builds documentation](http://cabal.readthedocs.io/en/latest/nix-local-build-overview.html)
+  * cabal.project support (see [Nix-style local builds documentation](https://cabal.readthedocs.io/en/latest/nix-local-build-overview.html))
   * Runs tests and builds benchmarks
   * Generates Haddocks
   * macOS (OSX) support
@@ -18,104 +17,157 @@
   .
   === Quick Start Guide
   .
-  Add a @tested-with@ line to your @.cabal@ file (e.g. @tested-with: GHC == 8.0.2 || == 8.2.2@) and then run @make-travis-yml yourpackage.cabal -o .travis.yml@ to generate the Travis-CI job script.
+  Add a @tested-with@ line to your @.cabal@ file (e.g. @tested-with: GHC == 8.0.2 || == 8.2.2@) and then run @haskell-ci yourpackage.cabal -o .travis.yml@ to generate the Travis-CI job script.
   .
-  See @make-travis-yml --help@ for more information.
+  See @haskell-ci --help@ for more information.
 
-  
-homepage:            http://haskell-ci.rtfd.org/
-bug-reports:         https://github.com/haskell-CI/haskell-ci/issues
-license:             BSD3
-license-file:        LICENSE
-author:              Herbert Valerio Riedel, Oleg Grenrus
-maintainer:          hvr@gnu.org
-category:            Development
-build-type:          Simple
-tested-with:
-  GHC == 7.8.4,
-  GHC == 7.10.3,
-  GHC == 8.0.2,
-  GHC == 8.2.2,
-  GHC == 8.4.3,
-  GHC == 8.6.1
+homepage:           https://haskell-ci.rtfd.org/
+bug-reports:        https://github.com/haskell-CI/haskell-ci/issues
+license:            BSD-3-Clause
+license-file:       LICENSE
+author:             Herbert Valerio Riedel, Oleg Grenrus
+maintainer:         hvr@gnu.org
+category:           Development
+build-type:         Simple
+tested-with:        GHC ==8.6.3 || ==8.4.4 || ==8.2.2 || ==8.0.2
 extra-source-files:
   fixtures/cabal.haskell-ci
-  fixtures/haskell-ci.cabal
-  fixtures/haskell-ci.cabal.stderr
-  fixtures/haskell-ci.cabal.travis.yml
-  fixtures/cabal.project.empty-line
-  fixtures/cabal.project.empty-line.stderr
-  fixtures/cabal.project.empty-line.travis.yml
   fixtures/cabal.project.fail-versions
   fixtures/cabal.project.fail-versions.stderr
+  fixtures/haskell-ci.cabal
+  fixtures/cabal.project.empty-line
   fixtures/cabal.project.messy
+  fixtures/cabal.project.copy-fields.all
+  fixtures/cabal.project.copy-fields.some
+  fixtures/cabal.project.copy-fields.none
+  fixtures/haskell-ci.cabal.stderr
+  fixtures/cabal.project.empty-line.stderr
   fixtures/cabal.project.messy.stderr
+  fixtures/cabal.project.copy-fields.all.stderr
+  fixtures/cabal.project.copy-fields.some.stderr
+  fixtures/cabal.project.copy-fields.none.stderr
+  fixtures/haskell-ci.cabal.travis.yml
+  fixtures/cabal.project.empty-line.travis.yml
   fixtures/cabal.project.messy.travis.yml
-  fixtures/doc/tutorial/tutorial.cabal
+  fixtures/cabal.project.copy-fields.all.travis.yml
+  fixtures/cabal.project.copy-fields.some.travis.yml
+  fixtures/cabal.project.copy-fields.none.travis.yml
   fixtures/servant-client-core/*.cabal
   fixtures/servant-client/*.cabal
   fixtures/servant-docs/*.cabal
   fixtures/servant-foreign/*.cabal
   fixtures/servant-server/*.cabal
   fixtures/servant/*.cabal
+  fixtures/doc/tutorial/tutorial.cabal
 
 source-repository head
-  type: git
+  type:     git
   location: https://github.com/haskell-CI/haskell-ci.git
 
 flag ShellCheck
   default: True
-  manual: True
+  manual:  True
 
-library
-  exposed-modules:     MakeTravisYml
-  ghc-options:         -Wall
-  if impl(ghc >= 8.0)
-    ghc-options:
-      -Wcompat
-      -Wnoncanonical-monad-instances
-      -Wnoncanonical-monadfail-instances
-  hs-source-dirs:      src
-  other-extensions:    CPP, ViewPatterns, NamedFieldPuns
-  build-depends:       base         >= 4.7  && < 4.12
-                     , Cabal        == 1.18.*
-                                 || == 1.20.*
-                                 || == 1.22.*
-                                 || == 1.24.*
-                                 || == 2.0.*
-                                 || == 2.2.*
-                     , containers   >= 0.4  && < 0.6
-                     , directory    >= 1.1  && < 1.4
-                     , filepath     >= 1.2  && < 1.5
-                     , transformers >= 0.3  && < 0.6
-                     , deepseq      >= 1.3  && < 1.5
-  default-language:    Haskell2010
+library haskell-ci-internal
+  exposed-modules:
+    HaskellCI
+    HaskellCI.Cli
+    HaskellCI.Config
+    HaskellCI.Config.ConstraintSet
+    HaskellCI.Config.CopyFields
+    HaskellCI.Config.Doctest
+    HaskellCI.Config.Dump
+    HaskellCI.Config.Folds
+    HaskellCI.Config.HLint
+    HaskellCI.Config.Installed
+    HaskellCI.Config.Jobs
+    HaskellCI.Extras
+    HaskellCI.GHC
+    HaskellCI.Glob
+    HaskellCI.MakeTravisOutput
+    HaskellCI.Newtypes
+    HaskellCI.Optimization
+    HaskellCI.OptionsGrammar
+    HaskellCI.OptparseGrammar
+    HaskellCI.Package
+    HaskellCI.ParsecError
+    HaskellCI.ParsecUtils
+    HaskellCI.Project
+    HaskellCI.TestedWith
+    HaskellCI.Version
 
+  -- vendored from Cabal development version
+  other-modules:      Distribution.Fields.Pretty
+  ghc-options:
+    -Wall -Wcompat -Wnoncanonical-monad-instances
+    -Wnoncanonical-monadfail-instances
+
+  hs-source-dirs:     src
+  default-extensions:
+    DeriveFoldable
+    DeriveFunctor
+    DeriveGeneric
+    DeriveTraversable
+
+  other-extensions:
+    CPP
+    NamedFieldPuns
+    OverloadedLabels
+    ViewPatterns
+
+  build-depends:
+    , base          >=4.9      && <4.13
+    , bytestring    ^>=0.10.8.1
+    , Cabal         ^>=2.4
+    , containers    ^>=0.5.7.1 || ^>=0.6.0.1
+    , deepseq       ^>=1.4.2.0
+    , directory     ^>=1.3.0.0
+    , filepath      ^>=1.4.1.1
+    , parsec        ^>=3.1.13.0
+    , pretty        ^>=1.1.3.3
+    , text          ^>=1.2.3.0
+    , transformers  ^>=0.5.2.0
+
+  -- other dependencies
+  build-depends:
+    , base-compat           ^>=0.10.5
+    , generic-lens          ^>=1.1.0.0
+    , HsYAML                ^>=0.1.1.3
+    , microlens             ^>=0.4.10
+    , optparse-applicative  ^>=0.14.3.0
+
+  default-language:   Haskell2010
+
   -- ShellCheck. Would need newer transformers for older GHC
-  if flag(ShellCheck) && impl(ghc >= 7.10 && <8.5)
-    build-depends: ShellCheck == 0.5.0
+  if (flag(shellcheck) && impl(ghc >=7.10 && <8.7))
+    build-depends: ShellCheck ==0.6.0
 
-executable make-travis-yml
-  main-is:             Main.hs
-  ghc-options:         -Wall
-  build-depends:       base, haskell-ci
-  default-language:    Haskell2010
+executable haskell-ci
+  main-is:          Main.hs
+  ghc-options:      -Wall
+  hs-source-dirs:   cli
+  build-depends:
+    , base
+    , haskell-ci-internal
 
+  default-language: Haskell2010
+
 test-suite golden
-  type:                exitcode-stdio-1.0
-  main-is:             Tests.hs
-  hs-source-dirs:      test
-  build-depends:       haskell-ci
-                       -- inherited constraints via lib:haskell-ci
-                     , base
-                     , bytestring
-                     , directory
-                     , filepath
-                     , transformers
-                       -- dependencies needing explicit constraints
-                     , tasty         >= 1.0      && < 1.2
-                     , tasty-golden  >= 2.3.1.1  && < 2.4
-                     , ansi-terminal >= 0.8.0.2  && <0.9
-                     , Diff          >= 0.3.4    && < 0.4
+  type:             exitcode-stdio-1.0
+  main-is:          Tests.hs
+  hs-source-dirs:   test
+  build-depends:
+    , ansi-terminal        ^>=0.8.0.2
+    , base
+    , bytestring
+    , Diff                 ^>=0.3.4
+    , directory
+    , filepath
+    , haskell-ci-internal
+    , tasty                >=1.0     && <1.2
+    , tasty-golden         ^>=2.3.1.1
+    , transformers
 
-  default-language:    Haskell2010
+  -- inherited constraints via lib:haskell-ci
+  -- dependencies needing explicit constraints
+  default-language: Haskell2010
diff --git a/fixtures/haskell-ci.cabal.stderr b/fixtures/haskell-ci.cabal.stderr
--- a/fixtures/haskell-ci.cabal.stderr
+++ b/fixtures/haskell-ci.cabal.stderr
@@ -1,1 +1,2 @@
-*INFO* Generating Travis-CI config for testing for GHC versions: 7.8.4 7.10.3 8.0.2 8.2.2 8.4.3 8.6.1
+*INFO* Generating Travis-CI config for testing for GHC versions: head 8.0.2 8.2.2 8.4.4 8.6.3
+*INFO* Also OSX jobs for: 8.4.4
diff --git a/fixtures/haskell-ci.cabal.travis.yml b/fixtures/haskell-ci.cabal.travis.yml
--- a/fixtures/haskell-ci.cabal.travis.yml
+++ b/fixtures/haskell-ci.cabal.travis.yml
@@ -1,11 +1,13 @@
 # This Travis job script has been generated by a script via
 #
-#   runghc make_travis_yml_2.hs '-o' '.travis.yml' '--config=cabal.haskell-ci' 'haskell-ci.cabal'
+#   haskell-ci '--output=.travis.yml' '--config=cabal.haskell-ci' 'haskell-ci.cabal'
 #
 # For more information, see https://github.com/haskell-CI/haskell-ci
 #
+# version: 0.2
+#
 language: c
-sudo: false
+dist: xenial
 
 git:
   submodules: false  # whether to recursively clone submodules
@@ -18,100 +20,104 @@
   directories:
     - $HOME/.cabal/packages
     - $HOME/.cabal/store
+    - $HOME/.ghc-install
 
 before_cache:
-  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/build-reports.log
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/build-reports.log
   # remove files that are regenerated by 'cabal update'
-  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/00-index.*
-  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/*.json
-  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.cache
-  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.tar
-  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.tar.idx
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/00-index.*
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/*.json
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.cache
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar
+  - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar.idx
 
-  - rm -rfv $HOME/.cabal/packages/head.hackage
+  - rm -rfv $CABALHOME/packages/head.hackage
 
 matrix:
   include:
-    - compiler: "ghc-8.6.1"
-      env: GHCHEAD=true
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-head,ghc-8.6.1], sources: [hvr-ghc]}}
-    - compiler: "ghc-8.4.3"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-8.4.3], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.6.3"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.6.3], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.4.4"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.4.4], sources: [hvr-ghc]}}
     - compiler: "ghc-8.2.2"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-8.2.2], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.2.2], sources: [hvr-ghc]}}
     - compiler: "ghc-8.0.2"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-8.0.2], sources: [hvr-ghc]}}
-    - compiler: "ghc-7.10.3"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-7.10.3], sources: [hvr-ghc]}}
-    - compiler: "ghc-7.8.4"
-    # env: TEST=--disable-tests BENCH=--disable-benchmarks
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-7.8.4], sources: [hvr-ghc]}}
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.0.2], sources: [hvr-ghc]}}
+    - compiler: "ghc-head"
+      env: GHCHEAD=true
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-head,ghc-head], sources: [hvr-ghc]}}
+    - compiler: "ghc-8.4.4"
+      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.4.4], sources: [hvr-ghc]}}
+      os: osx
 
   allow_failures:
-    - compiler: "ghc-8.6.1"
+    - compiler: "ghc-head"
 
 before_install:
-  - HC=${CC}
+  - HC=/opt/ghc/bin/${CC}
   - HCPKG=${HC/ghc/ghc-pkg}
   - unset CC
+  - CABAL=/opt/ghc/bin/cabal
+  - CABALHOME=$HOME/.cabal
+  - export PATH="$CABALHOME/bin:$PATH"
   - ROOTDIR=$(pwd)
-  - mkdir -p $HOME/.local/bin
-  - "PATH=/opt/ghc/bin:/opt/ghc-ppa-tools/bin:$HOME/local/bin:$PATH"
+  - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew update; brew upgrade python@3; curl https://haskell.futurice.com/haskell-on-macos.py | python3 - --make-dirs --install-dir=$HOME/.ghc-install --cabal-alias=head install cabal-install-head ${TRAVIS_COMPILER}; fi
+  - if [ "$TRAVIS_OS_NAME" = "osx" ]; then HC=$HOME/.ghc-install/ghc/bin/$TRAVIS_COMPILER; HCPKG=${HC/ghc/ghc-pkg}; CABAL=$HOME/.ghc-install/ghc/bin/cabal; fi
   - HCNUMVER=$(( $(${HC} --numeric-version|sed -E 's/([0-9]+)\.([0-9]+)\.([0-9]+).*/\1 * 10000 + \2 * 100 + \3/') ))
   - echo $HCNUMVER
 
 install:
-  - cabal --version
+  - ${CABAL} --version
   - echo "$(${HC} --version) [$(${HC} --print-project-git-commit-id 2> /dev/null || echo '?')]"
-  - BENCH=${BENCH---enable-benchmarks}
-  - TEST=${TEST---enable-tests}
-  - HADDOCK=${HADDOCK-true}
-  - UNCONSTRAINED=${UNCONSTRAINED-true}
-  - NOINSTALLEDCONSTRAINTS=${NOINSTALLEDCONSTRAINTS-false}
+  - TEST=--enable-tests
+  - BENCH=--enable-benchmarks
   - GHCHEAD=${GHCHEAD-false}
-  - travis_retry cabal update -v
-  - "sed -i.bak 's/^jobs:/-- jobs:/' ${HOME}/.cabal/config"
+  - travis_retry ${CABAL} update -v
+  - sed -i.bak 's/^jobs:/-- jobs:/' $CABALHOME/config
   - rm -fv cabal.project cabal.project.local
-  - "sed -i.bak 's/^-- jobs:.*/jobs: 2/' ${HOME}/.cabal/config"
-  - "if [ $HCNUMVER -ge 70800 ]; then sed -i.bak 's/-- ghc-options:.*/ghc-options: -j2/' ${HOME}/.cabal/config; fi"
+  - "sed -i.bak 's/^-- jobs:.*/jobs: 2/' $CABALHOME/config"
+  - "sed -i.bak 's/-- ghc-options:.*/ghc-options: -j2/' $CABALHOME/config"
   # Overlay Hackage Package Index for GHC HEAD: https://github.com/hvr/head.hackage
   - |
     if $GHCHEAD; then
-      sed -i 's/-- allow-newer: .*/allow-newer: *:base/' ${HOME}/.cabal/config
-      for pkg in $($HCPKG list --simple-output); do pkg=$(echo $pkg | sed 's/-[^-]*$//'); sed -i "s/allow-newer: /allow-newer: *:$pkg, /" ${HOME}/.cabal/config; done
+      sed -i 's/-- allow-newer: .*/allow-newer: *:base/' $CABALHOME/config
+      for pkg in $($HCPKG list --simple-output); do pkg=$(echo $pkg | sed 's/-[^-]*$//'); sed -i "s/allow-newer: /allow-newer: *:$pkg, /" $CABALHOME/config; done
 
-      echo 'repository head.hackage'                                                        >> ${HOME}/.cabal/config
-      echo '   url: http://head.hackage.haskell.org/'                                       >> ${HOME}/.cabal/config
-      echo '   secure: True'                                                                >> ${HOME}/.cabal/config
-      echo '   root-keys: 07c59cb65787dedfaef5bd5f987ceb5f7e5ebf88b904bbd4c5cbdeb2ff71b740' >> ${HOME}/.cabal/config
-      echo '              2e8555dde16ebd8df076f1a8ef13b8f14c66bad8eafefd7d9e37d0ed711821fb' >> ${HOME}/.cabal/config
-      echo '              8f79fd2389ab2967354407ec852cbe73f2e8635793ac446d09461ffb99527f6e' >> ${HOME}/.cabal/config
-      echo '   key-threshold: 3'                                                            >> ${HOME}/.cabal.config
+      echo 'repository head.hackage'                                                        >> $CABALHOME/config
+      echo '   url: http://head.hackage.haskell.org/'                                       >> $CABALHOME/config
+      echo '   secure: True'                                                                >> $CABALHOME/config
+      echo '   root-keys: 07c59cb65787dedfaef5bd5f987ceb5f7e5ebf88b904bbd4c5cbdeb2ff71b740' >> $CABALHOME/config
+      echo '              2e8555dde16ebd8df076f1a8ef13b8f14c66bad8eafefd7d9e37d0ed711821fb' >> $CABALHOME/config
+      echo '              8f79fd2389ab2967354407ec852cbe73f2e8635793ac446d09461ffb99527f6e' >> $CABALHOME/config
+      echo '   key-threshold: 3'                                                            >> $CABALHOME.config
 
-      grep -Ev -- '^\s*--' ${HOME}/.cabal/config | grep -Ev '^\s*$'
+      grep -Ev -- '^\s*--' $CABALHOME/config | grep -Ev '^\s*$'
 
-      cabal new-update head.hackage -v
+      ${CABAL} new-update head.hackage -v
     fi
-  - grep -Ev -- '^\s*--' ${HOME}/.cabal/config | grep -Ev '^\s*$'
-  - if [ $HCNUMVER -ge 80000 ]; then cabal new-install -w ${HC} -j2 --symlink-bindir=$HOME/.local/bin doctest --constraint='doctest ==0.16.*'; fi
-  - if [ $HCNUMVER -eq 80403 ]; then cabal new-install -w ${HC} -j2 --symlink-bindir=$HOME/.local/bin hlint --constraint='hlint ==2.1.*'; fi
-  - "printf 'packages: \".\"\\n' > cabal.project"
+  - grep -Ev -- '^\s*--' $CABALHOME/config | grep -Ev '^\s*$'
+  - ${CABAL} new-install -w ${HC} -j2 doctest --constraint='doctest ==0.16.*'
+  - if [ $HCNUMVER -eq 80603 ] ; then ${CABAL} new-install -w ${HC} -j2 hlint --constraint='hlint ==2.1.*' ; fi
+  - rm -f cabal.project
+  - touch cabal.project
+  - "printf 'packages: \".\"\\n' >> cabal.project"
   - echo 'package haskell-ci' >> cabal.project
   - "echo '  ghc-options: -Werror' >> cabal.project"
+  - "printf 'write-ghc-environment-files: always\\n' >> cabal.project"
+  - "echo 'keep-going: False' >> cabal.project"
+  - echo 'package bytestring' >> cabal.project
+  - "echo '  tests: False' >> cabal.project"
   - touch cabal.project.local
-  - "if ! $NOINSTALLEDCONSTRAINTS; then for pkg in $($HCPKG list --simple-output); do echo $pkg  | grep -vw -- haskell-ci | sed 's/^/constraints: /' | sed 's/-[^-]*$/ installed/' >> cabal.project.local; done; fi"
+  - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | grep -vE -- '^(Cabal|haskell-ci)$' | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"
   - cat cabal.project || true
   - cat cabal.project.local || true
-  - if [ -f "./configure.ac" ]; then
-      (cd "." && autoreconf -i);
-    fi
+  - if [ -f "./configure.ac" ]; then (cd "." && autoreconf -i); fi
   - rm -f cabal.project.freeze
-  - cabal new-build -w ${HC} ${TEST} ${BENCH} --project-file="cabal.project" --dep -j2 all
-  - cabal new-build -w ${HC} --disable-tests --disable-benchmarks --project-file="cabal.project" --dep -j2 all
+  - ${CABAL} new-freeze -w ${HC} ${TEST} ${BENCH} --project-file="cabal.project" --dry
+  - "cat \"cabal.project.freeze\" | sed -E 's/^(constraints: *| *)//' | sed 's/any.//'"
+  - rm  "cabal.project.freeze"
+  - ${CABAL} new-build -w ${HC} ${TEST} ${BENCH} --project-file="cabal.project" --dep -j2 all
+  - ${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks --project-file="cabal.project" --dep -j2 all
   - rm -rf .ghc.environment.* "."/dist
   - DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)
 
@@ -119,48 +125,51 @@
 # any command which exits with a non-zero exit code causes the build to fail.
 script:
   # test that source-distributions can be generated
-  - (cd "." && cabal sdist)
-  - mv "."/dist/haskell-ci-*.tar.gz ${DISTDIR}/
+  - ${CABAL} new-sdist all
+  - mv dist-newstyle/sdist/*.tar.gz ${DISTDIR}/
   - cd ${DISTDIR} || false
   - find . -maxdepth 1 -name '*.tar.gz' -exec tar -xvf '{}' \;
-  - "printf 'packages: haskell-ci-*/*.cabal\\n' > cabal.project"
+  - rm -f cabal.project
+  - touch cabal.project
+  - "printf 'packages: \"haskell-ci-*/*.cabal\"\\n' >> cabal.project"
   - echo 'package haskell-ci' >> cabal.project
   - "echo '  ghc-options: -Werror' >> cabal.project"
+  - "printf 'write-ghc-environment-files: always\\n' >> cabal.project"
+  - "echo 'keep-going: False' >> cabal.project"
+  - echo 'package bytestring' >> cabal.project
+  - "echo '  tests: False' >> cabal.project"
   - touch cabal.project.local
-  - "if ! $NOINSTALLEDCONSTRAINTS; then for pkg in $($HCPKG list --simple-output); do echo $pkg  | grep -vw -- haskell-ci | sed 's/^/constraints: /' | sed 's/-[^-]*$/ installed/' >> cabal.project.local; done; fi"
+  - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | grep -vE -- '^(Cabal|haskell-ci)$' | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"
   - cat cabal.project || true
   - cat cabal.project.local || true
   # this builds all libraries and executables (without tests/benchmarks)
-  - cabal new-build -w ${HC} --disable-tests --disable-benchmarks all
+  - ${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks all
 
   # build & run tests, build benchmarks
-  - cabal new-build -w ${HC} ${TEST} ${BENCH} all
-  - if [ "x$TEST" = "x--enable-tests" ]; then cabal new-test -w ${HC} ${TEST} ${BENCH} all; fi
+  - ${CABAL} new-build -w ${HC} ${TEST} ${BENCH} all
+  - if [ "x$TEST" = "x--enable-tests" ]; then ${CABAL} new-test -w ${HC} ${TEST} ${BENCH} all; fi
 
   # doctest
-  - if [ $HCNUMVER -ge 80000 ]; then (cd haskell-ci-* && doctest --fast src); fi
+  - (cd haskell-ci-* && doctest --fast -XDeriveFoldable -XDeriveFunctor -XDeriveGeneric -XDeriveTraversable src)
 
   # hlint
-  - if [ $HCNUMVER -eq 80403 ]; then (cd haskell-ci-* && hlint -h ${ROOTDIR}/.hlint.yaml src); fi
+  - if [ $HCNUMVER -eq 80603 ] ; then (cd haskell-ci-* && hlint -h ${ROOTDIR}/.hlint.yaml -XDeriveFoldable -XDeriveFunctor -XDeriveGeneric -XDeriveTraversable src) ; fi
+  - if [ $HCNUMVER -eq 80603 ] ; then (cd haskell-ci-* && hlint -h ${ROOTDIR}/.hlint.yaml cli) ; fi
 
   # cabal check
-  - (cd haskell-ci-* && cabal check)
-
-  # haddock
-  - rm -rf ./dist-newstyle
-  - if $HADDOCK; then cabal new-haddock -w ${HC} ${TEST} ${BENCH} all; else echo "Skipping haddock generation";fi
+  - (cd haskell-ci-* && ${CABAL} check)
 
   # Build without installed constraints for packages in global-db
-  - if $UNCONSTRAINED; then rm -f cabal.project.local; echo cabal new-build -w ${HC} --disable-tests --disable-benchmarks all; else echo "Not building without installed constraints"; fi
+  - rm -f cabal.project.local; ${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks all;
 
   # Constraint sets
   - rm -rf cabal.project.local
 
   # Constraint set deepseq-1.4
   - echo Constraint set deepseq-1.4 && echo -en 'travis_fold:start:constraint-sets-deepseq-1.4\\r'
-  - if [ $HCNUMVER -ge 70800 ] && [ $HCNUMVER -lt 71000 ]  ||  [ $HCNUMVER -eq 80202 ]; then cabal new-build -w ${HC} --disable-tests --disable-benchmarks --constraint='deepseq ==1.4.*' all; else echo skipping...; fi
+  - if [ $HCNUMVER -ge 70800 ] && [ $HCNUMVER -lt 71000 ]  ||  [ $HCNUMVER -eq 80202 ] ; then ${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks  --constraint='deepseq ==1.4.*' all ; fi
 
   - echo -en 'travis_fold:end:constraint-sets-deepseq-1.4\\r'
 
-# REGENDATA ["-o",".travis.yml","--config=cabal.haskell-ci","haskell-ci.cabal"]
+# REGENDATA ["--output=.travis.yml","--config=cabal.haskell-ci","haskell-ci.cabal"]
 # EOF
diff --git a/haskell-ci.cabal b/haskell-ci.cabal
--- a/haskell-ci.cabal
+++ b/haskell-ci.cabal
@@ -1,16 +1,15 @@
-cabal-version:       1.18
-name:                haskell-ci
-version:             0.1.0.0
-
-synopsis:            Cabal package script generator for Travis-CI
+cabal-version:      2.2
+name:               haskell-ci
+version:            0.2
+synopsis:           Cabal package script generator for Travis-CI
 description:
-  Script generator (@make-travis-yml@) for [Travis-CI](https://travis-ci.org/) for continuous-integration testing of Haskell Cabal packages.
+  Script generator (@haskell-ci@) for [Travis-CI](https://travis-ci.org/) for continuous-integration testing of Haskell Cabal packages.
   .
   Included features (not limited to):
   .
   * Multiple GHC support
   * Dependency caching
-  * cabal.project support (see [Nix-style local builds documentation](http://cabal.readthedocs.io/en/latest/nix-local-build-overview.html)
+  * cabal.project support (see [Nix-style local builds documentation](https://cabal.readthedocs.io/en/latest/nix-local-build-overview.html))
   * Runs tests and builds benchmarks
   * Generates Haddocks
   * macOS (OSX) support
@@ -18,104 +17,157 @@
   .
   === Quick Start Guide
   .
-  Add a @tested-with@ line to your @.cabal@ file (e.g. @tested-with: GHC == 8.0.2 || == 8.2.2@) and then run @make-travis-yml yourpackage.cabal -o .travis.yml@ to generate the Travis-CI job script.
+  Add a @tested-with@ line to your @.cabal@ file (e.g. @tested-with: GHC == 8.0.2 || == 8.2.2@) and then run @haskell-ci yourpackage.cabal -o .travis.yml@ to generate the Travis-CI job script.
   .
-  See @make-travis-yml --help@ for more information.
+  See @haskell-ci --help@ for more information.
 
-  
-homepage:            http://haskell-ci.rtfd.org/
-bug-reports:         https://github.com/haskell-CI/haskell-ci/issues
-license:             BSD3
-license-file:        LICENSE
-author:              Herbert Valerio Riedel, Oleg Grenrus
-maintainer:          hvr@gnu.org
-category:            Development
-build-type:          Simple
-tested-with:
-  GHC == 7.8.4,
-  GHC == 7.10.3,
-  GHC == 8.0.2,
-  GHC == 8.2.2,
-  GHC == 8.4.3,
-  GHC == 8.6.1
+homepage:           https://haskell-ci.rtfd.org/
+bug-reports:        https://github.com/haskell-CI/haskell-ci/issues
+license:            BSD-3-Clause
+license-file:       LICENSE
+author:             Herbert Valerio Riedel, Oleg Grenrus
+maintainer:         hvr@gnu.org
+category:           Development
+build-type:         Simple
+tested-with:        GHC ==8.6.3 || ==8.4.4 || ==8.2.2 || ==8.0.2
 extra-source-files:
   fixtures/cabal.haskell-ci
-  fixtures/haskell-ci.cabal
-  fixtures/haskell-ci.cabal.stderr
-  fixtures/haskell-ci.cabal.travis.yml
-  fixtures/cabal.project.empty-line
-  fixtures/cabal.project.empty-line.stderr
-  fixtures/cabal.project.empty-line.travis.yml
   fixtures/cabal.project.fail-versions
   fixtures/cabal.project.fail-versions.stderr
+  fixtures/haskell-ci.cabal
+  fixtures/cabal.project.empty-line
   fixtures/cabal.project.messy
+  fixtures/cabal.project.copy-fields.all
+  fixtures/cabal.project.copy-fields.some
+  fixtures/cabal.project.copy-fields.none
+  fixtures/haskell-ci.cabal.stderr
+  fixtures/cabal.project.empty-line.stderr
   fixtures/cabal.project.messy.stderr
+  fixtures/cabal.project.copy-fields.all.stderr
+  fixtures/cabal.project.copy-fields.some.stderr
+  fixtures/cabal.project.copy-fields.none.stderr
+  fixtures/haskell-ci.cabal.travis.yml
+  fixtures/cabal.project.empty-line.travis.yml
   fixtures/cabal.project.messy.travis.yml
-  fixtures/doc/tutorial/tutorial.cabal
+  fixtures/cabal.project.copy-fields.all.travis.yml
+  fixtures/cabal.project.copy-fields.some.travis.yml
+  fixtures/cabal.project.copy-fields.none.travis.yml
   fixtures/servant-client-core/*.cabal
   fixtures/servant-client/*.cabal
   fixtures/servant-docs/*.cabal
   fixtures/servant-foreign/*.cabal
   fixtures/servant-server/*.cabal
   fixtures/servant/*.cabal
+  fixtures/doc/tutorial/tutorial.cabal
 
 source-repository head
-  type: git
+  type:     git
   location: https://github.com/haskell-CI/haskell-ci.git
 
 flag ShellCheck
   default: True
-  manual: True
+  manual:  True
 
-library
-  exposed-modules:     MakeTravisYml
-  ghc-options:         -Wall
-  if impl(ghc >= 8.0)
-    ghc-options:
-      -Wcompat
-      -Wnoncanonical-monad-instances
-      -Wnoncanonical-monadfail-instances
-  hs-source-dirs:      src
-  other-extensions:    CPP, ViewPatterns, NamedFieldPuns
-  build-depends:       base         >= 4.7  && < 4.12
-                     , Cabal        == 1.18.*
-                                 || == 1.20.*
-                                 || == 1.22.*
-                                 || == 1.24.*
-                                 || == 2.0.*
-                                 || == 2.2.*
-                     , containers   >= 0.4  && < 0.6
-                     , directory    >= 1.1  && < 1.4
-                     , filepath     >= 1.2  && < 1.5
-                     , transformers >= 0.3  && < 0.6
-                     , deepseq      >= 1.3  && < 1.5
-  default-language:    Haskell2010
+library haskell-ci-internal
+  exposed-modules:
+    HaskellCI
+    HaskellCI.Cli
+    HaskellCI.Config
+    HaskellCI.Config.ConstraintSet
+    HaskellCI.Config.CopyFields
+    HaskellCI.Config.Doctest
+    HaskellCI.Config.Dump
+    HaskellCI.Config.Folds
+    HaskellCI.Config.HLint
+    HaskellCI.Config.Installed
+    HaskellCI.Config.Jobs
+    HaskellCI.Extras
+    HaskellCI.GHC
+    HaskellCI.Glob
+    HaskellCI.MakeTravisOutput
+    HaskellCI.Newtypes
+    HaskellCI.Optimization
+    HaskellCI.OptionsGrammar
+    HaskellCI.OptparseGrammar
+    HaskellCI.Package
+    HaskellCI.ParsecError
+    HaskellCI.ParsecUtils
+    HaskellCI.Project
+    HaskellCI.TestedWith
+    HaskellCI.Version
 
+  -- vendored from Cabal development version
+  other-modules:      Distribution.Fields.Pretty
+  ghc-options:
+    -Wall -Wcompat -Wnoncanonical-monad-instances
+    -Wnoncanonical-monadfail-instances
+
+  hs-source-dirs:     src
+  default-extensions:
+    DeriveFoldable
+    DeriveFunctor
+    DeriveGeneric
+    DeriveTraversable
+
+  other-extensions:
+    CPP
+    NamedFieldPuns
+    OverloadedLabels
+    ViewPatterns
+
+  build-depends:
+    , base          >=4.9      && <4.13
+    , bytestring    ^>=0.10.8.1
+    , Cabal         ^>=2.4
+    , containers    ^>=0.5.7.1 || ^>=0.6.0.1
+    , deepseq       ^>=1.4.2.0
+    , directory     ^>=1.3.0.0
+    , filepath      ^>=1.4.1.1
+    , parsec        ^>=3.1.13.0
+    , pretty        ^>=1.1.3.3
+    , text          ^>=1.2.3.0
+    , transformers  ^>=0.5.2.0
+
+  -- other dependencies
+  build-depends:
+    , base-compat           ^>=0.10.5
+    , generic-lens          ^>=1.1.0.0
+    , HsYAML                ^>=0.1.1.3
+    , microlens             ^>=0.4.10
+    , optparse-applicative  ^>=0.14.3.0
+
+  default-language:   Haskell2010
+
   -- ShellCheck. Would need newer transformers for older GHC
-  if flag(ShellCheck) && impl(ghc >= 7.10 && <8.5)
-    build-depends: ShellCheck == 0.5.0
+  if (flag(shellcheck) && impl(ghc >=7.10 && <8.7))
+    build-depends: ShellCheck ==0.6.0
 
-executable make-travis-yml
-  main-is:             Main.hs
-  ghc-options:         -Wall
-  build-depends:       base, haskell-ci
-  default-language:    Haskell2010
+executable haskell-ci
+  main-is:          Main.hs
+  ghc-options:      -Wall
+  hs-source-dirs:   cli
+  build-depends:
+    , base
+    , haskell-ci-internal
 
+  default-language: Haskell2010
+
 test-suite golden
-  type:                exitcode-stdio-1.0
-  main-is:             Tests.hs
-  hs-source-dirs:      test
-  build-depends:       haskell-ci
-                       -- inherited constraints via lib:haskell-ci
-                     , base
-                     , bytestring
-                     , directory
-                     , filepath
-                     , transformers
-                       -- dependencies needing explicit constraints
-                     , tasty         >= 1.0      && < 1.2
-                     , tasty-golden  >= 2.3.1.1  && < 2.4
-                     , ansi-terminal >= 0.8.0.2  && <0.9
-                     , Diff          >= 0.3.4    && < 0.4
+  type:             exitcode-stdio-1.0
+  main-is:          Tests.hs
+  hs-source-dirs:   test
+  build-depends:
+    , ansi-terminal        ^>=0.8.0.2
+    , base
+    , bytestring
+    , Diff                 ^>=0.3.4
+    , directory
+    , filepath
+    , haskell-ci-internal
+    , tasty                >=1.0     && <1.2
+    , tasty-golden         ^>=2.3.1.1
+    , transformers
 
-  default-language:    Haskell2010
+  -- inherited constraints via lib:haskell-ci
+  -- dependencies needing explicit constraints
+  default-language: Haskell2010
diff --git a/src/Distribution/Fields/Pretty.hs b/src/Distribution/Fields/Pretty.hs
new file mode 100644
--- /dev/null
+++ b/src/Distribution/Fields/Pretty.hs
@@ -0,0 +1,134 @@
+{-# LANGUAGE BangPatterns #-}
+-- | Cabal-like file AST types: 'Field', 'Section' etc,
+--
+-- This (intermediate) data type is used for pretty-printing.
+--
+-- @since 3.0.0.0
+--
+module Distribution.Fields.Pretty (
+    -- * Fields
+    PrettyField (..),
+    showFields,
+    showFields',
+    -- * Transformation from 'P.Field'
+    fromParsecFields,
+    genericFromParsecFields,
+    prettyFieldLines,
+    prettySectionArgs,
+    ) where
+
+import Data.Functor.Identity       (Identity (..))
+import Distribution.Pretty         (showToken)
+
+import Distribution.Parsec.Field (FieldName)
+import Distribution.Simple.Utils (fromUTF8BS)
+
+import qualified Distribution.Parsec.Parser as P
+
+import qualified Data.ByteString  as BS
+import qualified Text.PrettyPrint as PP
+
+data PrettyField
+    = PrettyField FieldName PP.Doc
+    | PrettySection FieldName [PP.Doc] [PrettyField]
+  deriving Show
+
+-- | Prettyprint a list of fields.
+showFields :: [PrettyField] -> String
+showFields = showFields' 4
+
+-- | 'showFields' with user specified indentation.
+showFields' :: Int -> [PrettyField] -> String
+showFields' n = unlines . renderFields indent where
+    -- few hardcoded, "unrolled"  variants.
+    indent | n == 4    = indent4
+           | n == 2    = indent2
+           | otherwise = (replicate (max n 1) ' ' ++)
+
+    indent4 :: String -> String
+    indent4 [] = []
+    indent4 xs = ' ' : ' ' : ' ' : ' ' : xs
+
+    indent2 :: String -> String
+    indent2 [] = []
+    indent2 xs = ' ' : ' ' : xs
+
+renderFields :: (String -> String) -> [PrettyField] -> [String]
+renderFields indent fields = flattenBlocks $ map (renderField indent len) fields
+  where
+    len = maxNameLength 0 fields
+
+    maxNameLength !acc []                          = acc
+    maxNameLength !acc (PrettyField name _ : rest) = maxNameLength (max acc (BS.length name)) rest
+    maxNameLength !acc (PrettySection {}   : rest) = maxNameLength acc rest
+
+-- | Block of lines,
+-- Boolean parameter tells whether block should be surrounded by empty lines
+data Block = Block Bool [String]
+
+flattenBlocks :: [Block] -> [String]
+flattenBlocks = go0 where
+    go0 [] = []
+    go0 (Block surr strs : blocks) = strs ++ go surr blocks
+
+    go _surr' [] = []
+    go  surr' (Block surr strs : blocks) = ins $ strs ++ go surr blocks where
+        ins | surr' || surr = ("" :)
+            | otherwise     = id
+
+renderField :: (String -> String) -> Int -> PrettyField -> Block
+renderField indent fw (PrettyField name doc) = Block False $ case lines narrow of
+    []           -> [ name' ++ ":" ]
+    [singleLine] | length singleLine < 60
+                 -> [ name' ++ ": " ++ replicate (fw - length name') ' ' ++ narrow ]
+    _            -> (name' ++ ":") : map indent (lines (PP.render doc))
+  where
+    name' = fromUTF8BS name
+    narrow = PP.renderStyle narrowStyle doc
+
+    narrowStyle :: PP.Style
+    narrowStyle = PP.style { PP.lineLength = PP.lineLength PP.style - fw }
+
+renderField indent _ (PrettySection name args fields) = Block True $
+    PP.render (PP.hsep $ PP.text (fromUTF8BS name) : args)
+    :
+    map indent (renderFields indent fields)
+
+-------------------------------------------------------------------------------
+-- Transform from Parsec.Field
+-------------------------------------------------------------------------------
+
+genericFromParsecFields
+    :: Applicative f
+    => (FieldName -> [P.FieldLine ann] -> f PP.Doc)     -- ^ transform field contents
+    -> (FieldName -> [P.SectionArg ann] -> f [PP.Doc])  -- ^ transform section arguments
+    -> [P.Field ann]
+    -> f [PrettyField]
+genericFromParsecFields f g = goMany where
+    goMany = traverse go
+
+    go (P.Field (P.Name _ann name) fls)          = PrettyField name <$> f name fls
+    go (P.Section (P.Name _ann name) secargs fs) = PrettySection name <$> g name secargs <*> goMany fs
+
+-- | Used in 'fromParsecFields'.
+prettyFieldLines :: FieldName -> [P.FieldLine ann] -> PP.Doc
+prettyFieldLines _ fls = PP.vcat
+    [ PP.text $ fromUTF8BS bs
+    | P.FieldLine _ bs <- fls
+    ]
+
+-- | Used in 'fromParsecFields'.
+prettySectionArgs :: FieldName -> [P.SectionArg ann] -> [PP.Doc]
+prettySectionArgs _ = map $ \sa -> case sa of
+    P.SecArgName _ bs  -> showToken $ fromUTF8BS bs
+    P.SecArgStr _ bs   -> showToken $ fromUTF8BS bs
+    P.SecArgOther _ bs -> PP.text $ fromUTF8BS bs
+
+-- | Simple variant of 'genericFromParsecField'
+fromParsecFields :: [P.Field ann] -> [PrettyField]
+fromParsecFields = runIdentity . genericFromParsecFields
+    (Identity .: prettyFieldLines)
+    (Identity .: prettySectionArgs)
+  where
+    (.:) :: (a -> b) -> (c -> d -> a) -> (c -> d -> b)
+    (f .: g) x y = f (g x y)
diff --git a/src/HaskellCI.hs b/src/HaskellCI.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI.hs
@@ -0,0 +1,1029 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE ViewPatterns #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE MultiWayIf #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE OverloadedLabels #-}
+
+-- | New-style @.travis.yml@ script generator using cabal 1.24's nix-style
+-- tech-preview facilities.
+--
+-- See also <https://github.com/haskell-CI/haskell-ci>
+--
+-- NB: This code deliberately avoids relying on non-standard packages and
+--     is expected to compile/work with at least GHC 7.0 through GHC 8.0
+module HaskellCI (
+    main,
+    -- * for tests
+    Result (..),
+    Diagnostic (..),
+    parseTravis,
+    formatDiagnostic, formatDiagnostics,
+    travisFromConfigFile, MakeTravisOutput, Options (..), defaultOptions,
+    ) where
+
+import Prelude ()
+import Prelude.Compat
+
+import Control.DeepSeq (force)
+import Control.Exception (evaluate)
+import Control.Monad (when, unless, liftM, forM_, mzero)
+import qualified Data.ByteString as BS
+import qualified Data.Foldable as F
+import qualified Data.Traversable as T
+import Data.Function
+import Data.List
+import Data.Maybe
+import Data.Set (Set)
+import qualified Data.Set as S
+import qualified Data.Map as M
+import System.Directory (doesDirectoryExist, doesFileExist)
+import System.Exit
+import System.FilePath.Posix ((</>), takeDirectory, takeFileName, takeExtension)
+import System.IO
+import Control.Monad.IO.Class
+import Control.Monad.Trans.Maybe
+import System.Environment (getArgs)
+import Control.Monad.Trans.Writer
+import Text.Read (readMaybe)
+import Distribution.Compat.ReadP (readP_to_S)
+
+
+import qualified Options.Applicative         as O
+
+import Distribution.Compiler (CompilerFlavor(..))
+import Distribution.Package hiding (Package, pkgName)
+import qualified Distribution.Package as Pkg
+import Distribution.PackageDescription (GenericPackageDescription,packageDescription, testedWith, package, condLibrary, condTestSuites)
+import Distribution.PackageDescription.Configuration (flattenPackageDescription)
+import qualified Distribution.PackageDescription as PD
+import qualified Distribution.ParseUtils as PU
+import Distribution.Text
+import Distribution.Version
+#if MIN_VERSION_Cabal(2,2,0)
+import Distribution.PackageDescription.Parsec (readGenericPackageDescription)
+#elif MIN_VERSION_Cabal(2,0,0)
+import Distribution.PackageDescription.Parse (readGenericPackageDescription)
+#else
+import Distribution.PackageDescription.Parse (readPackageDescription)
+import Distribution.Verbosity (Verbosity)
+#endif
+
+import qualified Distribution.FieldGrammar                    as C
+import qualified Distribution.Fields.Pretty                   as C
+import qualified Distribution.PackageDescription.FieldGrammar as C
+import qualified Distribution.Types.SourceRepo                as C
+import qualified Distribution.Types.VersionRange              as C
+import qualified Text.PrettyPrint                             as PP
+
+
+
+#if MIN_VERSION_base(4,9,0)
+import Data.Semigroup (Semigroup (..))
+#else
+import Data.Monoid ((<>))
+#endif
+
+-- lens
+import Lens.Micro
+import Data.Generics.Labels () -- IsLabel (->) ...
+
+import qualified Distribution.Types.BuildInfo.Lens          as L
+import qualified Distribution.Types.PackageDescription.Lens as L
+
+import HaskellCI.Cli
+import HaskellCI.Config
+import HaskellCI.Config.CopyFields
+import HaskellCI.Config.ConstraintSet
+import HaskellCI.Config.Doctest
+import HaskellCI.Config.Dump
+import HaskellCI.Config.Folds
+import HaskellCI.Config.HLint
+import HaskellCI.Config.Installed
+import HaskellCI.Config.Jobs
+import HaskellCI.Extras
+import HaskellCI.GHC
+import HaskellCI.Glob
+import HaskellCI.MakeTravisOutput
+import HaskellCI.Optimization
+import HaskellCI.Package
+import HaskellCI.Project
+import HaskellCI.TestedWith
+import HaskellCI.Version
+
+#ifndef CURRENT_PACKAGE_VERSION
+#define CURRENT_PACKAGE_VERSION "???"
+#endif
+
+-------------------------------------------------------------------------------
+-- Main
+-------------------------------------------------------------------------------
+
+main :: IO ()
+main = do
+    argv0 <- getArgs
+    (cmd, opts) <- O.execParser cliParserInfo
+    case cmd of
+        CommandListGHC -> do
+            putStrLn $ "Supported GHC versions:"
+            forM_ groupedVersions $ \(v, vs) -> do
+                putStr $ prettyMajVersion v ++ ": "
+                putStrLn $ intercalate ", " (map display vs)
+        CommandDumpConfig -> do
+            putStr $ unlines $ runDG configGrammar
+
+        CommandRegenerate -> do
+            let fp = ".travis.yml" -- make configurable?
+            contents <- readFile fp
+            case findArgv (lines contents) of
+                Nothing     -> do
+                    hPutStrLn stderr $ "Error: expected REGENDATA line in " ++ fp
+                    exitFailure
+                Just argv   -> do
+                    (f, opts') <- parseTravis argv
+                    doTravis argv f (opts' <> opts)
+        CommandTravis f -> doTravis argv0 f opts
+  where
+    findArgv :: [String] -> Maybe [String]
+    findArgv ls = do
+        l <- findMaybe (afterInfix "REGENDATA") ls
+        readMaybe l
+
+    groupedVersions :: [(Version, [Version])]
+    groupedVersions = map ((\vs -> (head vs, vs)) . sortBy (flip compare))
+                    . groupBy ((==) `on` ghcMajVer)
+                    $ sort knownGhcVersions
+
+    prettyMajVersion :: Version -> String
+    prettyMajVersion v
+        | Just v == ghcAlpha = "alpha"
+        | otherwise = case ghcMajVer v of (x,y) -> show x ++ "." ++ show y
+
+doTravis :: [String] -> FilePath -> Options -> IO ()
+doTravis args path opts = do
+    runYamlWriter (optOutput opts) $ travisFromConfigFile args opts path
+
+runYamlWriter :: Maybe FilePath -> YamlWriter IO () -> IO ()
+runYamlWriter mfp m = do
+    result <- execWriterT (runMaybeT m)
+    case result of
+        Failure (formatDiagnostics -> errors) -> hPutStr stderr errors >> exitFailure
+        Success (formatDiagnostics -> warnings) (unlines -> contents) -> do
+            contents' <- evaluate (force contents)
+            hPutStr stderr warnings
+            case mfp of
+                Nothing -> putStr contents'
+                Just fp -> writeFile fp contents'
+
+travisFromConfigFile
+    :: MonadIO m
+    => [String]
+    -> Options
+    -> FilePath
+    -> YamlWriter m ()
+travisFromConfigFile args opts path = do
+    cabalFiles <- getCabalFiles
+    config' <- maybe (return emptyConfig) readConfigFile (optConfig opts)
+    let config = optConfigMorphism opts config'
+    pkgs <- T.mapM (configFromCabalFile config opts) cabalFiles
+    (ghcs, prj) <- case checkVersions (cfgTestedWith config) pkgs of
+        Right x     -> return x
+        Left errors -> putStrLnErrs errors >> mzero
+    genTravisFromConfigs args opts isCabalProject config prj ghcs
+  where
+    isCabalProject :: Maybe FilePath
+    isCabalProject
+        | "cabal.project" `isPrefixOf` takeFileName path = Just path
+        | otherwise = Nothing
+
+    getCabalFiles :: MonadIO m => YamlWriter m (Project FilePath)
+    getCabalFiles
+        | isNothing isCabalProject = return $ emptyProject & #prjPackages .~ [path]
+        | otherwise = do
+            contents <- liftIO $ BS.readFile path
+            pkgs <- either putStrLnErr return $ parseProjectFile path contents
+            over #prjPackages concat `liftM` T.mapM findProjectPackage pkgs
+
+    rootdir = takeDirectory path
+
+    -- See findProjectPackages in cabal-install codebase
+    -- this is simple variant.
+    findProjectPackage :: MonadIO m => String -> YamlWriter m [FilePath]
+    findProjectPackage pkglocstr = do
+        mfp <- checkisFileGlobPackage pkglocstr `mplusMaybeT`
+               checkIsSingleFilePackage pkglocstr
+        maybe (putStrLnErr $ "bad package location: " ++ pkglocstr) return mfp
+
+    checkIsSingleFilePackage pkglocstr = do
+        let abspath = rootdir </> pkglocstr
+        isFile <- liftIO $ doesFileExist abspath
+        isDir  <- liftIO $ doesDirectoryExist abspath
+        if | isFile && takeExtension pkglocstr == ".cabal" -> return (Just [abspath])
+           | isDir -> checkisFileGlobPackage (pkglocstr </> "*.cabal")
+           | otherwise -> return Nothing
+
+    -- if it looks like glob, glob
+    checkisFileGlobPackage pkglocstr =
+        case filter (null . snd) $ readP_to_S parseFilePathGlobRel pkglocstr of
+            [(g, "")] -> do
+                files <- liftIO $ expandRelGlob rootdir g
+                let files' = filter ((== ".cabal") . takeExtension) files
+                -- if nothing is matched, skip.
+                if null files' then return Nothing else return (Just files')
+            _         -> return Nothing
+
+    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)
+
+configFromCabalFile
+    :: MonadIO m => Config ->  Options -> FilePath -> YamlWriter m (Package, Set Version)
+configFromCabalFile cfg opts cabalFile = do
+    gpd <- liftIO $ readGenericPackageDescription maxBound cabalFile
+
+    let compilers = testedWith $ packageDescription gpd
+        pkgNameStr = display $ Pkg.pkgName $ package $ packageDescription gpd
+
+    let unknownComps = nub [ c | (c,_) <- compilers, c /= GHC ]
+        ghcVerConstrs = [ vc | (GHC,vc) <- compilers ]
+        ghcVerConstrs' = simplifyVersionRange $ foldr unionVersionRanges noVersion ghcVerConstrs
+        twoDigitGhcVerConstrs = mapMaybe isTwoDigitGhcVersion ghcVerConstrs :: [Version]
+        specificGhcVers = nub $ mapMaybe isSpecificVersion ghcVerConstrs
+
+    unless (null twoDigitGhcVerConstrs) $ do
+        putStrLnWarn $ "'tested-with:' uses two digit GHC versions (which don't match any existing GHC version): " ++ intercalate ", " (map display twoDigitGhcVerConstrs)
+        putStrLnInfo $ "Either use wild-card format, for example 'tested-with: GHC ==7.10.*' or a specific existing version 'tested-with: GHC ==7.10.3'"
+
+    when (null compilers) $ do
+        putStrLnErr (unlines $
+                     [ "empty or missing top-level 'tested-with:' definition in " ++ cabalFile ++ " file; example definition:"
+                     , ""
+                     , "tested-with: " ++ intercalate ", " [ "GHC==" ++ display v | v <- lastStableGhcVers ]
+                     ])
+
+    unless (null unknownComps) $ do
+        putStrLnWarn $ "ignoring unsupported compilers mentioned in tested-with: " ++ show unknownComps
+
+    when (null ghcVerConstrs) $ do
+        putStrLnErr "'tested-with:' doesn't mention any 'GHC' version"
+
+    when (isNoVersion ghcVerConstrs') $ do
+        putStrLnErr "'tested-with:' describes an empty version range for 'GHC'"
+
+    when (isAnyVersion ghcVerConstrs') $ do
+        putStrLnErr "'tested-with:' allows /any/ 'GHC' version"
+
+    let unknownGhcVers = sort $ specificGhcVers \\ knownGhcVersions
+
+    unless (null unknownGhcVers) $ do
+        putStrLnErr ("'tested-with:' specifically refers to unknown 'GHC' versions: "
+                     ++ intercalate ", " (map display unknownGhcVers) ++ "\n"
+                     ++ "Known GHC versions: " ++ intercalate ", " (map display knownGhcVersions))
+
+    let knownGhcVersions'
+            | cfgLastInSeries cfg = filterLastMajor knownGhcVersions
+            | otherwise           = knownGhcVersions
+
+    let testedGhcVersions = filter (`withinRange` ghcVerConstrs') knownGhcVersions'
+
+    when (null testedGhcVersions) $ do
+        putStrLnErr "no known GHC version is allowed by the 'tested-with' specification"
+
+    forM_ (optCollections opts) $ \c -> do
+        let v = collToGhcVer c
+        unless (v `elem` testedGhcVersions) $
+            putStrLnErr $ unlines
+               [ "collection " ++ c ++ " requires GHC " ++ display v
+               , "add 'tested-width: GHC == " ++ display v ++ "' to your .cabal file"
+               ]
+
+    let pkg = Pkg pkgNameStr anyVersion (takeDirectory cabalFile) gpd
+
+    return (pkg, S.fromList testedGhcVersions)
+  where
+    lastStableGhcVers
+        = nubBy ((==) `on` ghcMajVer)
+        $ sortBy (flip compare)
+        $ filter (not . previewGHC . Just)
+        $ knownGhcVersions
+
+    isTwoDigitGhcVersion :: VersionRange -> Maybe Version
+    isTwoDigitGhcVersion vr = isSpecificVersion vr >>= t
+      where
+        t v | [_,_] <- versionNumbers v = Just v
+        t _                             = Nothing
+
+    filterLastMajor = map maximum . groupBy ((==) `on` ghcMajVer)
+
+genTravisFromConfigs
+    :: Monad m
+    => [String]
+    -> Options
+    -> Maybe FilePath
+    -> Config
+    -> Project Package
+    -> Set Version
+    -> YamlWriter m ()
+genTravisFromConfigs argv opts isCabalProject config prj@Project { prjPackages = pkgs } versions' = do
+    let folds = cfgFolds config
+
+    putStrLnInfo $
+        "Generating Travis-CI config for testing for GHC versions: " ++ ghcVersions
+
+    unless (null $ cfgOsx config) $  do
+        putStrLnInfo $ "Also OSX jobs for: " ++ ghcOsxVersions
+        unless (S.null omittedOsxVersions) $
+            putStrLnWarn $ "Not all GHC versions specified with --osx are generated: " ++ ghcOmittedOsxVersions
+
+    ---------------------------------------------------------------------------
+    -- travis.yml generation starts here
+
+    tellStrLns
+        [ "# This Travis job script has been generated by a script via"
+        , "#"
+        , rawRow $ "#   haskell-ci " ++ unwords [ "'" ++ a ++ "'" | a <- argv ]
+        , "#"
+        , "# For more information, see https://github.com/haskell-CI/haskell-ci"
+        , "#"
+        , rawRow $ "# version: " ++ CURRENT_PACKAGE_VERSION
+        , "#"
+        , "language: c"
+        , "dist: xenial"
+        , ""
+        , "git:"
+        , "  submodules: false  # whether to recursively clone submodules"
+        , ""
+        ]
+
+    let projectName = fromMaybe (pkgName $ head pkgs) (cfgProjectName config)
+    unless (null $ cfgIrcChannels config) $ tellStrLnsRaw $
+        [ "notifications:"
+        , "  irc:"
+        , "    channels:"
+        ] ++
+        [ "      - \"" ++ chan ++ "\"" | chan <- cfgIrcChannels config ] ++
+        [ "    skip_join: true"
+        , "    template:"
+        , "      - \"\\x0313" ++ projectName ++ "\\x03/\\x0306%{branch}\\x03 \\x0314%{commit}\\x03 %{build_url} %{message}\""
+        , ""
+        ]
+
+    unless (null $ cfgOnlyBranches config) $ tellStrLnsRaw $
+        [ "branches:"
+        , "  only:"
+        ] ++
+        [ "    - " ++ branch
+        | branch <- cfgOnlyBranches config
+        ] ++
+        [ ""
+        ]
+
+    -- cache directories
+    when (cfgCache config) $ tellStrLns
+        [ "cache:"
+        , "  directories:"
+        , "    - $HOME/.cabal/packages"
+        , "    - $HOME/.cabal/store"
+        ]
+
+
+    -- on OSX ghc is installed in $HOME so we can cache it
+    -- independently of linux
+    when (cfgCache config && not (null (cfgOsx config))) $ tellStrLns
+        [ "    - $HOME/.ghc-install"
+        ]
+
+    tellStrLn ""
+
+    -- postgresql
+    when (cfgPostgres config) $ tellStrLns
+        [ "services:"
+        , "- postgresql"
+        , "addons:"
+        , "  postgresql: '10'"
+        , ""
+        ]
+
+    -- before caching: clear some redundant stuff
+    when (cfgCache config) $ tellStrLns
+        [ "before_cache:"
+        , "  - rm -fv $CABALHOME/packages/hackage.haskell.org/build-reports.log"
+        , "  # remove files that are regenerated by 'cabal update'"
+        , "  - rm -fv $CABALHOME/packages/hackage.haskell.org/00-index.*" -- legacy
+        , "  - rm -fv $CABALHOME/packages/hackage.haskell.org/*.json" -- TUF meta-data
+        , "  - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.cache"
+        , "  - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar"
+        , "  - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar.idx"
+        , ""
+        , "  - rm -rfv $CABALHOME/packages/head.hackage" -- if we cache, it will break builds.
+        , ""
+        ]
+
+    tellStrLn "matrix:"
+    tellStrLn "  include:"
+
+    let colls = [ (collToGhcVer cid,cid) | cid <- reverse $ optCollections opts ]
+
+    let tellJob :: Monad m => Bool -> Maybe Version -> YamlWriter m ()
+        tellJob osx gv = do
+            let cvs = dispGhcVersion $ gv >> cfgCabalInstallVersion config
+                gvs = dispGhcVersion gv
+
+                xpkgs' = concatMap (',':) (S.toList $ cfgApt config)
+
+                colls' = [ cid | (v,cid) <- colls, Just v == gv ]
+
+            tellStrLnsRaw $ catMaybes
+                [ Just $ "    - compiler: \"ghc-" <> gvs <> "\""
+                , if | Just e <- gv >>= \v -> M.lookup v (cfgEnv config)
+                                     -> Just $ "      env: " ++ e
+                     | previewGHC gv -> Just $ "      env: GHCHEAD=true"
+                     | null colls'   -> Nothing
+                     | otherwise     -> Just $ "      env: 'COLLECTIONS=" ++ intercalate "," colls' ++ "'"
+                , Just $ "      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-" <> cvs <> ",ghc-" <> gvs <> xpkgs' <> "], sources: [hvr-ghc]}}"
+                ]
+
+            when osx $ tellStrLnsRaw
+                [ "      os: osx"
+                ]
+
+    -- newer GHC first, -head last (which is great).
+    -- Alpha release would go first though.
+    F.forM_ (reverse $ S.toList versions) $ tellJob False
+    F.forM_ (reverse $ S.toList osxVersions) $ tellJob True . Just
+
+    let allowFailures = headGhcVers `S.union` S.map Just (S.filter (`C.withinRange` cfgAllowFailures config) versions')
+    unless (S.null allowFailures) $ do
+        tellStrLn ""
+        tellStrLn "  allow_failures:"
+
+        F.forM_ allowFailures $ \gv -> do
+            let gvs = dispGhcVersion gv
+            tellStrLn $ concat [ "    - compiler: \"ghc-", gvs, "\"" ]
+
+    tellStrLns
+        [ ""
+        , "before_install:"
+        , sh "HC=/opt/ghc/bin/${CC}"
+        , sh' [2034,2039] "HCPKG=${HC/ghc/ghc-pkg}" -- SC2039. In POSIX sh, string replacement is undefined.
+        , sh "unset CC"
+        -- cabal
+        , sh "CABAL=/opt/ghc/bin/cabal"
+        , sh "CABALHOME=$HOME/.cabal"
+        -- PATH
+        , sh "export PATH=\"$CABALHOME/bin:$PATH\""
+        -- rootdir is useful for manual script additions
+        , sh "ROOTDIR=$(pwd)"
+        ]
+
+    -- macOS installing
+    let haskellOnMacos = "https://haskell.futurice.com/haskell-on-macos.py"
+    unless (null (cfgOsx config)) $ tellStrLns
+        [ sh $ "if [ \"$TRAVIS_OS_NAME\" = \"osx\" ]; then brew update; brew upgrade python@3; curl " ++ haskellOnMacos ++ " | python3 - --make-dirs --install-dir=$HOME/.ghc-install --cabal-alias=head install cabal-install-head ${TRAVIS_COMPILER}; fi"
+        , sh' [2034,2039] "if [ \"$TRAVIS_OS_NAME\" = \"osx\" ]; then HC=$HOME/.ghc-install/ghc/bin/$TRAVIS_COMPILER; HCPKG=${HC/ghc/ghc-pkg}; CABAL=$HOME/.ghc-install/ghc/bin/cabal; fi"
+        ]
+
+    -- HCNUMVER, numeric HC version, e.g. ghc 7.8.4 is 70804 and 7.10.3 is 71003
+    tellStrLns
+        [ sh $ "HCNUMVER=$(( $(${HC} --numeric-version|sed -E 's/([0-9]+)\\.([0-9]+)\\.([0-9]+).*/\\1 * 10000 + \\2 * 100 + \\3/') ))"
+        , sh "echo $HCNUMVER"
+        ]
+
+    unless (null colls) $
+       tellStrLn " - IFS=', ' read -a COLLS <<< \"$COLLECTIONS\""
+
+    tellStrLns
+        [ ""
+        , "install:"
+        , sh "${CABAL} --version"
+        , sh "echo \"$(${HC} --version) [$(${HC} --print-project-git-commit-id 2> /dev/null || echo '?')]\""
+        , sh "TEST=--enable-tests"
+        , shForJob versions' (invertVersionRange $ cfgTests config) "TEST=--disable-tests"
+        , sh "BENCH=--enable-benchmarks"
+        , shForJob versions' (invertVersionRange $ cfgBenchmarks config) "BENCH=--disable-benchmarks"
+        , sh "GHCHEAD=${GHCHEAD-false}"
+        ]
+
+    -- Update hackage index. Side-effect: ~/.cabal.config is created.
+    tellStrLns
+        [ sh "travis_retry ${CABAL} update -v"
+        , sh "sed -i.bak 's/^jobs:/-- jobs:/' $CABALHOME/config"
+        , sh "rm -fv cabal.project cabal.project.local"
+        ]
+
+    -- Cabal jobs
+    case cfgJobs config >>= cabalJobs of
+        Just n -> tellStrLns
+            [ sh $ "sed -i.bak 's/^-- jobs:.*/jobs: " ++ show n ++ "/' $CABALHOME/config"
+            ]
+        _ -> return ()
+
+    -- GHC jobs
+    case cfgJobs config >>= ghcJobs of
+        Just m -> tellStrLns
+            [ shForJob versions' (orLaterVersion (mkVersion [7,8])) $
+              "sed -i.bak 's/-- ghc-options:.*/ghc-options: -j" ++ show m ++ "/' $CABALHOME/config"
+            ]
+        _ -> return ()
+
+    -- Add head.hackage repository to ~/.cabal/config
+    -- (locally you want to add it to cabal.project)
+    unless (S.null headGhcVers) $ tellStrLns
+        [ "  # Overlay Hackage Package Index for GHC HEAD: https://github.com/hvr/head.hackage"
+        , "  - |"
+        , "    if $GHCHEAD; then"
+        , "      sed -i 's/-- allow-newer: .*/allow-newer: *:base/' $CABALHOME/config"
+        , "      for pkg in $($HCPKG list --simple-output); do pkg=$(echo $pkg | sed 's/-[^-]*$//'); sed -i \"s/allow-newer: /allow-newer: *:$pkg, /\" $CABALHOME/config; done"
+        , ""
+        , "      echo 'repository head.hackage'                                                        >> $CABALHOME/config"
+        , "      echo '   url: http://head.hackage.haskell.org/'                                       >> $CABALHOME/config"
+        , "      echo '   secure: True'                                                                >> $CABALHOME/config"
+        , "      echo '   root-keys: 07c59cb65787dedfaef5bd5f987ceb5f7e5ebf88b904bbd4c5cbdeb2ff71b740' >> $CABALHOME/config"
+        , "      echo '              2e8555dde16ebd8df076f1a8ef13b8f14c66bad8eafefd7d9e37d0ed711821fb' >> $CABALHOME/config"
+        , "      echo '              8f79fd2389ab2967354407ec852cbe73f2e8635793ac446d09461ffb99527f6e' >> $CABALHOME/config"
+        , "      echo '   key-threshold: 3'                                                            >> $CABALHOME.config"
+        , ""
+        , "      grep -Ev -- '^\\s*--' $CABALHOME/config | grep -Ev '^\\s*$'"
+        , ""
+        , "      ${CABAL} new-update head.hackage -v"
+        , "    fi"
+        ]
+
+    -- Output cabal.config
+    tellStrLns
+        [ sh "grep -Ev -- '^\\s*--' $CABALHOME/config | grep -Ev '^\\s*$'"
+        ]
+
+    -- Install doctest
+    let doctestVersionConstraint
+            | isAnyVersion (cfgDoctestVersion doctestConfig) = ""
+            | otherwise = " --constraint='doctest " ++ display (cfgDoctestVersion doctestConfig) ++ "'"
+    when (cfgDoctestEnabled doctestConfig) $ tellStrLns
+        [ shForJob versions' doctestJobVersionRange $ "${CABAL} new-install -w ${HC} -j2 doctest" ++ doctestVersionConstraint
+        ]
+
+    -- Install hlint
+    let hlintVersionConstraint
+            | isAnyVersion (cfgHLintVersion hlintConfig) = ""
+            | otherwise = " --constraint='hlint " ++ display (cfgHLintVersion hlintConfig) ++ "'"
+    when (cfgHLintEnabled hlintConfig) $ tellStrLns
+        [ shForJob versions' (hlintJobVersionRange versions (cfgHLintJob hlintConfig)) $
+          "${CABAL} new-install -w ${HC} -j2 hlint" ++ hlintVersionConstraint
+        ]
+
+    -- create cabal.project file
+    generateCabalProject False
+
+    let pkgFilter = intercalate " | " $ map (wrap.pkgName) pkgs
+        wrap s = "grep -Fv \"" ++ s ++ " ==\""
+    unless (null colls) $ tellStrLnsRaw
+        [ "  - for COLL in \"${COLLS[@]}\"; do"
+        , "      echo \"== collection $COLL ==\";"
+        , "      ghc-travis collection ${COLL} > /dev/null || break;"
+        , "      ghc-travis collection ${COLL} | " ++ pkgFilter ++ " > cabal.project.freeze;"
+        , "      grep ' collection-id' cabal.project.freeze;"
+        , "      rm -rf dist-newstyle/;"
+        , "      $(CABAL} new-build -w ${HC} ${TEST} ${BENCH} --project-file=\"" ++ projectFile ++ "\" --dep -j2 all;"
+        , "    done"
+        , ""
+        ]
+
+    forM_ pkgs $ \Pkg{pkgDir} -> tellStrLns
+        [ sh $ "if [ -f \"" ++ pkgDir ++ "/configure.ac\" ]; then (cd \"" ++ pkgDir ++ "\" && autoreconf -i); fi"
+        ]
+
+    let quotedRmPaths =
+          ".ghc.environment.*"
+          ++ " " ++
+          unwords (quotedPaths (\Pkg{pkgDir} -> pkgDir ++ "/dist"))
+
+    tellStrLns
+        [ sh $ "rm -f cabal.project.freeze"
+        ]
+
+    -- Install dependencies
+    when (cfgInstallDeps config) $ do
+        tellStrLns
+            -- dump install plan
+            [ sh $ "${CABAL} new-freeze -w ${HC} ${TEST} ${BENCH} --project-file=\"" ++ projectFile ++"\" --dry"
+            , sh $ "cat \"" ++ projectFile ++ ".freeze\" | sed -E 's/^(constraints: *| *)//' | sed 's/any.//'"
+            , sh $ "rm  \"" ++ projectFile ++ ".freeze\""
+
+            -- install dependencies
+            , sh $ "${CABAL} new-build -w ${HC} ${TEST} ${BENCH} --project-file=\"" ++ projectFile ++"\" --dep -j2 all"
+            ]
+        tellStrLns
+            [ shForJob versions' (cfgNoTestsNoBench config) $ "${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks --project-file=\"" ++ projectFile ++ "\" --dep -j2 all"
+            ]
+
+    tellStrLns
+        [ sh $ "rm -rf " ++ quotedRmPaths
+        , sh $  "DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)"
+        ]
+
+    tellStrLns
+        [ ""
+        , "# 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:"
+        , "  # test that source-distributions can be generated"
+        ]
+
+    foldedTellStrLns FoldSDist "Packaging..." folds $ do
+        tellStrLns
+            [ sh $ "${CABAL} new-sdist all"
+            ]
+
+    let tarFiles = "dist-newstyle" </> "sdist" </> "*.tar.gz"
+
+
+    foldedTellStrLns FoldUnpack "Unpacking..." folds $ do
+        tellStrLns
+            [ sh $ "mv " ++ tarFiles ++ " ${DISTDIR}/"
+            , sh $ "cd ${DISTDIR} || false" -- fail explicitly, makes SC happier
+            , sh $ "find . -maxdepth 1 -name '*.tar.gz' -exec tar -xvf '{}' \\;"
+            ]
+        generateCabalProject True
+
+    unless (equivVersionRanges noVersion $ cfgNoTestsNoBench config) $ foldedTellStrLns FoldBuild "Building..." folds $ tellStrLns
+        [ comment "this builds all libraries and executables (without tests/benchmarks)"
+        , shForJob versions' (cfgNoTestsNoBench config) $ "${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks all"
+        ]
+
+    tellStrLns [""]
+
+
+    foldedTellStrLns FoldBuildEverything
+        "Building with tests and benchmarks..." folds $ tellStrLns
+        [ comment "build & run tests, build benchmarks"
+        , sh "${CABAL} new-build -w ${HC} ${TEST} ${BENCH} all"
+        ]
+
+    -- cabal new-test fails if there are no test-suites.
+    when hasTests $
+        foldedTellStrLns FoldTest "Testing..." folds $ tellStrLns
+            [ sh $ mconcat
+                [ "if [ \"x$TEST\" = \"x--enable-tests\" ]; then "
+                , if cfgNoise config
+                     then "${CABAL} "
+                     else "(set -o pipefail; ${CABAL} -vnormal+nowrap+markoutput "
+                , "new-test -w ${HC} ${TEST} ${BENCH} all"
+                , if cfgNoise config
+                     then ""
+                     else " 2>&1 | sed '/^-----BEGIN CABAL OUTPUT-----$/,/^-----END CABAL OUTPUT-----$/d' )"
+                , "; fi"
+                ]
+            ]
+
+    tellStrLns [""]
+
+    when (cfgDoctestEnabled doctestConfig) $ do
+        let doctestOptions = unwords $ cfgDoctestOptions doctestConfig
+        tellStrLns [ comment "doctest" ]
+        foldedTellStrLns FoldDoctest "Doctest..." folds $ do
+            forM_ pkgs $ \Pkg{pkgName,pkgGpd,pkgJobs} -> do
+                forM_ (doctestArgs pkgGpd) $ \args -> do
+                    let args' = unwords args
+                    unless (null args) $ tellStrLns
+                        [ shForJob versions' (doctestJobVersionRange `intersectVersionRanges` pkgJobs) $
+                          "(cd " ++ pkgName ++ "-* && doctest " ++ doctestOptions ++ " " ++ args' ++ ")"
+                        ]
+        tellStrLns [ "" ]
+
+    when (cfgHLintEnabled hlintConfig) $ do
+        let "" <+> ys = ys
+            xs <+> "" = xs
+            xs <+> ys = xs ++ " " ++ ys
+
+            prependSpace "" = ""
+            prependSpace xs = " " ++ xs
+
+        let hlintOptions = prependSpace $ maybe "" ("-h ${ROOTDIR}/" ++) (cfgHLintYaml hlintConfig) <+> unwords (cfgHLintOptions hlintConfig)
+
+        tellStrLns [ comment "hlint" ]
+        foldedTellStrLns FoldHLint "HLint.." folds $ do
+            forM_ pkgs $ \Pkg{pkgName,pkgGpd,pkgJobs} -> do
+                -- note: similar arguments work so far for doctest and hlint
+                forM_ (hlintArgs pkgGpd) $ \args -> do
+                    let args' = unwords args
+                    unless (null args) $ tellStrLns
+                        [ shForJob versions' (hlintJobVersionRange versions (cfgHLintJob hlintConfig) `intersectVersionRanges` pkgJobs) $
+                          "(cd " ++ pkgName ++ "-* && hlint" ++ hlintOptions ++ " " ++ args' ++ ")"
+                        ]
+        tellStrLns [ "" ]
+
+    when (cfgCheck config) $
+        foldedTellStrLns FoldCheck "cabal check..." folds $ do
+            tellStrLns [ comment "cabal check" ]
+            forM_ pkgs $ \Pkg{pkgName,pkgJobs} -> tellStrLns
+                [ shForJob versions' pkgJobs $
+                  "(cd " ++ pkgName ++ "-* && ${CABAL} check)"
+                ]
+            tellStrLns [ "" ]
+
+    when (hasLibrary && not (equivVersionRanges noVersion $ cfgHaddock config)) $
+        foldedTellStrLns FoldHaddock "Haddock..." folds $ tellStrLns
+            [ comment "haddock"
+            , shForJob versions' (cfgHaddock config) "${CABAL} new-haddock -w ${HC} ${TEST} ${BENCH} all"
+            , ""
+            ]
+
+    unless (null colls) $
+        foldedTellStrLns FoldStackage "Stackage builds..." folds $ tellStrLnsRaw
+            [ "  # try building & testing for package collections"
+            , "  - for COLL in \"${COLLS[@]}\"; do"
+            , "      echo \"== collection $COLL ==\";"
+            , "      ghc-travis collection ${COLL} > /dev/null || break;"
+            , "      ghc-travis collection ${COLL} | " ++ pkgFilter ++ " > cabal.project.freeze;"
+            , "      grep ' collection-id' cabal.project.freeze;"
+            , "      rm -rf dist-newstyle/;"
+            , "      ${CABAL} new-build -w ${HC} ${TEST} ${BENCH} all || break;"
+            , "      if [ \"x$TEST\" = \"x--enable-tests\" ]; then ${CABAL} new-test -w ${HC} ${TEST} ${BENCH} all || break; fi;"
+            , "    done"
+            , ""
+            ]
+
+    -- Have to build last, as we remove cabal.project.local
+    unless (equivVersionRanges noVersion $ cfgUnconstrainted config) $ foldedTellStrLns FoldBuildInstalled
+        "Building without installed constraints for packages in global-db..." folds $ tellStrLns
+        [ comment "Build without installed constraints for packages in global-db"
+        , shForJob versions' (cfgUnconstrainted config) "rm -f cabal.project.local; ${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks all;"
+        , ""
+        ]
+
+    -- and now, as we don't have cabal.project.local;
+    -- we can test with other constraint sets
+    let constraintSets = cfgConstraintSets config
+    unless (null constraintSets) $ do
+        tellStrLns
+            [ comment "Constraint sets"
+            , sh "rm -rf cabal.project.local"
+            , ""
+            ]
+        forM_ constraintSets $ \cs -> do
+            let name = csName cs
+            let constraintFlags = concatMap (\x ->  " --constraint='" ++ x ++ "'") (csConstraints cs)
+            let cmd | csRunTests cs = "${CABAL} new-test  -w ${HC} --enable-tests  --enable-benchmarks"
+                    | otherwise     = "${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks"
+            tellStrLns [ comment $ "Constraint set " ++ name ]
+            foldedTellStrLns' FoldConstraintSets name ("Constraint set " ++ name) folds $ tellStrLns
+                [ shForJob versions' (csGhcVersions cs) $
+                  cmd ++ " " ++ constraintFlags ++ " all"
+                , ""
+                ]
+        tellStrLns [""]
+
+    tellStrLnsRaw
+        [ "# REGENDATA " ++ show argv
+        , "# EOF"
+        ]
+
+    return ()
+  where
+    doctestConfig = cfgDoctest config
+    hlintConfig   = cfgHLint config
+
+    hasTests   = F.any (\Pkg{pkgGpd} -> not . null $ condTestSuites pkgGpd) pkgs
+    hasLibrary = F.any (\Pkg{pkgGpd} -> isJust $ condLibrary pkgGpd) pkgs
+
+    -- GHC versions which need head.hackage
+    headGhcVers = S.filter previewGHC versions
+
+    generateCabalProject dist = do
+        tellStrLns
+            [ sh "rm -f cabal.project"
+            , sh "touch cabal.project"
+            ]
+        F.forM_  pkgs $ \pkg -> do
+            let p | dist      = pkgName pkg ++ "-*/*.cabal"
+                  | otherwise = pkgDir pkg
+            tellStrLns $
+                [ shForJob versions' (pkgJobs pkg) $ "printf 'packages: \"" ++ p ++ "\"\\n' >> cabal.project"
+                ]
+
+        case cfgCopyFields config of
+            CopyFieldsNone -> return ()
+            CopyFieldsAll  -> unless (null (prjOrigFields prj)) $ tellStrLns
+                [ sh $ "echo '" ++ l ++ "' >> cabal.project"
+                | l <- lines $ C.showFields' 2 $ prjOrigFields prj
+                , not (null l)
+                ]
+            CopyFieldsSome -> do
+                F.forM_ (prjConstraints prj) $ \xs -> do
+                    let s = concat (lines xs)
+                    tellStrLns
+                        [ sh $ "echo 'constraints: " ++ s ++ "' >> cabal.project"
+                        ]
+                F.forM_ (prjAllowNewer prj) $ \xs -> do
+                    let s = concat (lines xs)
+                    tellStrLns
+                        [ sh $ "echo 'allow-newer: " ++ s ++ "' >> cabal.project"
+                        ]
+                unless (null (cfgLocalGhcOptions config)) $ forM_ pkgs $ \Pkg{pkgName} -> do
+                    let s = unwords $ map (show . PU.showToken) $ cfgLocalGhcOptions config
+                    tellStrLns
+                        [ sh $ "echo 'package " ++ pkgName ++ "' >> cabal.project"
+                        , sh $ "echo '  ghc-options: " ++ s ++ "' >> cabal.project"
+                        ]
+
+                when (prjReorderGoals prj) $
+                    tellStrLns
+                        [ sh $ "echo 'reorder-goals: True' >> cabal.project"
+                        ]
+
+                F.forM_ (prjMaxBackjumps prj) $ \bj ->
+                    tellStrLns
+                        [ sh $ "echo 'max-backjumps: " ++ show bj ++ "' >> cabal.project"
+                        ]
+
+                case prjOptimization prj of
+                    OptimizationOn      -> return ()
+                    OptimizationOff     -> tellStrLns [ sh $ "echo 'optimization: False' >> cabal.project " ]
+                    OptimizationLevel l -> tellStrLns [ sh $ "echo 'optimization: " ++ show l ++ "' >> cabal.project " ]
+
+                F.forM_ (prjSourceRepos prj) $ \repo -> do
+                    let repo' = PP.render $ C.prettyFieldGrammar (C.sourceRepoFieldGrammar $ C.RepoKindUnknown "unused") repo
+                    tellStrLns [ sh $ "echo 'source-repository-package' >> cabal.project" ]
+                    tellStrLns [ sh $ "echo '  " ++ l ++ "' >> cabal.project" | l <- lines repo' ]
+
+        -- mandatory cabal.project setup
+        tellStrLns
+            [ sh $ "printf 'write-ghc-environment-files: always\\n' >> cabal.project"
+            ]
+
+        unless (null (cfgRawProject config)) $ tellStrLns
+            [ sh $ "echo '" ++ l ++ "' >> cabal.project"
+            | l <- lines $ C.showFields' 2 $ cfgRawProject config
+            , not (null l)
+            ]
+
+        -- also write cabal.project.local file with
+        -- @
+        -- constraints: base installed
+        -- constraints: array installed
+        -- ...
+        --
+        -- omitting any local package names
+        case normaliseInstalled (cfgInstalled config) of
+            InstalledDiff pns -> tellStrLns
+                [ sh $ "touch cabal.project.local"
+                , sh $ unwords
+                    [ "for pkg in $($HCPKG list --simple-output); do"
+                    , "echo $pkg"
+                    , "| sed 's/-[^-]*$//'"
+                    , "| grep -vE -- " ++ re
+                    , "| sed 's/^/constraints: /'"
+                    , "| sed 's/$/ installed/'"
+                    , ">> cabal.project.local; done"
+                    ]
+                ]
+              where
+                pns' = S.map unPackageName pns `S.union` foldMap (S.singleton . pkgName) pkgs
+                re = "'^(" ++ intercalate "|" (S.toList pns') ++ ")$'"
+
+            InstalledOnly pns | not (null pns') -> tellStrLns
+                [ sh $ "touch cabal.project.local"
+                , sh' [2043] $ unwords
+                    [ "for pkg in " ++ unwords (S.toList pns') ++ "; do"
+                    , "echo \"constraints: $pkg installed\" >> cabal.project"
+                    , ">> cabal.project.local; done"
+                    ]
+                ]
+              where
+                pns' = S.map unPackageName pns `S.difference` foldMap (S.singleton . pkgName) pkgs
+
+            -- otherwise: nothing
+            _ -> pure ()
+
+        tellStrLns
+            [ sh $ "cat cabal.project || true"
+            , sh $ "cat cabal.project.local || true"
+            ]
+
+    projectFile :: FilePath
+    projectFile = fromMaybe "cabal.project" isCabalProject
+
+    quotedPaths :: (Package -> FilePath) -> [String]
+    quotedPaths f = map (f . quote) pkgs
+      where
+        quote pkg = pkg{ pkgDir = "\"" ++ pkgDir pkg ++ "\"" }
+
+    showVersions :: Set (Maybe Version) -> String
+    showVersions = unwords . map dispGhcVersion . S.toList
+
+    -- specified ersions
+    osxVersions' :: Set Version
+    osxVersions' = cfgOsx config
+
+    versions :: Set (Maybe Version)
+    versions
+        | cfgGhcHead config = S.insert Nothing $ S.map Just versions'
+        | otherwise         = S.map Just versions'
+
+    ghcVersions :: String
+    ghcVersions = showVersions versions
+
+    osxVersions, omittedOsxVersions :: Set Version
+    (osxVersions, omittedOsxVersions) = S.partition (`S.member` versions') osxVersions'
+
+    ghcOsxVersions :: String
+    ghcOsxVersions = showVersions $ S.map Just osxVersions
+
+    ghcOmittedOsxVersions :: String
+    ghcOmittedOsxVersions = showVersions $ S.map Just omittedOsxVersions
+
+
+collToGhcVer :: String -> Version
+collToGhcVer cid = case simpleParse cid of
+  Nothing -> error ("invalid collection-id syntax " ++ show cid)
+  Just (PackageIdentifier n (versionNumbers -> v))
+    | display n /= "lts" -> error ("unknown collection " ++ show cid)
+    | isPrefixOf [0] v -> mkVersion [7,8,3]
+    | isPrefixOf [1] v -> mkVersion [7,8,4]
+    | isPrefixOf [2] v -> mkVersion [7,8,4]
+    | isPrefixOf [3] v -> mkVersion [7,10,2]
+    | isPrefixOf [4] v -> mkVersion [7,10,3]
+    | isPrefixOf [5] v -> mkVersion [7,10,3]
+    | isPrefixOf [6] v -> mkVersion [7,10,3]
+    | isPrefixOf [7] v -> mkVersion [8,0,1]
+    | otherwise -> error ("unknown collection " ++ show cid)
+
+-------------------------------------------------------------------------------
+-- Doctest
+-------------------------------------------------------------------------------
+
+doctestJobVersionRange :: VersionRange
+doctestJobVersionRange = orLaterVersion $ mkVersion [8,0]
+
+-- | Modules arguments to the library
+--
+-- * We check the library component
+--
+-- * If there are hs-source-dirs, use them
+--
+-- * otherwise use exposed + other modules
+--
+-- * Also add default-extensions
+--
+-- /Note:/ same argument work for hlint too, but not exactly
+--
+doctestArgs :: GenericPackageDescription -> [[String]]
+doctestArgs gpd =
+    [ libraryModuleArgs c
+    | c <- flattenPackageDescription gpd ^.. L.library . traverse
+    ] ++
+    [ libraryModuleArgs c
+    | c <- flattenPackageDescription gpd ^.. L.subLibraries . traverse
+    ]
+
+libraryModuleArgs :: PD.Library -> [String]
+libraryModuleArgs l
+    | null dirsOrMods = []
+    | otherwise       = exts ++ dirsOrMods
+  where
+    bi = l ^. L.buildInfo
+
+    dirsOrMods
+        | null (PD.hsSourceDirs bi) = map display (PD.exposedModules l)
+        | otherwise                 = PD.hsSourceDirs bi
+
+    exts = map (("-X" ++) . display) (PD.defaultExtensions bi)
+
+executableModuleArgs :: PD.Executable -> [String]
+executableModuleArgs e
+    | null dirsOrMods = []
+    | otherwise       = exts ++ dirsOrMods
+  where
+    bi = e ^. L.buildInfo
+
+    dirsOrMods
+        -- note: we don't try to find main_is location, if hsSourceDirs is empty.
+        | null (PD.hsSourceDirs bi) = map display (PD.otherModules bi)
+        | otherwise                 = PD.hsSourceDirs bi
+
+    exts = map (("-X" ++) . display) (PD.defaultExtensions bi)
+
+-------------------------------------------------------------------------------
+-- HLint
+-------------------------------------------------------------------------------
+
+hlintJobVersionRange :: Set (Maybe Version) -> HLintJob -> VersionRange
+hlintJobVersionRange vs HLintJobLatest = case S.maxView vs of
+    Just (Just v, _) -> thisVersion v
+    _                -> thisVersion $ mkVersion [8,6,3]
+hlintJobVersionRange _ (HLintJob v)   = thisVersion v
+
+hlintArgs :: GenericPackageDescription -> [[String]]
+hlintArgs gpd =
+    [ libraryModuleArgs c
+    | c <- flattenPackageDescription gpd ^.. L.library . traverse
+    ] ++
+    [ libraryModuleArgs c
+    | c <- flattenPackageDescription gpd ^.. L.subLibraries . traverse
+    ] ++
+    [ executableModuleArgs c
+    | c <- flattenPackageDescription gpd ^.. L.executables . traverse
+    ]
diff --git a/src/HaskellCI/Cli.hs b/src/HaskellCI/Cli.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/Cli.hs
@@ -0,0 +1,91 @@
+-- | Most of client interface.
+module HaskellCI.Cli where
+
+import           Prelude                  ()
+import           Prelude.Compat
+
+import           Control.Applicative      ((<|>))
+import           System.Exit              (exitFailure)
+import           System.IO                (hPutStrLn, stderr)
+
+import qualified Options.Applicative      as O
+
+import           HaskellCI.Config
+import           HaskellCI.OptparseGrammar
+
+-------------------------------------------------------------------------------
+-- Command
+-------------------------------------------------------------------------------
+
+data Command
+    = CommandTravis FilePath
+    | CommandRegenerate
+    | CommandListGHC
+    | CommandDumpConfig
+  deriving Show
+
+-------------------------------------------------------------------------------
+-- Options
+-------------------------------------------------------------------------------
+
+data Options = Options
+    { optCollections    :: [String]
+    , optOutput         :: Maybe FilePath
+    , optConfig         :: Maybe FilePath
+    , optConfigMorphism :: Config -> Config
+    }
+
+instance Semigroup Options where
+    Options a b d e <> Options a' b' d' e' =
+        Options (a <> a') (b <|> b') (d <|> d') (e' . e)
+
+defaultOptions :: Options
+defaultOptions = Options
+    { optCollections    = []
+    , optOutput         = Nothing
+    , optConfig         = Nothing
+    , optConfigMorphism = id
+    }
+
+-------------------------------------------------------------------------------
+-- Parsers
+-------------------------------------------------------------------------------
+
+optionsP :: O.Parser Options
+optionsP = Options
+    <$> pure []
+    <*> O.optional (O.strOption (O.long "output" <> O.short 'o' <> O.metavar "FILE" <> O.help "Optput file (stdout if omitted)"))
+    <*> O.optional (O.strOption (O.long "config" <> O.metavar "CONFIGFILE" <> O.help "Configuration file"))
+    <*> runOptparseGrammar configGrammar
+
+cliParserInfo :: O.ParserInfo (Command, Options)
+cliParserInfo = O.info ((,) <$> cmdP <*> optionsP O.<**> O.helper) $ mconcat
+    [ O.fullDesc
+    , O.header "haskell-ci - generate CI scripts for Haskell projects"
+    ]
+  where
+    cmdP = O.subparser (mconcat
+        [ O.command "regenerate"  $ O.info (pure CommandRegenerate) $ O.progDesc "Regenerate .travis.yml"
+        , O.command "travis"      $ O.info travisP                  $ O.progDesc "Generate travis-ci config"
+        , O.command "list-ghc"    $ O.info (pure CommandListGHC)    $ O.progDesc "List known GHC versions"
+        , O.command "dump-config" $ O.info (pure CommandDumpConfig) $ O.progDesc "Dump cabal.haskell-ci config with default values" 
+        ]) <|> travisP
+
+    travisP = CommandTravis
+        <$> O.strArgument (O.metavar "CABAL.FILE" <> O.help "Either <pkg.cabal> or cabal.project")
+
+-------------------------------------------------------------------------------
+-- Parsing helpers
+-------------------------------------------------------------------------------
+
+parseTravis :: [String] -> IO (FilePath, Options)
+parseTravis argv = case res of
+    O.Success x -> return x
+    O.Failure f -> case O.renderFailure f "haskell-ci" of
+        (help, _) -> hPutStrLn stderr help >> exitFailure
+    O.CompletionInvoked _ -> exitFailure -- unexpected
+  where
+    res = O.execParserPure (O.prefs mempty) (O.info ((,) <$> cmdP <*> optionsP) mempty) argv
+    cmdP = O.strArgument (O.metavar "CABAL.FILE" <> O.help "Either <pkg.cabal> or cabal.project")
+
+
diff --git a/src/HaskellCI/Config.hs b/src/HaskellCI/Config.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/Config.hs
@@ -0,0 +1,263 @@
+{-# LANGUAGE FlexibleInstances     #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedLabels      #-}
+{-# LANGUAGE OverloadedStrings     #-}
+module HaskellCI.Config where
+
+import           Control.Monad.IO.Class          (MonadIO (..))
+import           Data.Coerce                     (coerce)
+import           Data.Generics.Labels            ()
+import           Distribution.Simple.Utils       (fromUTF8BS)
+import           Distribution.Types.Version      (Version)
+import           Distribution.Types.VersionRange (VersionRange, anyVersion,
+                                                  noVersion)
+import           GHC.Generics                    (Generic)
+import           Lens.Micro                      (over)
+
+import qualified Data.ByteString                 as BS
+import qualified Data.Map                        as M
+import qualified Data.Set                        as S
+import qualified Distribution.CabalSpecVersion   as C
+import qualified Distribution.Compat.CharParsing as C
+import qualified Distribution.Compat.Newtype     as C
+import qualified Distribution.FieldGrammar       as C
+import qualified Distribution.Fields.Pretty      as C
+import qualified Distribution.Parsec.Class       as C
+import qualified Distribution.Parsec.Common      as C
+import qualified Distribution.Parsec.Newtypes    as C
+import qualified Distribution.Parsec.Parser      as C
+import qualified Distribution.Parsec.ParseResult as C
+import qualified Distribution.Pretty             as C
+import qualified Distribution.Types.Version      as C
+import qualified Text.PrettyPrint                as PP
+
+import           HaskellCI.Config.ConstraintSet
+import           HaskellCI.Config.CopyFields
+import           HaskellCI.Config.Doctest
+import           HaskellCI.Config.Folds
+import           HaskellCI.Config.HLint
+import           HaskellCI.Config.Installed
+import           HaskellCI.Config.Jobs
+import           HaskellCI.Newtypes
+import           HaskellCI.OptionsGrammar
+import           HaskellCI.ParsecUtils
+import           HaskellCI.TestedWith
+
+-- TODO: split other blocks like DoctestConfig
+data Config = Config
+    { cfgCabalInstallVersion :: Maybe Version
+    , cfgJobs                :: Maybe Jobs
+    , cfgTestedWith          :: !TestedWithJobs
+    , cfgCopyFields          :: !CopyFields
+    , cfgLocalGhcOptions     :: [String]
+    , cfgCache               :: !Bool
+    , cfgNoise               :: !Bool
+    , cfgInstallDeps         :: !Bool
+    , cfgInstalled           :: [Installed]
+    , cfgTests               :: !VersionRange
+    , cfgBenchmarks          :: !VersionRange
+    , cfgHaddock             :: !VersionRange
+    , cfgNoTestsNoBench      :: !VersionRange
+    , cfgUnconstrainted      :: !VersionRange
+    , cfgCheck               :: !Bool
+    , cfgOnlyBranches        :: [String]
+    , cfgIrcChannels         :: [String]
+    , cfgProjectName         :: Maybe String
+    , cfgFolds               :: S.Set Fold
+    , cfgGhcHead             :: !Bool
+    , cfgPostgres            :: !Bool
+    , cfgEnv                 :: M.Map Version String
+    , cfgAllowFailures       :: !VersionRange
+    , cfgLastInSeries        :: !Bool
+    , cfgOsx                 :: S.Set Version
+    , cfgApt                 :: S.Set String
+    , cfgDoctest             :: !DoctestConfig
+    , cfgHLint               :: !HLintConfig
+    , cfgConstraintSets      :: [ConstraintSet]
+    , cfgRawProject          :: [C.PrettyField]
+    }
+  deriving (Show, Generic)
+
+defaultCabalInstallVersion :: Maybe Version
+defaultCabalInstallVersion = Just (C.mkVersion [2,4])
+
+emptyConfig :: Config
+emptyConfig = Config
+    { cfgCabalInstallVersion = defaultCabalInstallVersion
+    , cfgJobs            = Nothing
+    , cfgTestedWith      = TestedWithUniform
+    , cfgCopyFields      = CopyFieldsSome
+    , cfgDoctest         = DoctestConfig
+        { cfgDoctestEnabled = False
+        , cfgDoctestOptions = []
+        , cfgDoctestVersion = defaultDoctestVersion
+        }
+    , cfgHLint = HLintConfig
+        { cfgHLintEnabled = False
+        , cfgHLintJob     = HLintJobLatest
+        , cfgHLintYaml    = Nothing
+        , cfgHLintVersion = defaultHLintVersion
+        , cfgHLintOptions = []
+        }
+    , cfgLocalGhcOptions = []
+    , cfgConstraintSets  = []
+    , cfgCache           = True
+    , cfgNoise           = True
+    , cfgInstalled       = []
+    , cfgInstallDeps     = True
+    , cfgTests           = anyVersion
+    , cfgBenchmarks      = anyVersion
+    , cfgHaddock         = anyVersion
+    , cfgNoTestsNoBench  = anyVersion
+    , cfgUnconstrainted  = anyVersion
+    , cfgCheck           = True
+    , cfgOnlyBranches    = []
+    , cfgIrcChannels     = []
+    , cfgProjectName     = Nothing
+    , cfgFolds           = S.empty
+    , cfgGhcHead         = False
+    , cfgPostgres        = False
+    , cfgEnv             = M.empty
+    , cfgAllowFailures   = noVersion
+    , cfgLastInSeries    = False
+    , cfgOsx             = S.empty
+    , cfgApt             = S.empty
+    , cfgRawProject      = []
+    }
+
+-------------------------------------------------------------------------------
+-- Grammar
+-------------------------------------------------------------------------------
+
+configGrammar
+    :: (OptionsGrammar g, Applicative (g Config), Applicative (g DoctestConfig), Applicative (g HLintConfig))
+    => g Config Config
+configGrammar = Config
+    <$> C.optionalFieldDefAla "cabal-install-version"     HeadVersion                         #cfgCabalInstallVersion defaultCabalInstallVersion
+        ^^^ metahelp "VERSION" "cabal-install version for all jobs"
+    <*> C.optionalField       "jobs"                                                          #cfgJobs
+        ^^^ metahelp "JOBS" "jobs (N:M - cabal:ghc)"
+    <*> C.optionalFieldDef    "jobs-selection"                                                #cfgTestedWith TestedWithUniform
+        ^^^ metahelp "uniform|any" "Jobs selection across packages"
+    <*> C.optionalFieldDef    "copy-fields"                                                   #cfgCopyFields CopyFieldsSome
+        ^^^ metahelp "none|some|all" "Copy ? fields from cabal.project fields"
+    <*> C.monoidalFieldAla    "local-ghc-options"         (C.alaList' C.NoCommaFSep C.Token') #cfgLocalGhcOptions
+        ^^^ metahelp "OPTS" "--ghc-options for local packages"
+    <*> C.booleanFieldDef     "cache"                                                         #cfgCache True
+        ^^^ help "Disable caching"
+    <*> C.booleanFieldDef     "cabal-noise"                                                   #cfgNoise True
+        ^^^ help "Make cabal less noisy"
+    <*> C.booleanFieldDef     "install-dependencies"                                          #cfgInstallDeps True
+        ^^^ help "Skip separate dependency installation step"
+    <*> C.monoidalFieldAla    "installed"                 (C.alaList C.FSep)                  #cfgInstalled
+        ^^^ metahelp "+/-PKG" "Specify 'constraint: ... installed' packages"
+    <*> rangeField            "tests"                                                         #cfgTests anyVersion
+        ^^^ metahelp "RANGE" "Build and run tests with"
+    <*> rangeField           "benchmarks"                                                     #cfgBenchmarks anyVersion
+        ^^^ metahelp "RANGE" "Build benchmarks"
+    <*> rangeField           "haddock"                                                        #cfgHaddock anyVersion
+        ^^^ metahelp "RANGE" "Haddock step"
+    <*> rangeField           "no-tests-no-benchmarks"                                         #cfgNoTestsNoBench anyVersion
+        ^^^ metahelp "RANGE" "Build without tests and benchmarks"
+    <*> rangeField            "unconstrained"                                                 #cfgUnconstrainted anyVersion
+        ^^^ metahelp "RANGE" "Make unconstrained build"
+    <*> C.booleanFieldDef "cabal-check"                                                       #cfgCheck True
+        ^^^ help "Disable cabal check run"
+    <*> C.monoidalFieldAla    "branches"                  (C.alaList' C.FSep C.Token')        #cfgOnlyBranches
+        ^^^ metahelp "BRANCH" "Enable builds only for specific branches"
+    <*> C.monoidalFieldAla    "irc-channels"              (C.alaList' C.FSep C.Token')        #cfgIrcChannels
+        ^^^ metahelp "IRC" "Enable IRC notifications to given channel (e.g. 'irc.freenode.org#haskell-lens')"
+    <*> C.optionalFieldAla    "project-name"              C.Token'                            #cfgProjectName
+        ^^^ metahelp "NAME" "Project name (used for IRC notifications), defaults to package name or name of first package listed in cabal.project file"
+    <*> C.monoidalFieldAla    "folds"                     Folds                               #cfgFolds
+        ^^^ metahelp "FOLD" "Build steps to fold"
+    <*> C.booleanFieldDef     "ghc-head"                                                      #cfgGhcHead False
+        ^^^ help "Add ghc-head job"
+    <*> C.booleanFieldDef     "postgresql"                                                    #cfgPostgres False
+        ^^^ help "Add postgresql service"
+    <*> C.monoidalFieldAla    "env"                       Env                                 #cfgEnv
+        ^^^ metahelp "ENV" "Environment variables per job (e.g. `8.0.2:HADDOCK=false`)"
+    <*> C.optionalFieldDefAla "allow-failures"            Range                               #cfgAllowFailures noVersion
+        ^^^ metahelp "JOB" "Allow failures of particular GHC version"
+    <*> C.booleanFieldDef     "last-in-series"                                                #cfgLastInSeries False
+        ^^^ help "[Discouraged] Assume there are only GHCs last in major series: 8.2.* will match only 8.2.2"
+    <*> C.monoidalFieldAla    "osx"                       (alaSet C.NoCommaFSep)              #cfgOsx
+        ^^^ metahelp "JOB" "Jobs to additionally build with OSX"
+    <*> C.monoidalFieldAla    "apt"                       (alaSet' C.NoCommaFSep C.Token')    #cfgApt
+        ^^^ metahelp "PKG" "Additional apt packages to install"
+    <*> C.blurFieldGrammar #cfgDoctest doctestConfigGrammar
+    <*> C.blurFieldGrammar #cfgHLint   hlintConfigGrammar
+    <*> pure [] -- constraint sets
+    <*> pure [] -- raw project fields
+
+-------------------------------------------------------------------------------
+-- Reading
+-------------------------------------------------------------------------------
+
+readConfigFile :: MonadIO m => FilePath -> m Config
+readConfigFile = liftIO . readAndParseFile parseConfigFile
+
+parseConfigFile :: [C.Field C.Position] -> C.ParseResult Config
+parseConfigFile fields0 = do
+    config <- C.parseFieldGrammar C.cabalSpecLatest fields configGrammar
+    config' <- traverse parseSection $ concat sections
+    return (foldr (.) id config' config)
+  where
+    (fields, sections) = C.partitionFields fields0
+
+    parseSection :: C.Section C.Position -> C.ParseResult (Config -> Config)
+    parseSection (C.MkSection (C.Name pos name) args cfields)
+        | name == "constraint-set" = do
+            name' <- parseName pos args
+            let (fs, _sections) = C.partitionFields cfields
+            cs <- C.parseFieldGrammar C.cabalSpecLatest fs (constraintSetGrammar name')
+            return $ over #cfgConstraintSets (cs :)
+        | name == "raw-project" = do
+            let fs = C.fromParsecFields cfields
+            return $ over #cfgRawProject (++ fs)
+        | otherwise = do
+            C.parseWarning pos C.PWTUnknownSection $ "Unknown section " ++ fromUTF8BS name
+            return id
+
+-------------------------------------------------------------------------------
+-- Env
+-------------------------------------------------------------------------------
+
+newtype Env = Env (M.Map Version String)
+
+instance C.Newtype Env (M.Map Version String) where
+    pack = coerce
+    unpack = coerce
+
+instance C.Parsec Env where
+    parsec = Env . M.fromList <$> C.parsecLeadingCommaList p where
+        p = do
+            v <- C.parsec
+            _ <- C.char ':'
+            s <- C.munch1 $ \c -> c /= ','
+            return (v, s)
+
+instance C.Pretty Env where
+    pretty (Env m) = PP.fsep . PP.punctuate PP.comma . map p . M.toList $ m where
+        p (v, s) = C.pretty v PP.<> PP.colon PP.<> PP.text s
+
+-------------------------------------------------------------------------------
+-- From Cabal
+-------------------------------------------------------------------------------
+
+parseName :: C.Position -> [C.SectionArg C.Position] -> C.ParseResult String
+parseName pos args = fromUTF8BS <$> parseNameBS pos args
+
+parseNameBS :: C.Position -> [C.SectionArg C.Position] -> C.ParseResult BS.ByteString
+parseNameBS pos args = case args of
+    [C.SecArgName _pos secName] ->
+         pure secName
+    [C.SecArgStr _pos secName] ->
+         pure secName
+    [] -> do
+         C.parseFailure pos "name required"
+         pure ""
+    _ -> do
+         -- TODO: pretty print args
+         C.parseFailure pos $ "Invalid name " ++ show args
+         pure ""
diff --git a/src/HaskellCI/Config/ConstraintSet.hs b/src/HaskellCI/Config/ConstraintSet.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/Config/ConstraintSet.hs
@@ -0,0 +1,35 @@
+{-# LANGUAGE OverloadedLabels  #-}
+{-# LANGUAGE OverloadedStrings #-}
+module HaskellCI.Config.ConstraintSet where
+
+import           Data.Generics.Labels         ()
+import           Distribution.Version
+import           GHC.Generics                 (Generic)
+
+import qualified Distribution.FieldGrammar    as C
+import qualified Distribution.Parsec.Newtypes as C
+
+import           HaskellCI.Newtypes
+
+data ConstraintSet = ConstraintSet
+    { csName        :: String
+    , csGhcVersions :: VersionRange
+    , csConstraints :: [String] -- we parse these simply as strings
+    , csRunTests    :: Bool
+    }
+  deriving (Show, Generic)
+
+emptyConstraintSet :: String -> ConstraintSet
+emptyConstraintSet n = ConstraintSet n anyVersion [] False
+
+-------------------------------------------------------------------------------
+-- Grammar
+-------------------------------------------------------------------------------
+
+constraintSetGrammar
+    :: (C.FieldGrammar g, Applicative (g ConstraintSet))
+    => String -> g ConstraintSet ConstraintSet
+constraintSetGrammar name = ConstraintSet name
+    <$> C.optionalFieldDef "ghc"                                           #csGhcVersions anyVersion
+    <*> C.monoidalFieldAla "constraints" (C.alaList' C.CommaVCat NoCommas) #csConstraints
+    <*> C.booleanFieldDef  "run-tests"                                     #csRunTests False
diff --git a/src/HaskellCI/Config/CopyFields.hs b/src/HaskellCI/Config/CopyFields.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/Config/CopyFields.hs
@@ -0,0 +1,32 @@
+{-# LANGUAGE FlexibleInstances     #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+module HaskellCI.Config.CopyFields where
+
+import qualified Distribution.Compat.CharParsing as C
+import qualified Distribution.Parsec.Class       as C
+import qualified Distribution.Pretty             as C
+import qualified Text.PrettyPrint                as PP
+
+data CopyFields
+    = CopyFieldsNone
+    | CopyFieldsSome
+    | CopyFieldsAll
+  deriving (Eq, Ord, Show, Enum, Bounded)
+
+-------------------------------------------------------------------------------
+-- Functions
+-------------------------------------------------------------------------------
+
+showCopyFields :: CopyFields -> String
+showCopyFields CopyFieldsNone = "none"
+showCopyFields CopyFieldsSome = "some"
+showCopyFields CopyFieldsAll  = "all"
+
+instance C.Pretty CopyFields where
+    pretty = PP.text . showCopyFields
+
+instance C.Parsec CopyFields where
+    parsec = C.choice
+        [ f <$ C.string (showCopyFields f)
+        | f <- [ minBound .. maxBound ]
+        ]
diff --git a/src/HaskellCI/Config/Doctest.hs b/src/HaskellCI/Config/Doctest.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/Config/Doctest.hs
@@ -0,0 +1,37 @@
+{-# LANGUAGE OverloadedLabels  #-}
+{-# LANGUAGE OverloadedStrings #-}
+module HaskellCI.Config.Doctest where
+
+import           Data.Generics.Labels         ()
+import           Distribution.Version
+import           GHC.Generics                 (Generic)
+
+import qualified Distribution.FieldGrammar    as C
+import qualified Distribution.Parsec.Newtypes as C
+
+import HaskellCI.OptionsGrammar
+
+data DoctestConfig = DoctestConfig
+    { cfgDoctestEnabled :: !Bool
+    , cfgDoctestOptions :: [String]
+    , cfgDoctestVersion :: !VersionRange
+    }
+  deriving (Show, Generic)
+
+defaultDoctestVersion :: VersionRange
+defaultDoctestVersion = withinVersion (mkVersion [0,16])
+
+-------------------------------------------------------------------------------
+-- Grammar
+-------------------------------------------------------------------------------
+
+doctestConfigGrammar
+    :: (OptionsGrammar g, Applicative (g DoctestConfig))
+    => g DoctestConfig DoctestConfig
+doctestConfigGrammar = DoctestConfig
+    <$> C.booleanFieldDef  "doctest"                                             #cfgDoctestEnabled False
+        ^^^ help "Enable Doctest job"
+    <*> C.monoidalFieldAla "doctest-options" (C.alaList' C.NoCommaFSep C.Token') #cfgDoctestOptions
+        ^^^ metahelp "OPTS" "Additional Doctest options"
+    <*> C.optionalFieldDef "doctest-version"                                     #cfgDoctestVersion defaultDoctestVersion
+        ^^^ metahelp "RANGE" "Doctest version"
diff --git a/src/HaskellCI/Config/Dump.hs b/src/HaskellCI/Config/Dump.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/Config/Dump.hs
@@ -0,0 +1,58 @@
+module HaskellCI.Config.Dump where
+
+import           Prelude                     ()
+import           Prelude.Compat
+
+import           Data.Coerce                 (coerce)
+import           Distribution.Simple.Utils   (fromUTF8BS)
+
+import qualified Distribution.Compat.Newtype as C
+import qualified Distribution.FieldGrammar   as C
+import qualified Distribution.Pretty         as C
+
+import           HaskellCI.OptionsGrammar
+
+-- TODO: with Cabal-2.6 this can be prettier, using Pretty.Field
+newtype DumpGrammar s a = DG { runDG :: [String] }
+  deriving Functor
+
+instance Applicative (DumpGrammar s) where
+    pure _ = DG []
+    DG f <*> DG x = DG (f ++ x)
+
+instance C.FieldGrammar DumpGrammar where
+    blurFieldGrammar _ = coerce
+
+    uniqueFieldAla _ _ _ = DG []
+
+    booleanFieldDef fn _ def = DG
+        [ fromUTF8BS fn ++ ": " ++ C.prettyShow def
+        , ""
+        ]
+
+    optionalFieldAla fn _ _ = DG
+        [ fromUTF8BS fn ++ ":"
+        , ""
+        ]
+
+    optionalFieldDefAla fn c _ def = DG
+        [ fromUTF8BS fn ++ ": " ++ C.prettyShow (C.pack' c def)
+        , ""
+        ]
+
+    monoidalFieldAla fn _ _ = DG
+        [ fromUTF8BS fn ++ ":"
+        , ""
+        ]
+
+    prefixedFields _ _   = pure []
+    knownField _         = pure ()
+    deprecatedSince _  _ = id
+    availableSince _ _   = id
+    hiddenField          = id
+
+instance OptionsGrammar DumpGrammar where
+    metahelp _ = help
+
+    help h (DG xs) = DG $
+        ("-- " ++ h) : xs
diff --git a/src/HaskellCI/Config/Folds.hs b/src/HaskellCI/Config/Folds.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/Config/Folds.hs
@@ -0,0 +1,74 @@
+{-# LANGUAGE FlexibleInstances     #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+module HaskellCI.Config.Folds where
+
+import           Data.Char                       (isUpper, toLower)
+import           Data.Coerce                     (coerce)
+import           Data.List                       (intercalate)
+
+import qualified Data.Map.Strict                 as M
+import qualified Data.Set                        as S
+import qualified Distribution.Compat.CharParsing as C
+import qualified Distribution.Compat.Newtype     as C
+import qualified Distribution.Parsec.Class       as C
+import qualified Distribution.Pretty             as C
+import qualified Text.PrettyPrint                as PP
+
+data Fold
+    = FoldSDist
+    | FoldUnpack
+    | FoldBuild
+    | FoldBuildInstalled
+    | FoldBuildEverything
+    | FoldTest
+    | FoldHaddock
+    | FoldStackage
+    | FoldCheck
+    | FoldDoctest
+    | FoldHLint
+    | FoldConstraintSets
+  deriving (Eq, Ord, Show, Enum, Bounded)
+
+-------------------------------------------------------------------------------
+-- Functions
+-------------------------------------------------------------------------------
+
+showFold :: Fold -> String
+showFold = dashise . drop 4 . show
+  where
+    dashise = intercalate "-" . map (map toLower) . split
+
+    split [] = []
+    split xs0 =
+        let (ys, xs1) = span isUpper xs0
+            (zs, xs2) = break isUpper xs1
+        in (ys ++ zs) : split xs2
+
+possibleFolds :: [Fold]
+possibleFolds = [minBound .. maxBound]
+
+-------------------------------------------------------------------------------
+-- Folds
+-------------------------------------------------------------------------------
+
+newtype Folds = Folds { getFolds :: S.Set Fold }
+
+instance C.Newtype Folds (S.Set Fold) where
+    pack = coerce
+    unpack = coerce
+
+instance C.Parsec Folds where
+    parsec = fmap (Folds . S.unions) $ manySpaces $ do
+        t <- C.parsecToken'
+        case t of
+            "all"          -> return $ S.fromList possibleFolds
+            "all-but-test" -> return $  S.delete FoldTest $ S.fromList possibleFolds
+            n -> case M.lookup n ps of
+                Just n' -> return $ S.singleton n'
+                Nothing -> fail $ "Illegal fold name: " ++ n
+      where
+        ps = M.fromList $ map (\x -> (showFold x, x)) possibleFolds
+        manySpaces p = C.many (p <* C.spaces)
+
+instance C.Pretty Folds where
+    pretty = PP.fsep . map (PP.text . showFold) . S.toList . getFolds
diff --git a/src/HaskellCI/Config/HLint.hs b/src/HaskellCI/Config/HLint.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/Config/HLint.hs
@@ -0,0 +1,65 @@
+{-# LANGUAGE OverloadedLabels  #-}
+{-# LANGUAGE OverloadedStrings #-}
+module HaskellCI.Config.HLint where
+
+import           Control.Applicative             ((<|>))
+import           Data.Generics.Labels            ()
+import           Distribution.Version
+import           GHC.Generics                    (Generic)
+
+import qualified Distribution.Compat.CharParsing as C
+import qualified Distribution.FieldGrammar       as C
+import qualified Distribution.Parsec.Class       as C
+import qualified Distribution.Parsec.Newtypes    as C
+import qualified Distribution.Pretty             as C
+import qualified Text.PrettyPrint                as PP
+
+import HaskellCI.OptionsGrammar
+
+data HLintConfig = HLintConfig
+    { cfgHLintEnabled :: !Bool
+    , cfgHLintJob     :: !HLintJob
+    , cfgHLintYaml    :: !(Maybe FilePath)
+    , cfgHLintOptions :: [String]
+    , cfgHLintVersion :: !VersionRange
+    }
+  deriving (Show, Generic)
+
+defaultHLintVersion :: VersionRange
+defaultHLintVersion = withinVersion (mkVersion [2,1])
+
+-------------------------------------------------------------------------------
+-- HLintJob
+-------------------------------------------------------------------------------
+
+data HLintJob
+    = HLintJobLatest    -- ^ run with latest GHC
+    | HLintJob Version  -- ^ run with specified GHC version
+  deriving (Eq, Show)
+
+instance C.Parsec HLintJob where
+    parsec = HLintJobLatest <$ C.string "latest"
+        <|> HLintJob <$> C.parsec
+
+instance C.Pretty HLintJob where
+    pretty HLintJobLatest = PP.text "latest"
+    pretty (HLintJob v)   = C.pretty v
+
+-------------------------------------------------------------------------------
+-- Grammar
+-------------------------------------------------------------------------------
+
+hlintConfigGrammar
+    :: (OptionsGrammar g, Applicative (g HLintConfig))
+    => g HLintConfig HLintConfig
+hlintConfigGrammar = HLintConfig
+    <$> C.booleanFieldDef  "hlint"                                             #cfgHLintEnabled False
+        ^^^ help "Enable HLint job"
+    <*> C.optionalFieldDef "hlint-job"                                         #cfgHLintJob HLintJobLatest
+        ^^^ metahelp "JOB" "Specify HLint job"
+    <*> C.optionalFieldAla "hlint-yaml"    C.FilePathNT                        #cfgHLintYaml
+        ^^^ metahelp "PATH" "Use specific .hlint.yaml"
+    <*> C.monoidalFieldAla "hlint-options" (C.alaList' C.NoCommaFSep C.Token') #cfgHLintOptions
+        ^^^ metahelp "OPTS" "Additional HLint options"
+    <*> C.optionalFieldDef "hlint-version"                                     #cfgHLintVersion defaultHLintVersion
+        ^^^ metahelp "RANGE" "HLint version"
diff --git a/src/HaskellCI/Config/Installed.hs b/src/HaskellCI/Config/Installed.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/Config/Installed.hs
@@ -0,0 +1,71 @@
+module HaskellCI.Config.Installed where
+
+import Control.Applicative ((<|>))
+import Data.List (foldl')
+
+import qualified Distribution.Types.PackageName as C
+import qualified Distribution.Compat.CharParsing as C
+import qualified Distribution.Parsec.Class       as C
+import qualified Distribution.Pretty             as C
+import qualified Text.PrettyPrint                as PP
+import qualified Data.Set as S
+
+-------------------------------------------------------------------------------
+-- Single action
+-------------------------------------------------------------------------------
+
+data Installed
+    = InstalledAll
+    | InstalledNone
+    | Add C.PackageName
+    | Remove C.PackageName
+  deriving (Eq, Show)
+
+instance C.Pretty Installed where
+    pretty InstalledAll  = PP.text "+all"
+    pretty InstalledNone = PP.text "-all"
+    pretty (Add pn)      = PP.char '+' PP.<> C.pretty pn
+    pretty (Remove pn)   = PP.char '-' PP.<> C.pretty pn
+
+instance C.Parsec Installed where
+    parsec = do
+        s <- True <$ C.char '+' <|> False <$ C.char '-'
+        pn <- C.parsec
+        return $ case (s, pn == C.mkPackageName "all") of
+            (True,  True)  -> InstalledAll
+            (True,  False) -> Add pn
+            (False, True)  -> InstalledNone
+            (False, False) -> Remove pn
+
+-------------------------------------------------------------------------------
+-- Normalised
+-------------------------------------------------------------------------------
+
+data InstalledNormalised
+    = InstalledDiff (S.Set C.PackageName)
+    | InstalledOnly (S.Set C.PackageName)
+  deriving Show
+
+-- | Normalised
+--
+-- >>> parseI = maybe (error "foo") id . traverse C.simpleParsec
+-- >>> normaliseInstalled $ parseI ["-Cabal"]
+-- InstalledDiff (fromList [PackageName "Cabal"])
+--
+-- >>> normaliseInstalled $ parseI ["-all"]
+-- InstalledOnly (fromList [])
+--
+-- >>> normaliseInstalled $ parseI ["-all", "+transformers"]
+-- InstalledOnly (fromList [PackageName "transformers"])
+--
+normaliseInstalled :: [Installed] -> InstalledNormalised
+normaliseInstalled = foldl' f (InstalledDiff S.empty) where
+    f :: InstalledNormalised -> Installed -> InstalledNormalised
+    f _ InstalledNone = InstalledOnly S.empty
+    f _ InstalledAll  = InstalledDiff S.empty
+
+    f (InstalledDiff s) (Remove p) = InstalledDiff (S.insert p s)
+    f (InstalledDiff s) (Add p)    = InstalledDiff (S.delete p s)
+
+    f (InstalledOnly s) (Remove p) = InstalledOnly (S.delete p s)
+    f (InstalledOnly s) (Add p)    = InstalledOnly (S.insert p s)
diff --git a/src/HaskellCI/Config/Jobs.hs b/src/HaskellCI/Config/Jobs.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/Config/Jobs.hs
@@ -0,0 +1,56 @@
+module HaskellCI.Config.Jobs where
+
+import Control.Applicative ((<|>))
+
+import qualified Distribution.Pretty             as C
+import qualified Text.PrettyPrint                as PP
+import qualified Distribution.Compat.CharParsing as C
+import qualified Distribution.Parsec.Class       as C
+
+-- | Jobs
+--
+-- * @N:M@ - @N@ ghcs (cabal -j), @M@ threads (ghc -j)
+--
+-- >>> let parseJobs = C.simpleParsec :: String -> Maybe Jobs
+-- >>> parseJobs "2:2"
+-- Just (BothJobs 2 2)
+--
+-- >>> parseJobs ":2"
+-- Just (GhcJobs 2)
+--
+-- >>> parseJobs "2"
+-- Just (CabalJobs 2)
+--
+-- >>> parseJobs "garbage"
+-- Nothing
+--
+data Jobs
+    = CabalJobs Int
+    | GhcJobs Int
+    | BothJobs Int Int
+  deriving (Show)
+
+cabalJobs :: Jobs -> Maybe Int
+cabalJobs (CabalJobs n)  = Just n
+cabalJobs (GhcJobs _)    = Nothing
+cabalJobs (BothJobs n _) = Just n
+
+ghcJobs :: Jobs -> Maybe Int
+ghcJobs (CabalJobs _)  = Nothing
+ghcJobs (GhcJobs m)    = Just m
+ghcJobs (BothJobs _ m) = Just m
+
+instance C.Parsec Jobs where
+    parsec = ghc <|> rest where
+        ghc  = C.char ':' *> (GhcJobs <$> C.integral)
+        rest = do
+            n <- C.integral
+            m' <- C.optional (C.char ':' *> C.integral)
+            return $ case m' of
+                Nothing -> CabalJobs n
+                Just m  -> BothJobs n m
+
+instance C.Pretty Jobs where
+    pretty (BothJobs n m) = PP.int n PP.<> PP.colon PP.<> PP.int m
+    pretty (CabalJobs n)  = PP.int n
+    pretty (GhcJobs m)    = PP.colon PP.<> PP.int m
diff --git a/src/HaskellCI/Extras.hs b/src/HaskellCI/Extras.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/Extras.hs
@@ -0,0 +1,34 @@
+-- | Various extras, are this in @base@ ?
+module HaskellCI.Extras where
+
+import           Control.Applicative ((<|>))
+import           Data.List           (isPrefixOf, tails)
+
+-- $setup
+-- >>> import Text.Read (readMaybe)
+
+-- | Return the part after the first argument
+--
+-- >>> afterInfix "BAR" "FOOBAR XYZZY"
+-- Just " XYZZY"
+--
+afterInfix :: Eq a => [a] -> [a] -> Maybe [a]
+afterInfix needle haystack = findMaybe (afterPrefix needle) (tails haystack)
+
+-- |
+--
+-- >>> afterPrefix "FOO" "FOOBAR"
+-- Just "BAR"
+--
+afterPrefix :: Eq a => [a] -> [a] -> Maybe [a]
+afterPrefix needle haystack
+    | needle `isPrefixOf` haystack = Just (drop (length needle) haystack)
+    | otherwise                    = Nothing
+
+-- |
+--
+-- >>> findMaybe readMaybe ["foo", "1", "bar"] :: Maybe Int
+-- Just 1
+--
+findMaybe :: (a -> Maybe b) -> [a] -> Maybe b
+findMaybe f = foldr (\a b -> f a <|> b) Nothing
diff --git a/src/HaskellCI/GHC.hs b/src/HaskellCI/GHC.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/GHC.hs
@@ -0,0 +1,36 @@
+-- | This module encodes what we know about GHC, including existing/supported versions.
+module HaskellCI.GHC where
+
+import           Distribution.Version (Version, mkVersion, versionNumbers)
+
+import qualified Distribution.Pretty             as C
+
+knownGhcVersions :: [Version]
+knownGhcVersions = fmap mkVersion
+    [ [7,0,1],  [7,0,2], [7,0,3], [7,0,4]
+    , [7,2,1],  [7,2,2]
+    , [7,4,1],  [7,4,2]
+    , [7,6,1],  [7,6,2], [7,6,3]
+    , [7,8,1],  [7,8,2], [7,8,3], [7,8,4]
+    , [7,10,1], [7,10,2], [7,10,3]
+    , [8,0,1],  [8,0,2]
+    , [8,2,1],  [8,2,2]
+    , [8,4,1],  [8,4,2], [8,4,3], [8,4,4]
+    , [8,6,1],  [8,6,2], [8,6,3]
+    ]
+
+ghcAlpha :: Maybe Version
+ghcAlpha = Nothing
+-- ghcAlpha = Just (mkVersion [8,8,1])
+--
+dispGhcVersion :: Maybe Version -> String
+dispGhcVersion = maybe "head" C.prettyShow
+
+-- | Alphas, RCs and HEAD.
+previewGHC :: Maybe Version -> Bool
+previewGHC = maybe True $ \v -> Just v == ghcAlpha || odd (snd (ghcMajVer v))
+
+ghcMajVer :: Version -> (Int,Int)
+ghcMajVer v
+    | x:y:_ <- versionNumbers v = (x,y)
+    | otherwise = error $ "panic: ghcMajVer called with " ++ show v
diff --git a/src/HaskellCI/Glob.hs b/src/HaskellCI/Glob.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/Glob.hs
@@ -0,0 +1,153 @@
+module HaskellCI.Glob where
+
+import Control.Applicative as App ((<$>))
+import Control.Monad (void, filterM, liftM2)
+import Control.Monad.IO.Class
+import Data.List (stripPrefix)
+import Distribution.Compat.ReadP
+import System.Directory (doesDirectoryExist, getDirectoryContents)
+import System.FilePath.Posix ((</>))
+
+-------------------------------------------------------------------------------
+-- Glob
+-------------------------------------------------------------------------------
+
+{-
+
+Globbing code and grammar judiciously stolen from cabal-install:
+
+FilePathGlob    ::= FilePathRoot FilePathGlobRel
+FilePathRoot    ::= {- empty -}        # relative to cabal.project
+                  | "/"                # Unix root
+                  | [a-zA-Z] ":" [/\\] # Windows root
+                  | "~"                # home directory
+
+FilePathGlobRel ::= Glob "/"  FilePathGlobRel # Unix directory
+                  | Glob "\\" FilePathGlobRel # Windows directory
+                  | Glob         # file
+                  | {- empty -}  # trailing slash
+
+Glob      ::= GlobPiece *
+GlobPiece ::= "*"            # wildcard
+            | [^*{},/\\] *   # literal string
+            | "\\" [*{},]    # escaped reserved character
+            | "{" Glob "," ... "," Glob "}" # union (match any of these)
+-}
+
+data FilePathGlob = FilePathGlob FilePathRoot FilePathGlobRel
+  deriving (Eq, Show)
+
+data FilePathGlobRel
+   = GlobDir  Glob FilePathGlobRel
+   | GlobFile Glob
+   | GlobDirTrailing -- trailing dir, a glob ending in '/'
+  deriving (Eq, Show)
+
+-- | 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)
+
+data FilePathRoot
+   = FilePathRelative
+   | FilePathRoot FilePath -- e.g. '/', 'c:\' or result of 'takeDrive'
+   | FilePathHomeDir
+  deriving (Eq, Show)
+
+parseFilePathGlobRel :: ReadP r FilePathGlobRel
+parseFilePathGlobRel =
+      parseGlob >>= \globpieces ->
+          asDir globpieces
+      <++ asTDir globpieces
+      <++ asFile globpieces
+  where
+    asDir  glob = do dirSep
+                     GlobDir glob <$> parseFilePathGlobRel
+    asTDir glob = do dirSep
+                     return (GlobDir glob GlobDirTrailing)
+    asFile glob = return (GlobFile glob)
+
+    dirSep = void (char '/')
+         +++ (do _ <- char '\\'
+                 -- check this isn't an escape code
+                 following <- look
+                 case following of
+                   (c:_) | isGlobEscapedChar c -> pfail
+                   _                           -> return ())
+
+parseGlob :: ReadP r Glob
+parseGlob = many1 parsePiece
+  where
+    parsePiece = literal +++ wildcard +++ union'
+
+    wildcard = char '*' >> return WildCard
+
+    union' = between (char '{') (char '}') $
+              fmap Union (sepBy1 parseGlob (char ','))
+
+    literal = Literal `fmap` litchars1
+
+    litchar = normal +++ escape
+
+    normal  = satisfy (\c -> not (isGlobEscapedChar c)
+                                && c /= '/' && c /= '\\')
+    escape  = char '\\' >> 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
+
+expandRelGlob :: MonadIO m => FilePath -> FilePathGlobRel -> m [FilePath]
+expandRelGlob root glob0 = liftIO $ 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 App.<$> mapM (\subdir -> go globPath (dir </> subdir)) subdirs
+
+    go GlobDirTrailing dir = return [dir]
+
+matchGlob :: Glob -> FilePath -> 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
diff --git a/src/HaskellCI/MakeTravisOutput.hs b/src/HaskellCI/MakeTravisOutput.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/MakeTravisOutput.hs
@@ -0,0 +1,212 @@
+{-# LANGUAGE CPP               #-}
+{-# LANGUAGE OverloadedStrings #-}
+module HaskellCI.MakeTravisOutput where
+
+import           Prelude                    ()
+import           Prelude.Compat
+
+import           Control.Monad              (mzero)
+import           Control.Monad.Trans.Class  (lift)
+import           Control.Monad.Trans.Maybe  (MaybeT)
+import           Control.Monad.Trans.Writer (WriterT, tell)
+import           Data.Functor.Identity      (Identity (..))
+import           Data.Maybe                 (mapMaybe)
+import           Data.Set                   (Set)
+import           Data.String                (IsString (..))
+import           Distribution.Version
+
+#ifdef MIN_VERSION_ShellCheck
+import           ShellCheck.Checker         (checkScript)
+import qualified ShellCheck.Interface       as SC
+#endif
+
+import qualified Data.Set                   as S
+import qualified Data.Text                  as T
+import qualified Data.Text.Encoding         as TE
+import qualified Data.YAML                  as YAML
+
+import           HaskellCI.Config.Folds
+import           HaskellCI.Version
+
+-- |  Encode shell command to be YAML safe and (optionally) ShellCheck it.
+sh :: String -> Row
+sh = sh'
+    [ 2034 -- VAR appears unused. Verify it or export it.
+    , 2086 -- SC2086: Double quote to prevent globbing and word splitting.
+    , 2002 -- SC2002: Useless cat. Consider 'cmd < file | ..' or 'cmd file | ..' instead.
+    ]
+
+shForJob :: Set Version -> VersionRange -> String -> Row
+shForJob  versions vr cmd
+    | all (`withinRange` vr) versions       = sh cmd
+    | not $ any (`withinRange` vr) versions = RowSkip
+    | otherwise                             = sh $ unwords
+        [ "if"
+        , ghcVersionPredicate vr
+        , "; then"
+        , cmd
+        , "; fi"
+        ]
+
+-- | Like 'sh' but with explicit SC exclude codes.
+sh' :: [Integer] -> String -> Row
+#ifndef MIN_VERSION_ShellCheck
+sh' _ cmd = rawRow (shImpl cmd)
+#else
+sh' excl cmd =
+    if null (SC.crComments res)
+    then rawRow $ shImpl cmd
+    else RowErr $ unlines $
+        ("ShellCheck! " ++ cmd) :
+        [ "SC" ++ show (SC.cCode c) ++ ": " ++ SC.cMessage c
+        | pc <- SC.crComments res
+        , let c = SC.pcComment pc
+        ]
+
+  where
+    res = runIdentity $ checkScript iface spec
+    iface = SC.SystemInterface $ \n -> return $ Left $ "cannot read file: " ++ n
+    spec  = SC.emptyCheckSpec { SC.csFilename = "stdin"
+                              , SC.csScript = cmd
+                              , SC.csExcludedWarnings = excl
+                              , SC.csShellTypeOverride = Just SC.Sh
+                              }
+#endif
+
+-- Non-ShellCheck version of sh'
+shImpl :: String -> String
+shImpl cmd
+    | needsEncoding = "  - " ++ show cmd
+    | otherwise     = "  - " ++ cmd
+  where
+    -- we try to decode optimisticly serialised "- cmd..."
+    -- if it's ok, we use it :)
+    -- otherwise escape with show
+    cmdT  = T.pack cmd
+    cmdBS = "- " <> TE.encodeUtf8 cmdT
+    needsEncoding = case YAML.decodeStrict cmdBS of
+        Right [[t]] | t == cmdT -> False
+        _           -> True
+
+comment :: String -> Row
+comment c = rawRow $ "  # " ++ c
+
+blank :: Row
+blank = rawRow ""
+
+rawRow :: String -> Row
+rawRow = Row
+
+type MakeTravisOutput = Result Diagnostic [String]
+
+data Diagnostic
+    = Info String
+    | Warn String
+    | Error String
+  deriving (Eq, Show)
+
+formatDiagnostics :: [Diagnostic] -> String
+formatDiagnostics = unlines . map formatDiagnostic
+
+formatDiagnostic :: Diagnostic -> String
+formatDiagnostic (Error s) = "*ERROR* " ++ s
+formatDiagnostic (Warn  s) = "*WARNING* " ++ s
+formatDiagnostic (Info  s) = "*INFO* " ++ s
+
+-- MaybeT is used to preserve the short-circuiting semantics of 'putStrLnErr'.
+type YamlWriter m a = MaybeT (WriterT MakeTravisOutput m) a
+
+putStrLnErr :: Monad m => String -> YamlWriter m a
+putStrLnErr m = do
+    lift . tell $ Failure [Error m]
+    mzero
+
+putStrLnErrs :: Monad m => [String] -> YamlWriter m ()
+putStrLnErrs [] = return ()
+putStrLnErrs ms = do
+    lift (tell (Failure (map Error ms)))
+    mzero
+
+putStrLnWarn, putStrLnInfo :: Monad m => String -> YamlWriter m ()
+putStrLnWarn m = lift . tell $ Success [Warn m] []
+putStrLnInfo m = lift . tell $ Success [Info m] []
+
+tellStrLn :: Monad m => String -> YamlWriter m ()
+tellStrLn str = lift . tell $ success [str]
+
+data Row
+    = Row String
+    | RowErr String
+    | RowSkip
+
+instance IsString Row where
+    fromString = rawRow
+
+tellStrLns :: Monad m => [Row] -> YamlWriter m ()
+tellStrLns rows = case sequenceRows rows of
+    Left err    -> lift $ tell $ Failure [Error err]
+    Right rows' -> lift $ tell $ success rows'
+
+tellStrLnsRaw :: Monad m => [String] -> YamlWriter m ()
+tellStrLnsRaw rows = lift $ tell $ success rows
+
+sequenceRows :: [Row] -> Either String [String]
+sequenceRows = sequenceA . mapMaybe f where
+    f (Row s)      = Just (Right s)
+    f (RowErr err) = Just (Left err)
+    f RowSkip      = Nothing
+
+-------------------------------------------------------------------------------
+-- Folded
+-------------------------------------------------------------------------------
+
+foldedTellStrLns
+    :: Monad m
+    => Fold
+    -> String
+    -> Set Fold
+    -> YamlWriter m ()
+    -> YamlWriter m ()
+foldedTellStrLns label = foldedTellStrLns' label ""
+
+foldedTellStrLns'
+    :: Monad m
+    => Fold
+    -> String
+    -> String
+    -> Set Fold
+    -> YamlWriter m ()
+    -> YamlWriter m ()
+foldedTellStrLns' label pfx prettyLabel labels output
+    | label `S.notMember` labels = output
+    | otherwise = tellStrLns prologue >> output >> tellStrLns epilogue
+  where
+    prologue = [ sh' [2039] $ concat
+        [ "echo ", prettyLabel
+        , " && echo -en 'travis_fold:start:", showFold' label, "\\\\r'"
+        ]]
+    epilogue = [ sh' [2039] $ "echo -en 'travis_fold:end:" ++ showFold' label ++ "\\\\r'" ]
+
+    showFold' l = showFold l ++ if null pfx then "" else "-" ++ pfx
+
+-------------------------------------------------------------------------------
+-- Result
+-------------------------------------------------------------------------------
+
+data Result e a
+    = Success [e] a
+    | Failure [e]
+    deriving (Eq, Show, Functor)
+
+success :: a -> Result e a
+success = Success []
+
+instance Monoid a => Monoid (Result e a) where
+    mempty = success mempty
+    mappend = (<>)
+
+instance Monoid a => Semigroup (Result e a) where
+    Failure err1   <> Failure err2   = Failure $ err1 <> err2
+    Failure err1   <> Success err2 _ = Failure $ err1 <> err2
+    Success err1 _ <> Failure err2   = Failure $ err1 <> err2
+    Success l1 o1  <> Success l2 o2  = Success (mappend l1 l2) (mappend o1 o2)
diff --git a/src/HaskellCI/Newtypes.hs b/src/HaskellCI/Newtypes.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/Newtypes.hs
@@ -0,0 +1,178 @@
+{-# LANGUAGE FlexibleInstances     #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE ScopedTypeVariables   #-}
+module HaskellCI.Newtypes where
+
+import           Control.Applicative             (liftA2, (<|>))
+import           Data.Char                       (isSpace)
+import           Data.Coerce                     (coerce)
+import           Data.Functor.Identity           (Identity (..))
+import           Data.Proxy                      (Proxy (..))
+
+import qualified Data.Set                        as S
+import qualified Distribution.Compat.CharParsing as C
+import qualified Distribution.Compat.Newtype     as C
+import qualified Distribution.Parsec.Class       as C
+import qualified Distribution.Parsec.Newtypes    as C
+import qualified Distribution.Pretty             as C
+import qualified Distribution.Types.Version      as C
+import qualified Distribution.Types.VersionRange as C
+import qualified Text.PrettyPrint                as PP
+
+import           HaskellCI.Version
+
+-------------------------------------------------------------------------------
+-- PackageLocation
+-------------------------------------------------------------------------------
+
+newtype PackageLocation = PackageLocation String
+
+instance C.Newtype PackageLocation [Char] where
+    pack = coerce
+    unpack = coerce
+
+-- | 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.
+instance C.Parsec PackageLocation where
+    parsec = PackageLocation <$> outerTerm
+      where
+        outerTerm = ($ "") <$> outerChars
+
+        outerChars, outerChar, innerChars, innerChar :: C.CabalParsing m => m ShowS
+        outerChars = foldr (.) id <$> C.some outerChar
+        innerChars = foldr (.) id <$> C.many innerChar
+
+        outerChar = do
+            c <- C.satisfy $ \c -> not (isSpace c || c == '}' || c == ',')
+            kont c
+
+        innerChar = do
+            c <- C.satisfy $ \c -> not (isSpace c || c == '}')
+            kont c
+
+        kont :: C.CabalParsing m => Char -> m ShowS
+        kont c = case c of
+           '{' -> do
+               cs <- innerChars
+               c' <- C.char '}'
+               return (showChar c . cs . showChar c')
+           _   -> return $ showChar c
+
+
+instance C.Pretty PackageLocation where
+    pretty (PackageLocation p) = PP.text p
+
+-------------------------------------------------------------------------------
+-- NoCommas: something which can be comma separated
+-------------------------------------------------------------------------------
+
+newtype NoCommas = NoCommas String
+
+instance C.Newtype NoCommas [Char] where
+    pack = coerce
+    unpack = coerce
+
+instance C.Parsec NoCommas where
+    parsec = NoCommas <$> liftA2 (:) (C.satisfy (not . isSpace)) (C.munch (/= ','))
+
+instance C.Pretty NoCommas where
+    pretty (NoCommas p) = PP.text p
+
+-------------------------------------------------------------------------------
+-- Head version
+-------------------------------------------------------------------------------
+
+newtype HeadVersion = HeadVersion { getHeadVersion :: Maybe C.Version }
+
+instance C.Newtype HeadVersion (Maybe C.Version) where
+    pack = coerce
+    unpack = coerce
+
+instance C.Parsec HeadVersion where
+    parsec = HeadVersion Nothing <$ C.string "head" <|>
+        HeadVersion . Just <$> C.parsec
+
+instance C.Pretty HeadVersion where
+    pretty (HeadVersion Nothing)  = PP.text "head"
+    pretty (HeadVersion (Just v)) = C.pretty v
+
+-------------------------------------------------------------------------------
+-- Newtype
+-------------------------------------------------------------------------------
+
+newtype Int' = Int' Int
+
+instance C.Newtype Int' Int where
+    pack = coerce
+    unpack = coerce
+
+instance C.Parsec Int' where
+    parsec = Int' <$> C.integral
+
+instance C.Pretty Int' where
+    pretty (Int' i) = PP.int i
+
+-------------------------------------------------------------------------------
+-- Range
+-------------------------------------------------------------------------------
+
+newtype Range = Range C.VersionRange
+
+instance C.Newtype Range C.VersionRange where
+    pack = coerce
+    unpack = coerce
+
+instance C.Parsec Range where
+    parsec = fmap Range $ C.parsec <|> fromBool <$> C.parsec where
+        fromBool True  = C.anyVersion
+        fromBool False = C.noVersion
+
+instance C.Pretty Range where
+    pretty (Range r)
+        | equivVersionRanges r C.anyVersion = C.pretty True
+        | equivVersionRanges r C.noVersion  = C.pretty False
+        | otherwise                         = C.pretty r
+
+-------------------------------------------------------------------------------
+-- AlaSet
+-------------------------------------------------------------------------------
+
+newtype AlaSet sep b a = AlaSet { getAlaSet :: S.Set a }
+
+alaSet :: sep -> S.Set a -> AlaSet sep (Identity a) a
+alaSet _ = AlaSet
+
+-- | More general version of 'alaSet'.
+alaSet' :: sep -> (a -> b) -> S.Set a -> AlaSet sep b a
+alaSet' _ _ = AlaSet
+
+instance C.Newtype (AlaSet sep wrapper a) (S.Set a) where
+    pack = AlaSet
+    unpack = getAlaSet
+
+instance (C.Newtype b a, Ord a, Sep sep, C.Parsec b) => C.Parsec (AlaSet sep b a) where
+    parsec   = C.pack . S.fromList . map (C.unpack :: b -> a) <$> parseSep (Proxy :: Proxy sep) C.parsec
+
+instance (C.Newtype b a, Sep sep, C.Pretty b) => C.Pretty (AlaSet sep b a) where
+    pretty = prettySep (Proxy :: Proxy sep) . map (C.pretty . (C.pack :: a -> b)) . S.toList . C.unpack
+
+-------------------------------------------------------------------------------
+-- From Cabal
+-------------------------------------------------------------------------------
+
+class    Sep sep  where
+    prettySep :: Proxy sep -> [PP.Doc] -> PP.Doc
+    parseSep :: C.CabalParsing m => Proxy sep -> m a -> m [a]
+
+instance Sep C.CommaVCat where
+    prettySep  _ = PP.vcat . PP.punctuate PP.comma
+    parseSep   _ = C.parsecLeadingCommaList
+instance Sep C.CommaFSep where
+    prettySep _ = PP.fsep . PP.punctuate PP.comma
+    parseSep   _ = C.parsecLeadingCommaList
+instance Sep C.NoCommaFSep where
+    prettySep _   = PP.fsep
+    parseSep  _ p = C.many (p <* C.spaces)
diff --git a/src/HaskellCI/Optimization.hs b/src/HaskellCI/Optimization.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/Optimization.hs
@@ -0,0 +1,27 @@
+module HaskellCI.Optimization where
+
+import Control.Applicative ((<|>))
+
+import qualified Distribution.Compat.CharParsing as C
+import qualified Distribution.Parsec.Class       as C
+import qualified Distribution.Pretty             as C
+import qualified Text.PrettyPrint                as PP
+
+data Optimization
+    = OptimizationOn
+    | OptimizationOff
+    | OptimizationLevel Int
+  deriving (Eq, Show)
+
+instance C.Parsec Optimization where
+    parsec = boolean <|> numeric where
+        boolean = ite OptimizationOn OptimizationOff <$> C.parsec
+        numeric = OptimizationLevel <$> C.integral
+
+        ite t _ True  = t
+        ite _ f False = f
+    
+instance C.Pretty Optimization where
+    pretty OptimizationOn        = C.pretty True
+    pretty OptimizationOff       = C.pretty False
+    pretty (OptimizationLevel l) = PP.int l
diff --git a/src/HaskellCI/OptionsGrammar.hs b/src/HaskellCI/OptionsGrammar.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/OptionsGrammar.hs
@@ -0,0 +1,24 @@
+module HaskellCI.OptionsGrammar (
+    OptionsGrammar (..),
+    (C.^^^),
+    )  where
+
+import qualified Distribution.Compat.Lens        as C
+import qualified Distribution.FieldGrammar       as C
+import qualified Distribution.Parsec.Field       as C
+import qualified Distribution.Types.VersionRange as C
+
+import           HaskellCI.Newtypes
+
+class C.FieldGrammar p => OptionsGrammar p where
+    metahelp :: String -> String -> p s a -> p s a
+    metahelp _ _ = id
+
+    help :: String -> p s a -> p s a
+    help _ = id
+
+    -- we treat range fields specially in options
+    rangeField :: C.FieldName -> C.ALens' s C.VersionRange -> C.VersionRange -> p s C.VersionRange
+    rangeField fn = C.optionalFieldDefAla fn Range
+
+instance OptionsGrammar C.ParsecFieldGrammar
diff --git a/src/HaskellCI/OptparseGrammar.hs b/src/HaskellCI/OptparseGrammar.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/OptparseGrammar.hs
@@ -0,0 +1,124 @@
+{-# LANGUAGE GADTs             #-}
+{-# LANGUAGE OverloadedStrings #-}
+module HaskellCI.OptparseGrammar (
+    OptparseGrammar,
+    runOptparseGrammar,
+    ) where
+
+import           Prelude                     ()
+import           Prelude.Compat
+
+import           Control.Applicative         (many)
+import           Data.Foldable               (asum)
+import           Distribution.Simple.Utils   (fromUTF8BS)
+
+import qualified Distribution.Compat.Lens    as C
+import qualified Distribution.Compat.Newtype as C
+import qualified Distribution.FieldGrammar   as C
+import qualified Distribution.Parsec.Class   as C
+import qualified Distribution.Parsec.Field   as C
+import qualified Distribution.Pretty         as C
+import qualified Distribution.Version        as C
+import qualified Options.Applicative         as O
+
+import           HaskellCI.OptionsGrammar
+import           HaskellCI.Version
+
+data SomeParser s where
+    SP :: (Maybe String -> Maybe String -> O.Parser (s -> s)) -> SomeParser s
+
+newtype OptparseGrammar s a = OG [SomeParser s]
+  deriving Functor
+
+runOptparseGrammar :: OptparseGrammar s a -> O.Parser (s -> s)
+runOptparseGrammar (OG ps) = fmap (foldr (flip (.)) id) $ many $ asum
+    [ p Nothing Nothing
+    | SP p <- ps
+    ]
+
+instance Applicative (OptparseGrammar s) where
+    pure _ = OG []
+    OG f <*> OG x = OG (f ++ x)
+
+instance C.FieldGrammar OptparseGrammar where
+    blurFieldGrammar l (OG ps) = OG
+        [ SP $ \v h -> fmap (l C.#%~) (p v h)
+        | SP p <- ps
+        ]
+
+    -- we don't support unique fields atm
+    uniqueFieldAla _ _ _ = OG []
+
+    -- the non default flag has help entry
+    booleanFieldDef fn l def = OG
+        [ SP $ \_m h -> setOG l $ O.flag' True  $ flagMods fn (th h)
+        , SP $ \_m h -> setOG l $ O.flag' False $ flagMods ("no-" <> fn) (fh h)
+        ]
+      where
+        th h = if def then Nothing else h
+        fh h = if def then h else Nothing
+
+    optionalFieldAla fn c l = OG
+        [ SP $ \m h -> setOptionalOG l $ O.option (C.unpack' c <$> readMParsec) $ optionMods fn m h ]
+
+    optionalFieldDefAla fn c l def = OG
+        [ SP $ \m h -> setOG l $ O.option (C.unpack' c <$> readMParsec) $ optionMods fn m (fmap hdef h) ]
+      where
+        hdef h = h ++ " (Default: " ++ C.prettyShow (C.pack' c def) ++ ")"
+
+    monoidalFieldAla fn c l = OG
+        [ SP $ \m h -> monoidOG l $ O.option (C.unpack' c <$> readMParsec) $ optionMods fn m h ]
+
+    prefixedFields _ _   = pure []
+    knownField _         = pure ()
+    deprecatedSince _  _ = id
+    availableSince _ _   = id
+    hiddenField          = id
+
+instance OptionsGrammar OptparseGrammar where
+    help h (OG ps) = OG
+        [ SP $ \m _h -> p m (Just h)
+        | SP p <- ps
+        ]
+
+    metahelp m h (OG ps) = OG
+        [ SP $ \_m _h -> p (Just m) (Just h)
+        | SP p <- ps
+        ]
+
+    -- example: @rangeField tests #cfgTests anyVersion@, generates options:
+    --
+    -- --tests
+    -- --no-tests
+    -- --tests-jobs RANGE
+    --
+    -- where the --no-tests has help, because it's not default.
+    --
+    rangeField fn l def = OG
+        [ SP $ \_m  h -> setOG l $ O.flag' C.anyVersion $ flagMods fn (th h)
+        , SP $ \_m  h -> setOG l $ O.flag' C.noVersion  $ flagMods ("no-" <> fn) (fh h)
+        , SP $ \_m _h -> setOG l $ O.option readMParsec $ O.long (fromUTF8BS $ fn <> "-jobs") <> O.metavar "RANGE"
+        ]
+      where
+        th h = if equivVersionRanges def C.anyVersion then Nothing else h
+        fh h = if equivVersionRanges def C.anyVersion then h else Nothing
+
+optionMods :: (O.HasName mods, O.HasMetavar mods) => C.FieldName -> Maybe String -> Maybe String -> O.Mod mods a
+optionMods fn mmetavar mhelp = flagMods fn mhelp
+    <> maybe mempty O.metavar mmetavar
+
+flagMods :: O.HasName mods => C.FieldName -> Maybe String -> O.Mod mods a
+flagMods fn mhelp = O.long (fromUTF8BS fn)
+    <> maybe mempty O.help mhelp
+
+readMParsec :: C.Parsec a => O.ReadM a
+readMParsec = O.eitherReader C.eitherParsec
+
+setOG :: C.ALens' s a -> O.Parser a -> O.Parser (s -> s)
+setOG l = fmap (l C.#~)
+
+setOptionalOG :: C.ALens' s (Maybe a) -> O.Parser a -> O.Parser (s -> s)
+setOptionalOG l = fmap $ \x -> l C.#~ Just x
+
+monoidOG :: Monoid a => C.ALens' s a -> O.Parser a -> O.Parser (s -> s)
+monoidOG l = fmap $ \x -> l C.#%~ \y -> mappend y x
diff --git a/src/HaskellCI/Package.hs b/src/HaskellCI/Package.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/Package.hs
@@ -0,0 +1,12 @@
+module HaskellCI.Package where
+
+import qualified Distribution.Types.GenericPackageDescription as C
+import qualified Distribution.Types.VersionRange              as C
+
+data Package = Pkg
+    { pkgName :: String
+    , pkgJobs :: C.VersionRange
+    , pkgDir  :: FilePath
+    , pkgGpd  :: C.GenericPackageDescription
+    }
+  deriving (Eq, Show)
diff --git a/src/HaskellCI/ParsecError.hs b/src/HaskellCI/ParsecError.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/ParsecError.hs
@@ -0,0 +1,108 @@
+module HaskellCI.ParsecError (
+    renderParseError,
+    ) where
+
+import           Prelude                    ()
+import           Prelude.Compat
+
+import           Distribution.Parsec.Common (PError (..), PWarning (..),
+                                             Position (..), showPos, zeroPos)
+import           Distribution.Simple.Utils  (fromUTF8BS)
+import           System.FilePath            (normalise)
+
+import qualified Data.ByteString            as BS
+import qualified Data.ByteString.Char8      as BS8
+
+-- | Render parse error highlighting the part of the input file.
+renderParseError
+    :: FilePath
+    -> BS.ByteString
+    -> [PError]
+    -> [PWarning]
+    -> String
+renderParseError filepath contents errors warnings
+    | null errors && null warnings = ""
+    | null errors = unlines $
+        ("Warnings encountered when parsing  file " <> filepath <> ":")
+        : renderedWarnings
+    | otherwise = unlines $
+        [ "Errors encountered when parsing file " <> filepath <> ":"
+        ]
+        ++ renderedErrors
+        ++ renderedWarnings
+  where
+    filepath' = normalise filepath
+
+    -- lines of the input file. 'lines' is taken, so they are called rows
+    -- contents, line number, whether it's empty line
+    rows :: [(String, Int, Bool)]
+    rows = zipWith f (BS8.lines contents) [1..] where
+        f bs i = let s = fromUTF8BS bs in (s, i, isEmptyOrComment s)
+
+    rowsZipper = listToZipper rows
+
+    isEmptyOrComment :: String -> Bool
+    isEmptyOrComment s = case dropWhile (== ' ') s of
+        ""          -> True   -- empty
+        ('-':'-':_) -> True   -- comment
+        _           -> False
+
+    renderedErrors   = concatMap renderError errors
+    renderedWarnings = concatMap renderWarning warnings
+
+    renderError :: PError -> [String]
+    renderError (PError pos@(Position row col) msg)
+        -- if position is 0:0, then it doesn't make sense to show input
+        -- looks like, Parsec errors have line-feed in them
+        | pos == zeroPos = msgs
+        | otherwise      = msgs ++ formatInput row col
+      where
+        msgs = [ "", filepath' ++ ":" ++ showPos pos ++ ": error:", trimLF msg, "" ]
+
+    renderWarning :: PWarning -> [String]
+    renderWarning (PWarning _ pos@(Position row col) msg)
+        | pos == zeroPos = msgs
+        | otherwise      = msgs ++ formatInput row col
+      where
+        msgs = [ "", filepath' ++ ":" ++ showPos pos ++ ": warning:", trimLF msg, "" ]
+
+    -- sometimes there are (especially trailing) newlines.
+    trimLF :: String -> String
+    trimLF = dropWhile (== '\n') . reverse . dropWhile (== '\n') . reverse
+
+    -- format line: prepend the given line number
+    formatInput :: Int -> Int -> [String]
+    formatInput row col = case advance (row - 1) rowsZipper of
+        Zipper xs ys -> before ++ after where
+            before = case span (\(_, _, b) -> b) xs of
+                (_, [])     -> []
+                (zs, z : _) -> map formatInputLine $ z : reverse zs
+
+            after  = case ys of
+                []        -> []
+                (z : _zs) ->
+                    [ formatInputLine z                             -- error line
+                    , "      | " ++ replicate (col - 1) ' ' ++ "^"  -- pointer: ^
+                    ]
+                    -- do we need rows after?
+                    -- ++ map formatInputLine (take 1 zs)           -- one row after
+
+    formatInputLine :: (String, Int, Bool) -> String
+    formatInputLine (str, row, _) = leftPadShow row ++ " | " ++ str
+
+    -- hopefully we don't need to work with over 99999 lines .cabal files
+    -- at that point small glitches in error messages are hopefully fine.
+    leftPadShow :: Int -> String
+    leftPadShow n = let s = show n in replicate (5 - length s) ' ' ++ s
+
+data Zipper a = Zipper [a] [a]
+
+listToZipper :: [a] -> Zipper a
+listToZipper = Zipper []
+
+advance :: Int -> Zipper a -> Zipper a
+advance n z@(Zipper xs ys)
+    | n <= 0 = z
+    | otherwise = case ys of
+        []      -> z
+        (y:ys') -> advance (n - 1) $ Zipper (y:xs) ys'
diff --git a/src/HaskellCI/ParsecUtils.hs b/src/HaskellCI/ParsecUtils.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/ParsecUtils.hs
@@ -0,0 +1,42 @@
+module HaskellCI.ParsecUtils where
+
+import           Control.Monad                   (unless)
+import           System.Directory                (doesFileExist)
+import           System.Exit                     (exitFailure)
+import           System.IO                       (hPutStr, stderr)
+
+import qualified Data.ByteString                 as BS
+import qualified Distribution.Parsec.Common      as C
+import qualified Distribution.Parsec.LexerMonad  as C
+import qualified Distribution.Parsec.Parser      as C
+import qualified Distribution.Parsec.ParseResult as C
+import qualified Text.Parsec                     as P
+
+import           HaskellCI.ParsecError
+
+readAndParseFile
+    :: ([C.Field C.Position] -> C.ParseResult a)  -- ^ File fields to final value parser
+    -> FilePath                                   -- ^ File to read
+    -> IO a
+readAndParseFile parser fpath = do
+    exists <- doesFileExist fpath
+    unless exists $ do
+        putStrLn $ "Error Parsing: file \"" ++ fpath ++ "\" doesn't exist. Cannot continue."
+        exitFailure
+    bs <- BS.readFile fpath
+    run bs $ case C.readFields' bs of
+        Right (fs, lexWarnings) -> do
+            C.parseWarnings (C.toPWarnings lexWarnings)
+            parser fs
+        Left perr -> C.parseFatalFailure pos (show perr) where
+            ppos = P.errorPos perr
+            pos  = C.Position (P.sourceLine ppos) (P.sourceColumn ppos)
+  where
+    run :: BS.ByteString -> C.ParseResult a -> IO a
+    run bs r = case C.runParseResult r of
+        (ws, Right x)      -> do
+            hPutStr stderr $ renderParseError fpath bs [] ws
+            return x
+        (ws, Left (_, es)) -> do
+            hPutStr stderr $ renderParseError fpath bs es ws
+            exitFailure
diff --git a/src/HaskellCI/Project.hs b/src/HaskellCI/Project.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/Project.hs
@@ -0,0 +1,91 @@
+{-# LANGUAGE OverloadedLabels  #-}
+{-# LANGUAGE OverloadedStrings #-}
+-- | Handling of @cabal.project@ file
+module HaskellCI.Project (
+    Project (..),
+    emptyProject,
+    parseProjectFile,
+    ) where
+
+import           Data.ByteString                              (ByteString)
+import           Data.Generics.Labels                         ()
+import           GHC.Generics                                 (Generic)
+import           Lens.Micro                                   (over)
+
+import qualified Data.Map.Strict                              as M
+import qualified Distribution.CabalSpecVersion                as C
+import qualified Distribution.FieldGrammar                    as C
+import qualified Distribution.PackageDescription.FieldGrammar as C
+import qualified Distribution.Parsec.Common                   as C
+import qualified Distribution.Parsec.Newtypes                 as C
+import qualified Distribution.Parsec.Parser                   as C
+import qualified Distribution.Parsec.ParseResult              as C
+import qualified Distribution.Types.SourceRepo                as C
+import qualified Distribution.Fields.Pretty as C
+
+import           HaskellCI.Newtypes
+import           HaskellCI.Optimization
+import           HaskellCI.ParsecError
+
+-- $setup
+-- >>> :seti -XOverloadedStrings
+
+data Project a = Project
+    { prjPackages     :: [a]
+    , prjConstraints  :: [String]
+    , prjAllowNewer   :: [String]
+    , prjReorderGoals :: Bool
+    , prjMaxBackjumps :: Maybe Int
+    , prjOptimization :: Optimization
+    , prjSourceRepos  :: [C.SourceRepo]
+    , prjOrigFields   :: [C.PrettyField]
+    }
+  deriving (Show, Functor, Foldable, Traversable, Generic)
+
+emptyProject :: Project [a]
+emptyProject = Project [] [] [] False Nothing OptimizationOn [] []
+
+-- | Parse project file. Extracts only few fields.
+--
+-- >>> fmap prjPackages $ parseProjectFile "cabal.project" "packages: foo bar/*.cabal"
+-- Right ["foo","bar/*.cabal"]
+--
+parseProjectFile :: FilePath -> ByteString -> Either String (Project String)
+parseProjectFile fp bs = do
+    fields0 <- either (Left . show) Right $ C.readFields bs
+    let (fields1, sections) = C.partitionFields fields0
+    let fields2 = M.filterWithKey (\k _ -> k `elem` knownFields) fields1
+    case C.runParseResult $ parse fields0 fields2 sections of
+        (_, Right x)       -> return x
+        (ws, Left (_, es)) -> Left $ renderParseError fp bs es ws
+  where
+    knownFields = C.fieldGrammarKnownFieldList $ grammar []
+
+    parse origFields fields sections = do
+        let prettyOrigFields = C.fromParsecFields $ filter notPackages origFields
+        prj <- C.parseFieldGrammar C.cabalSpecLatest fields $ grammar prettyOrigFields
+        foldr ($) prj <$> traverse parseSec (concat sections)
+
+    parseSec :: C.Section C.Position -> C.ParseResult (Project String -> Project String)
+    parseSec (C.MkSection (C.Name _pos name) [] fields) | name == "source-repository-package" = do
+        let fields' = fst $ C.partitionFields fields
+        repo <- C.parseFieldGrammar C.cabalSpecLatest fields' (C.sourceRepoFieldGrammar $ C.RepoKindUnknown "unused")
+        return $ over #prjSourceRepos (repo :)
+
+    parseSec _ = return id
+
+notPackages :: C.Field ann -> Bool
+notPackages (C.Field (C.Name _ "packages") _) = False
+notPackages _                                 = True
+
+
+grammar :: [C.PrettyField] -> C.ParsecFieldGrammar (Project String) (Project String)
+grammar origFields = Project
+    <$> C.monoidalFieldAla "packages"      (C.alaList' C.FSep PackageLocation) #prjPackages
+    <*> C.monoidalFieldAla "constraints"   (C.alaList' C.CommaVCat NoCommas)   #prjConstraints
+    <*> C.monoidalFieldAla "allow-newer"   (C.alaList' C.CommaVCat NoCommas)   #prjAllowNewer
+    <*> C.booleanFieldDef  "reorder-goals"                                     #prjReorderGoals False
+    <*> C.optionalFieldAla "max-backjumps" Int'                                    #prjMaxBackjumps
+    <*> C.optionalFieldDef "optimization"                                      #prjOptimization OptimizationOn
+    <*> pure []
+    <*> pure origFields
diff --git a/src/HaskellCI/TestedWith.hs b/src/HaskellCI/TestedWith.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/TestedWith.hs
@@ -0,0 +1,91 @@
+module HaskellCI.TestedWith (
+    TestedWithJobs (..),
+    checkVersions,
+    ) where
+
+import           Prelude                         ()
+import           Prelude.Compat
+
+import           Control.Applicative             ((<|>))
+import           Data.Generics.Labels            ()
+import           Data.List                       (intercalate)
+
+import qualified Data.Foldable                   as F
+import qualified Data.Set                        as S
+import qualified Distribution.Compat.CharParsing as C
+import qualified Distribution.Parsec.Class       as C
+import qualified Distribution.Pretty             as C
+import qualified Distribution.Types.Version      as C
+import qualified Distribution.Types.VersionRange as C
+import qualified Text.PrettyPrint                as PP
+
+import           HaskellCI.Package
+import           HaskellCI.Project
+
+data TestedWithJobs
+    = TestedWithUniform
+    | TestedWithAny
+  deriving (Eq, Show)
+
+instance C.Parsec TestedWithJobs where
+    parsec = TestedWithUniform <$ C.string "uniform"
+        <|> TestedWithAny <$ C.string "any"
+
+instance C.Pretty TestedWithJobs where
+    pretty TestedWithUniform = PP.text "uniform"
+    pretty TestedWithAny     = PP.text "any"
+
+-------------------------------------------------------------------------------
+-- Selection
+-------------------------------------------------------------------------------
+
+checkVersions
+    :: TestedWithJobs
+    -> Project (Package, S.Set C.Version)
+    -> Either [String] (S.Set C.Version, Project Package)
+checkVersions TestedWithUniform = checkVersionsUniform
+checkVersions TestedWithAny     = checkVersionsAny
+
+checkVersionsUniform
+    :: Project (Package, S.Set C.Version)
+    -> Either [String] (S.Set C.Version, Project Package)
+checkVersionsUniform prj | null (prjPackages prj) = Left ["Error reading cabal file(s)!"]
+checkVersionsUniform prj = do
+    let (errors, names) = F.foldl' collectConfig mempty prj
+    if not (null errors)
+    then Left errors
+    else Right (allVersions, prj { prjPackages = names })
+  where
+    allVersions = F.foldMap snd prj
+
+    collectConfig
+        :: ([String], [Package])
+        -> (Package, S.Set C.Version)
+        -> ([String], [Package])
+    collectConfig aggregate (pkg, testWith) =
+        aggregate <> (errors, [pkg])
+      where
+        symDiff a b = S.union a b `S.difference` S.intersection a b
+        diff = symDiff testWith allVersions
+        missingVersions = map C.prettyShow $ S.toList diff
+        errors | S.null diff = []
+               | otherwise = pure $ mconcat
+                    [ pkgName pkg
+                    , " is missing tested-with annotations for: "
+                    ] ++ intercalate "," missingVersions
+
+checkVersionsAny
+    :: Project (Package, S.Set C.Version)
+    -> Either [String] (S.Set C.Version, Project Package)
+checkVersionsAny prj | null (prjPackages prj) = Left ["Error reading cabal file(s)!"]
+checkVersionsAny prj =
+    Right (allVersions, uncurry f <$> prj)
+  where
+    allVersions = F.foldMap snd prj
+
+    f :: Package -> S.Set C.Version -> Package
+    f pkg vs = case S.toList vs of
+        []      -> pkg { pkgJobs = C.noVersion }
+        (v:vs') -> pkg { pkgJobs = foldr g (C.thisVersion v) vs' }
+
+    g = C.unionVersionRanges . C.thisVersion
diff --git a/src/HaskellCI/Version.hs b/src/HaskellCI/Version.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/Version.hs
@@ -0,0 +1,42 @@
+module HaskellCI.Version where
+
+import           Data.Function                      (on)
+import           Data.List                          (intercalate)
+
+import qualified Distribution.Version as C
+
+ghcVersionPredicate :: C.VersionRange -> String
+ghcVersionPredicate = conj . C.asVersionIntervals
+  where
+    conj = intercalate "  ||  " . map disj
+
+    disj :: C.VersionInterval -> String
+    disj (C.LowerBound v C.InclusiveBound, C.UpperBound u C.InclusiveBound)
+        | v == u                = "[ $HCNUMVER -eq " ++ f v ++ " ]"
+    disj (lb, C.NoUpperBound)
+        | isInclZero lb         = "true"
+        | otherwise             = lower lb
+    disj (lb, C.UpperBound v b)
+        | isInclZero lb         = upper v b
+        | otherwise             = lower lb ++ " && " ++ upper v b
+
+    isInclZero (C.LowerBound v C.InclusiveBound) = v == C.mkVersion [0]
+    isInclZero (C.LowerBound _ C.ExclusiveBound) = False
+
+    lower (C.LowerBound v C.InclusiveBound) = "[ $HCNUMVER -ge " ++ f v ++ " ]"
+    lower (C.LowerBound v C.ExclusiveBound) = "[ $HCNUMVER -gt " ++ f v ++ " ]"
+
+    upper v C.InclusiveBound = "[ $HCNUMVER -le " ++ f v ++ " ]"
+    upper v C.ExclusiveBound = "[ $HCNUMVER -lt " ++ f v ++ " ]"
+
+    f = ghcVersionToString
+
+ghcVersionToString :: C.Version -> String
+ghcVersionToString v =  case C.versionNumbers v of
+    []        -> "0"
+    [x]       -> show (x * 10000)
+    [x,y]     -> show (x * 10000 + y * 100)
+    (x:y:z:_) -> show (x * 10000 + y * 100 + z)
+
+equivVersionRanges :: C.VersionRange -> C.VersionRange -> Bool
+equivVersionRanges = on (==) C.asVersionIntervals
diff --git a/src/MakeTravisYml.hs b/src/MakeTravisYml.hs
deleted file mode 100644
--- a/src/MakeTravisYml.hs
+++ /dev/null
@@ -1,1905 +0,0 @@
-{-# LANGUAGE Haskell2010 #-}
-{-# LANGUAGE CPP #-}
-{-# LANGUAGE ViewPatterns #-}
-{-# LANGUAGE NamedFieldPuns #-}
-{-# LANGUAGE MultiWayIf #-}
-{-# LANGUAGE DeriveFunctor #-}
-{-# LANGUAGE DeriveFoldable #-}
-{-# LANGUAGE DeriveTraversable #-}
-
-#if !defined(MIN_VERSION_Cabal)
--- As a heuristic, if the macro isn't defined, be pessimistic and
--- assume an "old" Cabal version
-# define MIN_VERSION_Cabal(x,y,z) 0
-#endif
-
--- | New-style @.travis.yml@ script generator using cabal 1.24's nix-style
--- tech-preview facilities.
---
--- See also <https://github.com/haskell-CI/haskell-ci>
---
--- NB: This code deliberately avoids relying on non-standard packages and
---     is expected to compile/work with at least GHC 7.0 through GHC 8.0
-module MakeTravisYml (
-    main,
-    -- * for tests
-    Result (..),
-    Diagnostic (..),
-    parseOptsNoCommands,
-    formatDiagnostic, formatDiagnostics,
-    travisFromConfigFile, MakeTravisOutput, Options (..), defOptions, options,
-    ) where
-
-import Control.Applicative as App ((<$>),(<|>), pure)
-import Control.DeepSeq (force)
-import Control.Exception (evaluate)
-import Control.Monad (void, when, unless, filterM, liftM, liftM2, forM_, mzero, foldM, join)
-import Data.Char (isSpace, isUpper, toLower)
-import qualified Data.Foldable as F
-import qualified Data.Traversable as T
-import Data.Function
-import Data.List
-import Data.Maybe
-import Data.Monoid as Mon (Monoid (..), Endo (..))
-import Data.Either (partitionEithers)
-import Data.Set (Set)
-import qualified Data.Set as S
-import qualified Data.Map as M
-import System.Console.GetOpt
-import System.Directory (doesDirectoryExist, getDirectoryContents, doesFileExist)
-import System.Environment
-import System.Exit
-import System.FilePath.Posix ((</>), takeDirectory, takeFileName, takeExtension)
-import System.IO
-import Control.Monad.IO.Class
-import Control.Monad.Trans.Class (lift)
-import Control.Monad.Trans.Maybe
-import Control.Monad.Trans.Writer
-import Text.Read (readMaybe)
-
-import Distribution.Compiler (CompilerFlavor(..))
-import Distribution.Package hiding (Package, pkgName)
-import qualified Distribution.Package as Pkg
-import Distribution.PackageDescription (GenericPackageDescription,packageDescription, testedWith, package, condLibrary, condTestSuites)
-import Distribution.PackageDescription.Configuration (flattenPackageDescription)
-import qualified Distribution.PackageDescription as PD
-import qualified Distribution.ParseUtils as PU
-import Distribution.Text
-import Distribution.Version
-#if MIN_VERSION_Cabal(2,2,0)
-import Distribution.PackageDescription.Parsec (readGenericPackageDescription)
-#elif MIN_VERSION_Cabal(2,0,0)
-import Distribution.PackageDescription.Parse (readGenericPackageDescription)
-#else
-import Distribution.PackageDescription.Parse (readPackageDescription)
-import Distribution.Verbosity (Verbosity)
-#endif
-import Distribution.Compat.ReadP
-    ( ReadP, (<++), (+++), between, char, many1, munch1
-    , pfail, readP_to_S, readS_to_P, look
-    , satisfy, sepBy, sepBy1, gather, munch, skipSpaces)
-
-#ifdef MIN_VERSION_ShellCheck
-import ShellCheck.Checker (checkScript)
-import qualified ShellCheck.Interface as SC
-import qualified ShellCheck.Formatter.Format as SC
-import qualified ShellCheck.Formatter.TTY as SC.TTY
-import System.IO.Unsafe (unsafePerformIO)
-#endif
-
-#if MIN_VERSION_base(4,9,0)
-import Data.Semigroup (Semigroup (..))
-#else
-import Data.Monoid ((<>))
-#endif
-
-#if !(MIN_VERSION_Cabal(2,0,0))
--- compat helpers for pre-2.0
-
-readGenericPackageDescription :: Verbosity -> FilePath -> IO GenericPackageDescription
-readGenericPackageDescription = readPackageDescription
-
-mkVersion :: [Int] -> Version
-mkVersion vn = Version vn []
-
-versionNumbers :: Version -> [Int]
-versionNumbers (Version vn _) = vn
-
-#endif
-
--------------------------------------------------------------------------------
--- Hardcoded values
--------------------------------------------------------------------------------
-
-knownGhcVersions :: [Version]
-knownGhcVersions = fmap mkVersion
-    [ [7,0,1],  [7,0,2], [7,0,3], [7,0,4]
-    , [7,2,1],  [7,2,2]
-    , [7,4,1],  [7,4,2]
-    , [7,6,1],  [7,6,2], [7,6,3]
-    , [7,8,1],  [7,8,2], [7,8,3], [7,8,4]
-    , [7,10,1], [7,10,2], [7,10,3]
-    , [8,0,1],  [8,0,2]
-    , [8,2,1],  [8,2,2]
-    , [8,4,1],  [8,4,2], [8,4,3]
-    , [8,6,1]
-    ]
-
-ghcAlpha :: Maybe Version
--- ghcAlpha = Nothing
-ghcAlpha = Just (mkVersion [8,6,1])
-
-cabalVerMap :: [((Int, Int), Maybe Version)]
-cabalVerMap = fmap (fmap (fmap mkVersion))
-    [ ((7, 0), Just [2,2]) -- Use 2.2 for everything.
-    , ((7, 2), Just [2,2])
-    , ((7, 4), Just [2,2])
-    , ((7, 6), Just [2,2])
-    , ((7, 8), Just [2,2])
-    , ((7,10), Just [2,2])
-    , ((8, 0), Just [2,2])
-    , ((8, 2), Just [2,2])
-    , ((8, 4), Just [2,2])
-    ]
-
-defaultHLintVersion :: VersionRange
-defaultHLintVersion = withinVersion (mkVersion [2,1])
-
-defaultDoctestVersion :: VersionRange
-defaultDoctestVersion = withinVersion (mkVersion [0,16])
-
--------------------------------------------------------------------------------
--- Script
--------------------------------------------------------------------------------
-
--- |  Encode shell command to be YAML safe and (optionally) ShellCheck it.
-sh :: String -> String
-sh = sh'
-    [ 2034 -- VAR appears unused. Verify it or export it.
-    , 2086 -- SC2086: Double quote to prevent globbing and word splitting.
-    ]
-
--- | Like 'sh' but with explicit SC exclude codes.
-sh' :: [Integer] -> String -> String
-#ifndef MIN_VERSION_ShellCheck
-sh' _ = shImpl
-#else
-sh' excl cmd = unsafePerformIO $ do
-  res <- checkScript iface spec
-  case res of
-    (SC.CheckResult _ []) -> return (shImpl cmd)
-    _                     -> SC.onResult scFormatter res iface >> fail "ShellCheck!"
-  where
-    iface = SC.SystemInterface $ \n -> return $ Left $ "cannot read file: " ++ n
-    spec  = SC.emptyCheckSpec { SC.csFilename = "stdin"
-                              , SC.csScript = cmd
-                              , SC.csExcludedWarnings = excl
-                              , SC.csShellTypeOverride = Just SC.Sh
-                              }
-
-scFormatter :: SC.Formatter
-scFormatter = unsafePerformIO (SC.TTY.format (SC.FormatterOptions SC.ColorAlways))
-#endif
-
--- Non-ShellCheck version of sh'
-shImpl :: String -> String
-shImpl cmd
-    | ':' `elem` cmd = "  - " ++ show cmd
-    | otherwise      = "  - " ++ cmd
-
-comment :: String -> String
-comment = ("  # " ++)
-
-type MakeTravisOutput = Result Diagnostic [String]
-
-data Diagnostic
-    = Info String
-    | Warn String
-    | Error String
-  deriving (Eq, Show)
-
-formatDiagnostics :: [Diagnostic] -> String
-formatDiagnostics = unlines . map formatDiagnostic
-
-formatDiagnostic :: Diagnostic -> String
-formatDiagnostic (Error s) = "*ERROR* " ++ s
-formatDiagnostic (Warn  s) = "*WARNING* " ++ s
-formatDiagnostic (Info  s) = "*INFO* " ++ s
-
--- MaybeT is used to preserve the short-circuiting semantics of 'putStrLnErr'.
-type YamlWriter m a = MaybeT (WriterT MakeTravisOutput m) a
-
-putStrLnErr :: Monad m => String -> YamlWriter m a
-putStrLnErr m = do
-    lift . tell $ Failure [Error m]
-    mzero
-
-putStrLnErrs :: Monad m => [String] -> YamlWriter m ()
-putStrLnErrs [] = return ()
-putStrLnErrs ms = do
-    lift (tell (Failure (map Error ms)))
-    mzero
-
-putStrLnWarn, putStrLnInfo :: Monad m => String -> YamlWriter m ()
-putStrLnWarn m = lift . tell $ Success [Warn m] []
-putStrLnInfo m = lift . tell $ Success [Info m] []
-
-tellStrLn :: Monad m => String -> YamlWriter m ()
-tellStrLn str = lift . tell $ success [str]
-
-tellStrLns :: Monad m => [String] -> YamlWriter m ()
-tellStrLns = lift . tell . success
-
-foldedTellStrLns
-    :: Monad m
-    => Fold
-    -> String
-    -> Set Fold
-    -> YamlWriter m ()
-    -> YamlWriter m ()
-foldedTellStrLns label = foldedTellStrLns' label ""
-
-foldedTellStrLns'
-    :: Monad m
-    => Fold
-    -> String
-    -> String
-    -> Set Fold
-    -> YamlWriter m ()
-    -> YamlWriter m ()
-foldedTellStrLns' label pfx prettyLabel labels output
-    | label `S.notMember` labels = output
-    | otherwise = tellStrLns [prologue] >> output >> tellStrLns epilogue
-  where
-    prologue = mconcat
-        [ "  - echo ", prettyLabel
-        , " && echo -en 'travis_fold:start:", showFold' label, "\\\\r'" ]
-    epilogue = ["  - echo -en 'travis_fold:end:" ++ showFold' label ++ "\\\\r'" ]
-
-    showFold' l = showFold l ++ if null pfx then "" else "-" ++ pfx
-
--- | Return the part after the first argument
---
--- >>> afterInfix "BAR" "FOOBAR XYZZY"
--- Just " XYZZY"
---
-afterInfix :: Eq a => [a] -> [a] -> Maybe [a]
-afterInfix needle haystack = findMaybe (afterPrefix needle) (tails haystack)
-
--- |
---
--- >>> afterPrefix "FOO" "FOOBAR"
--- Just "BAR"
---
-afterPrefix :: Eq a => [a] -> [a] -> Maybe [a]
-afterPrefix needle haystack
-    | needle `isPrefixOf` haystack = Just (drop (length needle) haystack)
-    | otherwise                    = Nothing
-
--- |
---
--- >>> findMaybe readMaybe ["foo", "1", "bar"] :: Maybe Int
--- Just 1
---
-findMaybe :: (a -> Maybe b) -> [a] -> Maybe b
-findMaybe f = foldr (\a b -> f a App.<|> b) Nothing
-
--- | >>> maybeReadP PU.parseTokenQ' "foo"
--- Just "foo"
-maybeReadP :: ReadP a a -> String -> Maybe a
-maybeReadP p s = listToMaybe $
-    [ x
-    | (x, rest) <- readP_to_S p s
-    , all isSpace rest
-    ]
-
-main :: IO ()
-main = do
-    argv <- getArgs
-    (opts,argv',configFile,xpkgs) <- parseOpts argv
-    genTravisFromConfigFile (argv',opts) configFile xpkgs
-
-parseOpts :: [String] -> IO (Options, [String], FilePath, [String])
-parseOpts argv = case argv of
-    (cmd : argv') | cmd `isPrefixOf` "regenerate" -> do
-        let fp = fromMaybe  ".travis.yml" $ listToMaybe argv'
-        ls <- fmap lines (readFile fp >>= evaluate . force) -- strict IO
-        case findArgv ls of
-            Nothing     -> dieCli [Error $ "expected REGENDATA line in " ++ fp ++ "\n"]
-            Just argv'' -> parseOpts argv''
-    [cmd] | cmd `isPrefixOf` "list-ghc" -> do
-        putStrLn $ "Supported GHC versions:"
-        forM_ groupedVersions $ \(v, vs) -> do
-            putStr $ prettyMajVersion v ++ ": "
-            putStrLn $ intercalate ", " (map display vs)
-        exitSuccess
-    _ -> parseOptsNoCommands argv
-  where
-    groupedVersions :: [(Version, [Version])]
-    groupedVersions = map ((\vs -> (head vs, vs)) . sortBy (flip compare))
-                    . groupBy ((==) `on` ghcMajVer)
-                    $ sort knownGhcVersions
-
-    prettyMajVersion :: Version -> String
-    prettyMajVersion v
-        | Just v == ghcAlpha = "alpha"
-        | otherwise = case ghcMajVer v of (x,y) -> show x ++ "." ++ show y
-
-    findArgv :: [String] -> Maybe [String]
-    findArgv ls = do
-        l <- findMaybe (afterInfix "REGENDATA") ls
-        readMaybe l
-
--- Returns options, used argv, cabal file, xpkgs
-parseOptsNoCommands :: [String] -> IO (Options, [String], FilePath, [String])
-parseOptsNoCommands argv = case getOpt Permute options argv of
-    (opts',configFile:xpkgs,[]) -> do
-        opts <- foldOptions defOptions opts'
-        return (opts,argv,configFile,xpkgs)
-    (_,_,[]) -> dieCli [Error "expected .cabal or cabal.project file as first non-option argument\n"]
-    (_,_,errs) -> dieCli (map Error errs)
-  where
-    foldOptions :: Options -> [Result Diagnostic (Options -> Options)] -> IO Options
-    foldOptions def opts = case foldOptions' def opts of
-        Success ws x -> do
-            hPutStr stderr (formatDiagnostics ws)
-            return x
-        Failure errs -> dieCli errs
-
-    foldOptions' :: Options -> [Result e (Options -> Options)] -> Result e Options
-    foldOptions' opts = fmap (`appEndo` opts) . F.foldMap (fmap Endo)
-
-dieCli ::  [Diagnostic] -> IO a
-dieCli errs = hPutStrLn stderr (usageMsg errs) >> exitFailure
- where
-    usageMsg errs' = formatDiagnostics errs' ++ usageInfo h options ++ ex
-
-    h = intercalate "\n"
-        [ "Usage: runghc make_travis_yml_2.hs [OPTIONS] <cabal-file | cabal.project> <extra-apt-packages...>"
-        , ""
-        , "Available commands:"
-        , "    regenerate [TRAVIS.YAML]  Regenerate the file using the magic command in it. Default .travis.yml"
-        , "    list-ghc                  List GHC versions supported by this version of make-travis-yml"
-        , ""
-        , "Available options:"
-        ]
-
-    ex = unlines
-        [ ""
-        , "Example:"
-        , "    runghc make_travis_yml_2.hs -o .travis.yml someProject.cabal liblzma-dev"
-        ]
-
-runYamlWriter :: Maybe FilePath -> YamlWriter IO () -> IO ()
-runYamlWriter mfp m = do
-    result <- execWriterT (runMaybeT m)
-    case result of
-        Failure (formatDiagnostics -> errors) -> hPutStr stderr errors >> exitFailure
-        Success (formatDiagnostics -> warnings) (unlines -> contents) -> do
-            contents' <- evaluate (force contents)
-            hPutStr stderr warnings
-            case mfp of
-                Nothing -> putStr contents'
-                Just fp -> writeFile fp contents'
-
-ghcMajVer :: Version -> (Int,Int)
-ghcMajVer v
-    | x:y:_ <- versionNumbers v = (x,y)
-    | otherwise = error $ "panic: ghcMajVer called with " ++ show v
-
--- | Alphas, RCs and HEAD.
-previewGHC :: Maybe Version -> Bool
-previewGHC = maybe True $ \v -> Just v == ghcAlpha || odd (snd (ghcMajVer v))
-
-dispGhcVersion :: Maybe Version -> String
-dispGhcVersion = maybe "head" display
-
-data Package = Pkg
-    { pkgName :: String
-    , pkgDir :: FilePath
-    , pkgGpd :: GenericPackageDescription
-    } deriving (Eq, Show)
-
-genTravisFromConfigFile :: ([String],Options) -> FilePath -> [String] -> IO ()
-genTravisFromConfigFile args@(_, opts) path xpkgs =
-    runYamlWriter (optOutput opts) $ travisFromConfigFile args path xpkgs
-
-travisFromConfigFile
-    :: MonadIO m
-    => ([String],Options)
-    -> FilePath
-    -> [String]
-    -> YamlWriter m ()
-travisFromConfigFile args@(_, opts) path xpkgs = do
-    cabalFiles <- getCabalFiles
-    config' <- maybe (return emptyConfig) readConfigFile (optConfig opts)
-    let config = optConfigMorphism opts config'
-    pkgs <- T.mapM (configFromCabalFile config opts) cabalFiles
-    (ghcs, prj) <- checkVersions pkgs
-    genTravisFromConfigs args xpkgs isCabalProject config prj ghcs
-  where
-    checkVersions
-        :: MonadIO m
-        => Project (Package, Set Version)
-        -> YamlWriter m (Set Version, Project Package)
-    checkVersions prj | null (prjPackages prj) = putStrLnErr "Error reading cabal file(s)!"
-    checkVersions prj = do
-        let (errors, names) = F.foldl' collectConfig mempty prj
-        putStrLnErrs errors
-        return (allVersions, prj { prjPackages = names })
-      where
-        allVersions = F.foldMap snd prj
-
-        collectConfig
-            :: ([String], [Package])
-            -> (Package, Set Version)
-            -> ([String], [Package])
-        collectConfig aggregate (pkg, testWith) =
-            aggregate <> (errors, [pkg])
-          where
-            symDiff a b = S.union a b `S.difference` S.intersection a b
-            diff = symDiff testWith allVersions
-            missingVersions = map display $ S.toList diff
-            errors | S.null diff = []
-                   | otherwise = App.pure $ mconcat
-                        [ pkgName pkg
-                        , " is missing tested-with annotations for: "
-                        ] ++ intercalate "," missingVersions
-
-    isCabalProject :: Maybe FilePath
-    isCabalProject
-        | "cabal.project" `isPrefixOf` takeFileName path = Just path
-        | otherwise = Nothing
-
-    getCabalFiles :: MonadIO m => YamlWriter m (Project FilePath)
-    getCabalFiles
-        | isNothing isCabalProject = return (Project [path] Nothing Nothing)
-        | otherwise = do
-            contents <- liftIO $ readFile path
-            pkgs <- either putStrLnErr return $ parseProjectFile path contents
-            overPrjPackages concat `liftM` T.mapM findProjectPackage pkgs
-
-    rootdir = takeDirectory path
-
-    -- See findProjectPackages in cabal-install codebase
-    -- this is simple variant.
-    findProjectPackage :: MonadIO m => String -> YamlWriter m [FilePath]
-    findProjectPackage pkglocstr = do
-        mfp <- checkisFileGlobPackage pkglocstr `mplusMaybeT`
-               checkIsSingleFilePackage pkglocstr
-        maybe (putStrLnErr $ "bad package location: " ++ pkglocstr) return mfp
-
-    checkIsSingleFilePackage pkglocstr = do
-        let abspath = rootdir </> pkglocstr
-        isFile <- liftIO $ doesFileExist abspath
-        isDir  <- liftIO $ doesDirectoryExist abspath
-        if | isFile && takeExtension pkglocstr == ".cabal" -> return (Just [abspath])
-           | isDir -> checkisFileGlobPackage (pkglocstr </> "*.cabal")
-           | otherwise -> return Nothing
-
-    -- if it looks like glob, glob
-    checkisFileGlobPackage pkglocstr =
-        case filter (null . snd) $ readP_to_S parseFilePathGlobRel pkglocstr of
-            [(g, "")] -> do
-                files <- liftIO $ expandRelGlob rootdir g
-                let files' = filter ((== ".cabal") . takeExtension) files
-                -- if nothing is matched, skip.
-                if null files' then return Nothing else return (Just files')
-            _         -> return Nothing
-
-    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)
-
-configFromCabalFile
-    :: MonadIO m => Config ->  Options -> FilePath -> YamlWriter m (Package, Set Version)
-configFromCabalFile cfg opts cabalFile = do
-    gpd <- liftIO $ readGenericPackageDescription maxBound cabalFile
-
-    let compilers = testedWith $ packageDescription gpd
-        pkgNameStr = display $ Pkg.pkgName $ package $ packageDescription gpd
-
-    let unknownComps = nub [ c | (c,_) <- compilers, c /= GHC ]
-        ghcVerConstrs = [ vc | (GHC,vc) <- compilers ]
-        ghcVerConstrs' = simplifyVersionRange $ foldr unionVersionRanges noVersion ghcVerConstrs
-        twoDigitGhcVerConstrs = mapMaybe isTwoDigitGhcVersion ghcVerConstrs :: [Version]
-        specificGhcVers = nub $ mapMaybe isSpecificVersion ghcVerConstrs
-
-    unless (null twoDigitGhcVerConstrs) $ do
-        putStrLnWarn $ "'tested-with:' uses two digit GHC versions (which don't match any existing GHC version): " ++ intercalate ", " (map display twoDigitGhcVerConstrs)
-        putStrLnInfo $ "Either use wild-card format, for example 'tested-with: GHC ==7.10.*' or a specific existing version 'tested-with: GHC ==7.10.3'"
-
-    when (null compilers) $ do
-        putStrLnErr (unlines $
-                     [ "empty or missing top-level 'tested-with:' definition in " ++ cabalFile ++ " file; example definition:"
-                     , ""
-                     , "tested-with: " ++ intercalate ", " [ "GHC==" ++ display v | v <- lastStableGhcVers ]
-                     ])
-
-    unless (null unknownComps) $ do
-        putStrLnWarn $ "ignoring unsupported compilers mentioned in tested-with: " ++ show unknownComps
-
-    when (null ghcVerConstrs) $ do
-        putStrLnErr "'tested-with:' doesn't mention any 'GHC' version"
-
-    when (isNoVersion ghcVerConstrs') $ do
-        putStrLnErr "'tested-with:' describes an empty version range for 'GHC'"
-
-    when (isAnyVersion ghcVerConstrs') $ do
-        putStrLnErr "'tested-with:' allows /any/ 'GHC' version"
-
-    let unknownGhcVers = sort $ specificGhcVers \\ knownGhcVersions
-
-    unless (null unknownGhcVers) $ do
-        putStrLnErr ("'tested-with:' specifically refers to unknown 'GHC' versions: "
-                     ++ intercalate ", " (map display unknownGhcVers) ++ "\n"
-                     ++ "Known GHC versions: " ++ intercalate ", " (map display knownGhcVersions))
-
-    let knownGhcVersions'
-            | cfgLastInSeries cfg = filterLastMajor knownGhcVersions
-            | otherwise           = knownGhcVersions
-
-    let testedGhcVersions = filter (`withinRange` ghcVerConstrs') knownGhcVersions'
-
-    when (null testedGhcVersions) $ do
-        putStrLnErr "no known GHC version is allowed by the 'tested-with' specification"
-
-    forM_ (optCollections opts) $ \c -> do
-        let v = collToGhcVer c
-        unless (v `elem` testedGhcVersions) $
-            putStrLnErr $ unlines
-               [ "collection " ++ c ++ " requires GHC " ++ display v
-               , "add 'tested-width: GHC == " ++ display v ++ "' to your .cabal file"
-               ]
-
-    let pkg = Pkg pkgNameStr (takeDirectory cabalFile) gpd
-
-    return (pkg, S.fromList testedGhcVersions)
-  where
-    lastStableGhcVers
-        = nubBy ((==) `on` ghcMajVer)
-        $ sortBy (flip compare)
-        $ filter (not . previewGHC . Just)
-        $ knownGhcVersions
-
-    isTwoDigitGhcVersion :: VersionRange -> Maybe Version
-    isTwoDigitGhcVersion vr = isSpecificVersion vr >>= t
-      where
-        t v | [_,_] <- versionNumbers v = Just v
-        t _                             = Nothing
-
-    filterLastMajor = map maximum . groupBy ((==) `on` ghcMajVer)
-
-genTravisFromConfigs
-    :: Monad m
-    => ([String], Options)
-    -> [String]
-    -> Maybe FilePath
-    -> Config
-    -> Project Package
-    -> Set Version
-    -> YamlWriter m ()
-genTravisFromConfigs (argv,opts) xpkgs isCabalProject config prj@Project { prjPackages = pkgs } versions' = do
-    let folds = cfgFolds config
-
-    putStrLnInfo $
-        "Generating Travis-CI config for testing for GHC versions: " ++ ghcVersions
-
-    unless (null $ optOsx opts) $  do
-        putStrLnInfo $ "Also OSX jobs for: " ++ ghcOsxVersions
-        unless (S.null omittedOsxVersions) $
-            putStrLnWarn $ "Not all GHC versions specified with --osx are generated: " ++ ghcOmittedOsxVersions
-
-    ---------------------------------------------------------------------------
-    -- travis.yml generation starts here
-
-    tellStrLns
-        [ "# This Travis job script has been generated by a script via"
-        , "#"
-        , "#   runghc make_travis_yml_2.hs " ++ unwords [ "'" ++ a ++ "'" | a <- argv ]
-        , "#"
-        , "# For more information, see https://github.com/haskell-CI/haskell-ci"
-        , "#"
-        , "language: c"
-        , "sudo: false"
-        , ""
-        , "git:"
-        , "  submodules: false  # whether to recursively clone submodules"
-        , ""
-        ]
-
-    let projectName = fromMaybe (pkgName $ head pkgs) (cfgProjectName config)
-    unless (null $ cfgIrcChannels config) $ tellStrLns $
-        [ "notifications:"
-        , "  irc:"
-        , "    channels:"
-        ] ++
-        [ "      - \"" ++ chan ++ "\"" | chan <- cfgIrcChannels config ] ++
-        [ "    skip_join: true"
-        , "    template:"
-        , "      - \"\\x0313" ++ projectName ++ "\\x03/\\x0306%{branch}\\x03 \\x0314%{commit}\\x03 %{build_url} %{message}\""
-        , ""
-        ]
-
-    unless (null $ cfgOnlyBranches config) $ tellStrLns $
-        [ "branches:"
-        , "  only:"
-        ] ++
-        [ "    - " ++ branch
-        | branch <- cfgOnlyBranches config
-        ] ++
-        [ ""
-        ]
-
-    when (cfgCache config) $ tellStrLns
-        [ "cache:"
-        , "  directories:"
-        , "    - $HOME/.cabal/packages"
-        , "    - $HOME/.cabal/store"
-        ]
-
-    -- on OSX ghc is installed in $HOME so we can cache it
-    -- independently of linux
-    when (cfgCache config && not (null (optOsx opts))) $ tellStrLns
-        [ "    - $HOME/.ghc-install"
-        ]
-
-    when (cfgCache config) $ tellStrLns
-        [ ""
-        , "before_cache:"
-        , "  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/build-reports.log"
-        , "  # remove files that are regenerated by 'cabal update'"
-        , "  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/00-index.*" -- legacy
-        , "  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/*.json" -- TUF meta-data
-        , "  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.cache"
-        , "  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.tar"
-        , "  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.tar.idx"
-        , ""
-        , "  - rm -rfv $HOME/.cabal/packages/head.hackage" -- if we cache, it will break builds.
-        , ""
-        ]
-
-    tellStrLn "matrix:"
-    tellStrLn "  include:"
-
-    let colls = [ (collToGhcVer cid,cid) | cid <- reverse $ optCollections opts ]
-
-    let tellJob :: Monad m => Bool -> Maybe Version -> YamlWriter m ()
-        tellJob osx gv = do
-            let cvs = dispGhcVersion $ cfgCabalInstallVersion config <|> (lookupCabVer =<< gv)
-                gvs = dispGhcVersion gv
-
-                xpkgs' = concatMap (',':) xpkgs
-
-                colls' = [ cid | (v,cid) <- colls, Just v == gv ]
-
-            tellStrLns
-                [ "    - compiler: \"ghc-" <> gvs <> "\""
-                , if | Just e <- gv >>= \v -> M.lookup v (cfgEnv config)
-                                     -> "      env: " ++ e
-                     | previewGHC gv -> "      env: GHCHEAD=true"
-                     | null colls'   -> "    # env: TEST=--disable-tests BENCH=--disable-benchmarks"
-                     | otherwise     -> "      env: 'COLLECTIONS=" ++ intercalate "," colls' ++ "'"
-                , "      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-" <> cvs <> ",ghc-" <> gvs <> xpkgs' <> "], sources: [hvr-ghc]}}"
-                ]
-
-            when osx $ tellStrLns
-                [ "      os: osx"
-                ]
-
-    -- newer GHC first, -head last (which is great).
-    -- Alpha release would go first though.
-    F.forM_ (reverse $ S.toList versions) $ tellJob False
-    F.forM_ (reverse $ S.toList osxVersions) $ tellJob True . Just
-
-    let allowFailures = headGhcVers `S.union` S.map Just (cfgAllowFailures config)
-    unless (S.null allowFailures) $ do
-        tellStrLn ""
-        tellStrLn "  allow_failures:"
-
-        F.forM_ allowFailures $ \gv -> do
-            let gvs = dispGhcVersion gv
-            tellStrLn $ concat [ "    - compiler: \"ghc-", gvs, "\"" ]
-
-    tellStrLns
-        [ ""
-        , "before_install:"
-        , sh "HC=${CC}"
-        , sh' [2034,2039] "HCPKG=${HC/ghc/ghc-pkg}" -- SC2039. In POSIX sh, string replacement is undefined.
-        , sh "unset CC"
-        -- rootdir is useful for manual script additions
-        , sh "ROOTDIR=$(pwd)"
-        , sh "mkdir -p $HOME/.local/bin"
-        ]
-
-    let haskellOnMacos = "https://haskell.futurice.com/haskell-on-macos.py"
-
-    if null (optOsx opts)
-    then tellStrLns
-        [ sh "PATH=/opt/ghc/bin:/opt/ghc-ppa-tools/bin:$HOME/local/bin:$PATH"
-        ]
-    else tellStrLns
-        [ sh $ "if [ \"$(uname)\" = \"Darwin\" ]; then brew update; brew upgrade python@3; curl " ++ haskellOnMacos ++ " | python3 - --make-dirs --install-dir=$HOME/.ghc-install --cabal-alias=head install cabal-install-head ${HC}; fi"
-        , sh $ "if [ \"$(uname)\" = \"Darwin\" ]; then PATH=$HOME/.ghc-install/ghc/bin:$HOME/local/bin:$PATH; else PATH=/opt/ghc/bin:/opt/ghc-ppa-tools/bin:$HOME/local/bin:$PATH; fi"
-        ]
-
-    -- HCNUMVER, numeric HC version, e.g. ghc 7.8.4 is 70804 and 7.10.3 is 71003
-    tellStrLns
-        [ sh $ "HCNUMVER=$(( $(${HC} --numeric-version|sed -E 's/([0-9]+)\\.([0-9]+)\\.([0-9]+).*/\\1 * 10000 + \\2 * 100 + \\3/') ))"
-        , sh "echo $HCNUMVER"
-        ]
-
-    unless (null colls) $
-       tellStrLn " - IFS=', ' read -a COLLS <<< \"$COLLECTIONS\""
-
-    tellStrLns
-        [ ""
-        , "install:"
-        , sh "cabal --version"
-        , sh "echo \"$(${HC} --version) [$(${HC} --print-project-git-commit-id 2> /dev/null || echo '?')]\""
-        , sh "BENCH=${BENCH---enable-benchmarks}"
-        , sh "TEST=${TEST---enable-tests}"
-        , sh "HADDOCK=${HADDOCK-true}"
-        , sh "UNCONSTRAINED=${UNCONSTRAINED-true}"
-        , sh "NOINSTALLEDCONSTRAINTS=${NOINSTALLEDCONSTRAINTS-false}"
-        , sh "GHCHEAD=${GHCHEAD-false}"
-        ]
-
-    -- Update hackage index. Side-effect: ~/.cabal.config is created.
-    tellStrLns
-        [ sh "travis_retry cabal update -v"
-        , sh "sed -i.bak 's/^jobs:/-- jobs:/' ${HOME}/.cabal/config"
-        , sh "rm -fv cabal.project cabal.project.local"
-        ]
-
-    -- Cabal jobs
-    case cfgJobs config of
-        (Just n, _) -> tellStrLns
-            [ sh $ "sed -i.bak 's/^-- jobs:.*/jobs: " ++ show n ++ "/' ${HOME}/.cabal/config"
-            ]
-        _ -> return ()
-
-    -- GHC jobs
-    case cfgJobs config of
-        (_, Just m) -> tellStrLns
-            [ sh $ "if [ $HCNUMVER -ge 70800 ]; then sed -i.bak 's/-- ghc-options:.*/ghc-options: -j" ++ show m ++ "/' ${HOME}/.cabal/config; fi"
-            ]
-        _ -> return ()
-
-    -- Add head.hackage repository to ~/.cabal/config
-    -- (locally you want to add it to cabal.project)
-    unless (S.null headGhcVers) $ tellStrLns
-        [ "  # Overlay Hackage Package Index for GHC HEAD: https://github.com/hvr/head.hackage"
-        , "  - |"
-        , "    if $GHCHEAD; then"
-        , "      sed -i 's/-- allow-newer: .*/allow-newer: *:base/' ${HOME}/.cabal/config"
-        , "      for pkg in $($HCPKG list --simple-output); do pkg=$(echo $pkg | sed 's/-[^-]*$//'); sed -i \"s/allow-newer: /allow-newer: *:$pkg, /\" ${HOME}/.cabal/config; done"
-        , ""
-        , "      echo 'repository head.hackage'                                                        >> ${HOME}/.cabal/config"
-        , "      echo '   url: http://head.hackage.haskell.org/'                                       >> ${HOME}/.cabal/config"
-        , "      echo '   secure: True'                                                                >> ${HOME}/.cabal/config"
-        , "      echo '   root-keys: 07c59cb65787dedfaef5bd5f987ceb5f7e5ebf88b904bbd4c5cbdeb2ff71b740' >> ${HOME}/.cabal/config"
-        , "      echo '              2e8555dde16ebd8df076f1a8ef13b8f14c66bad8eafefd7d9e37d0ed711821fb' >> ${HOME}/.cabal/config"
-        , "      echo '              8f79fd2389ab2967354407ec852cbe73f2e8635793ac446d09461ffb99527f6e' >> ${HOME}/.cabal/config"
-        , "      echo '   key-threshold: 3'                                                            >> ${HOME}/.cabal.config"
-        , ""
-        , "      grep -Ev -- '^\\s*--' ${HOME}/.cabal/config | grep -Ev '^\\s*$'"
-        , ""
-        , "      cabal new-update head.hackage -v"
-        , "    fi"
-        ]
-
-    -- Output cabal.config
-    tellStrLns
-        [ sh "grep -Ev -- '^\\s*--' ${HOME}/.cabal/config | grep -Ev '^\\s*$'"
-        ]
-
-    -- Install doctest
-    let doctestVersionConstraint
-            | isAnyVersion (cfgDoctestVersion config) = ""
-            | otherwise = " --constraint='doctest " ++ display (cfgDoctestVersion config) ++ "'"
-    when (cfgDoctest config) $ tellStrLns
-        [ sh $ "if [ $HCNUMVER -ge 80000 ]; then cabal new-install -w ${HC} -j2 --symlink-bindir=$HOME/.local/bin doctest" ++ doctestVersionConstraint ++ "; fi"
-        ]
-
-    -- Install hlint
-    let hlintVersionConstraint
-            | isAnyVersion (cfgHLintVersion config) = ""
-            | otherwise = " --constraint='hlint " ++ display (cfgHLintVersion config) ++ "'"
-    when (cfgHLint config) $ tellStrLns
-        [ sh $ "if [ $HCNUMVER -eq 80403 ]; then cabal new-install -w ${HC} -j2 --symlink-bindir=$HOME/.local/bin hlint" ++ hlintVersionConstraint ++ "; fi"
-        ]
-
-    -- create cabal.project file
-    generateCabalProject False
-
-    let pkgFilter = intercalate " | " $ map (wrap.pkgName) pkgs
-        wrap s = "grep -Fv \"" ++ s ++ " ==\""
-    unless (null colls) $ tellStrLns
-        [ "  - for COLL in \"${COLLS[@]}\"; do"
-        , "      echo \"== collection $COLL ==\";"
-        , "      ghc-travis collection ${COLL} > /dev/null || break;"
-        , "      ghc-travis collection ${COLL} | " ++ pkgFilter ++ " > cabal.project.freeze;"
-        , "      grep ' collection-id' cabal.project.freeze;"
-        , "      rm -rf dist-newstyle/;"
-        , "      cabal new-build -w ${HC} ${TEST} ${BENCH} --project-file=\"" ++ projectFile ++ "\" --dep -j2 all;"
-        , "    done"
-        , ""
-        ]
-
-    forM_ pkgs $ \Pkg{pkgDir} -> tellStrLns
-        [ "  - if [ -f \"" ++ pkgDir ++ "/configure.ac\" ]; then"
-        , "      (cd \"" ++ pkgDir ++ "\" && autoreconf -i);"
-        , "    fi"
-        ]
-
-    let quotedRmPaths =
-          ".ghc.environment.*"
-          ++ " " ++
-          quotedPaths (\Pkg{pkgDir} -> pkgDir ++ "/dist")
-
-    tellStrLns
-        [ sh $ "rm -f cabal.project.freeze"
-        ]
-
-    -- Install dependencies
-    when (cfgInstallDeps config) $ do
-        tellStrLns
-            [ sh $ "cabal new-build -w ${HC} ${TEST} ${BENCH} --project-file=\"" ++ projectFile ++"\" --dep -j2 all"
-            ]
-        when (cfgNoTestsNoBench config) $ tellStrLns
-            [ sh $ "cabal new-build -w ${HC} --disable-tests --disable-benchmarks --project-file=\"" ++ projectFile ++ "\" --dep -j2 all"
-            ]
-
-    tellStrLns
-        [ sh $ "rm -rf " ++ quotedRmPaths
-        , sh $  "DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)"
-        ]
-
-    tellStrLns
-        [ ""
-        , "# 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:"
-        , "  # test that source-distributions can be generated"
-        ]
-
-    foldedTellStrLns FoldSDist "Packaging..." folds $ do
-        forM_ pkgs $ \Pkg{pkgDir} -> tellStrLns
-            [ sh $ "(cd \"" ++ pkgDir ++ "\" && cabal sdist)"
-            ]
-
-    let tarFiles = quotedPaths $ \Pkg{pkgDir,pkgName} ->
-                pkgDir </> "dist" </> pkgName ++ "-*.tar.gz"
-
-
-    foldedTellStrLns FoldUnpack "Unpacking..." folds $ do
-        tellStrLns
-            [ sh $ "mv " ++ tarFiles ++ " ${DISTDIR}/"
-            , sh $ "cd ${DISTDIR} || false" -- fail explicitly, makes SC happier
-            , sh $ "find . -maxdepth 1 -name '*.tar.gz' -exec tar -xvf '{}' \\;"
-            ]
-        generateCabalProject True
-
-    when (cfgNoTestsNoBench config) $ foldedTellStrLns FoldBuild "Building..." folds $ tellStrLns
-        [ comment "this builds all libraries and executables (without tests/benchmarks)"
-        , sh "cabal new-build -w ${HC} --disable-tests --disable-benchmarks all"
-        ]
-
-    tellStrLns [""]
-
-
-    foldedTellStrLns FoldBuildEverything
-        "Building with tests and benchmarks..." folds $ tellStrLns
-        [ comment "build & run tests, build benchmarks"
-        , sh "cabal new-build -w ${HC} ${TEST} ${BENCH} all"
-        ]
-
-    -- cabal new-test fails if there are no test-suites.
-    when hasTests $
-        foldedTellStrLns FoldTest "Testing..." folds $ tellStrLns
-            [ sh $ mconcat
-                [ "if [ \"x$TEST\" = \"x--enable-tests\" ]; then "
-                , if cfgNoise config
-                     then "cabal "
-                     else "(set -o pipefail; cabal -vnormal+nowrap+markoutput "
-                , "new-test -w ${HC} ${TEST} ${BENCH} all"
-                , if cfgNoise config
-                     then ""
-                     else " 2>&1 | sed '/^-----BEGIN CABAL OUTPUT-----$/,/^-----END CABAL OUTPUT-----$/d' )"
-                , "; fi"
-                ]
-            ]
-
-    tellStrLns [""]
-
-    when (cfgDoctest config) $ do
-        let doctestOptions = unwords $ cfgDoctestOptions config
-        tellStrLns [ comment "doctest" ]
-        foldedTellStrLns FoldDoctest "Doctest..." folds $ do
-            forM_ pkgs $ \Pkg{pkgName,pkgGpd} -> do
-                let args = doctestArgs pkgGpd
-                    args' = unwords args
-                unless (null args) $ tellStrLns
-                    [ sh $ "if [ $HCNUMVER -ge 80000 ]; then (cd " ++ pkgName ++ "-* && doctest " ++ doctestOptions ++ " " ++ args' ++ "); fi"
-                    ]
-        tellStrLns [ "" ]
-
-    when (cfgHLint config) $ do
-        let "" <+> ys = ys
-            xs <+> "" = xs
-            xs <+> ys = xs ++ " " ++ ys
-
-            prependSpace "" = ""
-            prependSpace xs = " " ++ xs
-
-        let hlintOptions = prependSpace $ maybe "" ("-h ${ROOTDIR}/" ++) (cfgHLintYaml config) <+> unwords (cfgHLintOptions config)
-
-        tellStrLns [ comment "hlint" ]
-        foldedTellStrLns FoldHLint "HLint.." folds $ do
-            forM_ pkgs $ \Pkg{pkgName,pkgGpd} -> do
-                -- note: same arguments work so far for doctest and hlint
-                let args = doctestArgs pkgGpd
-                    args' = unwords args
-                unless (null args) $ tellStrLns
-                    [ sh $ "if [ $HCNUMVER -eq 80403 ]; then (cd " ++ pkgName ++ "-* && hlint" ++ hlintOptions ++ " " ++ args' ++ "); fi"
-                    ]
-        tellStrLns [ "" ]
-
-    when (cfgCheck config) $
-        foldedTellStrLns FoldCheck "cabal check..." folds $ do
-            tellStrLns [ comment "cabal check" ]
-            forM_ pkgs $ \Pkg{pkgName} -> tellStrLns
-                [ sh $ "(cd " ++ pkgName ++ "-* && cabal check)"
-
-                ]
-            tellStrLns [ "" ]
-
-    when hasLibrary $
-        foldedTellStrLns FoldHaddock "Haddock..." folds $ tellStrLns
-            [ comment "haddock"
-            , sh "rm -rf ./dist-newstyle"
-            , sh "if $HADDOCK; then cabal new-haddock -w ${HC} ${TEST} ${BENCH} all; else echo \"Skipping haddock generation\";fi"
-            , ""
-            ]
-
-    unless (null colls) $
-        foldedTellStrLns FoldStackage "Stackage builds..." folds $ tellStrLns
-            [ "  # try building & testing for package collections"
-            , "  - for COLL in \"${COLLS[@]}\"; do"
-            , "      echo \"== collection $COLL ==\";"
-            , "      ghc-travis collection ${COLL} > /dev/null || break;"
-            , "      ghc-travis collection ${COLL} | " ++ pkgFilter ++ " > cabal.project.freeze;"
-            , "      grep ' collection-id' cabal.project.freeze;"
-            , "      rm -rf dist-newstyle/;"
-            , "      cabal new-build -w ${HC} ${TEST} ${BENCH} all || break;"
-            , "      if [ \"x$TEST\" = \"x--enable-tests\" ]; then cabal new-test -w ${HC} ${TEST} ${BENCH} all || break; fi;"
-            , "    done"
-            , ""
-            ]
-
-    -- Have to build last, as we remove cabal.project.local
-    when (cfgUnconstrainted config) $ foldedTellStrLns FoldBuildInstalled
-        "Building without installed constraints for packages in global-db..." folds $ tellStrLns
-        [ comment "Build without installed constraints for packages in global-db"
-        -- SC2046: Quote this to prevent word splitting.
-        -- here we split on purpose!
-        , sh' [2046, 2086] $ unwords
-            [ "if $UNCONSTRAINED;"
-            , "then rm -f cabal.project.local; echo cabal new-build -w ${HC} --disable-tests --disable-benchmarks all;"
-            , "else echo \"Not building without installed constraints\"; fi"
-            ]
-        , ""
-        ]
-
-    -- and now, as we don't have cabal.project.local;
-    -- we can test with other constraint sets
-    let constraintSets = cfgConstraintSets config
-    unless (null constraintSets) $ do
-        tellStrLns
-            [ comment "Constraint sets"
-            , sh "rm -rf cabal.project.local"
-            , ""
-            ]
-        forM_ constraintSets $ \cs -> do
-            let name = csName cs
-            let constraintFlags = concatMap (\x ->  " --constraint='" ++ x ++ "'") (csConstraints cs)
-            tellStrLns [ comment  "Constraint set " ++ name ]
-            foldedTellStrLns' FoldConstraintSets name ("Constraint set " ++ name) folds $ tellStrLns
-                [ sh' [2086] $ "if " ++ ghcVersionPredicate (csGhcVersions cs) ++ "; then cabal new-build -w ${HC} --disable-tests --disable-benchmarks" ++ constraintFlags ++ " all; else echo skipping...; fi"
-                , ""
-                ]
-        tellStrLns [""]
-
-    tellStrLns
-        [ "# REGENDATA " ++ show argv
-        , "# EOF"
-        ]
-
-    return ()
-  where
-    hasTests   = F.any (\Pkg{pkgGpd} -> not . null $ condTestSuites pkgGpd) pkgs
-    hasLibrary = F.any (\Pkg{pkgGpd} -> isJust $ condLibrary pkgGpd) pkgs
-
-    -- GHC versions which need head.hackage
-    headGhcVers = S.filter previewGHC versions
-
-    generateCabalProject dist = do
-        tellStrLns
-            [ sh $ "printf 'packages: " ++ cabalPaths ++ "\\n' > cabal.project"
-            ]
-        F.forM_ (prjConstraints prj) $ \xs -> do
-            let s = concat (lines xs)
-            tellStrLns
-                [ sh $ "echo 'constraints: " ++ s ++ "' >> cabal.project"
-                ]
-        F.forM_ (prjAllowNewer prj) $ \xs -> do
-            let s = concat (lines xs)
-            tellStrLns
-                [ sh $ "echo 'allow-newer: " ++ s ++ "' >> cabal.project"
-                ]
-        unless (null (cfgLocalGhcOptions config)) $ forM_ pkgs $ \Pkg{pkgName} -> do
-            let s = unwords $ map (show . PU.showToken) $ cfgLocalGhcOptions config
-            tellStrLns
-                [ sh $ "echo 'package " ++ pkgName ++ "' >> cabal.project"
-                , sh $ "echo '  ghc-options: " ++ s ++ "' >> cabal.project"
-                ]
-
-        -- also write cabal.project.local file with
-        -- @
-        -- constraints: base installed
-        -- constraints: array installed
-        -- ...
-        --
-        -- omitting any local package names
-        tellStrLns
-            [ sh $ "touch cabal.project.local"
-            , sh $ unwords
-                [ "if ! $NOINSTALLEDCONSTRAINTS; then"
-                , "for pkg in $($HCPKG list --simple-output); do"
-                , "echo $pkg"
-                , concatMap (\Pkg{pkgName} -> " | grep -vw -- " ++ pkgName) pkgs
-                , "| sed 's/^/constraints: /'"
-                , "| sed 's/-[^-]*$/ installed/'"
-                , ">> cabal.project.local; done; fi"
-                ]
-            ]
-
-        tellStrLns
-            [ sh $ "cat cabal.project || true"
-            , sh $ "cat cabal.project.local || true"
-            ]
-      where
-        cabalPaths
-            | dist      = quotedPaths $ \Pkg{pkgName} -> pkgName ++ "-*/*.cabal"
-            | otherwise = quotedPaths $ \Pkg{pkgDir}  -> pkgDir
-
-    projectFile :: FilePath
-    projectFile = fromMaybe "cabal.project" isCabalProject
-
-    quotedPaths :: (Package -> FilePath) -> String
-    quotedPaths f = unwords $ map (f . quote) pkgs
-      where
-        quote pkg = pkg{ pkgDir = "\"" ++ pkgDir pkg ++ "\"" }
-
-    showVersions :: Set (Maybe Version) -> String
-    showVersions = unwords . map dispGhcVersion . S.toList
-
-    -- specified ersions
-    osxVersions' :: Set Version
-    osxVersions' = S.fromList $ mapMaybe simpleParse $ optOsx opts
-
-    versions :: Set (Maybe Version)
-    versions
-        | cfgGhcHead config = S.insert Nothing $ S.map Just versions'
-        | otherwise         = S.map Just versions'
-
-    ghcVersions :: String
-    ghcVersions = showVersions versions
-
-    osxVersions, omittedOsxVersions :: Set Version
-    (osxVersions, omittedOsxVersions) = S.partition (`S.member` versions') osxVersions'
-
-    ghcOsxVersions :: String
-    ghcOsxVersions = showVersions $ S.map Just osxVersions
-
-    ghcOmittedOsxVersions :: String
-    ghcOmittedOsxVersions = showVersions $ S.map Just omittedOsxVersions
-
-    lookupCabVer :: Version -> Maybe Version
-    lookupCabVer v = join $ lookup (ghcMajVer v) cabalVerMap
-
--- | Modules arguments to the library
---
--- * We check the library component
---
--- * If there are hs-source-dirs, use them
---
--- * otherwise use exposed + other modules
---
--- * Also add default-extensions
---
--- /Note:/ same argument work for hlint too!
---
-doctestArgs :: GenericPackageDescription -> [String]
-doctestArgs gpd = case PD.library $ flattenPackageDescription gpd of
-    Nothing -> []
-    Just l  -> exts ++ dirsOrMods
-      where
-        bi = PD.libBuildInfo l
-
-        dirsOrMods
-            | null (PD.hsSourceDirs bi) = map display (PD.exposedModules l)
-            | otherwise = PD.hsSourceDirs bi
-
-        exts = map (("-X" ++) . display) (PD.defaultExtensions bi)
-
-collToGhcVer :: String -> Version
-collToGhcVer cid = case simpleParse cid of
-  Nothing -> error ("invalid collection-id syntax " ++ show cid)
-  Just (PackageIdentifier n (versionNumbers -> v))
-    | display n /= "lts" -> error ("unknown collection " ++ show cid)
-    | isPrefixOf [0] v -> mkVersion [7,8,3]
-    | isPrefixOf [1] v -> mkVersion [7,8,4]
-    | isPrefixOf [2] v -> mkVersion [7,8,4]
-    | isPrefixOf [3] v -> mkVersion [7,10,2]
-    | isPrefixOf [4] v -> mkVersion [7,10,3]
-    | isPrefixOf [5] v -> mkVersion [7,10,3]
-    | isPrefixOf [6] v -> mkVersion [7,10,3]
-    | isPrefixOf [7] v -> mkVersion [8,0,1]
-    | otherwise -> error ("unknown collection " ++ show cid)
-
--------------------------------------------------------------------------------
--- Jobs
--------------------------------------------------------------------------------
-
--- | parse jobs defintion
---
--- * N:M - N ghcs (cabal -j), M threads (ghc -j)
---
--- >>> let parseJobs = maybeReadP parseJobsQ
--- >>> parseJobs "2:2"
--- Just (Just 2,Just 2)
---
--- >>> parseJobs ":2"
--- Just (Nothing,Just 2)
---
--- >>> parseJobs "2"
--- Just (Just 2,Nothing)
---
--- >>> parseJobs "garbage"
--- Nothing
---
-parseJobsQ :: ReadP r (Maybe Int, Maybe Int)
-parseJobsQ = nm <++ m <++ n <++ return (Nothing, Nothing)
-  where
-    nm = do
-      x <- parseInt
-      _ <- char ':'
-      y <- parseInt
-      return (Just x, Just y)
-
-    m = do
-      _ <- char ':'
-      y <- parseInt
-      return (Nothing, Just y)
-
-    n = do
-      x <- parseInt
-      return (Just x, Nothing)
-
--------------------------------------------------------------------------------
--- Project file
--------------------------------------------------------------------------------
-
-data Project a = Project
-    { prjPackages    :: [a]
-    , prjConstraints :: Maybe String
-    , prjAllowNewer  :: Maybe String
-    }
-  deriving (Show, Functor, F.Foldable, T.Traversable)
-
-overPrjPackages :: ([a] -> [b]) -> Project a -> Project b
-overPrjPackages f prj = prj { prjPackages = f (prjPackages prj) }
-
-emptyProject :: Project [a]
-emptyProject = Project [] Nothing Nothing
-
--- | Parse project file. Extracts only @packages@ field.
---
--- >>> fmap prjPackages $ parseProjectFile "cabal.project" "packages: foo bar/*.cabal"
--- Right ["foo","bar/*.cabal"]
---
-parseProjectFile :: FilePath -> String -> Either String (Project String)
-parseProjectFile path contents =
-    case PU.parseFields legacyProjectConfigFieldDescrs emptyProject contents of
-        PU.ParseOk _ x -> Right x
-        PU.ParseFailed err -> Left $ case PU.locatedErrorMsg err of
-            (l, msg) -> "ERROR " ++ path ++ ":" ++ show l ++ ": " ++ msg
-
-legacyProjectConfigFieldDescrs :: [PU.FieldDescr (Project String)]
-legacyProjectConfigFieldDescrs =
-    [ PU.listField "packages"
-        (error "we don't pretty print") -- pretty
-        parsePackageLocationTokenQ -- parse
-        prjPackages
-        (\x prj -> prj { prjPackages = x })
-    , PU.simpleField "constraints"
-        (error "we don't pretty print") -- pretty
-        (fmap Just PU.parseFreeText)
-        prjConstraints
-        (\x prj -> prj { prjConstraints = maybeAlt2 commaConcat (prjConstraints prj) x })
-    , PU.simpleField "allow-newer"
-        (error "we don't pretty print") -- pretty
-        (fmap Just PU.parseFreeText)
-        prjAllowNewer
-        (\x prj -> prj { prjAllowNewer = maybeAlt2 commaConcat (prjAllowNewer prj) x })
-    ]
-  where
-    maybeAlt2 _ Nothing  x        = x
-    maybeAlt2 _ x        Nothing  = x
-    maybeAlt2 f (Just x) (Just y) = Just (f x y)
-
-    commaConcat x y
-        | all isSpace x = y
-        | all isSpace y = x
-        | otherwise     = x ++ ", " ++ y
-
--------------------------------------------------------------------------------
--- Options
--------------------------------------------------------------------------------
-
-data Options = Options
-    { optCollections :: [String]
-    , optOutput :: Maybe FilePath
-    , optOsx :: [String]
-    , optConfig :: Maybe FilePath
-    , optConfigMorphism :: Config -> Config
-    }
-
-defOptions :: Options
-defOptions = Options
-    { optCollections = []
-    , optOutput = Nothing
-    , optOsx = []
-    , optConfig = Nothing
-    , optConfigMorphism = id
-    }
-
-options :: [OptDescr (Result Diagnostic (Options -> Options))]
-options =
-    [ Option [] ["no-cache"]
-      (NoArg $ successCM $ \cfg -> cfg { cfgCache = False })
-      "disable Travis caching"
-    , Option [] ["no-cabal-noise"]
-      (NoArg $ successCM $ \cfg -> cfg { cfgNoise = False })
-      "remove cabal noise from test output"
-    , Option [] ["no-cabal-check"]
-      (NoArg $ successCM $ \cfg -> cfg { cfgCheck = False })
-      "Disable cabal check"
-    , Option [] ["no-install-dependencies"]
-      (NoArg $ successCM $ \cfg -> cfg { cfgInstallDeps = False })
-      "Disable installing dependencies in a seperate step"
-    , Option [] ["no-no-tests-no-bench"]
-      (NoArg $ successCM $ \cfg -> cfg { cfgNoTestsNoBench = False })
-      "Don't build with --no-tests --no-benchmarks"
-    , Option [] ["no-unconstrained"]
-      (NoArg $ successCM $ \cfg -> cfg { cfgUnconstrainted = False })
-      "Build also without 'installed' constraints"
-    , Option [] ["ghc-head"]
-      (NoArg $ successCM $ \cfg -> cfg { cfgGhcHead = True })
-      "Build also with ghc-head"
-    , Option ['c'] ["collection"]
-      (ReqArg (success' $ \arg opts -> opts { optCollections = arg : optCollections opts }) "CID")
-      "enable package collection(s) (e.g. 'lts-7'), use multiple times for multiple collections"
-    , Option ['f'] ["fold"]
-      (flip OptArg "FOLDS" $ \arg -> case arg of
-        Nothing   -> successCM $ \cfg -> cfg { cfgFolds = S.fromList possibleFolds }
-        Just arg' -> case maybeReadP parseFoldQ arg' of
-            Nothing -> Failure [Error $ "cannot parse --fold argument: " ++ arg' ++ "\n"]
-            Just f  -> successCM $ \cfg -> cfg { cfgFolds = f (cfgFolds cfg) })
-      ("build output(s) to fold, use multiple times for multiple folds. No argument defaults to 'all'. Possible values: all, all-but-test, " ++ intercalate ", " (map showFold possibleFolds))
-    , Option [] ["irc-channel"]
-      (ReqArg (successCM' $ \arg cfg -> cfg { cfgIrcChannels = arg : cfgIrcChannels cfg }) "HOST#CHANNEL")
-      "enable IRC notifcations to given channel (e.g. 'irc.freenode.org#haskell-lens'), use multiple times for multiple channels"
-    , Option ['n'] ["name"]
-      (ReqArg (successCM' $ \arg cfg -> cfg { cfgProjectName = Just arg }) "NAME")
-      "project name (used for IRC notifications), defaults to package name or name of first package listed in cabal.project file"
-    , Option ['b'] ["branch"]
-      (ReqArg (successCM' $ \arg cfg -> cfg { cfgOnlyBranches = arg : cfgOnlyBranches cfg }) "BRANCH")
-      "enable builds only for specific brances, use multiple times for multiple branches"
-    , Option ['o'] ["output"]
-      (ReqArg (success' $ \arg opts -> opts { optOutput = Just arg }) "OUTPUT")
-      "output file (stdout if omitted)"
-    , Option [] ["config"]
-      (OptArg (success' $ \arg opts -> opts { optConfig = Just $ fromMaybe "cabal.make-travis-yml" arg }) "CONFIG")
-      "config file, currently used only to specify constraint sets"
-    , Option [] ["osx"]
-      (ReqArg (success' $ \arg opts -> opts { optOsx = arg : optOsx opts }) "GHC")
-      "generate osx build job with ghc version"
-    , Option ['j'] ["jobs"]
-      (reqArgReadP parseJobsQ (\jobs cfg -> cfg { cfgJobs = jobs }) "JOBS")
-      "jobs (N:M - cabal:ghc)"
-    , Option [] ["local-ghc-options"]
-      (reqArgReadP parseOptsQ (\xs cfg -> cfg { cfgLocalGhcOptions = xs }) "OPTIONS")
-      "--ghc-options for local packages"
-    , Option ['d'] ["doctest"]
-      (NoArg $ successCM $ \cfg -> cfg { cfgDoctest = True })
-      "Run doctest using .ghc.environment files."
-    , Option [] ["doctest-options"]
-      (reqArgReadP parseOptsQ (\xs cfg -> cfg { cfgDoctestOptions = xs }) "OPTIONS")
-      "Additional doctest options."
-    , Option [] ["doctest-version"]
-      (reqArgReadP parse (\arg cfg -> cfg { cfgDoctestVersion = arg }) "VERSION")
-      "Doctest version range"
-    , Option ['l'] ["hlint"]
-      (NoArg $ successCM $ \cfg -> cfg { cfgHLint = True })
-      "Run hlint (only on GHC-8.4.3 target)"
-    , Option [] ["hlint-yaml"]
-      (ReqArg (successCM' $ \arg cfg -> cfg { cfgHLintYaml = Just arg }) "HLINT.YAML")
-      "Relative path to .hlint.yaml."
-    , Option [] ["hlint-options"]
-      (reqArgReadP parseOptsQ (\xs cfg -> cfg { cfgHLintOptions = xs }) "OPTIONS")
-      "Additional hlint options."
-    , Option [] ["hlint-version"]
-      (reqArgReadP parse (\arg cfg -> cfg { cfgHLintVersion = arg }) "VERSION")
-      "HLint version range"
-    , Option [] ["cabal-install-version"]
-      (reqArgReadP parse (\arg cfg -> cfg { cfgCabalInstallVersion = Just arg }) "VERSION")
-      "cabal-install version for all jobs, overrides default"
-    , Option [] ["cabal-install-head"]
-      (NoArg $ successCM $ \cfg -> cfg { cfgCabalInstallVersion = Nothing })
-      "Use cabal-install-head for all jobs, overrides default"
-    , Option [] ["env"]
-      (reqArgReadP envP (\(k, v) cfg -> cfg { cfgEnv = M.insert k v (cfgEnv cfg) }) "ENVDECL")
-      "Environment (e.g. `8.0.2:HADDOCK=false`)"
-    , Option [] ["allow-failure"]
-      (reqArgReadP parse (\arg cfg -> cfg { cfgAllowFailures = S.insert arg (cfgAllowFailures cfg) }) "GHCVERSION")
-      "Allow failures of particular GHC version"
-    , Option [] ["last-in-series"]
-      (NoArg $ successCM $ \cfg -> cfg { cfgLastInSeries = True })
-      "[Discouraged] Assume there are only GHCs last in major series: 8.0.* will match only 8.2.2"
-    ]
-  where
-    overCM f opts = opts
-        { optConfigMorphism = f . optConfigMorphism opts
-        }
-
-    success' f arg = success (f arg)
-
-    successCM = success . overCM
-    successCM' f arg = successCM (f arg)
-
-    reqArgReadP :: ReadP a a -> (a -> Config -> Config) -> String -> ArgDescr (Result Diagnostic (Options -> Options))
-    reqArgReadP p f n = flip ReqArg n $ \arg -> case maybeReadP  p arg of
-        Nothing -> Failure [Error $  "cannot parse: " ++ arg ]
-        Just x  -> successCM' f x
-
-    envP :: ReadP r (Version, String)
-    envP = do
-        ghc <- parse
-        skipSpaces
-        _ <- char ':'
-        skipSpaces
-        v <- munch (const True)
-        return (ghc, v)
-
--------------------------------------------------------------------------------
--- Result
--------------------------------------------------------------------------------
-
-data Result e a
-    = Success [e] a
-    | Failure [e]
-    deriving (Eq, Show, Functor)
-
-success :: a -> Result e a
-success = Success []
-
-instance Monoid a => Mon.Monoid (Result e a) where
-    mempty = success mempty
-#if MIN_VERSION_base(4,9,0)
-    mappend = (<>)
-
-instance Monoid a => Semigroup (Result e a) where
-    Failure err1   <> Failure err2   = Failure $ err1 <> err2
-    Failure err1   <> Success err2 _ = Failure $ err1 <> err2
-    Success err1 _ <> Failure err2   = Failure $ err1 <> err2
-    Success l1 o1  <> Success l2 o2  = Success (mappend l1 l2) (mappend o1 o2)
-#else
-    Failure err1   `mappend` Failure err2   = Failure $ err1 `mappend` err2
-    Failure err1   `mappend` Success err2 _ = Failure $ err1 `mappend` err2
-    Success err1 _ `mappend` Failure err2   = Failure $ err1 `mappend` err2
-    Success l1 o1  `mappend` Success l2 o2  = Success (mappend l1 l2) (mappend o1 o2)
-#endif
-
--------------------------------------------------------------------------------
--- Fold
--------------------------------------------------------------------------------
-
-data Fold
-    = FoldSDist
-    | FoldUnpack
-    | FoldBuild
-    | FoldBuildInstalled
-    | FoldBuildEverything
-    | FoldTest
-    | FoldHaddock
-    | FoldStackage
-    | FoldCheck
-    | FoldDoctest
-    | FoldHLint
-    | FoldConstraintSets
-  deriving (Eq, Ord, Show, Enum, Bounded)
-
-showFold :: Fold -> String
-showFold = dashise . drop 4 . show
-  where
-    dashise = intercalate "-" . map (map toLower) . split
-
-    split [] = []
-    split xs0 =
-        let (ys, xs1) = span isUpper xs0
-            (zs, xs2) = break isUpper xs1
-        in (ys ++ zs) : split xs2
-
-possibleFolds :: [Fold]
-possibleFolds = [minBound .. maxBound]
-
-parseFoldQ :: ReadP r (Set Fold -> Set Fold)
-parseFoldQ = do
-    t <- PU.parseTokenQ
-    case t of
-        "all"          -> return $ const $ S.fromList possibleFolds
-        "all-but-test" -> return $ const $ S.delete FoldTest $ S.fromList possibleFolds
-        n -> case M.lookup n ps of
-            Just n' -> return (S.insert n')
-            Nothing -> fail $ "Illegal fold name: " ++ n
-  where
-    ps = M.fromList $ map (\x -> (showFold x, x)) possibleFolds
-
--------------------------------------------------------------------------------
--- Config file
--------------------------------------------------------------------------------
-
-data Config = Config
-    { cfgCabalInstallVersion :: Maybe Version
-    , cfgHLint           :: !Bool
-    , cfgHLintYaml       :: !(Maybe FilePath)
-    , cfgHLintVersion    :: !VersionRange
-    , cfgHLintOptions    :: [String]
-    , cfgJobs            :: (Maybe Int, Maybe Int)
-    , cfgDoctest         :: !Bool
-    , cfgDoctestOptions  :: [String]
-    , cfgDoctestVersion  :: !VersionRange
-    , cfgLocalGhcOptions :: [String]
-    , cfgConstraintSets  :: [ConstraintSet]
-    , cfgCache           :: !Bool
-    , cfgCheck           :: !Bool
-    , cfgNoise           :: !Bool
-    , cfgNoTestsNoBench  :: !Bool
-    , cfgUnconstrainted  :: !Bool
-    , cfgInstallDeps     :: !Bool
-    , cfgOnlyBranches    :: [String]
-    , cfgIrcChannels     :: [String]
-    , cfgProjectName     :: Maybe String
-    , cfgFolds           :: Set Fold
-    , cfgGhcHead         :: !Bool
-    , cfgEnv             :: M.Map Version String
-    , cfgAllowFailures   :: S.Set Version
-    , cfgLastInSeries    :: !Bool
-    }
-  deriving (Show)
-
-emptyConfig :: Config
-emptyConfig = Config
-    { cfgCabalInstallVersion = Nothing
-    , cfgHLint           = False
-    , cfgHLintYaml       = Nothing
-    , cfgHLintVersion    = defaultHLintVersion
-    , cfgHLintOptions    = []
-    , cfgJobs            = (Nothing, Nothing)
-    , cfgDoctest         = False
-    , cfgDoctestOptions  = []
-    , cfgDoctestVersion  = defaultDoctestVersion
-    , cfgLocalGhcOptions = []
-    , cfgConstraintSets  = []
-    , cfgCache           = True
-    , cfgCheck           = True
-    , cfgNoise           = True
-    , cfgNoTestsNoBench  = True
-    , cfgUnconstrainted  = True
-    , cfgInstallDeps     = True
-    , cfgOnlyBranches    = []
-    , cfgIrcChannels     = []
-    , cfgProjectName     = Nothing
-    , cfgFolds           = S.empty
-    , cfgGhcHead         = False
-    , cfgEnv             = M.empty
-    , cfgAllowFailures   = S.empty
-    , cfgLastInSeries    = False
-    }
-
-configFieldDescrs :: [PU.FieldDescr Config]
-configFieldDescrs =
-    [ PU.simpleField  "jobs"
-        (error "we don't pretty print")
-        parseJobsQ
-        cfgJobs
-        (\x cfg -> cfg { cfgJobs = x })
-    , PU.boolField  "hlint"
-        cfgHLint
-        (\b cfg -> cfg { cfgHLint = b })
-    , PU.simpleField "hlint-yaml"
-        (error "we don't pretty print")
-        (fmap Just PU.parseFilePathQ)
-        cfgHLintYaml
-        (\x cfg -> cfg { cfgHLintYaml = x })
-    , PU.simpleField "hlint-version"
-        (error "we don't pretty print")
-        parse
-        cfgHLintVersion
-        (\x cfg -> cfg { cfgHLintVersion = x })
-    -- TODO: hlint-options
-    , PU.boolField  "doctest"
-        cfgDoctest
-        (\b cfg -> cfg { cfgDoctest = b })
-    , PU.simpleField "doctest-options"
-        (error "we don't pretty print")
-        parseOptsQ
-        cfgDoctestOptions
-        (\x cfg -> cfg { cfgDoctestOptions = cfgDoctestOptions cfg ++ x })
-    , PU.simpleField "doctest-version"
-        (error "we don't pretty print")
-        parse
-        cfgDoctestVersion
-        (\x cfg -> cfg { cfgDoctestVersion = x })
-    , PU.simpleField "cabal-install-version"
-        (error "we don't pretty print")
-        (fmap Just parse)
-        cfgCabalInstallVersion
-        (\x cfg -> cfg { cfgCabalInstallVersion = x })
-    , PU.simpleField "local-ghc-options"
-        (error "we don't pretty print")
-        parseOptsQ
-        cfgLocalGhcOptions
-        (\x cfg -> cfg { cfgLocalGhcOptions = cfgLocalGhcOptions cfg ++ x })
-    , PU.boolField  "cache"
-        cfgCache
-        (\b cfg -> cfg { cfgCache = b })
-    , PU.boolField  "cabal-noise"
-        cfgNoise
-        (\b cfg -> cfg { cfgNoise = b })
-    , PU.boolField  "cabal-check"
-        cfgCheck
-        (\b cfg -> cfg { cfgCheck = b })
-    , PU.boolField  "install-dependencies-step"
-        cfgInstallDeps
-        (\b cfg -> cfg { cfgInstallDeps = b })
-    , PU.boolField  "no-tests-no-benchmarks"
-        cfgNoTestsNoBench
-        (\b cfg -> cfg { cfgNoTestsNoBench = b })
-    , PU.boolField  "unconstrained-step"
-        cfgUnconstrainted
-        (\b cfg -> cfg { cfgUnconstrainted = b })
-    , PU.listField  "irc-channels"
-        (error "we don't pretty print")
-        PU.parseTokenQ
-        cfgIrcChannels
-        (\x cfg -> cfg { cfgIrcChannels = x })
-    , PU.simpleField "name"
-        (error "we don't pretty print")
-        (fmap Just PU.parseTokenQ)
-        cfgProjectName
-        (\x cfg -> cfg { cfgProjectName = x })
-    , PU.listField  "branches"
-        (error "we don't pretty print")
-        PU.parseTokenQ
-        cfgOnlyBranches
-        (\x cfg -> cfg { cfgOnlyBranches = x })
-    , PU.simpleField  "folds"
-        (error "we don't pretty print")
-        (sepBy parseFoldQ (munch1 isSpace))
-        (\cfg -> [\_ -> cfgFolds cfg])
-        (\x cfg -> cfg { cfgFolds = foldl' (flip id) (cfgFolds cfg) x })
-    , PU.boolField "ghc-head"
-        cfgGhcHead
-        (\b cfg -> cfg { cfgGhcHead = b })
-    -- , PU.simpleField "env" -- TODO
-    ]
-
-parseOptsQ :: ReadP r [String]
-parseOptsQ = sepBy PU.parseTokenQ' (munch1 isSpace)
-
-readConfigFile :: MonadIO m => FilePath -> YamlWriter m Config
-readConfigFile path = do
-    contents <- liftIO $ readFile path
-    parseConfigFile path contents
-
-parseConfigFile :: Monad m => FilePath -> String -> YamlWriter m Config
-parseConfigFile path contents = toWriter $ do
-    fields' <- PU.readFields contents
-    let (fields, sections) = partitionEithers (map classify fields')
-    config <- accumFields configFieldDescrs emptyConfig fields
-    go config sections
-  where
-    toWriter r = case r of
-        PU.ParseOk ws x -> do
-            forM_ ws $ \w -> putStrLnWarn (PU.showPWarning path w)
-            return x
-        PU.ParseFailed err -> case PU.locatedErrorMsg err of
-            (l, msg) -> putStrLnErr $ path ++ ":" ++ show l ++ ": " ++ msg
-
-    classify x@PU.IfBlock {} = Right x
-    classify x@PU.Section {} = Right x
-    classify x@PU.F {}       = Left x
-
-    go :: Config -> [PU.Field] -> PU.ParseResult Config
-    go  cfg [] = return cfg
-    go _cfg (PU.IfBlock {} : _fields) = fail "if conditional found"
-    go  cfg (PU.F {} : fields)        = go cfg fields
-    go  cfg (PU.Section line name arg subfields : fields)
-        | name == "constraint-set" = do
-            cs <- accumFields constraintSetFieldDescrs (emptyConstraintSet arg) subfields
-            let cfg' = cfg { cfgConstraintSets = cfgConstraintSets cfg ++ [cs] }
-            go cfg' fields
-        | otherwise = do
-            PU.warning $ "Unknown section " ++ name ++ " on line " ++ show line
-            go cfg fields
-
--------------------------------------------------------------------------------
--- ConstraintSet
--------------------------------------------------------------------------------
-
-data ConstraintSet = ConstraintSet
-    { csName        :: String
-    , csGhcVersions :: VersionRange
-    , csConstraints :: [String] -- we parse these simply as strings
-    }
-  deriving (Show)
-
-emptyConstraintSet :: String -> ConstraintSet
-emptyConstraintSet n = ConstraintSet n anyVersion []
-
-constraintSetFieldDescrs :: [PU.FieldDescr ConstraintSet]
-constraintSetFieldDescrs =
-    [ PU.listField "constraints"
-        (error "we don't pretty print") -- pretty
-        (parseHaskellString <++ munch1 (`notElem` [',', '"']))
-        csConstraints
-        (\c cs -> cs { csConstraints = csConstraints cs ++ c })
-    , PU.simpleField "ghc"
-        (error "we don't pretty print") -- pretty
-        Distribution.Text.parse
-        csGhcVersions
-        (\c cs -> cs { csGhcVersions = c })
-    ]
-
-ghcVersionPredicate :: VersionRange -> String
-ghcVersionPredicate = conj . asVersionIntervals
-  where
-    conj = intercalate "  ||  " . map disj
-
-    disj :: VersionInterval -> String
-    disj (LowerBound v InclusiveBound, UpperBound u InclusiveBound)
-        | v == u              = "[ $HCNUMVER -eq " ++ f v ++ " ]"
-    disj (lb, NoUpperBound)   = lower lb
-    disj (lb, UpperBound v b) = lower lb ++ " && " ++ upper v b
-
-    lower (LowerBound v InclusiveBound) = "[ $HCNUMVER -ge " ++ f v ++ " ]"
-    lower (LowerBound v ExclusiveBound) = "[ $HCNUMVER -gt " ++ f v ++ " ]"
-
-    upper v InclusiveBound = "[ $HCNUMVER -le " ++ f v ++ " ]"
-    upper v ExclusiveBound = "[ $HCNUMVER -lt " ++ f v ++ " ]"
-
-    f v =  case versionNumbers v of
-        []        -> "0"
-        [x]       -> show (x * 10000)
-        [x,y]     -> show (x * 10000 + y * 100)
-        (x:y:z:_) -> show (x * 10000 + y * 100 + z)
-
--------------------------------------------------------------------------------
--- From Cabal
--------------------------------------------------------------------------------
-
-accumFields :: [PU.FieldDescr a] -> a -> [PU.Field] -> PU.ParseResult a
-accumFields fields = foldM setField
-  where
-    fieldMap = M.fromList
-        [ (name, f) | f@(PU.FieldDescr name _ _) <- fields ]
-    setField accum (PU.F line name value) = case M.lookup name fieldMap of
-      Just (PU.FieldDescr _ _ set) -> set line value accum
-      Nothing -> do
-          PU.warning $ "Unrecognized field " ++ name ++ " on line " ++ show line
-          return accum
-    setField accum f = do
-        PU.warning ("Unrecognized stanza on line " ++ show (PU.lineNo f))
-        return accum
-
--------------------------------------------------------------------------------
--- From cabal-install
--------------------------------------------------------------------------------
-
--- | 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 <++ parsePackageLocationToken
-  where
-    parsePackageLocationToken :: ReadP r String
-    parsePackageLocationToken = fmap fst (gather outerTerm)
-      where
-        outerTerm   = alternateEither1 outerToken (braces innerTerm)
-        innerTerm   = alternateEither  innerToken (braces innerTerm)
-        outerToken  = void $ munch1 outerChar
-        innerToken  = void $ munch1 innerChar
-        outerChar c = not (isSpace c || c == '{' || c == '}' || c == ',')
-        innerChar c = not (isSpace c || c == '{' || c == '}')
-        braces      = between (char '{') (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 = many1 q >> alternatePQs p q
-    alternateQsP     q p = alternate1QsP q p +++ return ()
-
-parseHaskellString :: ReadP r String
-parseHaskellString = readS_to_P reads
-
-parseInt :: ReadP r Int
-parseInt = readS_to_P reads
-
--------------------------------------------------------------------------------
--- Glob
--------------------------------------------------------------------------------
-
-{-
-
-Globbing code and grammar judiciously stolen from cabal-install:
-
-FilePathGlob    ::= FilePathRoot FilePathGlobRel
-FilePathRoot    ::= {- empty -}        # relative to cabal.project
-                  | "/"                # Unix root
-                  | [a-zA-Z] ":" [/\\] # Windows root
-                  | "~"                # home directory
-
-FilePathGlobRel ::= Glob "/"  FilePathGlobRel # Unix directory
-                  | Glob "\\" FilePathGlobRel # Windows directory
-                  | Glob         # file
-                  | {- empty -}  # trailing slash
-
-Glob      ::= GlobPiece *
-GlobPiece ::= "*"            # wildcard
-            | [^*{},/\\] *   # literal string
-            | "\\" [*{},]    # escaped reserved character
-            | "{" Glob "," ... "," Glob "}" # union (match any of these)
--}
-
-data FilePathGlob = FilePathGlob FilePathRoot FilePathGlobRel
-  deriving (Eq, Show)
-
-data FilePathGlobRel
-   = GlobDir  Glob FilePathGlobRel
-   | GlobFile Glob
-   | GlobDirTrailing -- trailing dir, a glob ending in '/'
-  deriving (Eq, Show)
-
--- | 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)
-
-data FilePathRoot
-   = FilePathRelative
-   | FilePathRoot FilePath -- e.g. '/', 'c:\' or result of 'takeDrive'
-   | FilePathHomeDir
-  deriving (Eq, Show)
-
-parseFilePathGlobRel :: ReadP r FilePathGlobRel
-parseFilePathGlobRel =
-      parseGlob >>= \globpieces ->
-          asDir globpieces
-      <++ asTDir globpieces
-      <++ asFile globpieces
-  where
-    asDir  glob = do dirSep
-                     GlobDir glob <$> parseFilePathGlobRel
-    asTDir glob = do dirSep
-                     return (GlobDir glob GlobDirTrailing)
-    asFile glob = return (GlobFile glob)
-
-    dirSep = void (char '/')
-         +++ (do _ <- char '\\'
-                 -- check this isn't an escape code
-                 following <- look
-                 case following of
-                   (c:_) | isGlobEscapedChar c -> pfail
-                   _                           -> return ())
-
-parseGlob :: ReadP r Glob
-parseGlob = many1 parsePiece
-  where
-    parsePiece = literal +++ wildcard +++ union'
-
-    wildcard = char '*' >> return WildCard
-
-    union' = between (char '{') (char '}') $
-              fmap Union (sepBy1 parseGlob (char ','))
-
-    literal = Literal `fmap` litchars1
-
-    litchar = normal +++ escape
-
-    normal  = satisfy (\c -> not (isGlobEscapedChar c)
-                                && c /= '/' && c /= '\\')
-    escape  = char '\\' >> 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
-
-expandRelGlob :: MonadIO m => FilePath -> FilePathGlobRel -> m [FilePath]
-expandRelGlob root glob0 = liftIO $ 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 App.<$> mapM (\subdir -> go globPath (dir </> subdir)) subdirs
-
-    go GlobDirTrailing dir = return [dir]
-
-matchGlob :: Glob -> FilePath -> 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
diff --git a/test/Tests.hs b/test/Tests.hs
--- a/test/Tests.hs
+++ b/test/Tests.hs
@@ -1,7 +1,7 @@
 {-# LANGUAGE ViewPatterns #-}
 module Main (main) where
 
-import MakeTravisYml hiding (main)
+import HaskellCI hiding (main)
 
 import Control.Applicative ((<$>), (<*>))
 import Control.Exception (ErrorCall(..), throwIO, try)
@@ -12,7 +12,6 @@
 import Data.List (isPrefixOf, stripPrefix)
 import Data.Maybe (mapMaybe)
 import Data.Monoid (mconcat)
-import System.Console.GetOpt (getOpt, ArgOrder(Permute))
 import System.Directory (doesFileExist, removeFile, setCurrentDirectory)
 import System.Exit (ExitCode(..), exitFailure)
 import System.FilePath (addExtension, (</>))
@@ -33,6 +32,11 @@
         , fixtureGoldenTest "cabal.project.empty-line"
         , fixtureGoldenTest "cabal.project.fail-versions"
         , fixtureGoldenTest "cabal.project.messy"
+        , testGroup "copy-fields"
+            [ fixtureGoldenTest "cabal.project.copy-fields.all"
+            , fixtureGoldenTest "cabal.project.copy-fields.some"
+            , fixtureGoldenTest "cabal.project.copy-fields.none"
+            ]
         ]
 
 linesToArgv :: String -> Maybe [String]
@@ -51,8 +55,8 @@
 -- @
 fixtureGoldenTest :: FilePath -> TestTree
 fixtureGoldenTest fp = cabalGoldenTest fp outputRef errorRef $ do
-    (argv, opts, xpkgs) <- makeTravisFlags
-    let genConfig = travisFromConfigFile (argv, opts) fp xpkgs
+    (argv, opts) <- makeTravisFlags
+    let genConfig = travisFromConfigFile argv opts fp
     execWriterT (runMaybeT genConfig)
   where
     outputRef = addExtension fp "travis.yml"
@@ -63,17 +67,19 @@
         | refExists = (linesToArgv . BS8.unpack) `fmap` BS.readFile outputRef
         | otherwise = return $ Just [fp]
 
-    makeTravisFlags :: IO ([String], Options, [String])
+    makeTravisFlags :: IO ([String], Options)
     makeTravisFlags = do
         result <- doesFileExist outputRef >>= referenceArgv
         case result of
             Nothing -> throwIO (ErrorCall "No REGENDATA in result file.")
             Just argv -> do
-                (opts, _argv, _fp, xpkgs) <- parseOpts argv
-                return (argv, opts, xpkgs)
+                (opts, _fp) <- parseOpts argv
+                return (argv, opts)
 
-parseOpts :: [String] -> IO (Options, [String], FilePath, [String])
-parseOpts = parseOptsNoCommands
+parseOpts :: [String] -> IO (Options, FilePath)
+parseOpts argv = do
+    (path, opts) <- parseTravis argv
+    return (opts, path)
 
 cabalGoldenTest
     :: TestName
