diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,11 @@
+## 0.4
+
+* Make default `--output` to be `.travis.yml`; use `--stdout` to output to standard output.
+* Add GHC-8.8 support
+* Use cabal-install-3.0 by default
+* Experimental support for GHCJS jobs
+* A lot of new configuration options
+
 ## 0.2.1
 
 * `local-ghc-options` are always applied (independent of `copy-fields` value)
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,31 +1,674 @@
-Copyright (c) 2017-2018, Herbert Valerio Riedel
-              2017-2018, Oleg Grenrus
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
 
-All rights reserved.
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
 
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
+                            Preamble
 
-    * Redistributions of source code must retain the above copyright
-      notice, this list of conditions and the following disclaimer.
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
 
-    * Redistributions in binary form must reproduce the above
-      copyright notice, this list of conditions and the following
-      disclaimer in the documentation and/or other materials provided
-      with the distribution.
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
 
-    * Neither the name of Herbert Valerio Riedel nor the names of other
-      contributors may be used to endorse or promote products derived
-      from this software without specific prior written permission.
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
 
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<https://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<https://www.gnu.org/licenses/why-not-lgpl.html>.
diff --git a/fixtures/cabal.haskell-ci b/fixtures/cabal.haskell-ci
--- a/fixtures/cabal.haskell-ci
+++ b/fixtures/cabal.haskell-ci
@@ -1,6 +1,10 @@
 -- Most settings can be given as command line arguments
 -- Config file is convenient if you maintain large amount of packages
 
+-- Supported: xenial and bionic;
+-- though bionic doesn't seem to be officially supported by Travis
+distribution: xenial
+
 jobs: 2:2
 
 -- Add GHC head job
@@ -12,6 +16,19 @@
 -- remove cabal noise from test output
 -- cabal-noise: False
 
+-- Build tests. In addition to True and False you may specify
+-- a version range, e.g. >= 8.0 to build tests only in some jobs.
+tests: True
+
+-- Run tests. Note that only built tests are run. Accepts booleans or version range.
+run-tests: True
+
+-- Build benchmarks. There are no way to run benchmarks. Accepts booleans or version range.
+benchmarks: True
+
+-- Build haddocks. Accepts booleans or version range.
+haddock: True
+
 -- Run cabal check
 -- cabal-check: True
 
@@ -51,8 +68,12 @@
 -- doctest-version: ==0.13.*
 
 -- macOS job
-osx: 8.4.4
+osx: 8.4.4 8.6.5
 
+-- Define per-job environment variables
+-- env: 8.4.4:FOO=bar,
+--      8.6.4:FOO=baz
+
 -- Constraint sets
 -- Package will be build with different constraints.
 -- This is useful to check compatibility with older versions of dependencies.
@@ -60,7 +81,18 @@
 constraint-set deepseq-1.4
   ghc: (>= 7.8 && <7.10) || == 8.2.2
   constraints: deepseq ==1.4.*
+  constraints: binary installed
 
+  -- Constraint sets accept booleans for few steps, as the main script
+  -- Defaults are False.
+  -- These fields don't accept version ranges: you should rather create
+  -- another constraint set.
+
+  -- tests: False
+  -- run-tests: False
+  -- benchmarks: False
+  haddock: True
+
 -- Copy over additional properties specified in a cabal.project file.
 -- Possible values are:
 --   none: Do not copy any properties from the cabal.project file.
@@ -75,6 +107,28 @@
 --         * source-repository-package
 --   all: Copy every property from the cabal.project file.
 copy-fields: some
+
+-- Configure haskell-ci's behavior when presented multiple packages (e.g., a
+-- cabal.project file that specifies multiple .cabal files), each of which
+-- having its own range of GHC versions in its tested-with stanza.
+-- Possible values are:
+--   uniform: Every package must have the same range of versions in its
+--            tested-with stanza. (Default)
+--   any:     Take the union of all packages' version ranges. This implies
+--            that different packages' version ranges can be disjoint.
+jobs-selection: uniform
+
+-- Controls whether the haskell-ci version should be inserted into the
+-- generated Travis YAML file. (Default: True)
+--
+-- We set this option to False here only to prevent version number churn in
+-- the commit history, as version numbers for haskell-ci HEAD change extremely
+-- frequently.
+insert-version: False
+
+-- Controls whether the output of `cabal` invocations should be highlighted
+-- using special colors in the Travis output.
+color: True
 
 -- Include these fields "as is" in generated cabal.project
 raw-project
diff --git a/fixtures/cabal.project.copy-fields.all.stderr b/fixtures/cabal.project.copy-fields.all.stderr
--- a/fixtures/cabal.project.copy-fields.all.stderr
+++ b/fixtures/cabal.project.copy-fields.all.stderr
@@ -1,1 +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 8.6.4
+*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 8.6.4 8.6.5 8.8.1
diff --git a/fixtures/cabal.project.copy-fields.all.travis.yml b/fixtures/cabal.project.copy-fields.all.travis.yml
--- a/fixtures/cabal.project.copy-fields.all.travis.yml
+++ b/fixtures/cabal.project.copy-fields.all.travis.yml
@@ -1,22 +1,18 @@
 # 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'
+#   haskell-ci '-o' 'cabal.project.empty-line.travis.yml' '--config=cabal.project.haskell-ci' '--copy-fields=all' 'cabal.project.empty-line'
 #
 # For more information, see https://github.com/haskell-CI/haskell-ci
 #
-# version: 0.2.1
-#
 language: c
 dist: xenial
-
 git:
-  submodules: false  # whether to recursively clone submodules
-
+  # whether to recursively clone submodules
+  submodules: false
 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'
@@ -25,142 +21,184 @@
   - 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.4"
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.6.4], 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.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]}}
-
+    - compiler: ghc-8.8.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.8.1","cabal-install-3.0"]}}
+    - compiler: ghc-8.6.5
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.5","cabal-install-3.0"]}}
+    - compiler: ghc-8.6.4
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.4","cabal-install-3.0"]}}
+    - compiler: ghc-8.6.3
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.3","cabal-install-3.0"]}}
+    - compiler: ghc-8.6.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.2","cabal-install-3.0"]}}
+    - compiler: ghc-8.6.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.1","cabal-install-3.0"]}}
+    - compiler: ghc-8.4.4
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.4","cabal-install-3.0"]}}
+    - compiler: ghc-8.4.3
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.3","cabal-install-3.0"]}}
+    - compiler: ghc-8.4.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.2","cabal-install-3.0"]}}
+    - compiler: ghc-8.4.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.1","cabal-install-3.0"]}}
+    - compiler: ghc-8.2.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.2.2","cabal-install-3.0"]}}
+    - compiler: ghc-8.2.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.2.1","cabal-install-3.0"]}}
+    - compiler: ghc-8.0.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.0.2","cabal-install-3.0"]}}
+    - compiler: ghc-8.0.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.0.1","cabal-install-3.0"]}}
+    - compiler: ghc-7.10.3
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.10.3","cabal-install-3.0"]}}
+    - compiler: ghc-7.10.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.10.2","cabal-install-3.0"]}}
+    - compiler: ghc-7.10.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.10.1","cabal-install-3.0"]}}
+    - compiler: ghc-7.8.4
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.8.4","cabal-install-3.0"]}}
+    - compiler: ghc-7.8.3
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.8.3","cabal-install-3.0"]}}
+    - compiler: ghc-7.8.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.8.2","cabal-install-3.0"]}}
+    - compiler: ghc-7.8.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.8.1","cabal-install-3.0"]}}
 before_install:
-  - HC=/opt/ghc/bin/${CC}
-  - HCPKG=${HC/ghc/ghc-pkg}
+  - HC=$(echo "/opt/$CC/bin/ghc" | sed 's/-/\//')
+  - WITHCOMPILER="-w $HC"
+  - HCPKG="$HC-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/') ))
+  - TOP=$(pwd)
+  - "HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\\d+)\\.(\\d+)\\.(\\d+)(\\.(\\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))')"
   - echo $HCNUMVER
-
+  - CABAL="$CABAL -vnormal+nowrap+markoutput"
+  - set -o pipefail
+  - |
+    echo 'function blue(s) { printf "\033[0;34m" s "\033[0m " }'           >> .colorful.awk
+    echo 'BEGIN { state = "output"; }'                                     >> .colorful.awk
+    echo '/^-----BEGIN CABAL OUTPUT-----$/ { state = "cabal" }'            >> .colorful.awk
+    echo '/^-----END CABAL OUTPUT-----$/ { state = "output" }'             >> .colorful.awk
+    echo '!/^(-----BEGIN CABAL OUTPUT-----|-----END CABAL OUTPUT-----)/ {' >> .colorful.awk
+    echo '  if (state == "cabal") {'                                       >> .colorful.awk
+    echo '    print blue($0)'                                              >> .colorful.awk
+    echo '  } else {'                                                      >> .colorful.awk
+    echo '    print $0'                                                    >> .colorful.awk
+    echo '  }'                                                             >> .colorful.awk
+    echo '}'                                                               >> .colorful.awk
+  - cat .colorful.awk
+  - |
+    color_cabal_output () {
+      awk -f $TOP/.colorful.awk
+    }
+  - echo text | color_cabal_output
 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
+  - HEADHACKAGE=false
+  - rm -f $CABALHOME/config
+  - |
+    echo "verbose: normal +nowrap +markoutput"          >> $CABALHOME/config
+    echo "remote-build-reporting: anonymous"            >> $CABALHOME/config
+    echo "write-ghc-environment-files: always"          >> $CABALHOME/config
+    echo "remote-repo-cache: $CABALHOME/packages"       >> $CABALHOME/config
+    echo "logs-dir:          $CABALHOME/logs"           >> $CABALHOME/config
+    echo "world-file:        $CABALHOME/world"          >> $CABALHOME/config
+    echo "extra-prog-path:   $CABALHOME/bin"            >> $CABALHOME/config
+    echo "symlink-bindir:    $CABALHOME/bin"            >> $CABALHOME/config
+    echo "installdir:        $CABALHOME/bin"            >> $CABALHOME/config
+    echo "build-summary:     $CABALHOME/logs/build.log" >> $CABALHOME/config
+    echo "store-dir:         $CABALHOME/store"          >> $CABALHOME/config
+    echo "install-dirs user"                            >> $CABALHOME/config
+    echo "  prefix: $CABALHOME"                         >> $CABALHOME/config
+    echo "repository hackage.haskell.org"               >> $CABALHOME/config
+    echo "  url: http://hackage.haskell.org/"           >> $CABALHOME/config
+  - cat $CABALHOME/config
+  - rm -fv cabal.project cabal.project.local cabal.project.freeze
+  - travis_retry ${CABAL} v2-update -v
+  # Generate cabal.project
+  - rm -rf cabal.project cabal.project.local cabal.project.freeze
   - 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"
+  - |
+    echo "packages: servant" >> cabal.project
+    echo "packages: servant-client" >> cabal.project
+    echo "packages: servant-docs" >> cabal.project
+    echo "packages: servant-server" >> cabal.project
+  - |
+    echo "package servant"                         >> cabal.project
+    echo "  tests: False"                          >> cabal.project
+    echo ""                                        >> 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
+  - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | (grep -vE -- '^(servant|servant-client|servant-docs|servant-server)$' || true) | 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.
+  - ${CABAL} v2-freeze $WITHCOMPILER ${TEST} ${BENCH} | color_cabal_output
+  - "cat cabal.project.freeze | sed -E 's/^(constraints: *| *)//' | sed 's/any.//'"
+  - rm  cabal.project.freeze
+  - ${CABAL} v2-build $WITHCOMPILER ${TEST} ${BENCH} --dep -j2 all | color_cabal_output
+  - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --dep -j2 all | color_cabal_output
 script:
-  # test that source-distributions can be generated
-  - ${CABAL} new-sdist all
+  - DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)
+  # Packaging...
+  - ${CABAL} v2-sdist all | color_cabal_output
+  # Unpacking...
   - mv dist-newstyle/sdist/*.tar.gz ${DISTDIR}/
   - cd ${DISTDIR} || false
-  - find . -maxdepth 1 -name '*.tar.gz' -exec tar -xvf '{}' \;
-  - rm -f cabal.project
+  - find . -maxdepth 1 -type f -name '*.tar.gz' -exec tar -xvf '{}' \;
+  - find . -maxdepth 1 -type f -name '*.tar.gz' -exec rm       '{}' \;
+  - PKGDIR_servant="$(find . -maxdepth 1 -type d -regex '.*/servant-[0-9.]*')"
+  - PKGDIR_servant_client="$(find . -maxdepth 1 -type d -regex '.*/servant-client-[0-9.]*')"
+  - PKGDIR_servant_docs="$(find . -maxdepth 1 -type d -regex '.*/servant-docs-[0-9.]*')"
+  - PKGDIR_servant_server="$(find . -maxdepth 1 -type d -regex '.*/servant-server-[0-9.]*')"
+  # Generate cabal.project
+  - rm -rf cabal.project cabal.project.local cabal.project.freeze
   - 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"
+  - |
+    echo "packages: ${PKGDIR_servant}" >> cabal.project
+    echo "packages: ${PKGDIR_servant_client}" >> cabal.project
+    echo "packages: ${PKGDIR_servant_docs}" >> cabal.project
+    echo "packages: ${PKGDIR_servant_server}" >> cabal.project
+  - |
+    echo "package servant"                         >> cabal.project
+    echo "  tests: False"                          >> cabal.project
+    echo ""                                        >> 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
+  - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | (grep -vE -- '^(servant|servant-client|servant-docs|servant-server)$' || true) | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"
   - cat cabal.project || true
   - cat cabal.project.local || true
+  # Building...
   # this builds all libraries and executables (without tests/benchmarks)
-  - ${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks all
-
+  - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks all | color_cabal_output
+  # Building with tests and benchmarks...
   # 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;
+  - ${CABAL} v2-build $WITHCOMPILER ${TEST} ${BENCH} all | color_cabal_output
+  # Testing...
+  - ${CABAL} v2-test $WITHCOMPILER ${TEST} ${BENCH} all | color_cabal_output
+  # cabal check...
+  - (cd ${PKGDIR_servant} && ${CABAL} -vnormal check)
+  - (cd ${PKGDIR_servant_client} && ${CABAL} -vnormal check)
+  - (cd ${PKGDIR_servant_docs} && ${CABAL} -vnormal check)
+  - (cd ${PKGDIR_servant_server} && ${CABAL} -vnormal check)
+  # haddock...
+  - ${CABAL} v2-haddock $WITHCOMPILER ${TEST} ${BENCH} all | color_cabal_output
+  # Building without installed constraints for packages in global-db...
+  - rm -f cabal.project.local
+  - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks all | color_cabal_output
 
-# REGENDATA ["-o","cabal.project.empty-line.travis.yml","--copy-fields=all","cabal.project.empty-line"]
+# REGENDATA ["-o","cabal.project.empty-line.travis.yml","--config=cabal.project.haskell-ci","--copy-fields=all","cabal.project.empty-line"]
 # EOF
diff --git a/fixtures/cabal.project.copy-fields.none.stderr b/fixtures/cabal.project.copy-fields.none.stderr
--- a/fixtures/cabal.project.copy-fields.none.stderr
+++ b/fixtures/cabal.project.copy-fields.none.stderr
@@ -1,1 +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 8.6.4
+*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 8.6.4 8.6.5 8.8.1
diff --git a/fixtures/cabal.project.copy-fields.none.travis.yml b/fixtures/cabal.project.copy-fields.none.travis.yml
--- a/fixtures/cabal.project.copy-fields.none.travis.yml
+++ b/fixtures/cabal.project.copy-fields.none.travis.yml
@@ -1,22 +1,18 @@
 # 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'
+#   haskell-ci '-o' 'cabal.project.empty-line.travis.yml' '--config=cabal.project.haskell-ci' '--copy-fields=none' 'cabal.project.empty-line'
 #
 # For more information, see https://github.com/haskell-CI/haskell-ci
 #
-# version: 0.2.1
-#
 language: c
 dist: xenial
-
 git:
-  submodules: false  # whether to recursively clone submodules
-
+  # whether to recursively clone submodules
+  submodules: false
 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'
@@ -25,132 +21,172 @@
   - 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.4"
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.6.4], 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.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]}}
-
+    - compiler: ghc-8.8.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.8.1","cabal-install-3.0"]}}
+    - compiler: ghc-8.6.5
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.5","cabal-install-3.0"]}}
+    - compiler: ghc-8.6.4
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.4","cabal-install-3.0"]}}
+    - compiler: ghc-8.6.3
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.3","cabal-install-3.0"]}}
+    - compiler: ghc-8.6.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.2","cabal-install-3.0"]}}
+    - compiler: ghc-8.6.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.1","cabal-install-3.0"]}}
+    - compiler: ghc-8.4.4
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.4","cabal-install-3.0"]}}
+    - compiler: ghc-8.4.3
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.3","cabal-install-3.0"]}}
+    - compiler: ghc-8.4.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.2","cabal-install-3.0"]}}
+    - compiler: ghc-8.4.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.1","cabal-install-3.0"]}}
+    - compiler: ghc-8.2.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.2.2","cabal-install-3.0"]}}
+    - compiler: ghc-8.2.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.2.1","cabal-install-3.0"]}}
+    - compiler: ghc-8.0.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.0.2","cabal-install-3.0"]}}
+    - compiler: ghc-8.0.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.0.1","cabal-install-3.0"]}}
+    - compiler: ghc-7.10.3
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.10.3","cabal-install-3.0"]}}
+    - compiler: ghc-7.10.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.10.2","cabal-install-3.0"]}}
+    - compiler: ghc-7.10.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.10.1","cabal-install-3.0"]}}
+    - compiler: ghc-7.8.4
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.8.4","cabal-install-3.0"]}}
+    - compiler: ghc-7.8.3
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.8.3","cabal-install-3.0"]}}
+    - compiler: ghc-7.8.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.8.2","cabal-install-3.0"]}}
+    - compiler: ghc-7.8.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.8.1","cabal-install-3.0"]}}
 before_install:
-  - HC=/opt/ghc/bin/${CC}
-  - HCPKG=${HC/ghc/ghc-pkg}
+  - HC=$(echo "/opt/$CC/bin/ghc" | sed 's/-/\//')
+  - WITHCOMPILER="-w $HC"
+  - HCPKG="$HC-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/') ))
+  - TOP=$(pwd)
+  - "HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\\d+)\\.(\\d+)\\.(\\d+)(\\.(\\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))')"
   - echo $HCNUMVER
-
+  - CABAL="$CABAL -vnormal+nowrap+markoutput"
+  - set -o pipefail
+  - |
+    echo 'function blue(s) { printf "\033[0;34m" s "\033[0m " }'           >> .colorful.awk
+    echo 'BEGIN { state = "output"; }'                                     >> .colorful.awk
+    echo '/^-----BEGIN CABAL OUTPUT-----$/ { state = "cabal" }'            >> .colorful.awk
+    echo '/^-----END CABAL OUTPUT-----$/ { state = "output" }'             >> .colorful.awk
+    echo '!/^(-----BEGIN CABAL OUTPUT-----|-----END CABAL OUTPUT-----)/ {' >> .colorful.awk
+    echo '  if (state == "cabal") {'                                       >> .colorful.awk
+    echo '    print blue($0)'                                              >> .colorful.awk
+    echo '  } else {'                                                      >> .colorful.awk
+    echo '    print $0'                                                    >> .colorful.awk
+    echo '  }'                                                             >> .colorful.awk
+    echo '}'                                                               >> .colorful.awk
+  - cat .colorful.awk
+  - |
+    color_cabal_output () {
+      awk -f $TOP/.colorful.awk
+    }
+  - echo text | color_cabal_output
 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
+  - HEADHACKAGE=false
+  - rm -f $CABALHOME/config
+  - |
+    echo "verbose: normal +nowrap +markoutput"          >> $CABALHOME/config
+    echo "remote-build-reporting: anonymous"            >> $CABALHOME/config
+    echo "write-ghc-environment-files: always"          >> $CABALHOME/config
+    echo "remote-repo-cache: $CABALHOME/packages"       >> $CABALHOME/config
+    echo "logs-dir:          $CABALHOME/logs"           >> $CABALHOME/config
+    echo "world-file:        $CABALHOME/world"          >> $CABALHOME/config
+    echo "extra-prog-path:   $CABALHOME/bin"            >> $CABALHOME/config
+    echo "symlink-bindir:    $CABALHOME/bin"            >> $CABALHOME/config
+    echo "installdir:        $CABALHOME/bin"            >> $CABALHOME/config
+    echo "build-summary:     $CABALHOME/logs/build.log" >> $CABALHOME/config
+    echo "store-dir:         $CABALHOME/store"          >> $CABALHOME/config
+    echo "install-dirs user"                            >> $CABALHOME/config
+    echo "  prefix: $CABALHOME"                         >> $CABALHOME/config
+    echo "repository hackage.haskell.org"               >> $CABALHOME/config
+    echo "  url: http://hackage.haskell.org/"           >> $CABALHOME/config
+  - cat $CABALHOME/config
+  - rm -fv cabal.project cabal.project.local cabal.project.freeze
+  - travis_retry ${CABAL} v2-update -v
+  # Generate cabal.project
+  - rm -rf cabal.project cabal.project.local cabal.project.freeze
   - 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"
+  - |
+    echo "packages: servant" >> cabal.project
+    echo "packages: servant-client" >> cabal.project
+    echo "packages: servant-docs" >> cabal.project
+    echo "packages: servant-server" >> cabal.project
+  - |
+  - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | (grep -vE -- '^(servant|servant-client|servant-docs|servant-server)$' || true) | 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.
+  - ${CABAL} v2-freeze $WITHCOMPILER ${TEST} ${BENCH} | color_cabal_output
+  - "cat cabal.project.freeze | sed -E 's/^(constraints: *| *)//' | sed 's/any.//'"
+  - rm  cabal.project.freeze
+  - ${CABAL} v2-build $WITHCOMPILER ${TEST} ${BENCH} --dep -j2 all | color_cabal_output
+  - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --dep -j2 all | color_cabal_output
 script:
-  # test that source-distributions can be generated
-  - ${CABAL} new-sdist all
+  - DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)
+  # Packaging...
+  - ${CABAL} v2-sdist all | color_cabal_output
+  # Unpacking...
   - mv dist-newstyle/sdist/*.tar.gz ${DISTDIR}/
   - cd ${DISTDIR} || false
-  - find . -maxdepth 1 -name '*.tar.gz' -exec tar -xvf '{}' \;
-  - rm -f cabal.project
+  - find . -maxdepth 1 -type f -name '*.tar.gz' -exec tar -xvf '{}' \;
+  - find . -maxdepth 1 -type f -name '*.tar.gz' -exec rm       '{}' \;
+  - PKGDIR_servant="$(find . -maxdepth 1 -type d -regex '.*/servant-[0-9.]*')"
+  - PKGDIR_servant_client="$(find . -maxdepth 1 -type d -regex '.*/servant-client-[0-9.]*')"
+  - PKGDIR_servant_docs="$(find . -maxdepth 1 -type d -regex '.*/servant-docs-[0-9.]*')"
+  - PKGDIR_servant_server="$(find . -maxdepth 1 -type d -regex '.*/servant-server-[0-9.]*')"
+  # Generate cabal.project
+  - rm -rf cabal.project cabal.project.local cabal.project.freeze
   - 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"
+  - |
+    echo "packages: ${PKGDIR_servant}" >> cabal.project
+    echo "packages: ${PKGDIR_servant_client}" >> cabal.project
+    echo "packages: ${PKGDIR_servant_docs}" >> cabal.project
+    echo "packages: ${PKGDIR_servant_server}" >> cabal.project
+  - |
+  - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | (grep -vE -- '^(servant|servant-client|servant-docs|servant-server)$' || true) | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"
   - cat cabal.project || true
   - cat cabal.project.local || true
+  # Building...
   # this builds all libraries and executables (without tests/benchmarks)
-  - ${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks all
-
+  - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks all | color_cabal_output
+  # Building with tests and benchmarks...
   # 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;
+  - ${CABAL} v2-build $WITHCOMPILER ${TEST} ${BENCH} all | color_cabal_output
+  # Testing...
+  - ${CABAL} v2-test $WITHCOMPILER ${TEST} ${BENCH} all | color_cabal_output
+  # cabal check...
+  - (cd ${PKGDIR_servant} && ${CABAL} -vnormal check)
+  - (cd ${PKGDIR_servant_client} && ${CABAL} -vnormal check)
+  - (cd ${PKGDIR_servant_docs} && ${CABAL} -vnormal check)
+  - (cd ${PKGDIR_servant_server} && ${CABAL} -vnormal check)
+  # haddock...
+  - ${CABAL} v2-haddock $WITHCOMPILER ${TEST} ${BENCH} all | color_cabal_output
+  # Building without installed constraints for packages in global-db...
+  - rm -f cabal.project.local
+  - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks all | color_cabal_output
 
-# REGENDATA ["-o","cabal.project.empty-line.travis.yml","--copy-fields=none","cabal.project.empty-line"]
+# REGENDATA ["-o","cabal.project.empty-line.travis.yml","--config=cabal.project.haskell-ci","--copy-fields=none","cabal.project.empty-line"]
 # EOF
diff --git a/fixtures/cabal.project.copy-fields.some.stderr b/fixtures/cabal.project.copy-fields.some.stderr
--- a/fixtures/cabal.project.copy-fields.some.stderr
+++ b/fixtures/cabal.project.copy-fields.some.stderr
@@ -1,1 +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 8.6.4
+*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 8.6.4 8.6.5 8.8.1
diff --git a/fixtures/cabal.project.copy-fields.some.travis.yml b/fixtures/cabal.project.copy-fields.some.travis.yml
--- a/fixtures/cabal.project.copy-fields.some.travis.yml
+++ b/fixtures/cabal.project.copy-fields.some.travis.yml
@@ -1,22 +1,18 @@
 # 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'
+#   haskell-ci '-o' 'cabal.project.empty-line.travis.yml' '--config=cabal.project.haskell-ci' '--copy-fields=some' 'cabal.project.empty-line'
 #
 # For more information, see https://github.com/haskell-CI/haskell-ci
 #
-# version: 0.2.1
-#
 language: c
 dist: xenial
-
 git:
-  submodules: false  # whether to recursively clone submodules
-
+  # whether to recursively clone submodules
+  submodules: false
 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'
@@ -25,138 +21,178 @@
   - 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.4"
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.6.4], 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.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]}}
-
+    - compiler: ghc-8.8.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.8.1","cabal-install-3.0"]}}
+    - compiler: ghc-8.6.5
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.5","cabal-install-3.0"]}}
+    - compiler: ghc-8.6.4
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.4","cabal-install-3.0"]}}
+    - compiler: ghc-8.6.3
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.3","cabal-install-3.0"]}}
+    - compiler: ghc-8.6.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.2","cabal-install-3.0"]}}
+    - compiler: ghc-8.6.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.1","cabal-install-3.0"]}}
+    - compiler: ghc-8.4.4
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.4","cabal-install-3.0"]}}
+    - compiler: ghc-8.4.3
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.3","cabal-install-3.0"]}}
+    - compiler: ghc-8.4.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.2","cabal-install-3.0"]}}
+    - compiler: ghc-8.4.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.1","cabal-install-3.0"]}}
+    - compiler: ghc-8.2.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.2.2","cabal-install-3.0"]}}
+    - compiler: ghc-8.2.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.2.1","cabal-install-3.0"]}}
+    - compiler: ghc-8.0.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.0.2","cabal-install-3.0"]}}
+    - compiler: ghc-8.0.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.0.1","cabal-install-3.0"]}}
+    - compiler: ghc-7.10.3
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.10.3","cabal-install-3.0"]}}
+    - compiler: ghc-7.10.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.10.2","cabal-install-3.0"]}}
+    - compiler: ghc-7.10.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.10.1","cabal-install-3.0"]}}
+    - compiler: ghc-7.8.4
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.8.4","cabal-install-3.0"]}}
+    - compiler: ghc-7.8.3
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.8.3","cabal-install-3.0"]}}
+    - compiler: ghc-7.8.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.8.2","cabal-install-3.0"]}}
+    - compiler: ghc-7.8.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.8.1","cabal-install-3.0"]}}
 before_install:
-  - HC=/opt/ghc/bin/${CC}
-  - HCPKG=${HC/ghc/ghc-pkg}
+  - HC=$(echo "/opt/$CC/bin/ghc" | sed 's/-/\//')
+  - WITHCOMPILER="-w $HC"
+  - HCPKG="$HC-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/') ))
+  - TOP=$(pwd)
+  - "HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\\d+)\\.(\\d+)\\.(\\d+)(\\.(\\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))')"
   - echo $HCNUMVER
-
+  - CABAL="$CABAL -vnormal+nowrap+markoutput"
+  - set -o pipefail
+  - |
+    echo 'function blue(s) { printf "\033[0;34m" s "\033[0m " }'           >> .colorful.awk
+    echo 'BEGIN { state = "output"; }'                                     >> .colorful.awk
+    echo '/^-----BEGIN CABAL OUTPUT-----$/ { state = "cabal" }'            >> .colorful.awk
+    echo '/^-----END CABAL OUTPUT-----$/ { state = "output" }'             >> .colorful.awk
+    echo '!/^(-----BEGIN CABAL OUTPUT-----|-----END CABAL OUTPUT-----)/ {' >> .colorful.awk
+    echo '  if (state == "cabal") {'                                       >> .colorful.awk
+    echo '    print blue($0)'                                              >> .colorful.awk
+    echo '  } else {'                                                      >> .colorful.awk
+    echo '    print $0'                                                    >> .colorful.awk
+    echo '  }'                                                             >> .colorful.awk
+    echo '}'                                                               >> .colorful.awk
+  - cat .colorful.awk
+  - |
+    color_cabal_output () {
+      awk -f $TOP/.colorful.awk
+    }
+  - echo text | color_cabal_output
 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
+  - HEADHACKAGE=false
+  - rm -f $CABALHOME/config
+  - |
+    echo "verbose: normal +nowrap +markoutput"          >> $CABALHOME/config
+    echo "remote-build-reporting: anonymous"            >> $CABALHOME/config
+    echo "write-ghc-environment-files: always"          >> $CABALHOME/config
+    echo "remote-repo-cache: $CABALHOME/packages"       >> $CABALHOME/config
+    echo "logs-dir:          $CABALHOME/logs"           >> $CABALHOME/config
+    echo "world-file:        $CABALHOME/world"          >> $CABALHOME/config
+    echo "extra-prog-path:   $CABALHOME/bin"            >> $CABALHOME/config
+    echo "symlink-bindir:    $CABALHOME/bin"            >> $CABALHOME/config
+    echo "installdir:        $CABALHOME/bin"            >> $CABALHOME/config
+    echo "build-summary:     $CABALHOME/logs/build.log" >> $CABALHOME/config
+    echo "store-dir:         $CABALHOME/store"          >> $CABALHOME/config
+    echo "install-dirs user"                            >> $CABALHOME/config
+    echo "  prefix: $CABALHOME"                         >> $CABALHOME/config
+    echo "repository hackage.haskell.org"               >> $CABALHOME/config
+    echo "  url: http://hackage.haskell.org/"           >> $CABALHOME/config
+  - cat $CABALHOME/config
+  - rm -fv cabal.project cabal.project.local cabal.project.freeze
+  - travis_retry ${CABAL} v2-update -v
+  # Generate cabal.project
+  - rm -rf cabal.project cabal.project.local cabal.project.freeze
   - 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"
+  - |
+    echo "packages: servant" >> cabal.project
+    echo "packages: servant-client" >> cabal.project
+    echo "packages: servant-docs" >> cabal.project
+    echo "packages: servant-server" >> 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
+  - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | (grep -vE -- '^(servant|servant-client|servant-docs|servant-server)$' || true) | 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.
+  - ${CABAL} v2-freeze $WITHCOMPILER ${TEST} ${BENCH} | color_cabal_output
+  - "cat cabal.project.freeze | sed -E 's/^(constraints: *| *)//' | sed 's/any.//'"
+  - rm  cabal.project.freeze
+  - ${CABAL} v2-build $WITHCOMPILER ${TEST} ${BENCH} --dep -j2 all | color_cabal_output
+  - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --dep -j2 all | color_cabal_output
 script:
-  # test that source-distributions can be generated
-  - ${CABAL} new-sdist all
+  - DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)
+  # Packaging...
+  - ${CABAL} v2-sdist all | color_cabal_output
+  # Unpacking...
   - mv dist-newstyle/sdist/*.tar.gz ${DISTDIR}/
   - cd ${DISTDIR} || false
-  - find . -maxdepth 1 -name '*.tar.gz' -exec tar -xvf '{}' \;
-  - rm -f cabal.project
+  - find . -maxdepth 1 -type f -name '*.tar.gz' -exec tar -xvf '{}' \;
+  - find . -maxdepth 1 -type f -name '*.tar.gz' -exec rm       '{}' \;
+  - PKGDIR_servant="$(find . -maxdepth 1 -type d -regex '.*/servant-[0-9.]*')"
+  - PKGDIR_servant_client="$(find . -maxdepth 1 -type d -regex '.*/servant-client-[0-9.]*')"
+  - PKGDIR_servant_docs="$(find . -maxdepth 1 -type d -regex '.*/servant-docs-[0-9.]*')"
+  - PKGDIR_servant_server="$(find . -maxdepth 1 -type d -regex '.*/servant-server-[0-9.]*')"
+  # Generate cabal.project
+  - rm -rf cabal.project cabal.project.local cabal.project.freeze
   - 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"
+  - |
+    echo "packages: ${PKGDIR_servant}" >> cabal.project
+    echo "packages: ${PKGDIR_servant_client}" >> cabal.project
+    echo "packages: ${PKGDIR_servant_docs}" >> cabal.project
+    echo "packages: ${PKGDIR_servant_server}" >> 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
+  - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | (grep -vE -- '^(servant|servant-client|servant-docs|servant-server)$' || true) | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"
   - cat cabal.project || true
   - cat cabal.project.local || true
+  # Building...
   # this builds all libraries and executables (without tests/benchmarks)
-  - ${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks all
-
+  - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks all | color_cabal_output
+  # Building with tests and benchmarks...
   # 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;
+  - ${CABAL} v2-build $WITHCOMPILER ${TEST} ${BENCH} all | color_cabal_output
+  # Testing...
+  - ${CABAL} v2-test $WITHCOMPILER ${TEST} ${BENCH} all | color_cabal_output
+  # cabal check...
+  - (cd ${PKGDIR_servant} && ${CABAL} -vnormal check)
+  - (cd ${PKGDIR_servant_client} && ${CABAL} -vnormal check)
+  - (cd ${PKGDIR_servant_docs} && ${CABAL} -vnormal check)
+  - (cd ${PKGDIR_servant_server} && ${CABAL} -vnormal check)
+  # haddock...
+  - ${CABAL} v2-haddock $WITHCOMPILER ${TEST} ${BENCH} all | color_cabal_output
+  # Building without installed constraints for packages in global-db...
+  - rm -f cabal.project.local
+  - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks all | color_cabal_output
 
-# REGENDATA ["-o","cabal.project.empty-line.travis.yml","--copy-fields=some","cabal.project.empty-line"]
+# REGENDATA ["-o","cabal.project.empty-line.travis.yml","--config=cabal.project.haskell-ci","--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.4.4 8.6.1 8.6.2 8.6.3 8.6.4
+*INFO* Generating Travis-CI config for testing for GHC versions: ghc-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 8.6.4 8.6.5 8.8.1
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,22 +1,18 @@
 # This Travis job script has been generated by a script via
 #
-#   haskell-ci '-o' 'cabal.project.empty-line.travis.yml' '--ghc-head' 'cabal.project.empty-line'
+#   haskell-ci '-o' 'cabal.project.empty-line.travis.yml' '--config=cabal.project.haskell-ci' '--ghc-head' 'cabal.project.empty-line'
 #
 # For more information, see https://github.com/haskell-CI/haskell-ci
 #
-# version: 0.2.1
-#
 language: c
 dist: xenial
-
 git:
-  submodules: false  # whether to recursively clone submodules
-
+  # whether to recursively clone submodules
+  submodules: false
 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'
@@ -25,162 +21,194 @@
   - 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.4"
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.6.4], 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.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]}}
-    - compiler: "ghc-head"
-      env: GHCHEAD=true
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-head,ghc-head], sources: [hvr-ghc]}}
-
+    - compiler: ghc-8.8.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.8.1","cabal-install-3.0"]}}
+    - compiler: ghc-8.6.5
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.5","cabal-install-3.0"]}}
+    - compiler: ghc-8.6.4
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.4","cabal-install-3.0"]}}
+    - compiler: ghc-8.6.3
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.3","cabal-install-3.0"]}}
+    - compiler: ghc-8.6.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.2","cabal-install-3.0"]}}
+    - compiler: ghc-8.6.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.1","cabal-install-3.0"]}}
+    - compiler: ghc-8.4.4
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.4","cabal-install-3.0"]}}
+    - compiler: ghc-8.4.3
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.3","cabal-install-3.0"]}}
+    - compiler: ghc-8.4.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.2","cabal-install-3.0"]}}
+    - compiler: ghc-8.4.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.1","cabal-install-3.0"]}}
+    - compiler: ghc-8.2.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.2.2","cabal-install-3.0"]}}
+    - compiler: ghc-8.2.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.2.1","cabal-install-3.0"]}}
+    - compiler: ghc-8.0.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.0.2","cabal-install-3.0"]}}
+    - compiler: ghc-8.0.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.0.1","cabal-install-3.0"]}}
+    - compiler: ghc-7.10.3
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.10.3","cabal-install-3.0"]}}
+    - compiler: ghc-7.10.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.10.2","cabal-install-3.0"]}}
+    - compiler: ghc-7.10.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.10.1","cabal-install-3.0"]}}
+    - compiler: ghc-7.8.4
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.8.4","cabal-install-3.0"]}}
+    - compiler: ghc-7.8.3
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.8.3","cabal-install-3.0"]}}
+    - compiler: ghc-7.8.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.8.2","cabal-install-3.0"]}}
+    - compiler: ghc-7.8.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.8.1","cabal-install-3.0"]}}
+    - compiler: ghc-head
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-head","cabal-install-head"]}}
   allow_failures:
-    - compiler: "ghc-head"
-
+    - compiler: ghc-head
 before_install:
-  - HC=/opt/ghc/bin/${CC}
-  - HCPKG=${HC/ghc/ghc-pkg}
+  - HC=$(echo "/opt/$CC/bin/ghc" | sed 's/-/\//')
+  - WITHCOMPILER="-w $HC"
+  - HCPKG="$HC-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/') ))
+  - TOP=$(pwd)
+  - "HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\\d+)\\.(\\d+)\\.(\\d+)(\\.(\\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))')"
   - echo $HCNUMVER
-
+  - CABAL="$CABAL -vnormal+nowrap+markoutput"
+  - set -o pipefail
+  - |
+    echo 'function blue(s) { printf "\033[0;34m" s "\033[0m " }'           >> .colorful.awk
+    echo 'BEGIN { state = "output"; }'                                     >> .colorful.awk
+    echo '/^-----BEGIN CABAL OUTPUT-----$/ { state = "cabal" }'            >> .colorful.awk
+    echo '/^-----END CABAL OUTPUT-----$/ { state = "output" }'             >> .colorful.awk
+    echo '!/^(-----BEGIN CABAL OUTPUT-----|-----END CABAL OUTPUT-----)/ {' >> .colorful.awk
+    echo '  if (state == "cabal") {'                                       >> .colorful.awk
+    echo '    print blue($0)'                                              >> .colorful.awk
+    echo '  } else {'                                                      >> .colorful.awk
+    echo '    print $0'                                                    >> .colorful.awk
+    echo '  }'                                                             >> .colorful.awk
+    echo '}'                                                               >> .colorful.awk
+  - cat .colorful.awk
+  - |
+    color_cabal_output () {
+      awk -f $TOP/.colorful.awk
+    }
+  - echo text | color_cabal_output
 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
-  # Overlay Hackage Package Index for GHC HEAD: https://github.com/hvr/head.hackage
+  - HEADHACKAGE=false
+  - if [ $HCNUMVER -gt 80801 ] ; then HEADHACKAGE=true ; fi
+  - rm -f $CABALHOME/config
   - |
-    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
+    echo "verbose: normal +nowrap +markoutput"          >> $CABALHOME/config
+    echo "remote-build-reporting: anonymous"            >> $CABALHOME/config
+    echo "write-ghc-environment-files: always"          >> $CABALHOME/config
+    echo "remote-repo-cache: $CABALHOME/packages"       >> $CABALHOME/config
+    echo "logs-dir:          $CABALHOME/logs"           >> $CABALHOME/config
+    echo "world-file:        $CABALHOME/world"          >> $CABALHOME/config
+    echo "extra-prog-path:   $CABALHOME/bin"            >> $CABALHOME/config
+    echo "symlink-bindir:    $CABALHOME/bin"            >> $CABALHOME/config
+    echo "installdir:        $CABALHOME/bin"            >> $CABALHOME/config
+    echo "build-summary:     $CABALHOME/logs/build.log" >> $CABALHOME/config
+    echo "store-dir:         $CABALHOME/store"          >> $CABALHOME/config
+    echo "install-dirs user"                            >> $CABALHOME/config
+    echo "  prefix: $CABALHOME"                         >> $CABALHOME/config
+    echo "repository hackage.haskell.org"               >> $CABALHOME/config
+    echo "  url: http://hackage.haskell.org/"           >> $CABALHOME/config
+  - |
+    if $HEADHACKAGE; then
+    echo "allow-newer: $($HCPKG list --simple-output | sed -E 's/([a-zA-Z-]+)-[0-9.]+/*:\1/g')" >> $CABALHOME/config
+    echo "repository head.hackage.ghc.haskell.org"                                        >> $CABALHOME/config
+    echo "   url: https://ghc.gitlab.haskell.org/head.hackage/"                           >> $CABALHOME/config
+    echo "   secure: True"                                                                >> $CABALHOME/config
+    echo "   root-keys: 7541f32a4ccca4f97aea3b22f5e593ba2c0267546016b992dfadcd2fe944e55d" >> $CABALHOME/config
+    echo "              26021a13b401500c8eb2761ca95c61f2d625bfef951b939a8124ed12ecf07329" >> $CABALHOME/config
+    echo "              f76d08be13e9a61a377a85e2fb63f4c5435d40f8feb3e12eb05905edb8cdea89" >> $CABALHOME/config
+    echo "   key-threshold: 3"                                                            >> $CABALHOME/config
     fi
-  - grep -Ev -- '^\s*--' $CABALHOME/config | grep -Ev '^\s*$'
-  - rm -f cabal.project
+  - cat $CABALHOME/config
+  - rm -fv cabal.project cabal.project.local cabal.project.freeze
+  - travis_retry ${CABAL} v2-update -v
+  # Generate cabal.project
+  - rm -rf cabal.project cabal.project.local cabal.project.freeze
   - 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' >> 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"
+  - |
+    echo "packages: servant" >> cabal.project
+    echo "packages: servant-client" >> cabal.project
+    echo "packages: servant-docs" >> cabal.project
+    echo "packages: servant-server" >> cabal.project
+  - |
+    echo "constraints: foundatiion >= 0.14"        >> cabal.project
+    echo "allow-newer: servant-js:servant"         >> cabal.project
+    echo "allow-newer: servant-js:servant-foreign" >> cabal.project
+  - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | (grep -vE -- '^(servant|servant-client|servant-docs|servant-server)$' || true) | 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.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)
-
-# 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.
+  - ${CABAL} v2-freeze $WITHCOMPILER ${TEST} ${BENCH} | color_cabal_output
+  - "cat cabal.project.freeze | sed -E 's/^(constraints: *| *)//' | sed 's/any.//'"
+  - rm  cabal.project.freeze
+  - ${CABAL} v2-build $WITHCOMPILER ${TEST} ${BENCH} --dep -j2 all | color_cabal_output
+  - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --dep -j2 all | color_cabal_output
 script:
-  # test that source-distributions can be generated
-  - ${CABAL} new-sdist all
+  - DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)
+  # Packaging...
+  - ${CABAL} v2-sdist all | color_cabal_output
+  # Unpacking...
   - mv dist-newstyle/sdist/*.tar.gz ${DISTDIR}/
   - cd ${DISTDIR} || false
-  - find . -maxdepth 1 -name '*.tar.gz' -exec tar -xvf '{}' \;
-  - rm -f cabal.project
+  - find . -maxdepth 1 -type f -name '*.tar.gz' -exec tar -xvf '{}' \;
+  - find . -maxdepth 1 -type f -name '*.tar.gz' -exec rm       '{}' \;
+  - PKGDIR_servant="$(find . -maxdepth 1 -type d -regex '.*/servant-[0-9.]*')"
+  - PKGDIR_servant_client="$(find . -maxdepth 1 -type d -regex '.*/servant-client-[0-9.]*')"
+  - PKGDIR_servant_docs="$(find . -maxdepth 1 -type d -regex '.*/servant-docs-[0-9.]*')"
+  - PKGDIR_servant_server="$(find . -maxdepth 1 -type d -regex '.*/servant-server-[0-9.]*')"
+  # Generate cabal.project
+  - rm -rf cabal.project cabal.project.local cabal.project.freeze
   - 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' >> 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"
+  - |
+    echo "packages: ${PKGDIR_servant}" >> cabal.project
+    echo "packages: ${PKGDIR_servant_client}" >> cabal.project
+    echo "packages: ${PKGDIR_servant_docs}" >> cabal.project
+    echo "packages: ${PKGDIR_servant_server}" >> cabal.project
+  - |
+    echo "constraints: foundatiion >= 0.14"        >> cabal.project
+    echo "allow-newer: servant-js:servant"         >> cabal.project
+    echo "allow-newer: servant-js:servant-foreign" >> cabal.project
+  - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | (grep -vE -- '^(servant|servant-client|servant-docs|servant-server)$' || true) | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"
   - cat cabal.project || true
   - cat cabal.project.local || true
+  # Building...
   # this builds all libraries and executables (without tests/benchmarks)
-  - ${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks all
-
+  - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks all | color_cabal_output
+  # Building with tests and benchmarks...
   # 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;
+  - ${CABAL} v2-build $WITHCOMPILER ${TEST} ${BENCH} all | color_cabal_output
+  # Testing...
+  - ${CABAL} v2-test $WITHCOMPILER ${TEST} ${BENCH} all | color_cabal_output
+  # cabal check...
+  - (cd ${PKGDIR_servant} && ${CABAL} -vnormal check)
+  - (cd ${PKGDIR_servant_client} && ${CABAL} -vnormal check)
+  - (cd ${PKGDIR_servant_docs} && ${CABAL} -vnormal check)
+  - (cd ${PKGDIR_servant_server} && ${CABAL} -vnormal check)
+  # haddock...
+  - ${CABAL} v2-haddock $WITHCOMPILER ${TEST} ${BENCH} all | color_cabal_output
+  # Building without installed constraints for packages in global-db...
+  - rm -f cabal.project.local
+  - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks all | color_cabal_output
 
-# REGENDATA ["-o","cabal.project.empty-line.travis.yml","--ghc-head","cabal.project.empty-line"]
+# REGENDATA ["-o","cabal.project.empty-line.travis.yml","--config=cabal.project.haskell-ci","--ghc-head","cabal.project.empty-line"]
 # EOF
diff --git a/fixtures/cabal.project.fail-versions.stderr b/fixtures/cabal.project.fail-versions.stderr
--- a/fixtures/cabal.project.fail-versions.stderr
+++ b/fixtures/cabal.project.fail-versions.stderr
@@ -1,3 +1,3 @@
-*ERROR* servant-client-core is missing tested-with annotations for: 7.8.1,7.8.2,7.8.3,7.8.4
-*ERROR* servant-foreign is missing tested-with annotations for: 7.8.1,7.8.2,7.8.3,7.8.4
-*ERROR* tutorial is missing tested-with annotations for: 7.8.1,7.8.2,7.8.3,7.8.4
+*ERROR* servant-client-core is missing tested-with annotations for: ghc-7.8.1,ghc-7.8.2,ghc-7.8.3,ghc-7.8.4
+*ERROR* servant-foreign is missing tested-with annotations for: ghc-7.8.1,ghc-7.8.2,ghc-7.8.3,ghc-7.8.4
+*ERROR* tutorial is missing tested-with annotations for: ghc-7.8.1,ghc-7.8.2,ghc-7.8.3,ghc-7.8.4
diff --git a/fixtures/cabal.project.haskell-ci b/fixtures/cabal.project.haskell-ci
new file mode 100644
--- /dev/null
+++ b/fixtures/cabal.project.haskell-ci
@@ -0,0 +1,2 @@
+-- Common configuration options for .travis.yml files in fixtures/
+insert-version: False
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.4.4 8.6.1 8.6.2 8.6.3 8.6.4
+*INFO* Generating Travis-CI config for testing for GHC versions: ghc-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 8.6.4 8.6.5 8.8.1
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,22 +1,18 @@
 # This Travis job script has been generated by a script via
 #
-#   haskell-ci '-o' 'cabal.project.messy.travis.yml' '--ghc-head' '--apt=fftw3-dev' '--installed=-all +deepseq' 'cabal.project.messy'
+#   haskell-ci '-o' 'cabal.project.messy.travis.yml' '--config=cabal.project.haskell-ci' '--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.1
-#
 language: c
 dist: xenial
-
 git:
-  submodules: false  # whether to recursively clone submodules
-
+  # whether to recursively clone submodules
+  submodules: false
 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'
@@ -25,156 +21,188 @@
   - 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.4"
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.6.4,fftw3-dev], sources: [hvr-ghc]}}
-    - 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"
-      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"
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.4.3,fftw3-dev], sources: [hvr-ghc]}}
-    - compiler: "ghc-8.4.2"
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.4.2,fftw3-dev], sources: [hvr-ghc]}}
-    - compiler: "ghc-8.4.1"
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.4.1,fftw3-dev], sources: [hvr-ghc]}}
-    - compiler: "ghc-8.2.2"
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.2.2,fftw3-dev], sources: [hvr-ghc]}}
-    - compiler: "ghc-8.2.1"
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.2.1,fftw3-dev], sources: [hvr-ghc]}}
-    - compiler: "ghc-8.0.2"
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.0.2,fftw3-dev], sources: [hvr-ghc]}}
-    - compiler: "ghc-8.0.1"
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.0.1,fftw3-dev], sources: [hvr-ghc]}}
-    - compiler: "ghc-7.10.3"
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.10.3,fftw3-dev], sources: [hvr-ghc]}}
-    - compiler: "ghc-7.10.2"
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.10.2,fftw3-dev], sources: [hvr-ghc]}}
-    - compiler: "ghc-7.10.1"
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.10.1,fftw3-dev], sources: [hvr-ghc]}}
-    - compiler: "ghc-7.8.4"
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.8.4,fftw3-dev], sources: [hvr-ghc]}}
-    - compiler: "ghc-7.8.3"
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.8.3,fftw3-dev], sources: [hvr-ghc]}}
-    - compiler: "ghc-7.8.2"
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-7.8.2,fftw3-dev], sources: [hvr-ghc]}}
-    - compiler: "ghc-7.8.1"
-      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,fftw3-dev], sources: [hvr-ghc]}}
-
+    - compiler: ghc-8.8.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.8.1","cabal-install-3.0","fftw3-dev"]}}
+    - compiler: ghc-8.6.5
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.5","cabal-install-3.0","fftw3-dev"]}}
+    - compiler: ghc-8.6.4
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.4","cabal-install-3.0","fftw3-dev"]}}
+    - compiler: ghc-8.6.3
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.3","cabal-install-3.0","fftw3-dev"]}}
+    - compiler: ghc-8.6.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.2","cabal-install-3.0","fftw3-dev"]}}
+    - compiler: ghc-8.6.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.1","cabal-install-3.0","fftw3-dev"]}}
+    - compiler: ghc-8.4.4
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.4","cabal-install-3.0","fftw3-dev"]}}
+    - compiler: ghc-8.4.3
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.3","cabal-install-3.0","fftw3-dev"]}}
+    - compiler: ghc-8.4.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.2","cabal-install-3.0","fftw3-dev"]}}
+    - compiler: ghc-8.4.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.1","cabal-install-3.0","fftw3-dev"]}}
+    - compiler: ghc-8.2.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.2.2","cabal-install-3.0","fftw3-dev"]}}
+    - compiler: ghc-8.2.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.2.1","cabal-install-3.0","fftw3-dev"]}}
+    - compiler: ghc-8.0.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.0.2","cabal-install-3.0","fftw3-dev"]}}
+    - compiler: ghc-8.0.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.0.1","cabal-install-3.0","fftw3-dev"]}}
+    - compiler: ghc-7.10.3
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.10.3","cabal-install-3.0","fftw3-dev"]}}
+    - compiler: ghc-7.10.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.10.2","cabal-install-3.0","fftw3-dev"]}}
+    - compiler: ghc-7.10.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.10.1","cabal-install-3.0","fftw3-dev"]}}
+    - compiler: ghc-7.8.4
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.8.4","cabal-install-3.0","fftw3-dev"]}}
+    - compiler: ghc-7.8.3
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.8.3","cabal-install-3.0","fftw3-dev"]}}
+    - compiler: ghc-7.8.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.8.2","cabal-install-3.0","fftw3-dev"]}}
+    - compiler: ghc-7.8.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.8.1","cabal-install-3.0","fftw3-dev"]}}
+    - compiler: ghc-head
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-head","cabal-install-head","fftw3-dev"]}}
   allow_failures:
-    - compiler: "ghc-head"
-
+    - compiler: ghc-head
 before_install:
-  - HC=/opt/ghc/bin/${CC}
-  - HCPKG=${HC/ghc/ghc-pkg}
+  - HC=$(echo "/opt/$CC/bin/ghc" | sed 's/-/\//')
+  - WITHCOMPILER="-w $HC"
+  - HCPKG="$HC-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/') ))
+  - TOP=$(pwd)
+  - "HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\\d+)\\.(\\d+)\\.(\\d+)(\\.(\\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))')"
   - echo $HCNUMVER
-
+  - CABAL="$CABAL -vnormal+nowrap+markoutput"
+  - set -o pipefail
+  - |
+    echo 'function blue(s) { printf "\033[0;34m" s "\033[0m " }'           >> .colorful.awk
+    echo 'BEGIN { state = "output"; }'                                     >> .colorful.awk
+    echo '/^-----BEGIN CABAL OUTPUT-----$/ { state = "cabal" }'            >> .colorful.awk
+    echo '/^-----END CABAL OUTPUT-----$/ { state = "output" }'             >> .colorful.awk
+    echo '!/^(-----BEGIN CABAL OUTPUT-----|-----END CABAL OUTPUT-----)/ {' >> .colorful.awk
+    echo '  if (state == "cabal") {'                                       >> .colorful.awk
+    echo '    print blue($0)'                                              >> .colorful.awk
+    echo '  } else {'                                                      >> .colorful.awk
+    echo '    print $0'                                                    >> .colorful.awk
+    echo '  }'                                                             >> .colorful.awk
+    echo '}'                                                               >> .colorful.awk
+  - cat .colorful.awk
+  - |
+    color_cabal_output () {
+      awk -f $TOP/.colorful.awk
+    }
+  - echo text | color_cabal_output
 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
-  # Overlay Hackage Package Index for GHC HEAD: https://github.com/hvr/head.hackage
+  - HEADHACKAGE=false
+  - if [ $HCNUMVER -gt 80801 ] ; then HEADHACKAGE=true ; fi
+  - rm -f $CABALHOME/config
   - |
-    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
+    echo "verbose: normal +nowrap +markoutput"          >> $CABALHOME/config
+    echo "remote-build-reporting: anonymous"            >> $CABALHOME/config
+    echo "write-ghc-environment-files: always"          >> $CABALHOME/config
+    echo "remote-repo-cache: $CABALHOME/packages"       >> $CABALHOME/config
+    echo "logs-dir:          $CABALHOME/logs"           >> $CABALHOME/config
+    echo "world-file:        $CABALHOME/world"          >> $CABALHOME/config
+    echo "extra-prog-path:   $CABALHOME/bin"            >> $CABALHOME/config
+    echo "symlink-bindir:    $CABALHOME/bin"            >> $CABALHOME/config
+    echo "installdir:        $CABALHOME/bin"            >> $CABALHOME/config
+    echo "build-summary:     $CABALHOME/logs/build.log" >> $CABALHOME/config
+    echo "store-dir:         $CABALHOME/store"          >> $CABALHOME/config
+    echo "install-dirs user"                            >> $CABALHOME/config
+    echo "  prefix: $CABALHOME"                         >> $CABALHOME/config
+    echo "repository hackage.haskell.org"               >> $CABALHOME/config
+    echo "  url: http://hackage.haskell.org/"           >> $CABALHOME/config
+  - |
+    if $HEADHACKAGE; then
+    echo "allow-newer: $($HCPKG list --simple-output | sed -E 's/([a-zA-Z-]+)-[0-9.]+/*:\1/g')" >> $CABALHOME/config
+    echo "repository head.hackage.ghc.haskell.org"                                        >> $CABALHOME/config
+    echo "   url: https://ghc.gitlab.haskell.org/head.hackage/"                           >> $CABALHOME/config
+    echo "   secure: True"                                                                >> $CABALHOME/config
+    echo "   root-keys: 7541f32a4ccca4f97aea3b22f5e593ba2c0267546016b992dfadcd2fe944e55d" >> $CABALHOME/config
+    echo "              26021a13b401500c8eb2761ca95c61f2d625bfef951b939a8124ed12ecf07329" >> $CABALHOME/config
+    echo "              f76d08be13e9a61a377a85e2fb63f4c5435d40f8feb3e12eb05905edb8cdea89" >> $CABALHOME/config
+    echo "   key-threshold: 3"                                                            >> $CABALHOME/config
     fi
-  - grep -Ev -- '^\s*--' $CABALHOME/config | grep -Ev '^\s*$'
-  - rm -f cabal.project
+  - cat $CABALHOME/config
+  - rm -fv cabal.project cabal.project.local cabal.project.freeze
+  - travis_retry ${CABAL} v2-update -v
+  # Generate cabal.project
+  - rm -rf cabal.project cabal.project.local cabal.project.freeze
   - 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 deepseq; do echo \"constraints: $pkg installed\" >> cabal.project >> cabal.project.local; done"
+  - |
+    echo "packages: servant" >> cabal.project
+    echo "packages: servant-client" >> cabal.project
+    echo "packages: servant-docs" >> cabal.project
+    echo "packages: servant-server" >> cabal.project
+  - |
+  - "for pkg in deepseq; do echo \"constraints: $pkg 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.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)
-
-# 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.
+  - ${CABAL} v2-freeze $WITHCOMPILER ${TEST} ${BENCH} | color_cabal_output
+  - "cat cabal.project.freeze | sed -E 's/^(constraints: *| *)//' | sed 's/any.//'"
+  - rm  cabal.project.freeze
+  - ${CABAL} v2-build $WITHCOMPILER ${TEST} ${BENCH} --dep -j2 all | color_cabal_output
+  - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --dep -j2 all | color_cabal_output
 script:
-  # test that source-distributions can be generated
-  - ${CABAL} new-sdist all
+  - DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)
+  # Packaging...
+  - ${CABAL} v2-sdist all | color_cabal_output
+  # Unpacking...
   - mv dist-newstyle/sdist/*.tar.gz ${DISTDIR}/
   - cd ${DISTDIR} || false
-  - find . -maxdepth 1 -name '*.tar.gz' -exec tar -xvf '{}' \;
-  - rm -f cabal.project
+  - find . -maxdepth 1 -type f -name '*.tar.gz' -exec tar -xvf '{}' \;
+  - find . -maxdepth 1 -type f -name '*.tar.gz' -exec rm       '{}' \;
+  - PKGDIR_servant="$(find . -maxdepth 1 -type d -regex '.*/servant-[0-9.]*')"
+  - PKGDIR_servant_client="$(find . -maxdepth 1 -type d -regex '.*/servant-client-[0-9.]*')"
+  - PKGDIR_servant_docs="$(find . -maxdepth 1 -type d -regex '.*/servant-docs-[0-9.]*')"
+  - PKGDIR_servant_server="$(find . -maxdepth 1 -type d -regex '.*/servant-server-[0-9.]*')"
+  # Generate cabal.project
+  - rm -rf cabal.project cabal.project.local cabal.project.freeze
   - 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 deepseq; do echo \"constraints: $pkg installed\" >> cabal.project >> cabal.project.local; done"
+  - |
+    echo "packages: ${PKGDIR_servant}" >> cabal.project
+    echo "packages: ${PKGDIR_servant_client}" >> cabal.project
+    echo "packages: ${PKGDIR_servant_docs}" >> cabal.project
+    echo "packages: ${PKGDIR_servant_server}" >> cabal.project
+  - |
+  - "for pkg in deepseq; do echo \"constraints: $pkg installed\" >> cabal.project.local; done"
   - cat cabal.project || true
   - cat cabal.project.local || true
+  # Building...
   # this builds all libraries and executables (without tests/benchmarks)
-  - ${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks all
-
+  - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks all | color_cabal_output
+  # Building with tests and benchmarks...
   # 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;
+  - ${CABAL} v2-build $WITHCOMPILER ${TEST} ${BENCH} all | color_cabal_output
+  # Testing...
+  - ${CABAL} v2-test $WITHCOMPILER ${TEST} ${BENCH} all | color_cabal_output
+  # cabal check...
+  - (cd ${PKGDIR_servant} && ${CABAL} -vnormal check)
+  - (cd ${PKGDIR_servant_client} && ${CABAL} -vnormal check)
+  - (cd ${PKGDIR_servant_docs} && ${CABAL} -vnormal check)
+  - (cd ${PKGDIR_servant_server} && ${CABAL} -vnormal check)
+  # haddock...
+  - ${CABAL} v2-haddock $WITHCOMPILER ${TEST} ${BENCH} all | color_cabal_output
+  # Building without installed constraints for packages in global-db...
+  - rm -f cabal.project.local
+  - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks all | color_cabal_output
 
-# REGENDATA ["-o","cabal.project.messy.travis.yml","--ghc-head","--apt=fftw3-dev","--installed=-all +deepseq","cabal.project.messy"]
+# REGENDATA ["-o","cabal.project.messy.travis.yml","--config=cabal.project.haskell-ci","--ghc-head","--apt=fftw3-dev","--installed=-all +deepseq","cabal.project.messy"]
 # EOF
diff --git a/fixtures/cabal.project.travis-patch b/fixtures/cabal.project.travis-patch
new file mode 100644
--- /dev/null
+++ b/fixtures/cabal.project.travis-patch
@@ -0,0 +1,1 @@
+packages: servant
diff --git a/fixtures/cabal.project.travis-patch.patch b/fixtures/cabal.project.travis-patch.patch
new file mode 100644
--- /dev/null
+++ b/fixtures/cabal.project.travis-patch.patch
@@ -0,0 +1,13 @@
+diff --git a/fixtures/cabal.project.travis-patch.travis.yml b/fixtures/cabal.project.travis-patch.travis.yml
+index 9a725c1..0554fed 100644
+--- a/fixtures/cabal.project.travis-patch.travis.yml
++++ b/fixtures/cabal.project.travis-patch.travis.yml
+@@ -8,7 +8,7 @@
+ #
+ language: c
+ dist: xenial
+-git:
++ git:
+   # whether to recursively clone submodules
+   submodules: false
+ cache:
diff --git a/fixtures/cabal.project.travis-patch.stderr b/fixtures/cabal.project.travis-patch.stderr
new file mode 100644
--- /dev/null
+++ b/fixtures/cabal.project.travis-patch.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 8.6.4 8.6.5 8.8.1
diff --git a/fixtures/cabal.project.travis-patch.travis.yml b/fixtures/cabal.project.travis-patch.travis.yml
new file mode 100644
--- /dev/null
+++ b/fixtures/cabal.project.travis-patch.travis.yml
@@ -0,0 +1,177 @@
+# This Travis job script has been generated by a script via
+#
+#   haskell-ci '--config=cabal.project.haskell-ci' '--travis-patches=cabal.project.travis-patch.patch' 'cabal.project.travis-patch'
+#
+# For more information, see https://github.com/haskell-CI/haskell-ci
+#
+language: c
+dist: xenial
+ git:
+  # whether to recursively clone submodules
+  submodules: false
+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.8.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.8.1","cabal-install-3.0"]}}
+    - compiler: ghc-8.6.5
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.5","cabal-install-3.0"]}}
+    - compiler: ghc-8.6.4
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.4","cabal-install-3.0"]}}
+    - compiler: ghc-8.6.3
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.3","cabal-install-3.0"]}}
+    - compiler: ghc-8.6.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.2","cabal-install-3.0"]}}
+    - compiler: ghc-8.6.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.1","cabal-install-3.0"]}}
+    - compiler: ghc-8.4.4
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.4","cabal-install-3.0"]}}
+    - compiler: ghc-8.4.3
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.3","cabal-install-3.0"]}}
+    - compiler: ghc-8.4.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.2","cabal-install-3.0"]}}
+    - compiler: ghc-8.4.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.1","cabal-install-3.0"]}}
+    - compiler: ghc-8.2.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.2.2","cabal-install-3.0"]}}
+    - compiler: ghc-8.2.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.2.1","cabal-install-3.0"]}}
+    - compiler: ghc-8.0.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.0.2","cabal-install-3.0"]}}
+    - compiler: ghc-8.0.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.0.1","cabal-install-3.0"]}}
+    - compiler: ghc-7.10.3
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.10.3","cabal-install-3.0"]}}
+    - compiler: ghc-7.10.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.10.2","cabal-install-3.0"]}}
+    - compiler: ghc-7.10.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.10.1","cabal-install-3.0"]}}
+    - compiler: ghc-7.8.4
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.8.4","cabal-install-3.0"]}}
+    - compiler: ghc-7.8.3
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.8.3","cabal-install-3.0"]}}
+    - compiler: ghc-7.8.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.8.2","cabal-install-3.0"]}}
+    - compiler: ghc-7.8.1
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.8.1","cabal-install-3.0"]}}
+before_install:
+  - HC=$(echo "/opt/$CC/bin/ghc" | sed 's/-/\//')
+  - WITHCOMPILER="-w $HC"
+  - HCPKG="$HC-pkg"
+  - unset CC
+  - CABAL=/opt/ghc/bin/cabal
+  - CABALHOME=$HOME/.cabal
+  - export PATH="$CABALHOME/bin:$PATH"
+  - TOP=$(pwd)
+  - "HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\\d+)\\.(\\d+)\\.(\\d+)(\\.(\\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))')"
+  - echo $HCNUMVER
+  - CABAL="$CABAL -vnormal+nowrap+markoutput"
+  - set -o pipefail
+  - |
+    echo 'function blue(s) { printf "\033[0;34m" s "\033[0m " }'           >> .colorful.awk
+    echo 'BEGIN { state = "output"; }'                                     >> .colorful.awk
+    echo '/^-----BEGIN CABAL OUTPUT-----$/ { state = "cabal" }'            >> .colorful.awk
+    echo '/^-----END CABAL OUTPUT-----$/ { state = "output" }'             >> .colorful.awk
+    echo '!/^(-----BEGIN CABAL OUTPUT-----|-----END CABAL OUTPUT-----)/ {' >> .colorful.awk
+    echo '  if (state == "cabal") {'                                       >> .colorful.awk
+    echo '    print blue($0)'                                              >> .colorful.awk
+    echo '  } else {'                                                      >> .colorful.awk
+    echo '    print $0'                                                    >> .colorful.awk
+    echo '  }'                                                             >> .colorful.awk
+    echo '}'                                                               >> .colorful.awk
+  - cat .colorful.awk
+  - |
+    color_cabal_output () {
+      awk -f $TOP/.colorful.awk
+    }
+  - echo text | color_cabal_output
+install:
+  - ${CABAL} --version
+  - echo "$(${HC} --version) [$(${HC} --print-project-git-commit-id 2> /dev/null || echo '?')]"
+  - TEST=--enable-tests
+  - BENCH=--enable-benchmarks
+  - HEADHACKAGE=false
+  - rm -f $CABALHOME/config
+  - |
+    echo "verbose: normal +nowrap +markoutput"          >> $CABALHOME/config
+    echo "remote-build-reporting: anonymous"            >> $CABALHOME/config
+    echo "write-ghc-environment-files: always"          >> $CABALHOME/config
+    echo "remote-repo-cache: $CABALHOME/packages"       >> $CABALHOME/config
+    echo "logs-dir:          $CABALHOME/logs"           >> $CABALHOME/config
+    echo "world-file:        $CABALHOME/world"          >> $CABALHOME/config
+    echo "extra-prog-path:   $CABALHOME/bin"            >> $CABALHOME/config
+    echo "symlink-bindir:    $CABALHOME/bin"            >> $CABALHOME/config
+    echo "installdir:        $CABALHOME/bin"            >> $CABALHOME/config
+    echo "build-summary:     $CABALHOME/logs/build.log" >> $CABALHOME/config
+    echo "store-dir:         $CABALHOME/store"          >> $CABALHOME/config
+    echo "install-dirs user"                            >> $CABALHOME/config
+    echo "  prefix: $CABALHOME"                         >> $CABALHOME/config
+    echo "repository hackage.haskell.org"               >> $CABALHOME/config
+    echo "  url: http://hackage.haskell.org/"           >> $CABALHOME/config
+  - cat $CABALHOME/config
+  - rm -fv cabal.project cabal.project.local cabal.project.freeze
+  - travis_retry ${CABAL} v2-update -v
+  # Generate cabal.project
+  - rm -rf cabal.project cabal.project.local cabal.project.freeze
+  - touch cabal.project
+  - |
+    echo "packages: servant" >> cabal.project
+  - |
+  - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | (grep -vE -- '^(servant)$' || true) | 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
+  - ${CABAL} v2-freeze $WITHCOMPILER ${TEST} ${BENCH} | color_cabal_output
+  - "cat cabal.project.freeze | sed -E 's/^(constraints: *| *)//' | sed 's/any.//'"
+  - rm  cabal.project.freeze
+  - ${CABAL} v2-build $WITHCOMPILER ${TEST} ${BENCH} --dep -j2 all | color_cabal_output
+  - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --dep -j2 all | color_cabal_output
+script:
+  - DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)
+  # Packaging...
+  - ${CABAL} v2-sdist all | color_cabal_output
+  # Unpacking...
+  - mv dist-newstyle/sdist/*.tar.gz ${DISTDIR}/
+  - cd ${DISTDIR} || false
+  - find . -maxdepth 1 -type f -name '*.tar.gz' -exec tar -xvf '{}' \;
+  - find . -maxdepth 1 -type f -name '*.tar.gz' -exec rm       '{}' \;
+  - PKGDIR_servant="$(find . -maxdepth 1 -type d -regex '.*/servant-[0-9.]*')"
+  # Generate cabal.project
+  - rm -rf cabal.project cabal.project.local cabal.project.freeze
+  - touch cabal.project
+  - |
+    echo "packages: ${PKGDIR_servant}" >> cabal.project
+  - |
+  - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | (grep -vE -- '^(servant)$' || true) | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"
+  - cat cabal.project || true
+  - cat cabal.project.local || true
+  # Building...
+  # this builds all libraries and executables (without tests/benchmarks)
+  - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks all | color_cabal_output
+  # Building with tests and benchmarks...
+  # build & run tests, build benchmarks
+  - ${CABAL} v2-build $WITHCOMPILER ${TEST} ${BENCH} all | color_cabal_output
+  # Testing...
+  - ${CABAL} v2-test $WITHCOMPILER ${TEST} ${BENCH} all | color_cabal_output
+  # cabal check...
+  - (cd ${PKGDIR_servant} && ${CABAL} -vnormal check)
+  # haddock...
+  - ${CABAL} v2-haddock $WITHCOMPILER ${TEST} ${BENCH} all | color_cabal_output
+  # Building without installed constraints for packages in global-db...
+  - rm -f cabal.project.local
+  - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks all | color_cabal_output
+
+# REGENDATA ["--config=cabal.project.haskell-ci","--travis-patches=cabal.project.travis-patch.patch","cabal.project.travis-patch"]
+# EOF
diff --git a/fixtures/haskell-ci.cabal b/fixtures/haskell-ci.cabal
--- a/fixtures/haskell-ci.cabal
+++ b/fixtures/haskell-ci.cabal
@@ -1,6 +1,6 @@
 cabal-version:      2.2
 name:               haskell-ci
-version:            0.2.1
+version:            0.4
 synopsis:           Cabal package script generator for Travis-CI
 description:
   Script generator (@haskell-ci@) for [Travis-CI](https://travis-ci.org/) for continuous-integration testing of Haskell Cabal packages.
@@ -13,6 +13,7 @@
   * Runs tests and builds benchmarks
   * Generates Haddocks
   * macOS (OSX) support
+  * GHCJS support
   * building with specific constraints
   .
   === Quick Start Guide
@@ -23,16 +24,17 @@
 
 homepage:           https://haskell-ci.rtfd.org/
 bug-reports:        https://github.com/haskell-CI/haskell-ci/issues
-license:            BSD-3-Clause
+license:            GPL-3.0-or-later
 license-file:       LICENSE
 author:             Herbert Valerio Riedel, Oleg Grenrus
 maintainer:         hvr@gnu.org
 category:           Development
 build-type:         Simple
-tested-with:        GHC ==8.6.4 || ==8.4.4 || ==8.2.2 || ==8.0.2
+tested-with:        GHC ==8.6.5 || ==8.4.4 || ==8.2.2
 extra-source-files:
   CHANGELOG.md
   fixtures/cabal.haskell-ci
+  fixtures/cabal.project.haskell-ci
   fixtures/cabal.project.fail-versions
   fixtures/cabal.project.fail-versions.stderr
   fixtures/haskell-ci.cabal
@@ -53,6 +55,10 @@
   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/cabal.project.travis-patch
+  fixtures/cabal.project.travis-patch.patch
+  fixtures/cabal.project.travis-patch.stderr
+  fixtures/cabal.project.travis-patch.travis.yml
   fixtures/servant-client-core/*.cabal
   fixtures/servant-client/*.cabal
   fixtures/servant-docs/*.cabal
@@ -73,6 +79,7 @@
   exposed-modules:
     HaskellCI
     HaskellCI.Cli
+    HaskellCI.Compiler
     HaskellCI.Config
     HaskellCI.Config.ConstraintSet
     HaskellCI.Config.CopyFields
@@ -82,10 +89,12 @@
     HaskellCI.Config.HLint
     HaskellCI.Config.Installed
     HaskellCI.Config.Jobs
-    HaskellCI.Extras
-    HaskellCI.GHC
+    HaskellCI.Config.Ubuntu
+    HaskellCI.Diagnostics
     HaskellCI.Glob
-    HaskellCI.MakeTravisOutput
+    HaskellCI.Jobs
+    HaskellCI.List
+    HaskellCI.MonadErr
     HaskellCI.Newtypes
     HaskellCI.Optimization
     HaskellCI.OptionsGrammar
@@ -93,22 +102,32 @@
     HaskellCI.Package
     HaskellCI.ParsecError
     HaskellCI.ParsecUtils
+    HaskellCI.Prelude
     HaskellCI.Project
+    HaskellCI.Sh
+    HaskellCI.ShVersionRange
     HaskellCI.TestedWith
-    HaskellCI.Version
+    HaskellCI.Tools
+    HaskellCI.Travis
+    HaskellCI.Travis.Yaml
+    HaskellCI.YamlSyntax
 
-  -- 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:
+    BangPatterns
+    NoImplicitPrelude
+    DeriveAnyClass
     DeriveFoldable
     DeriveFunctor
     DeriveGeneric
     DeriveTraversable
+    DerivingStrategies
+    GeneralizedNewtypeDeriving
+    ScopedTypeVariables
 
   other-extensions:
     CPP
@@ -117,35 +136,44 @@
     ViewPatterns
 
   build-depends:
-    , base          >=4.9      && <4.13
+    , base          >=4.10     && <4.13
     , bytestring    ^>=0.10.8.1
-    , Cabal         ^>=2.4
+    , Cabal         ^>=3.0
     , containers    ^>=0.5.7.1 || ^>=0.6.0.1
     , deepseq       ^>=1.4.2.0
     , directory     ^>=1.3.0.0
     , filepath      ^>=1.4.1.1
+    , mtl           >=2.2.2
     , parsec        ^>=3.1.13.0
     , pretty        ^>=1.1.3.3
+    , process       ^>=1.4.3.0 || ^>=1.6.1.0
     , text          ^>=1.2.3.0
     , transformers  ^>=0.5.2.0
 
+  if !impl(ghc >=8.2)
+    build-depends: bifunctors ^>=5.5.4
+
   -- other dependencies
   build-depends:
+    , aeson                 ^>=1.4.2.0
     , base-compat           ^>=0.10.5
+    , exceptions            ^>=0.10.0
     , generic-lens          ^>=1.1.0.0
     , HsYAML                ^>=0.1.1.3
+    , lattices              ^>=2
     , microlens             ^>=0.4.10
     , optparse-applicative  ^>=0.14.3.0
+    , temporary             ^>=1.3
+    , unordered-containers  ^>=0.2.10.0
 
   default-language:   Haskell2010
 
   -- ShellCheck. Would need newer transformers for older GHC
   if (flag(shellcheck) && impl(ghc >=7.10 && <8.7))
-    build-depends: ShellCheck ==0.6.0
+    build-depends: ShellCheck ==0.7.0
 
 executable haskell-ci
   main-is:          Main.hs
-  ghc-options:      -Wall
   hs-source-dirs:   cli
   build-depends:
     , base
@@ -158,14 +186,14 @@
   main-is:          Tests.hs
   hs-source-dirs:   test
   build-depends:
-    , ansi-terminal        ^>=0.8.0.2
+    , ansi-terminal        ^>=0.8.0.2 || ^>=0.9.1
     , base
     , bytestring
     , Diff                 ^>=0.3.4
     , directory
     , filepath
     , haskell-ci-internal
-    , tasty                >=1.0     && <1.2
+    , tasty                >=1.0     && <1.3
     , tasty-golden         ^>=2.3.1.1
     , transformers
 
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,2 +1,2 @@
-*INFO* Generating Travis-CI config for testing for GHC versions: head 8.0.2 8.2.2 8.4.4 8.6.4
-*INFO* Also OSX jobs for: 8.4.4
+*INFO* Generating Travis-CI config for testing for GHC versions: ghc-head 8.2.2 8.4.4 8.6.5
+*INFO* Also OSX jobs for: 8.4.4 8.6.5
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,27 +1,22 @@
 # This Travis job script has been generated by a script via
 #
-#   haskell-ci '--output=.travis.yml' '--config=cabal.haskell-ci' 'haskell-ci.cabal'
+#   haskell-ci '--config=cabal.haskell-ci' 'cabal.project'
 #
 # For more information, see https://github.com/haskell-CI/haskell-ci
 #
-# version: 0.2.1
-#
 language: c
 dist: xenial
-
 git:
-  submodules: false  # whether to recursively clone submodules
-
+  # whether to recursively clone submodules
+  submodules: false
 branches:
   only:
     - master
-
 cache:
   directories:
     - $HOME/.cabal/packages
     - $HOME/.cabal/store
     - $HOME/.ghc-install
-
 before_cache:
   - rm -fv $CABALHOME/packages/hackage.haskell.org/build-reports.log
   # remove files that are regenerated by 'cabal update'
@@ -30,146 +25,175 @@
   - 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.4"
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.6.4], 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"
-      addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.4,ghc-8.2.2], 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-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]}}
+    - compiler: ghc-8.6.5
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.5","cabal-install-3.0"]}}
+    - compiler: ghc-8.4.4
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.4","cabal-install-3.0"]}}
+    - compiler: ghc-8.2.2
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.2.2","cabal-install-3.0"]}}
+    - compiler: ghc-head
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-head","cabal-install-head"]}}
+    - compiler: ghc-8.6.5
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.5","cabal-install-3.0"]}}
       os: osx
-
+    - compiler: ghc-8.4.4
+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.4","cabal-install-3.0"]}}
+      os: osx
   allow_failures:
-    - compiler: "ghc-head"
-
+    - compiler: ghc-head
 before_install:
-  - HC=/opt/ghc/bin/${CC}
-  - HCPKG=${HC/ghc/ghc-pkg}
+  - HC=$(echo "/opt/$CC/bin/ghc" | sed 's/-/\//')
+  - WITHCOMPILER="-w $HC"
+  - HCPKG="$HC-pkg"
   - unset CC
   - CABAL=/opt/ghc/bin/cabal
   - CABALHOME=$HOME/.cabal
   - export PATH="$CABALHOME/bin:$PATH"
-  - ROOTDIR=$(pwd)
+  - TOP=$(pwd)
   - 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/') ))
+  - if [ "$TRAVIS_OS_NAME" = "osx" ]; then HC=$HOME/.ghc-install/ghc/bin/$TRAVIS_COMPILER; WITHCOMPILER="-w $HC"; HCPKG=${HC/ghc/ghc-pkg}; CABAL=$HOME/.ghc-install/ghc/bin/cabal; fi
+  - "HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\\d+)\\.(\\d+)\\.(\\d+)(\\.(\\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))')"
   - echo $HCNUMVER
-
+  - CABAL="$CABAL -vnormal+nowrap+markoutput"
+  - set -o pipefail
+  - |
+    echo 'function blue(s) { printf "\033[0;34m" s "\033[0m " }'           >> .colorful.awk
+    echo 'BEGIN { state = "output"; }'                                     >> .colorful.awk
+    echo '/^-----BEGIN CABAL OUTPUT-----$/ { state = "cabal" }'            >> .colorful.awk
+    echo '/^-----END CABAL OUTPUT-----$/ { state = "output" }'             >> .colorful.awk
+    echo '!/^(-----BEGIN CABAL OUTPUT-----|-----END CABAL OUTPUT-----)/ {' >> .colorful.awk
+    echo '  if (state == "cabal") {'                                       >> .colorful.awk
+    echo '    print blue($0)'                                              >> .colorful.awk
+    echo '  } else {'                                                      >> .colorful.awk
+    echo '    print $0'                                                    >> .colorful.awk
+    echo '  }'                                                             >> .colorful.awk
+    echo '}'                                                               >> .colorful.awk
+  - cat .colorful.awk
+  - |
+    color_cabal_output () {
+      awk -f $TOP/.colorful.awk
+    }
+  - echo text | color_cabal_output
 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
-  - "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
+  - HEADHACKAGE=false
+  - if [ $HCNUMVER -gt 80605 ] ; then HEADHACKAGE=true ; fi
+  - rm -f $CABALHOME/config
   - |
-    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
+    echo "verbose: normal +nowrap +markoutput"          >> $CABALHOME/config
+    echo "remote-build-reporting: anonymous"            >> $CABALHOME/config
+    echo "write-ghc-environment-files: always"          >> $CABALHOME/config
+    echo "remote-repo-cache: $CABALHOME/packages"       >> $CABALHOME/config
+    echo "logs-dir:          $CABALHOME/logs"           >> $CABALHOME/config
+    echo "world-file:        $CABALHOME/world"          >> $CABALHOME/config
+    echo "extra-prog-path:   $CABALHOME/bin"            >> $CABALHOME/config
+    echo "symlink-bindir:    $CABALHOME/bin"            >> $CABALHOME/config
+    echo "installdir:        $CABALHOME/bin"            >> $CABALHOME/config
+    echo "build-summary:     $CABALHOME/logs/build.log" >> $CABALHOME/config
+    echo "store-dir:         $CABALHOME/store"          >> $CABALHOME/config
+    echo "install-dirs user"                            >> $CABALHOME/config
+    echo "  prefix: $CABALHOME"                         >> $CABALHOME/config
+    echo "repository hackage.haskell.org"               >> $CABALHOME/config
+    echo "  url: http://hackage.haskell.org/"           >> $CABALHOME/config
+  - |
+    if $HEADHACKAGE; then
+    echo "allow-newer: $($HCPKG list --simple-output | sed -E 's/([a-zA-Z-]+)-[0-9.]+/*:\1/g')" >> $CABALHOME/config
+    echo "repository head.hackage.ghc.haskell.org"                                        >> $CABALHOME/config
+    echo "   url: https://ghc.gitlab.haskell.org/head.hackage/"                           >> $CABALHOME/config
+    echo "   secure: True"                                                                >> $CABALHOME/config
+    echo "   root-keys: 7541f32a4ccca4f97aea3b22f5e593ba2c0267546016b992dfadcd2fe944e55d" >> $CABALHOME/config
+    echo "              26021a13b401500c8eb2761ca95c61f2d625bfef951b939a8124ed12ecf07329" >> $CABALHOME/config
+    echo "              f76d08be13e9a61a377a85e2fb63f4c5435d40f8feb3e12eb05905edb8cdea89" >> $CABALHOME/config
+    echo "   key-threshold: 3"                                                            >> $CABALHOME/config
     fi
-  - grep -Ev -- '^\s*--' $CABALHOME/config | grep -Ev '^\s*$'
-  - ${CABAL} new-install -w ${HC} -j2 doctest --constraint='doctest ==0.16.*'
-  - if [ $HCNUMVER -eq 80604 ] ; then ${CABAL} new-install -w ${HC} -j2 hlint --constraint='hlint ==2.1.*' ; fi
-  - rm -f cabal.project
+  - "echo 'jobs: 2' >> $CABALHOME/config"
+  - |
+    echo "program-default-options" >> $CABALHOME/config
+    echo "  ghc-options: -j2"      >> $CABALHOME/config
+  - cat $CABALHOME/config
+  - rm -fv cabal.project cabal.project.local cabal.project.freeze
+  - travis_retry ${CABAL} v2-update -v
+  - (cd /tmp && ${CABAL} v2-install $WITHCOMPILER -j2 doctest --constraint='doctest ==0.16.*' | color_cabal_output)
+  - if [ $HCNUMVER -ge 80600 ] && [ $HCNUMVER -lt 80606 ] ; then (cd /tmp && ${CABAL} v2-install $WITHCOMPILER -j2 hlint --constraint='hlint ==2.1.*' | color_cabal_output) ; fi
+  # Generate cabal.project
+  - rm -rf cabal.project cabal.project.local cabal.project.freeze
   - touch cabal.project
-  - "printf 'packages: \".\"\\n' >> cabal.project"
-  - echo 'package haskell-ci' >> cabal.project
-  - "echo '  ghc-options: -Werror' >> cabal.project"
-  - "echo 'keep-going: False' >> cabal.project"
-  - echo 'package bytestring' >> cabal.project
-  - "echo '  tests: False' >> 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 -- '^(Cabal|haskell-ci)$' | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"
+  - |
+    echo "packages: ." >> cabal.project
+  - |
+    echo "package haskell-ci"     >> cabal.project
+    echo "  ghc-options: -Werror" >> cabal.project
+    echo ""                       >> cabal.project
+    echo "keep-going: False"      >> cabal.project
+    echo ""                       >> cabal.project
+    echo "package bytestring"     >> cabal.project
+    echo "  tests: False"         >> cabal.project
+  - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | (grep -vE -- '^(Cabal|haskell-ci)$' || true) | 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
-  - rm -f cabal.project.freeze
-  - ${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)
-
-# 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.
+  - ${CABAL} v2-freeze $WITHCOMPILER ${TEST} ${BENCH} | color_cabal_output
+  - "cat cabal.project.freeze | sed -E 's/^(constraints: *| *)//' | sed 's/any.//'"
+  - rm  cabal.project.freeze
+  - ${CABAL} v2-build $WITHCOMPILER ${TEST} ${BENCH} --dep -j2 all | color_cabal_output
+  - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --dep -j2 all | color_cabal_output
 script:
-  # test that source-distributions can be generated
-  - ${CABAL} new-sdist all
+  - DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)
+  # Packaging...
+  - ${CABAL} v2-sdist all | color_cabal_output
+  # Unpacking...
   - mv dist-newstyle/sdist/*.tar.gz ${DISTDIR}/
   - cd ${DISTDIR} || false
-  - find . -maxdepth 1 -name '*.tar.gz' -exec tar -xvf '{}' \;
-  - rm -f cabal.project
+  - find . -maxdepth 1 -type f -name '*.tar.gz' -exec tar -xvf '{}' \;
+  - find . -maxdepth 1 -type f -name '*.tar.gz' -exec rm       '{}' \;
+  - PKGDIR_haskell_ci="$(find . -maxdepth 1 -type d -regex '.*/haskell-ci-[0-9.]*')"
+  # Generate cabal.project
+  - rm -rf cabal.project cabal.project.local cabal.project.freeze
   - touch cabal.project
-  - "printf 'packages: \"haskell-ci-*/*.cabal\"\\n' >> cabal.project"
-  - echo 'package haskell-ci' >> cabal.project
-  - "echo '  ghc-options: -Werror' >> cabal.project"
-  - "echo 'keep-going: False' >> cabal.project"
-  - echo 'package bytestring' >> cabal.project
-  - "echo '  tests: False' >> 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 -- '^(Cabal|haskell-ci)$' | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"
+  - |
+    echo "packages: ${PKGDIR_haskell_ci}" >> cabal.project
+  - |
+    echo "package haskell-ci"     >> cabal.project
+    echo "  ghc-options: -Werror" >> cabal.project
+    echo ""                       >> cabal.project
+    echo "keep-going: False"      >> cabal.project
+    echo ""                       >> cabal.project
+    echo "package bytestring"     >> cabal.project
+    echo "  tests: False"         >> cabal.project
+  - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | (grep -vE -- '^(Cabal|haskell-ci)$' || true) | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"
   - cat cabal.project || true
   - cat cabal.project.local || true
+  # Building...
   # this builds all libraries and executables (without tests/benchmarks)
-  - ${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks all
-
+  - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks all | color_cabal_output
+  # Building with tests and benchmarks...
   # 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
-
-  # doctest
-  - (cd haskell-ci-* && doctest --fast -XDeriveFoldable -XDeriveFunctor -XDeriveGeneric -XDeriveTraversable src)
-
-  # hlint
-  - if [ $HCNUMVER -eq 80604 ] ; then (cd haskell-ci-* && hlint -h ${ROOTDIR}/.hlint.yaml -XDeriveFoldable -XDeriveFunctor -XDeriveGeneric -XDeriveTraversable src) ; fi
-  - if [ $HCNUMVER -eq 80604 ] ; then (cd haskell-ci-* && hlint -h ${ROOTDIR}/.hlint.yaml cli) ; fi
-
-  # cabal check
-  - (cd haskell-ci-* && ${CABAL} check)
-
-  # Build without installed constraints for packages in global-db
-  - rm -f cabal.project.local; ${CABAL} new-build -w ${HC} --disable-tests --disable-benchmarks all;
-
+  - ${CABAL} v2-build $WITHCOMPILER ${TEST} ${BENCH} all | color_cabal_output
+  # Testing...
+  - ${CABAL} v2-test $WITHCOMPILER ${TEST} ${BENCH} all | color_cabal_output
+  # Doctest...
+  - (cd ${PKGDIR_haskell_ci} && doctest --fast -XBangPatterns -XNoImplicitPrelude -XDeriveAnyClass -XDeriveFoldable -XDeriveFunctor -XDeriveGeneric -XDeriveTraversable -XDerivingStrategies -XGeneralizedNewtypeDeriving -XScopedTypeVariables src)
+  # HLint..
+  - if [ $HCNUMVER -ge 80600 ] && [ $HCNUMVER -lt 80606 ] ; then (cd ${PKGDIR_haskell_ci} && hlint -h ${TOP}/.hlint.yaml -XBangPatterns -XNoImplicitPrelude -XDeriveAnyClass -XDeriveFoldable -XDeriveFunctor -XDeriveGeneric -XDeriveTraversable -XDerivingStrategies -XGeneralizedNewtypeDeriving -XScopedTypeVariables src) ; fi
+  - if [ $HCNUMVER -ge 80600 ] && [ $HCNUMVER -lt 80606 ] ; then (cd ${PKGDIR_haskell_ci} && hlint -h ${TOP}/.hlint.yaml cli) ; fi
+  # cabal check...
+  - (cd ${PKGDIR_haskell_ci} && ${CABAL} -vnormal check)
+  # Building without installed constraints for packages in global-db...
+  - rm -f cabal.project.local
+  - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks all | color_cabal_output
   # 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 ; fi
-
+  - echo 'Constraint set deepseq-1.4' && echo -en 'travis_fold:start:constraint-sets-deepseq-1.4\\r'
+  - if [ $HCNUMVER -lt 80400 ] ; then ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --constraint='deepseq ==1.4.*' --constraint='binary installed' all | color_cabal_output ; fi
+  - if [ $HCNUMVER -lt 80400 ] ; then ${CABAL} v2-haddock $WITHCOMPILER --disable-tests --disable-benchmarks --constraint='deepseq ==1.4.*' --constraint='binary installed' all | color_cabal_output ; fi
   - echo -en 'travis_fold:end:constraint-sets-deepseq-1.4\\r'
 
-# REGENDATA ["--output=.travis.yml","--config=cabal.haskell-ci","haskell-ci.cabal"]
+# REGENDATA ["--config=cabal.haskell-ci","cabal.project"]
 # EOF
diff --git a/haskell-ci.cabal b/haskell-ci.cabal
--- a/haskell-ci.cabal
+++ b/haskell-ci.cabal
@@ -1,6 +1,6 @@
 cabal-version:      2.2
 name:               haskell-ci
-version:            0.2.1
+version:            0.4
 synopsis:           Cabal package script generator for Travis-CI
 description:
   Script generator (@haskell-ci@) for [Travis-CI](https://travis-ci.org/) for continuous-integration testing of Haskell Cabal packages.
@@ -13,6 +13,7 @@
   * Runs tests and builds benchmarks
   * Generates Haddocks
   * macOS (OSX) support
+  * GHCJS support
   * building with specific constraints
   .
   === Quick Start Guide
@@ -23,16 +24,17 @@
 
 homepage:           https://haskell-ci.rtfd.org/
 bug-reports:        https://github.com/haskell-CI/haskell-ci/issues
-license:            BSD-3-Clause
+license:            GPL-3.0-or-later
 license-file:       LICENSE
 author:             Herbert Valerio Riedel, Oleg Grenrus
 maintainer:         hvr@gnu.org
 category:           Development
 build-type:         Simple
-tested-with:        GHC ==8.6.4 || ==8.4.4 || ==8.2.2 || ==8.0.2
+tested-with:        GHC ==8.6.5 || ==8.4.4 || ==8.2.2
 extra-source-files:
   CHANGELOG.md
   fixtures/cabal.haskell-ci
+  fixtures/cabal.project.haskell-ci
   fixtures/cabal.project.fail-versions
   fixtures/cabal.project.fail-versions.stderr
   fixtures/haskell-ci.cabal
@@ -53,6 +55,10 @@
   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/cabal.project.travis-patch
+  fixtures/cabal.project.travis-patch.patch
+  fixtures/cabal.project.travis-patch.stderr
+  fixtures/cabal.project.travis-patch.travis.yml
   fixtures/servant-client-core/*.cabal
   fixtures/servant-client/*.cabal
   fixtures/servant-docs/*.cabal
@@ -73,6 +79,7 @@
   exposed-modules:
     HaskellCI
     HaskellCI.Cli
+    HaskellCI.Compiler
     HaskellCI.Config
     HaskellCI.Config.ConstraintSet
     HaskellCI.Config.CopyFields
@@ -82,10 +89,12 @@
     HaskellCI.Config.HLint
     HaskellCI.Config.Installed
     HaskellCI.Config.Jobs
-    HaskellCI.Extras
-    HaskellCI.GHC
+    HaskellCI.Config.Ubuntu
+    HaskellCI.Diagnostics
     HaskellCI.Glob
-    HaskellCI.MakeTravisOutput
+    HaskellCI.Jobs
+    HaskellCI.List
+    HaskellCI.MonadErr
     HaskellCI.Newtypes
     HaskellCI.Optimization
     HaskellCI.OptionsGrammar
@@ -93,22 +102,32 @@
     HaskellCI.Package
     HaskellCI.ParsecError
     HaskellCI.ParsecUtils
+    HaskellCI.Prelude
     HaskellCI.Project
+    HaskellCI.Sh
+    HaskellCI.ShVersionRange
     HaskellCI.TestedWith
-    HaskellCI.Version
+    HaskellCI.Tools
+    HaskellCI.Travis
+    HaskellCI.Travis.Yaml
+    HaskellCI.YamlSyntax
 
-  -- 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:
+    BangPatterns
+    NoImplicitPrelude
+    DeriveAnyClass
     DeriveFoldable
     DeriveFunctor
     DeriveGeneric
     DeriveTraversable
+    DerivingStrategies
+    GeneralizedNewtypeDeriving
+    ScopedTypeVariables
 
   other-extensions:
     CPP
@@ -117,35 +136,44 @@
     ViewPatterns
 
   build-depends:
-    , base          >=4.9      && <4.13
+    , base          >=4.10     && <4.13
     , bytestring    ^>=0.10.8.1
-    , Cabal         ^>=2.4
+    , Cabal         ^>=3.0
     , containers    ^>=0.5.7.1 || ^>=0.6.0.1
     , deepseq       ^>=1.4.2.0
     , directory     ^>=1.3.0.0
     , filepath      ^>=1.4.1.1
+    , mtl           >=2.2.2
     , parsec        ^>=3.1.13.0
     , pretty        ^>=1.1.3.3
+    , process       ^>=1.4.3.0 || ^>=1.6.1.0
     , text          ^>=1.2.3.0
     , transformers  ^>=0.5.2.0
 
+  if !impl(ghc >=8.2)
+    build-depends: bifunctors ^>=5.5.4
+
   -- other dependencies
   build-depends:
+    , aeson                 ^>=1.4.2.0
     , base-compat           ^>=0.10.5
+    , exceptions            ^>=0.10.0
     , generic-lens          ^>=1.1.0.0
     , HsYAML                ^>=0.1.1.3
+    , lattices              ^>=2
     , microlens             ^>=0.4.10
     , optparse-applicative  ^>=0.14.3.0
+    , temporary             ^>=1.3
+    , unordered-containers  ^>=0.2.10.0
 
   default-language:   Haskell2010
 
   -- ShellCheck. Would need newer transformers for older GHC
   if (flag(shellcheck) && impl(ghc >=7.10 && <8.7))
-    build-depends: ShellCheck ==0.6.0
+    build-depends: ShellCheck ==0.7.0
 
 executable haskell-ci
   main-is:          Main.hs
-  ghc-options:      -Wall
   hs-source-dirs:   cli
   build-depends:
     , base
@@ -158,14 +186,14 @@
   main-is:          Tests.hs
   hs-source-dirs:   test
   build-depends:
-    , ansi-terminal        ^>=0.8.0.2
+    , ansi-terminal        ^>=0.8.0.2 || ^>=0.9.1
     , base
     , bytestring
     , Diff                 ^>=0.3.4
     , directory
     , filepath
     , haskell-ci-internal
-    , tasty                >=1.0     && <1.2
+    , tasty                >=1.0     && <1.3
     , tasty-golden         ^>=2.3.1.1
     , transformers
 
diff --git a/src/Distribution/Fields/Pretty.hs b/src/Distribution/Fields/Pretty.hs
deleted file mode 100644
--- a/src/Distribution/Fields/Pretty.hs
+++ /dev/null
@@ -1,134 +0,0 @@
-{-# 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
--- a/src/HaskellCI.hs
+++ b/src/HaskellCI.hs
@@ -1,1033 +1,353 @@
-{-# 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"
-                        ]
-
-                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' ]
-
-
-        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"
-                ]
-
-        -- raw-project is after local-ghc-options so we can override per package.
-        unless (null (cfgRawProject config)) $ tellStrLns
-            [ sh $ "echo '" ++ l ++ "' >> cabal.project"
-            | l <- lines $ C.showFields' 2 $ cfgRawProject config
-            , not (null l)
-            ]
-
-        -- mandatory cabal.project setup
-        -- this have to be last, so configuration doesn't override it.
-        tellStrLns
-            [ sh $ "printf 'write-ghc-environment-files: always\\n' >> cabal.project"
-            ]
-
-        -- 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
-    ]
+{-# LANGUAGE CPP                 #-}
+{-# LANGUAGE MultiWayIf          #-}
+{-# LANGUAGE OverloadedLabels    #-}
+{-# LANGUAGE OverloadedStrings   #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+-- | 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
+    parseTravis,
+    travisFromConfigFile, Options (..), defaultOptions,
+    ) where
+
+import HaskellCI.Prelude
+
+import Data.List                    (intercalate, isPrefixOf, nub, nubBy, sort, sortBy, (\\))
+import System.Directory             (canonicalizePath, doesDirectoryExist, doesFileExist, makeRelativeToCurrentDirectory, setCurrentDirectory)
+import System.Environment           (getArgs)
+import System.Exit                  (ExitCode (..), exitFailure)
+import System.FilePath.Posix        (takeDirectory, takeExtension, takeFileName, (</>))
+import System.IO                    (hClose, hFlush, hPutStr, hPutStrLn, stderr)
+import System.IO.Temp               (withSystemTempFile)
+import System.Process               (readProcessWithExitCode)
+import Text.ParserCombinators.ReadP (readP_to_S)
+
+import Distribution.PackageDescription        (package, packageDescription, testedWith)
+import Distribution.PackageDescription.Parsec (readGenericPackageDescription)
+import Distribution.Text
+import Distribution.Version
+
+import qualified Data.ByteString       as BS
+import qualified Data.List.NonEmpty    as NE
+import qualified Data.Set              as S
+import qualified Data.Traversable      as T
+import qualified Distribution.Compiler as Compiler
+import qualified Distribution.Package  as Pkg
+import qualified Options.Applicative   as O
+
+import HaskellCI.Cli
+import HaskellCI.Compiler
+import HaskellCI.Config
+import HaskellCI.Config.Dump
+import HaskellCI.Diagnostics
+import HaskellCI.Glob
+import HaskellCI.Jobs
+import HaskellCI.Package
+import HaskellCI.Project
+import HaskellCI.TestedWith
+import HaskellCI.Travis
+import HaskellCI.YamlSyntax
+
+-------------------------------------------------------------------------------
+-- Main
+-------------------------------------------------------------------------------
+
+main :: IO ()
+main = do
+    argv0 <- getArgs
+    (cmd, opts) <- O.execParser cliParserInfo
+    case cmd of
+        CommandListGHC -> do
+            putStrLn $ "Supported GHC versions:"
+            for_ groupedVersions $ \(v, vs) -> do
+                putStr $ prettyMajVersion v ++ ": "
+                putStrLn $ intercalate ", " (map display $ toList vs)
+        CommandDumpConfig -> do
+            putStr $ unlines $ runDG configGrammar
+
+        CommandRegenerate -> do
+            let fp = case optOutput opts of
+                    Just (OutputFile fp') -> fp'
+                    _                     -> defaultTravisPath
+
+            -- read, and then change to the directory
+            contents <- readFile fp
+            absFp <- canonicalizePath fp
+            let dir = takeDirectory fp
+            setCurrentDirectory dir
+            newFp <- makeRelativeToCurrentDirectory absFp
+
+            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 (optionsWithOutputFile newFp <> 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, NonEmpty Version)]
+    groupedVersions = map ((\vs -> (head vs, vs)) . NE.sortBy (flip compare))
+                    . groupBy ((==) `on` ghcMajVer)
+                    $ sort knownGhcVersions
+
+    prettyMajVersion :: Version -> String
+    prettyMajVersion v = case ghcMajVer v of
+        (x, y) -> show x ++ "." ++ show y
+
+defaultTravisPath :: FilePath
+defaultTravisPath = ".travis.yml"
+
+doTravis :: [String] -> FilePath -> Options -> IO ()
+doTravis args path opts = do
+    ls <- travisFromConfigFile args opts path
+    let contents = unlines ls
+    case optOutput opts of
+        Nothing              -> writeFile defaultTravisPath contents
+        Just OutputStdout    -> putStr contents
+        Just (OutputFile fp) -> writeFile fp contents
+
+travisFromConfigFile
+    :: forall m. (MonadIO m, MonadDiagnostics m, MonadMask m)
+    => [String]
+    -> Options
+    -> FilePath
+    -> m [String]
+travisFromConfigFile args opts path = do
+    cabalFiles <- getCabalFiles
+    config' <- maybe (return emptyConfig) readConfigFile (optConfig opts)
+    let config = optConfigMorphism opts config'
+    pkgs <- T.mapM (configFromCabalFile config) cabalFiles
+    (ghcs, prj) <- case checkVersions (cfgTestedWith config) pkgs of
+        Right x     -> return x
+        Left []     -> putStrLnErr "panic: checkVersions failed without errors"
+        Left (e:es) -> putStrLnErrs (e :| es)
+
+    let prj' | cfgGhcHead config = over (mapped . #pkgJobs) (S.insert GHCHead) prj
+             | otherwise         = prj
+
+    ls <- genTravisFromConfigs args config prj' ghcs
+    patchTravis config ls
+  where
+    isCabalProject :: Maybe FilePath
+    isCabalProject
+        | "cabal.project" `isPrefixOf` takeFileName path = Just path
+        | otherwise = Nothing
+
+    getCabalFiles :: m (Project Void FilePath)
+    getCabalFiles
+        | isNothing isCabalProject = return $ emptyProject & #prjPackages .~ [path]
+        | otherwise = do
+            contents <- liftIO $ BS.readFile path
+            prj  <- either putStrLnErr return $ parseProjectFile path contents
+            prj' <- bitraverse findOptProjectPackage findProjectPackage prj
+            return prj'
+                { prjPackages    = concat $ prjPackages prj' ++ prjOptPackages prj'
+                , prjOptPackages = []
+                }
+
+    rootdir = takeDirectory path
+
+    -- See findProjectPackages in cabal-install codebase
+    -- this is simple variant.
+    findProjectPackage :: String -> m [FilePath]
+    findProjectPackage pkglocstr = do
+        mfp <- checkisFileGlobPackage pkglocstr `mplusMaybeT`
+               checkIsSingleFilePackage pkglocstr
+        maybe (putStrLnErr $ "bad package location: " ++ pkglocstr) return mfp
+
+    findOptProjectPackage :: String -> m [FilePath]
+    findOptProjectPackage pkglocstr = do
+        mfp <- checkisFileGlobPackage pkglocstr `mplusMaybeT`
+               checkIsSingleFilePackage pkglocstr
+        maybe (return []) 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)
+
+genTravisFromConfigs
+    :: (Monad m, MonadDiagnostics m)
+    => [String]
+    -> Config
+    -> Project Void Package
+    -> Set CompilerVersion
+    -> m [String]
+genTravisFromConfigs argv config prj vs = do
+    let jobVersions = makeJobVersions config vs
+    case makeTravis argv config prj jobVersions of
+        Left err     -> putStrLnErr $ displayException err
+        Right travis -> do
+            describeJobs (cfgTestedWith config) jobVersions (prjPackages prj)
+            return $
+                lines (prettyYaml id $ reann (travisHeader (cfgInsertVersion config) argv ++) $ toYaml travis)
+                ++
+                [ ""
+                , "# REGENDATA " ++ show argv
+                , "# EOF"
+                ]
+
+-- | Adjust the generated Travis YAML output with patch files, if specified.
+-- We do this in a temporary file in case the user did not pass --output (as
+-- it would be awkward to patch the generated output otherwise).
+patchTravis
+    :: (MonadIO m, MonadMask m)
+    => Config -> [String] -> m [String]
+patchTravis cfg ls
+  | null patches = pure ls
+  | otherwise =
+      withSystemTempFile ".travis.yml.tmp" $ \fp h -> liftIO $ do
+        hPutStr h $ unlines ls
+        hFlush h
+        for_ patches $ applyPatch fp
+        hClose h
+        lines <$> readFile fp
+  where
+    patches :: [FilePath]
+    patches = cfgTravisPatches cfg
+
+    applyPatch :: FilePath -- ^ The temporary file path to patch
+               -> FilePath -- ^ The path of the .patch file
+               -> IO ()
+    applyPatch temp patch = do
+        exists <- doesFileExist patch
+        unless exists $ putStrLnErr $ "Cannot find " ++ patch
+        (ec, stdOut, stdErr) <- readProcessWithExitCode
+            "patch" [ "--input", patch
+            , "--silent"
+            , temp
+            ] ""
+        case ec of
+            ExitSuccess -> pure ()
+            ExitFailure n -> putStrLnErr $ unlines
+                [ "patch returned exit code " ++ show n
+                , "Stdout: " ++ stdOut
+                , "Stderr: " ++ stdErr
+                ]
+
+configFromCabalFile
+    :: (MonadIO m, MonadDiagnostics m)
+    => Config -> FilePath -> m Package
+configFromCabalFile cfg 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 /= Compiler.GHC, c /= Compiler.GHCJS ]
+
+        ghcVerConstrs   = [ vc | (Compiler.GHC,vc) <- compilers ]
+        ghcVerConstrs'  = simplifyVersionRange $ foldr unionVersionRanges noVersion ghcVerConstrs
+        specificGhcVers = nub $ mapMaybe isSpecificVersion ghcVerConstrs
+
+        ghcjsVerConstrs   = [ vc | (Compiler.GHCJS,vc) <- compilers ]
+        ghcjsVerConstrs'  = simplifyVersionRange $ foldr unionVersionRanges noVersion ghcjsVerConstrs
+        specificGhcjsVers = nub $ mapMaybe isSpecificVersion ghcjsVerConstrs
+
+        twoDigitGhcVerConstrs = mapMaybe isTwoDigitGhcVersion ghcVerConstrs :: [Version]
+
+    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 unknownGhcjsVers = sort $ specificGhcjsVers \\ knownGhcjsVersions
+    unless (null unknownGhcjsVers) $ do
+        putStrLnErr ("'tested-with:' specifically refers to unknown 'GHCJS' versions: "
+                     ++ intercalate ", " (map display unknownGhcjsVers) ++ "\n"
+                     ++ "Known GHCJS versions: " ++ intercalate ", " (map display knownGhcjsVersions))
+
+    let knownGhcVersions'
+            | cfgLastInSeries cfg = filterLastMajor knownGhcVersions
+            | otherwise           = knownGhcVersions
+
+    let testedGhcVersions   = filter (`withinRange` ghcVerConstrs') knownGhcVersions'
+    let testedGhcjsVersions = filter (`withinRange` ghcjsVerConstrs') knownGhcjsVersions
+
+    when (null testedGhcVersions) $ do
+        putStrLnErr "no known GHC version is allowed by the 'tested-with' specification"
+
+    let compilerRange :: Set CompilerVersion
+        compilerRange = S.fromList $
+            [ GHC v
+            | v <- testedGhcVersions
+            ] ++
+            [ GHCJS v
+            | v <- testedGhcjsVersions
+            ]
+
+    let pkg = Pkg pkgNameStr compilerRange (takeDirectory cabalFile) gpd
+
+    return pkg
+  where
+    lastStableGhcVers
+        = nubBy ((==) `on` ghcMajVer)
+        $ sortBy (flip compare)
+        $ filter (not . previewGHC defaultHeadHackage . GHC)
+        $ 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)
diff --git a/src/HaskellCI/Cli.hs b/src/HaskellCI/Cli.hs
--- a/src/HaskellCI/Cli.hs
+++ b/src/HaskellCI/Cli.hs
@@ -1,18 +1,22 @@
+{-# LANGUAGE CPP #-}
+
 -- | Most of client interface.
 module HaskellCI.Cli where
 
-import           Prelude                  ()
-import           Prelude.Compat
+import HaskellCI.Prelude
 
-import           Control.Applicative      ((<|>))
-import           System.Exit              (exitFailure)
-import           System.IO                (hPutStrLn, stderr)
+import System.Exit (exitFailure)
+import System.IO   (hPutStrLn, stderr)
 
-import qualified Options.Applicative      as O
+import qualified Options.Applicative as O
 
-import           HaskellCI.Config
-import           HaskellCI.OptparseGrammar
+import HaskellCI.Config
+import HaskellCI.OptparseGrammar
 
+#ifndef CURRENT_PACKAGE_VERSION
+#define CURRENT_PACKAGE_VERSION "???"
+#endif
+
 -------------------------------------------------------------------------------
 -- Command
 -------------------------------------------------------------------------------
@@ -29,37 +33,56 @@
 -------------------------------------------------------------------------------
 
 data Options = Options
-    { optCollections    :: [String]
-    , optOutput         :: Maybe FilePath
+    { optOutput         :: Maybe Output
     , optConfig         :: Maybe FilePath
     , optConfigMorphism :: Config -> Config
     }
 
+data Output = OutputStdout | OutputFile FilePath
+
 instance Semigroup Options where
-    Options a b d e <> Options a' b' d' e' =
-        Options (a <> a') (b <|> b') (d <|> d') (e' . e)
+    Options b d e <> Options b' d' e' =
+        Options (b <|> b') (d <|> d') (e' . e)
 
 defaultOptions :: Options
 defaultOptions = Options
-    { optCollections    = []
-    , optOutput         = Nothing
+    { optOutput         = Nothing
     , optConfig         = Nothing
     , optConfigMorphism = id
     }
 
+optionsWithOutputFile :: FilePath -> Options
+optionsWithOutputFile fp = defaultOptions
+    { optOutput = Just (OutputFile fp)
+    }
+
 -------------------------------------------------------------------------------
 -- 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 outputP
     <*> O.optional (O.strOption (O.long "config" <> O.metavar "CONFIGFILE" <> O.help "Configuration file"))
     <*> runOptparseGrammar configGrammar
 
+outputP :: O.Parser Output
+outputP =
+    OutputFile <$> O.strOption (O.long "output" <> O.short 'o' <> O.metavar "FILE" <> O.help "Output file") <|>
+    O.flag' OutputStdout (O.long "stdout" <> O.help "Use stdout output")
+
+versionP :: O.Parser (a -> a)
+versionP = O.infoOption haskellCIVerStr $ mconcat
+    [ O.long "version"
+    , O.short 'V'
+    , O.help "Print version information"
+    ]
+
+haskellCIVerStr :: String
+haskellCIVerStr = CURRENT_PACKAGE_VERSION
+
 cliParserInfo :: O.ParserInfo (Command, Options)
-cliParserInfo = O.info ((,) <$> cmdP <*> optionsP O.<**> O.helper) $ mconcat
+cliParserInfo = O.info ((,) <$> cmdP <*> optionsP O.<**> versionP O.<**> O.helper) $ mconcat
     [ O.fullDesc
     , O.header "haskell-ci - generate CI scripts for Haskell projects"
     ]
@@ -68,7 +91,7 @@
         [ 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" 
+        , O.command "dump-config" $ O.info (pure CommandDumpConfig) $ O.progDesc "Dump cabal.haskell-ci config with default values"
         ]) <|> travisP
 
     travisP = CommandTravis
diff --git a/src/HaskellCI/Compiler.hs b/src/HaskellCI/Compiler.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/Compiler.hs
@@ -0,0 +1,165 @@
+-- | This module encodes what we know about GHC, including existing/supported versions.
+module HaskellCI.Compiler (
+    -- * Compiler version
+    CompilerVersion (..),
+    maybeGHC,
+    isGHCJS,
+    previewGHC,
+    -- * Compiler version range
+    CompilerRange (..),
+    compilerWithinRange,
+    invertCompilerRange,
+    -- * Known versions
+    knownGhcVersions,
+    knownGhcjsVersions,
+    -- * Showing
+    dispGhcVersion,
+    dispGhcVersionShort,
+    dispCabalVersion,
+    -- * Cabal version
+    correspondingCabalVersion,
+    -- * Misc
+    ghcMajVer,
+    ) where
+
+import HaskellCI.Prelude
+
+import Data.Set             (Set)
+import Distribution.Version (Version, VersionRange, hasUpperBound, invertVersionRange, mkVersion, versionNumbers, withinRange)
+
+import qualified Data.Set            as S
+import qualified Distribution.Pretty as C
+
+-------------------------------------------------------------------------------
+-- CompilerVersion
+-------------------------------------------------------------------------------
+
+data CompilerVersion
+    = GHCHead
+    | GHC Version
+    | GHCJS Version
+  deriving (Eq, Ord, Show)
+
+maybeGHC :: a -> (Version -> a) -> CompilerVersion -> a
+maybeGHC _ f (GHC v) = f v
+maybeGHC x _ _       = x
+
+isGHCJS :: CompilerVersion -> Bool
+isGHCJS (GHCJS _) = True
+isGHCJS _         = False
+
+-------------------------------------------------------------------------------
+-- CompilerRange
+-------------------------------------------------------------------------------
+
+data CompilerRange
+    = Range VersionRange
+    | RangeGHC
+    | RangeGHCJS
+    | RangePoints (Set CompilerVersion)
+    | RangeInter CompilerRange CompilerRange
+    | RangeUnion CompilerRange CompilerRange
+  deriving (Show)
+
+instance Lattice CompilerRange where
+    (/\) = RangeInter
+    (\/) = RangeUnion
+
+instance BoundedJoinSemiLattice CompilerRange where
+    bottom = RangePoints S.empty
+
+instance BoundedMeetSemiLattice CompilerRange where
+    top = RangePoints allCompilerVersions
+
+compilerWithinRange :: CompilerVersion -> CompilerRange -> Bool
+compilerWithinRange v         (RangeInter a b) = compilerWithinRange v a /\ compilerWithinRange v b
+compilerWithinRange v         (RangeUnion a b) = compilerWithinRange v a \/ compilerWithinRange v b
+compilerWithinRange (GHC v)   (Range vr)       = withinRange v vr
+compilerWithinRange (GHCJS v) (Range vr)       = withinRange v vr
+compilerWithinRange GHCHead   (Range vr)       = not (hasUpperBound vr)
+compilerWithinRange (GHC _)   RangeGHC         = True
+compilerWithinRange GHCHead   RangeGHC         = True
+compilerWithinRange (GHCJS _) RangeGHC         = False
+compilerWithinRange (GHC _)   RangeGHCJS       = False
+compilerWithinRange GHCHead   RangeGHCJS       = False
+compilerWithinRange (GHCJS _) RangeGHCJS       = True
+compilerWithinRange v         (RangePoints vs) = S.member v vs
+
+invertCompilerRange :: CompilerRange -> CompilerRange
+invertCompilerRange (Range vr)       = Range (invertVersionRange vr)
+invertCompilerRange RangeGHC         = RangeGHCJS
+invertCompilerRange RangeGHCJS       = RangeGHC
+invertCompilerRange (RangeInter a b) = RangeUnion (invertCompilerRange a) (invertCompilerRange b)
+invertCompilerRange (RangeUnion a b) = RangeInter (invertCompilerRange a) (invertCompilerRange b)
+invertCompilerRange (RangePoints vs) = RangePoints (S.difference allCompilerVersions vs)
+
+-------------------------------------------------------------------------------
+-- Known versions
+-------------------------------------------------------------------------------
+
+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], [8,6,4], [8,6,5]
+    , [8,8,1]
+    ]
+
+knownGhcjsVersions :: [Version]
+knownGhcjsVersions = fmap mkVersion
+    [ [8,4]
+    ]
+
+allCompilerVersions :: Set CompilerVersion
+allCompilerVersions = S.insert GHCHead $ S.fromList $
+    [ GHC v | v <- knownGhcVersions ] ++
+    [ GHCJS v | v <- knownGhcjsVersions ]
+
+-------------------------------------------------------------------------------
+-- Combinators
+-------------------------------------------------------------------------------
+
+correspondingCabalVersion
+    :: Maybe Version    -- ^ Preferred Cabal Version
+    -> CompilerVersion  -- ^ GHC Version
+    -> Maybe Version
+correspondingCabalVersion Nothing   _         = Nothing
+correspondingCabalVersion (Just _)  GHCHead   = Nothing
+correspondingCabalVersion (Just _)  (GHCJS _) = Just (mkVersion [3,0])
+correspondingCabalVersion (Just cv) (GHC gv)
+    | gv >= mkVersion [8,8] = Just $ max (mkVersion [3,0]) cv
+    | otherwise             = Just $ max (mkVersion [2,4]) cv
+
+dispGhcVersion :: CompilerVersion -> String
+dispGhcVersion GHCHead   = "ghc-head"
+dispGhcVersion (GHC v)   = "ghc-" ++ C.prettyShow v
+dispGhcVersion (GHCJS v) = "ghcjs-" ++ C.prettyShow v
+
+dispGhcVersionShort :: CompilerVersion -> String
+dispGhcVersionShort GHCHead   = "ghc-head"
+dispGhcVersionShort (GHC v)   = C.prettyShow v
+dispGhcVersionShort (GHCJS v) = "ghcjs-" ++ C.prettyShow v
+
+dispCabalVersion :: Maybe Version -> String
+dispCabalVersion = maybe "head" C.prettyShow
+
+-- | Alphas, RCs and HEAD.
+previewGHC
+    :: VersionRange     -- ^ head.hackage range
+    -> CompilerVersion
+    -> Bool
+previewGHC _vr GHCHead   = True
+previewGHC  vr (GHC v)   = withinRange v vr || odd (snd (ghcMajVer v))
+previewGHC _vr (GHCJS _) = False
+
+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/Config.hs b/src/HaskellCI/Config.hs
--- a/src/HaskellCI/Config.hs
+++ b/src/HaskellCI/Config.hs
@@ -4,16 +4,12 @@
 {-# 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 HaskellCI.Prelude
 
+import Distribution.Simple.Utils       (fromUTF8BS)
+import Distribution.Types.Version      (Version)
+import Distribution.Types.VersionRange (VersionRange, anyVersion, noVersion)
+
 import qualified Data.ByteString                 as BS
 import qualified Data.Map                        as M
 import qualified Data.Set                        as S
@@ -21,44 +17,50 @@
 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.Fields             as C
+import qualified Distribution.Parsec             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 Distribution.Types.VersionRange 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
+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.Config.Ubuntu
+import HaskellCI.Newtypes
+import HaskellCI.OptionsGrammar
+import HaskellCI.ParsecUtils
+import HaskellCI.TestedWith
 
+defaultHeadHackage :: VersionRange
+defaultHeadHackage = C.orLaterVersion (C.mkVersion [8,9])
+
 -- TODO: split other blocks like DoctestConfig
 data Config = Config
     { cfgCabalInstallVersion :: Maybe Version
     , cfgJobs                :: Maybe Jobs
+    , cfgUbuntu              :: !Ubuntu
     , cfgTestedWith          :: !TestedWithJobs
     , cfgCopyFields          :: !CopyFields
     , cfgLocalGhcOptions     :: [String]
+    , cfgSubmodules          :: !Bool
     , cfgCache               :: !Bool
-    , cfgNoise               :: !Bool
     , cfgInstallDeps         :: !Bool
     , cfgInstalled           :: [Installed]
     , cfgTests               :: !VersionRange
+    , cfgRunTests            :: !VersionRange
     , cfgBenchmarks          :: !VersionRange
     , cfgHaddock             :: !VersionRange
     , cfgNoTestsNoBench      :: !VersionRange
     , cfgUnconstrainted      :: !VersionRange
+    , cfgHeadHackage         :: !VersionRange
+    , cfgGhcjsTests          :: !Bool
     , cfgCheck               :: !Bool
     , cfgOnlyBranches        :: [String]
     , cfgIrcChannels         :: [String]
@@ -71,26 +73,31 @@
     , cfgLastInSeries        :: !Bool
     , cfgOsx                 :: S.Set Version
     , cfgApt                 :: S.Set String
+    , cfgTravisPatches       :: [FilePath]
+    , cfgInsertVersion       :: !Bool
+    , cfgColor               :: !Bool
     , cfgDoctest             :: !DoctestConfig
     , cfgHLint               :: !HLintConfig
     , cfgConstraintSets      :: [ConstraintSet]
-    , cfgRawProject          :: [C.PrettyField]
+    , cfgRawProject          :: [C.PrettyField ()]
     }
-  deriving (Show, Generic)
+  deriving (Generic)
 
 defaultCabalInstallVersion :: Maybe Version
-defaultCabalInstallVersion = Just (C.mkVersion [2,4])
+defaultCabalInstallVersion = Just (C.mkVersion [3,0])
 
 emptyConfig :: Config
 emptyConfig = Config
     { cfgCabalInstallVersion = defaultCabalInstallVersion
     , cfgJobs            = Nothing
+    , cfgUbuntu          = Xenial
     , cfgTestedWith      = TestedWithUniform
     , cfgCopyFields      = CopyFieldsSome
     , cfgDoctest         = DoctestConfig
-        { cfgDoctestEnabled = False
-        , cfgDoctestOptions = []
-        , cfgDoctestVersion = defaultDoctestVersion
+        { cfgDoctestEnabled    = noVersion
+        , cfgDoctestOptions    = []
+        , cfgDoctestVersion    = defaultDoctestVersion
+        , cfgDoctestFilterPkgs = []
         }
     , cfgHLint = HLintConfig
         { cfgHLintEnabled = False
@@ -101,15 +108,18 @@
         }
     , cfgLocalGhcOptions = []
     , cfgConstraintSets  = []
+    , cfgSubmodules      = False
     , cfgCache           = True
-    , cfgNoise           = True
     , cfgInstalled       = []
     , cfgInstallDeps     = True
     , cfgTests           = anyVersion
+    , cfgRunTests        = anyVersion
     , cfgBenchmarks      = anyVersion
     , cfgHaddock         = anyVersion
     , cfgNoTestsNoBench  = anyVersion
     , cfgUnconstrainted  = anyVersion
+    , cfgHeadHackage     = defaultHeadHackage
+    , cfgGhcjsTests      = False
     , cfgCheck           = True
     , cfgOnlyBranches    = []
     , cfgIrcChannels     = []
@@ -122,6 +132,9 @@
     , cfgLastInSeries    = False
     , cfgOsx             = S.empty
     , cfgApt             = S.empty
+    , cfgTravisPatches   = []
+    , cfgInsertVersion   = True
+    , cfgColor           = True
     , cfgRawProject      = []
     }
 
@@ -137,22 +150,26 @@
         ^^^ metahelp "VERSION" "cabal-install version for all jobs"
     <*> C.optionalField       "jobs"                                                          #cfgJobs
         ^^^ metahelp "JOBS" "jobs (N:M - cabal:ghc)"
+    <*> C.optionalFieldDef    "distribution"                                                  #cfgUbuntu Xenial
+        ^^^ metahelp "DIST" "distribution version (xenial, bionic)"
     <*> 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     "submodules"                                                    #cfgSubmodules False
+        ^^^ help "Clone submodules, i.e. recursively"
     <*> 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"
+        ^^^ metahelp "RANGE" "Build tests with"
+    <*> rangeField            "run-tests"                                                     #cfgRunTests anyVersion
+        ^^^ metahelp "RANGE" "Run tests with (note: only built tests are run)"
     <*> rangeField           "benchmarks"                                                     #cfgBenchmarks anyVersion
         ^^^ metahelp "RANGE" "Build benchmarks"
     <*> rangeField           "haddock"                                                        #cfgHaddock anyVersion
@@ -161,6 +178,10 @@
         ^^^ metahelp "RANGE" "Build without tests and benchmarks"
     <*> rangeField            "unconstrained"                                                 #cfgUnconstrainted anyVersion
         ^^^ metahelp "RANGE" "Make unconstrained build"
+    <*> rangeField            "head-hackage"                                                  #cfgHeadHackage defaultHeadHackage
+        ^^^ metahelp "RANGE" "Use head.hackage repository. Also marks as allow-failures"
+    <*> C.booleanFieldDef "ghcjs-tests"                                                       #cfgGhcjsTests False
+        ^^^ help "Run tests with GHCJS (experimental, relies on cabal-plan finding test-suites)"
     <*> C.booleanFieldDef "cabal-check"                                                       #cfgCheck True
         ^^^ help "Disable cabal check run"
     <*> C.monoidalFieldAla    "branches"                  (C.alaList' C.FSep C.Token')        #cfgOnlyBranches
@@ -185,6 +206,12 @@
         ^^^ 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.monoidalFieldAla    "travis-patches"            (C.alaList' C.NoCommaFSep C.Token') #cfgTravisPatches
+        ^^^ metahelp "PATCH" ".patch files to apply to the generated Travis YAML file"
+    <*> C.booleanFieldDef "insert-version"                                                    #cfgInsertVersion True
+        ^^^ help "Don't insert the haskell-ci version into the generated Travis YAML file"
+    <*> C.booleanFieldDef     "color"                                                         #cfgColor True
+        ^^^ help "Disable coloring cabal output"
     <*> C.blurFieldGrammar #cfgDoctest doctestConfigGrammar
     <*> C.blurFieldGrammar #cfgHLint   hlintConfigGrammar
     <*> pure [] -- constraint sets
@@ -214,7 +241,7 @@
             return $ over #cfgConstraintSets (cs :)
         | name == "raw-project" = do
             let fs = C.fromParsecFields cfields
-            return $ over #cfgRawProject (++ fs)
+            return $ over #cfgRawProject (++ map void fs)
         | otherwise = do
             C.parseWarning pos C.PWTUnknownSection $ "Unknown section " ++ fromUTF8BS name
             return id
@@ -224,10 +251,7 @@
 -------------------------------------------------------------------------------
 
 newtype Env = Env (M.Map Version String)
-
-instance C.Newtype Env (M.Map Version String) where
-    pack = coerce
-    unpack = coerce
+  deriving anyclass (C.Newtype (M.Map Version String))
 
 instance C.Parsec Env where
     parsec = Env . M.fromList <$> C.parsecLeadingCommaList p where
diff --git a/src/HaskellCI/Config/ConstraintSet.hs b/src/HaskellCI/Config/ConstraintSet.hs
--- a/src/HaskellCI/Config/ConstraintSet.hs
+++ b/src/HaskellCI/Config/ConstraintSet.hs
@@ -2,25 +2,26 @@
 {-# LANGUAGE OverloadedStrings #-}
 module HaskellCI.Config.ConstraintSet where
 
-import           Data.Generics.Labels         ()
-import           Distribution.Version
-import           GHC.Generics                 (Generic)
+import HaskellCI.Prelude
 
 import qualified Distribution.FieldGrammar    as C
 import qualified Distribution.Parsec.Newtypes as C
 
-import           HaskellCI.Newtypes
+import HaskellCI.Newtypes
 
 data ConstraintSet = ConstraintSet
     { csName        :: String
     , csGhcVersions :: VersionRange
     , csConstraints :: [String] -- we parse these simply as strings
+    , csTests       :: Bool
     , csRunTests    :: Bool
+    , csBenchmarks  :: Bool
+    , csHaddock     :: Bool
     }
   deriving (Show, Generic)
 
 emptyConstraintSet :: String -> ConstraintSet
-emptyConstraintSet n = ConstraintSet n anyVersion [] False
+emptyConstraintSet n = ConstraintSet n anyVersion [] False False False False
 
 -------------------------------------------------------------------------------
 -- Grammar
@@ -32,4 +33,7 @@
 constraintSetGrammar name = ConstraintSet name
     <$> C.optionalFieldDef "ghc"                                           #csGhcVersions anyVersion
     <*> C.monoidalFieldAla "constraints" (C.alaList' C.CommaVCat NoCommas) #csConstraints
+    <*> C.booleanFieldDef  "tests"                                         #csTests False
     <*> C.booleanFieldDef  "run-tests"                                     #csRunTests False
+    <*> C.booleanFieldDef  "benchmarks"                                    #csBenchmarks False
+    <*> C.booleanFieldDef  "haddock"                                       #csHaddock False
diff --git a/src/HaskellCI/Config/CopyFields.hs b/src/HaskellCI/Config/CopyFields.hs
--- a/src/HaskellCI/Config/CopyFields.hs
+++ b/src/HaskellCI/Config/CopyFields.hs
@@ -2,8 +2,10 @@
 {-# LANGUAGE MultiParamTypeClasses #-}
 module HaskellCI.Config.CopyFields where
 
+import HaskellCI.Prelude
+
 import qualified Distribution.Compat.CharParsing as C
-import qualified Distribution.Parsec.Class       as C
+import qualified Distribution.Parsec             as C
 import qualified Distribution.Pretty             as C
 import qualified Text.PrettyPrint                as PP
 
diff --git a/src/HaskellCI/Config/Doctest.hs b/src/HaskellCI/Config/Doctest.hs
--- a/src/HaskellCI/Config/Doctest.hs
+++ b/src/HaskellCI/Config/Doctest.hs
@@ -2,19 +2,21 @@
 {-# LANGUAGE OverloadedStrings #-}
 module HaskellCI.Config.Doctest where
 
-import           Data.Generics.Labels         ()
-import           Distribution.Version
-import           GHC.Generics                 (Generic)
+import HaskellCI.Prelude
 
-import qualified Distribution.FieldGrammar    as C
-import qualified Distribution.Parsec.Newtypes as C
+import Distribution.Version (mkVersion, noVersion, withinVersion)
 
+import qualified Distribution.FieldGrammar      as C
+import qualified Distribution.Parsec.Newtypes   as C
+import qualified Distribution.Types.PackageName as C
+
 import HaskellCI.OptionsGrammar
 
 data DoctestConfig = DoctestConfig
-    { cfgDoctestEnabled :: !Bool
-    , cfgDoctestOptions :: [String]
-    , cfgDoctestVersion :: !VersionRange
+    { cfgDoctestEnabled    :: !VersionRange
+    , cfgDoctestOptions    :: [String]
+    , cfgDoctestVersion    :: !VersionRange
+    , cfgDoctestFilterPkgs :: ![C.PackageName]
     }
   deriving (Show, Generic)
 
@@ -29,9 +31,11 @@
     :: (OptionsGrammar g, Applicative (g DoctestConfig))
     => g DoctestConfig DoctestConfig
 doctestConfigGrammar = DoctestConfig
-    <$> C.booleanFieldDef  "doctest"                                             #cfgDoctestEnabled False
+    <$> rangeField         "doctest"                                             #cfgDoctestEnabled noVersion
         ^^^ 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"
+    <*> C.monoidalFieldAla "doctest-filter-packages" (C.alaList C.NoCommaFSep)   #cfgDoctestFilterPkgs
+        ^^^ metahelp "PKGS" "Filter packages from .ghc.environment file"
diff --git a/src/HaskellCI/Config/Dump.hs b/src/HaskellCI/Config/Dump.hs
--- a/src/HaskellCI/Config/Dump.hs
+++ b/src/HaskellCI/Config/Dump.hs
@@ -1,16 +1,14 @@
 module HaskellCI.Config.Dump where
 
-import           Prelude                     ()
-import           Prelude.Compat
+import HaskellCI.Prelude
 
-import           Data.Coerce                 (coerce)
-import           Distribution.Simple.Utils   (fromUTF8BS)
+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
+import HaskellCI.OptionsGrammar
 
 -- TODO: with Cabal-2.6 this can be prettier, using Pretty.Field
 newtype DumpGrammar s a = DG { runDG :: [String] }
@@ -45,10 +43,21 @@
         , ""
         ]
 
+    freeTextField fn _ = DG
+        [ fromUTF8BS fn ++ ":"
+        , ""
+        ]
+
+    freeTextFieldDef fn _ = DG
+        [ fromUTF8BS fn ++ ":"
+        , ""
+        ]
+
     prefixedFields _ _   = pure []
     knownField _         = pure ()
     deprecatedSince _  _ = id
     availableSince _ _   = id
+    removedIn _ _        = id
     hiddenField          = id
 
 instance OptionsGrammar DumpGrammar where
diff --git a/src/HaskellCI/Config/Folds.hs b/src/HaskellCI/Config/Folds.hs
--- a/src/HaskellCI/Config/Folds.hs
+++ b/src/HaskellCI/Config/Folds.hs
@@ -2,15 +2,13 @@
 {-# LANGUAGE MultiParamTypeClasses #-}
 module HaskellCI.Config.Folds where
 
-import           Data.Char                       (isUpper, toLower)
-import           Data.Coerce                     (coerce)
-import           Data.List                       (intercalate)
+import HaskellCI.Prelude
 
 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.Parsec             as C
 import qualified Distribution.Pretty             as C
 import qualified Text.PrettyPrint                as PP
 
@@ -52,10 +50,7 @@
 -------------------------------------------------------------------------------
 
 newtype Folds = Folds { getFolds :: S.Set Fold }
-
-instance C.Newtype Folds (S.Set Fold) where
-    pack = coerce
-    unpack = coerce
+  deriving anyclass (C.Newtype (S.Set Fold))
 
 instance C.Parsec Folds where
     parsec = fmap (Folds . S.unions) $ manySpaces $ do
diff --git a/src/HaskellCI/Config/HLint.hs b/src/HaskellCI/Config/HLint.hs
--- a/src/HaskellCI/Config/HLint.hs
+++ b/src/HaskellCI/Config/HLint.hs
@@ -2,14 +2,13 @@
 {-# LANGUAGE OverloadedStrings #-}
 module HaskellCI.Config.HLint where
 
-import           Control.Applicative             ((<|>))
-import           Data.Generics.Labels            ()
-import           Distribution.Version
-import           GHC.Generics                    (Generic)
+import HaskellCI.Prelude
 
+import Distribution.Version (withinVersion)
+
 import qualified Distribution.Compat.CharParsing as C
 import qualified Distribution.FieldGrammar       as C
-import qualified Distribution.Parsec.Class       as C
+import qualified Distribution.Parsec             as C
 import qualified Distribution.Parsec.Newtypes    as C
 import qualified Distribution.Pretty             as C
 import qualified Text.PrettyPrint                as PP
diff --git a/src/HaskellCI/Config/Installed.hs b/src/HaskellCI/Config/Installed.hs
--- a/src/HaskellCI/Config/Installed.hs
+++ b/src/HaskellCI/Config/Installed.hs
@@ -1,14 +1,13 @@
 module HaskellCI.Config.Installed where
 
-import Control.Applicative ((<|>))
-import Data.List (foldl')
+import HaskellCI.Prelude
 
-import qualified Distribution.Types.PackageName as C
+import qualified Data.Set                        as S
 import qualified Distribution.Compat.CharParsing as C
-import qualified Distribution.Parsec.Class       as C
+import qualified Distribution.Parsec             as C
 import qualified Distribution.Pretty             as C
+import qualified Distribution.Types.PackageName  as C
 import qualified Text.PrettyPrint                as PP
-import qualified Data.Set as S
 
 -------------------------------------------------------------------------------
 -- Single action
diff --git a/src/HaskellCI/Config/Jobs.hs b/src/HaskellCI/Config/Jobs.hs
--- a/src/HaskellCI/Config/Jobs.hs
+++ b/src/HaskellCI/Config/Jobs.hs
@@ -1,11 +1,11 @@
 module HaskellCI.Config.Jobs where
 
-import Control.Applicative ((<|>))
+import HaskellCI.Prelude
 
+import qualified Distribution.Compat.CharParsing as C
+import qualified Distribution.Parsec             as C
 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
 --
diff --git a/src/HaskellCI/Config/Ubuntu.hs b/src/HaskellCI/Config/Ubuntu.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/Config/Ubuntu.hs
@@ -0,0 +1,25 @@
+module HaskellCI.Config.Ubuntu where
+
+import HaskellCI.Prelude
+
+import qualified Distribution.Parsec as C
+import qualified Distribution.Pretty as C
+import qualified Text.PrettyPrint    as PP
+
+data Ubuntu = Xenial | Bionic
+  deriving (Eq, Ord, Show)
+
+instance C.Parsec Ubuntu where
+    parsec = do
+        t <- C.parsecToken
+        case t of
+            "xenial" -> return Xenial
+            "bionic" -> return Bionic
+            _        -> fail $ "Unknown ubuntu release " ++ t
+
+instance C.Pretty Ubuntu where
+    pretty = PP.text . showUbuntu
+
+showUbuntu :: Ubuntu -> String
+showUbuntu Xenial = "xenial"
+showUbuntu Bionic = "bionic"
diff --git a/src/HaskellCI/Diagnostics.hs b/src/HaskellCI/Diagnostics.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/Diagnostics.hs
@@ -0,0 +1,45 @@
+module HaskellCI.Diagnostics where
+
+import HaskellCI.Prelude
+
+import Control.Monad.Trans.Maybe (MaybeT (..))
+import Control.Monad.Writer      (WriterT, runWriterT, tell)
+import System.Exit               (exitFailure)
+import System.IO                 (hPutStrLn, stderr)
+
+class Monad m => MonadDiagnostics m where
+    putStrLnErr  :: String -> m a
+    putStrLnErrs :: NonEmpty String -> m a
+    putStrLnWarn :: String -> m ()
+    putStrLnInfo :: String -> m ()
+
+instance MonadDiagnostics IO where
+    putStrLnErr err = do
+        hPutStrLn stderr $ "*ERROR* " ++ err
+        exitFailure
+
+    putStrLnErrs errs = do
+        for_ errs $ \err -> hPutStrLn stderr $ "*ERROR* " ++ err
+        exitFailure
+
+    putStrLnWarn = hPutStrLn stderr . ("*WARNING* " ++)
+    putStrLnInfo = hPutStrLn stderr . ("*INFO* " ++)
+
+newtype DiagnosticsT m a = Diagnostics { unDiagnostics :: MaybeT (WriterT [String] m) a }
+  deriving stock (Functor)
+  deriving newtype (Applicative, Monad, MonadIO, MonadCatch, MonadMask, MonadThrow)
+
+runDiagnosticsT :: DiagnosticsT m a -> m (Maybe a, [String])
+runDiagnosticsT (Diagnostics m) = runWriterT (runMaybeT m)
+
+instance Monad m => MonadDiagnostics (DiagnosticsT m) where
+    putStrLnWarn err = Diagnostics $ tell ["*WARNING* " ++ err]
+    putStrLnInfo err = Diagnostics $ tell ["*INFO* " ++ err]
+
+    putStrLnErr err = Diagnostics $ do
+        tell ["*ERROR* " ++ err]
+        MaybeT $ return Nothing
+
+    putStrLnErrs errs = Diagnostics $ do
+        tell $ map ("*ERROR* " ++) (toList errs)
+        MaybeT $ return Nothing
diff --git a/src/HaskellCI/Extras.hs b/src/HaskellCI/Extras.hs
deleted file mode 100644
--- a/src/HaskellCI/Extras.hs
+++ /dev/null
@@ -1,34 +0,0 @@
--- | 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
deleted file mode 100644
--- a/src/HaskellCI/GHC.hs
+++ /dev/null
@@ -1,36 +0,0 @@
--- | 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], [8,6,4]
-    ]
-
-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
--- a/src/HaskellCI/Glob.hs
+++ b/src/HaskellCI/Glob.hs
@@ -1,13 +1,13 @@
 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 ((</>))
+import HaskellCI.Prelude
 
+import Control.Applicative       as App ((<$>))
+import Control.Monad             (filterM, liftM2, void)
+import System.Directory          (doesDirectoryExist, getDirectoryContents)
+import System.FilePath.Posix     ((</>))
+import Text.ParserCombinators.ReadP
+
 -------------------------------------------------------------------------------
 -- Glob
 -------------------------------------------------------------------------------
@@ -58,7 +58,7 @@
    | FilePathHomeDir
   deriving (Eq, Show)
 
-parseFilePathGlobRel :: ReadP r FilePathGlobRel
+parseFilePathGlobRel :: ReadP FilePathGlobRel
 parseFilePathGlobRel =
       parseGlob >>= \globpieces ->
           asDir globpieces
@@ -79,7 +79,7 @@
                    (c:_) | isGlobEscapedChar c -> pfail
                    _                           -> return ())
 
-parseGlob :: ReadP r Glob
+parseGlob :: ReadP Glob
 parseGlob = many1 parsePiece
   where
     parsePiece = literal +++ wildcard +++ union'
@@ -97,10 +97,10 @@
                                 && c /= '/' && c /= '\\')
     escape  = char '\\' >> satisfy isGlobEscapedChar
 
-    litchars1 :: ReadP r [Char]
+    litchars1 :: ReadP [Char]
     litchars1 = liftM2 (:) litchar litchars
 
-    litchars :: ReadP r [Char]
+    litchars :: ReadP [Char]
     litchars = litchars1 <++ return []
 
 isGlobEscapedChar :: Char -> Bool
diff --git a/src/HaskellCI/Jobs.hs b/src/HaskellCI/Jobs.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/Jobs.hs
@@ -0,0 +1,67 @@
+{-# LANGUAGE RecordWildCards #-}
+-- | Which jobs to generate. Also helper for diagnostics output.
+module HaskellCI.Jobs where
+
+import HaskellCI.Prelude
+
+import qualified Data.Set            as S
+import qualified Distribution.Pretty as C
+
+import HaskellCI.Compiler
+import HaskellCI.Config
+import HaskellCI.Diagnostics
+import HaskellCI.Package
+import HaskellCI.TestedWith
+
+data JobVersions = JobVersions
+    { versions           :: Set CompilerVersion  -- ^ all jobs
+    , osxVersions        :: Set Version          -- ^ osx jobs: GHC only
+    , omittedOsxVersions :: Set Version
+    }
+
+describeJobs :: MonadDiagnostics m => TestedWithJobs -> JobVersions -> [Package] -> m ()
+describeJobs twj JobVersions {..} pkgs = do
+    putStrLnInfo $ "Generating Travis-CI config for testing for GHC versions: " ++ ghcVersions
+    case twj of
+        TestedWithUniform -> pure ()
+        TestedWithAny     -> for_ pkgs $ \pkg -> do
+            -- this omits HEAD version.
+            let vr = pkgJobs pkg
+            let vs = showVersions vr
+            putStrLnInfo $ pkgName pkg ++ " " ++ ": " ++ vs
+
+    unless (null osxVersions) $  do
+        putStrLnInfo $ "Also OSX jobs for: " ++ ghcOsxVersions
+        unless (S.null omittedOsxVersions) $
+            putStrLnWarn $ "Not all GHC versions specified with --osx are generated: " ++ ghcOmittedOsxVersions
+  where
+    showVersions :: Set CompilerVersion -> String
+    showVersions = unwords . map dispGhcVersionShort . S.toList
+
+    showVersionsV :: Set Version -> String
+    showVersionsV = unwords . map C.prettyShow . S.toList
+
+    ghcVersions :: String
+    ghcVersions = showVersions versions
+
+    ghcOsxVersions :: String
+    ghcOsxVersions = showVersionsV osxVersions
+
+    ghcOmittedOsxVersions :: String
+    ghcOmittedOsxVersions = showVersionsV omittedOsxVersions
+
+makeJobVersions :: Config -> Set CompilerVersion -> JobVersions
+makeJobVersions Config {..} versions' = JobVersions {..} where
+    -- All jobs
+    versions :: Set CompilerVersion
+    versions
+        | cfgGhcHead = S.insert GHCHead versions'
+        | otherwise  = versions'
+
+    osxVersions' :: Set Version
+    osxVersions' = cfgOsx
+
+    osxVersions, omittedOsxVersions :: Set Version
+    (osxVersions, omittedOsxVersions) = S.partition (\x -> GHC x `S.member` versions') osxVersions'
+
+
diff --git a/src/HaskellCI/List.hs b/src/HaskellCI/List.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/List.hs
@@ -0,0 +1,34 @@
+{-# LANGUAGE RankNTypes #-}
+module HaskellCI.List (
+    ListBuilder,
+    buildList,
+    item,
+    ) where
+
+import HaskellCI.Prelude
+
+import Control.Monad (ap)
+
+newtype ListBuilder x a = LB { unLB :: forall r. (([x] -> [x]) -> a -> r) -> r }
+
+instance Functor (ListBuilder x) where
+    fmap f (LB k) = LB $ k $ \endo a k' -> k' endo (f a)
+
+instance Applicative (ListBuilder x) where
+    pure x = LB $ \f -> f id x
+    (<*>)  = ap
+
+instance Monad (ListBuilder x) where
+    return = pure
+
+    m >>= k =
+        LB $ \r ->
+        unLB m $ \endo1 a ->
+        unLB (k a) $ \endo2 b ->
+        r (endo1 . endo2) b
+
+buildList :: ListBuilder x () -> [x]
+buildList (LB f) = f $ \endo _ -> endo []
+
+item :: x -> ListBuilder x ()
+item x = LB $ \f -> f (x :) ()
diff --git a/src/HaskellCI/MakeTravisOutput.hs b/src/HaskellCI/MakeTravisOutput.hs
deleted file mode 100644
--- a/src/HaskellCI/MakeTravisOutput.hs
+++ /dev/null
@@ -1,212 +0,0 @@
-{-# 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/MonadErr.hs b/src/HaskellCI/MonadErr.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/MonadErr.hs
@@ -0,0 +1,12 @@
+{-# LANGUAGE FlexibleInstances      #-}
+{-# LANGUAGE FunctionalDependencies #-}
+module HaskellCI.MonadErr where
+
+import HaskellCI.Prelude
+
+-- | 'MonadErr' is 'MonadError' without @catch@
+class Monad m => MonadErr e m | m -> e where
+    throwErr :: e -> m a
+
+instance MonadErr e (Either e) where
+    throwErr = Left
diff --git a/src/HaskellCI/Newtypes.hs b/src/HaskellCI/Newtypes.hs
--- a/src/HaskellCI/Newtypes.hs
+++ b/src/HaskellCI/Newtypes.hs
@@ -1,35 +1,25 @@
 {-# 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 HaskellCI.Prelude
 
 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             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
+  deriving anyclass (C.Newtype String)
 
 -- | 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
@@ -70,10 +60,7 @@
 -------------------------------------------------------------------------------
 
 newtype NoCommas = NoCommas String
-
-instance C.Newtype NoCommas [Char] where
-    pack = coerce
-    unpack = coerce
+  deriving anyclass (C.Newtype String)
 
 instance C.Parsec NoCommas where
     parsec = NoCommas <$> liftA2 (:) (C.satisfy (not . isSpace)) (C.munch (/= ','))
@@ -86,10 +73,7 @@
 -------------------------------------------------------------------------------
 
 newtype HeadVersion = HeadVersion { getHeadVersion :: Maybe C.Version }
-
-instance C.Newtype HeadVersion (Maybe C.Version) where
-    pack = coerce
-    unpack = coerce
+  deriving anyclass (C.Newtype (Maybe C.Version))
 
 instance C.Parsec HeadVersion where
     parsec = HeadVersion Nothing <$ C.string "head" <|>
@@ -104,10 +88,7 @@
 -------------------------------------------------------------------------------
 
 newtype Int' = Int' Int
-
-instance C.Newtype Int' Int where
-    pack = coerce
-    unpack = coerce
+  deriving anyclass (C.Newtype Int)
 
 instance C.Parsec Int' where
     parsec = Int' <$> C.integral
@@ -120,10 +101,7 @@
 -------------------------------------------------------------------------------
 
 newtype Range = Range C.VersionRange
-
-instance C.Newtype Range C.VersionRange where
-    pack = coerce
-    unpack = coerce
+  deriving anyclass (C.Newtype C.VersionRange)
 
 instance C.Parsec Range where
     parsec = fmap Range $ C.parsec <|> fromBool <$> C.parsec where
@@ -141,6 +119,7 @@
 -------------------------------------------------------------------------------
 
 newtype AlaSet sep b a = AlaSet { getAlaSet :: S.Set a }
+  deriving anyclass (C.Newtype (S.Set a))
 
 alaSet :: sep -> S.Set a -> AlaSet sep (Identity a) a
 alaSet _ = AlaSet
@@ -149,30 +128,12 @@
 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
--------------------------------------------------------------------------------
+instance (C.Newtype a b, Ord a, C.Sep sep, C.Parsec b) => C.Parsec (AlaSet sep b a) where
+    parsec   = C.pack . S.fromList . map (C.unpack :: b -> a) <$> C.parseSep (hack (Proxy :: Proxy sep)) C.parsec
 
-class    Sep sep  where
-    prettySep :: Proxy sep -> [PP.Doc] -> PP.Doc
-    parseSep :: C.CabalParsing m => Proxy sep -> m a -> m [a]
+instance (C.Newtype a b, C.Sep sep, C.Pretty b) => C.Pretty (AlaSet sep b a) where
+    pretty = C.prettySep (hack (Proxy :: Proxy sep)) . map (C.pretty . (C.pack :: a -> b)) . S.toList . C.unpack
 
-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)
+-- Someone (= me) forgot to export Distribution.Parsec.Newtypes.P
+hack :: Proxy a -> proxy a
+hack _ = undefined
diff --git a/src/HaskellCI/Optimization.hs b/src/HaskellCI/Optimization.hs
--- a/src/HaskellCI/Optimization.hs
+++ b/src/HaskellCI/Optimization.hs
@@ -1,9 +1,9 @@
 module HaskellCI.Optimization where
 
-import Control.Applicative ((<|>))
+import HaskellCI.Prelude
 
 import qualified Distribution.Compat.CharParsing as C
-import qualified Distribution.Parsec.Class       as C
+import qualified Distribution.Parsec             as C
 import qualified Distribution.Pretty             as C
 import qualified Text.PrettyPrint                as PP
 
@@ -20,7 +20,7 @@
 
         ite t _ True  = t
         ite _ f False = f
-    
+
 instance C.Pretty Optimization where
     pretty OptimizationOn        = C.pretty True
     pretty OptimizationOff       = C.pretty False
diff --git a/src/HaskellCI/OptionsGrammar.hs b/src/HaskellCI/OptionsGrammar.hs
--- a/src/HaskellCI/OptionsGrammar.hs
+++ b/src/HaskellCI/OptionsGrammar.hs
@@ -3,12 +3,14 @@
     (C.^^^),
     )  where
 
+import HaskellCI.Prelude
+
 import qualified Distribution.Compat.Lens        as C
 import qualified Distribution.FieldGrammar       as C
-import qualified Distribution.Parsec.Field       as C
+import qualified Distribution.Fields             as C
 import qualified Distribution.Types.VersionRange as C
 
-import           HaskellCI.Newtypes
+import HaskellCI.Newtypes
 
 class C.FieldGrammar p => OptionsGrammar p where
     metahelp :: String -> String -> p s a -> p s a
diff --git a/src/HaskellCI/OptparseGrammar.hs b/src/HaskellCI/OptparseGrammar.hs
--- a/src/HaskellCI/OptparseGrammar.hs
+++ b/src/HaskellCI/OptparseGrammar.hs
@@ -5,24 +5,22 @@
     runOptparseGrammar,
     ) where
 
-import           Prelude                     ()
-import           Prelude.Compat
+import HaskellCI.Prelude
 
-import           Control.Applicative         (many)
-import           Data.Foldable               (asum)
-import           Distribution.Simple.Utils   (fromUTF8BS)
+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.Fields         as C
+import qualified Distribution.Parsec         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
+import HaskellCI.OptionsGrammar
 
 data SomeParser s where
     SP :: (Maybe String -> Maybe String -> O.Parser (s -> s)) -> SomeParser s
@@ -73,7 +71,14 @@
     knownField _         = pure ()
     deprecatedSince _  _ = id
     availableSince _ _   = id
+    removedIn _ _        = id
     hiddenField          = id
+
+    freeTextField fn l = OG
+        [ SP $ \m h -> setOptionalOG l $ O.strOption $ optionMods fn m h ]
+
+    freeTextFieldDef fn l = OG
+        [ SP $ \m h -> setOG l $ O.strOption $ optionMods fn m h ]
 
 instance OptionsGrammar OptparseGrammar where
     help h (OG ps) = OG
diff --git a/src/HaskellCI/Package.hs b/src/HaskellCI/Package.hs
--- a/src/HaskellCI/Package.hs
+++ b/src/HaskellCI/Package.hs
@@ -1,12 +1,15 @@
 module HaskellCI.Package where
 
+import HaskellCI.Prelude
+
 import qualified Distribution.Types.GenericPackageDescription as C
-import qualified Distribution.Types.VersionRange              as C
 
+import HaskellCI.Compiler
+
 data Package = Pkg
     { pkgName :: String
-    , pkgJobs :: C.VersionRange
+    , pkgJobs :: Set CompilerVersion
     , pkgDir  :: FilePath
     , pkgGpd  :: C.GenericPackageDescription
     }
-  deriving (Eq, Show)
+  deriving (Eq, Show, Generic)
diff --git a/src/HaskellCI/ParsecError.hs b/src/HaskellCI/ParsecError.hs
--- a/src/HaskellCI/ParsecError.hs
+++ b/src/HaskellCI/ParsecError.hs
@@ -2,16 +2,15 @@
     renderParseError,
     ) where
 
-import           Prelude                    ()
-import           Prelude.Compat
+import Prelude ()
+import Prelude.Compat
 
-import           Distribution.Parsec.Common (PError (..), PWarning (..),
-                                             Position (..), showPos, zeroPos)
-import           Distribution.Simple.Utils  (fromUTF8BS)
-import           System.FilePath            (normalise)
+import Distribution.Parsec       (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
+import qualified Data.ByteString       as BS
+import qualified Data.ByteString.Char8 as BS8
 
 -- | Render parse error highlighting the part of the input file.
 renderParseError
diff --git a/src/HaskellCI/ParsecUtils.hs b/src/HaskellCI/ParsecUtils.hs
--- a/src/HaskellCI/ParsecUtils.hs
+++ b/src/HaskellCI/ParsecUtils.hs
@@ -1,18 +1,18 @@
 module HaskellCI.ParsecUtils where
 
-import           Control.Monad                   (unless)
-import           System.Directory                (doesFileExist)
-import           System.Exit                     (exitFailure)
-import           System.IO                       (hPutStr, stderr)
+import HaskellCI.Prelude
 
-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 System.Directory (doesFileExist)
+import System.Exit      (exitFailure)
+import System.IO        (hPutStr, stderr)
 
-import           HaskellCI.ParsecError
+import qualified Data.ByteString                as BS
+import qualified Distribution.Fields            as C
+import qualified Distribution.Fields.LexerMonad as C (toPWarnings)
+import qualified Distribution.Parsec            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
diff --git a/src/HaskellCI/Prelude.hs b/src/HaskellCI/Prelude.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/Prelude.hs
@@ -0,0 +1,88 @@
+{-# OPTIONS_GHC -Wno-orphans #-}
+module HaskellCI.Prelude (
+    module Prelude.Compat,
+    module X,
+    module HaskellCI.Prelude,
+    ) where
+
+import Prelude.Compat hiding (head, tail)
+
+import Algebra.Lattice        as X (BoundedJoinSemiLattice (..), BoundedLattice, BoundedMeetSemiLattice (..), Lattice (..))
+import Control.Applicative    as X (liftA2, (<|>))
+import Control.Exception      as X (Exception (..))
+import Control.Monad          as X (ap, unless, void, when)
+import Control.Monad.Catch    as X (MonadCatch, MonadMask, MonadThrow)
+import Control.Monad.IO.Class as X (MonadIO (..))
+import Data.Bifoldable        as X (Bifoldable (..))
+import Data.Bifunctor         as X (Bifunctor (..))
+import Data.Bitraversable     as X (Bitraversable (..), bifoldMapDefault, bimapDefault)
+import Data.ByteString        as X (ByteString)
+import Data.Char              as X (isSpace, isUpper, toLower)
+import Data.Coerce            as X (coerce)
+import Data.Foldable          as X (for_, toList, traverse_)
+import Data.Function          as X (on)
+import Data.Functor.Identity  as X (Identity (..))
+import Data.List              as X (foldl', intercalate, isPrefixOf, nub, stripPrefix, tails)
+import Data.List.NonEmpty     as X (NonEmpty (..), groupBy)
+import Data.Maybe             as X (fromMaybe, isJust, isNothing, mapMaybe)
+import Data.Proxy             as X (Proxy (..))
+import Data.Set               as X (Set)
+import Data.String            as X (IsString (fromString))
+import Data.Void              as X (Void)
+import GHC.Generics           as X (Generic)
+import Lens.Micro             as X (mapped, over, (&), (.~), (<&>), (^.), (^..))
+import Text.Read              as X (readMaybe)
+
+import Distribution.Version as X (Version, VersionRange, anyVersion, mkVersion, noVersion)
+
+import qualified Distribution.Version as C
+
+import Data.Generics.Labels ()
+
+-------------------------------------------------------------------------------
+-- Extras
+-------------------------------------------------------------------------------
+
+head :: NonEmpty a -> a
+head (x :| _) = x
+
+-- $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
+
+-- | Whether two ranges are equivalent.
+equivVersionRanges :: C.VersionRange -> C.VersionRange -> Bool
+equivVersionRanges = on (==) C.asVersionIntervals
+
+-------------------------------------------------------------------------------
+-- Orphans
+-------------------------------------------------------------------------------
+
+instance Lattice VersionRange where
+    (/\) = C.intersectVersionRanges
+    (\/) = C.unionVersionRanges
diff --git a/src/HaskellCI/Project.hs b/src/HaskellCI/Project.hs
--- a/src/HaskellCI/Project.hs
+++ b/src/HaskellCI/Project.hs
@@ -7,50 +7,54 @@
     parseProjectFile,
     ) where
 
-import           Data.ByteString                              (ByteString)
-import           Data.Generics.Labels                         ()
-import           GHC.Generics                                 (Generic)
-import           Lens.Micro                                   (over)
+import HaskellCI.Prelude
 
 import qualified Data.Map.Strict                              as M
 import qualified Distribution.CabalSpecVersion                as C
 import qualified Distribution.FieldGrammar                    as C
+import qualified Distribution.Fields                          as C
 import qualified Distribution.PackageDescription.FieldGrammar as C
-import qualified Distribution.Parsec.Common                   as C
+import qualified Distribution.Parsec                          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
+import HaskellCI.Newtypes
+import HaskellCI.Optimization
+import HaskellCI.ParsecError
 
 -- $setup
 -- >>> :seti -XOverloadedStrings
 
-data Project a = Project
+data Project b a = Project
     { prjPackages     :: [a]
+    , prjOptPackages  :: [b]
     , prjConstraints  :: [String]
     , prjAllowNewer   :: [String]
     , prjReorderGoals :: Bool
     , prjMaxBackjumps :: Maybe Int
     , prjOptimization :: Optimization
     , prjSourceRepos  :: [C.SourceRepo]
-    , prjOrigFields   :: [C.PrettyField]
+    , prjOrigFields   :: [C.PrettyField ()]
     }
-  deriving (Show, Functor, Foldable, Traversable, Generic)
+  deriving (Functor, Foldable, Traversable, Generic)
 
-emptyProject :: Project [a]
-emptyProject = Project [] [] [] False Nothing OptimizationOn [] []
+instance Bifunctor Project where bimap = bimapDefault
+instance Bifoldable Project where bifoldMap = bifoldMapDefault
 
+instance Bitraversable Project where
+    bitraverse f g prj = (\b a -> prj { prjPackages = a, prjOptPackages = b })
+        <$> traverse f (prjOptPackages prj)
+        <*> traverse g (prjPackages prj)
+
+emptyProject :: Project b 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 :: FilePath -> ByteString -> Either String (Project String String)
 parseProjectFile fp bs = do
     fields0 <- either (Left . show) Right $ C.readFields bs
     let (fields1, sections) = C.partitionFields fields0
@@ -62,11 +66,11 @@
     knownFields = C.fieldGrammarKnownFieldList $ grammar []
 
     parse origFields fields sections = do
-        let prettyOrigFields = C.fromParsecFields $ filter notPackages origFields
+        let prettyOrigFields = map void $ 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.Section C.Position -> C.ParseResult (Project String String -> Project String 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")
@@ -78,14 +82,14 @@
 notPackages (C.Field (C.Name _ "packages") _) = False
 notPackages _                                 = True
 
-
-grammar :: [C.PrettyField] -> C.ParsecFieldGrammar (Project String) (Project String)
+grammar :: [C.PrettyField ()] -> C.ParsecFieldGrammar (Project String String) (Project String 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
+    <$> C.monoidalFieldAla "packages"          (C.alaList' C.FSep PackageLocation) #prjPackages
+    <*> C.monoidalFieldAla "optional-packages" (C.alaList' C.FSep PackageLocation) #prjOptPackages
+    <*> 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/Sh.hs b/src/HaskellCI/Sh.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/Sh.hs
@@ -0,0 +1,148 @@
+{-# LANGUAGE CPP                   #-}
+{-# LANGUAGE DeriveFunctor         #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+module HaskellCI.Sh (
+    Sh (..),
+    isComment,
+    MonadSh (..),
+    sh,
+    ShM (..),
+    runSh,
+    ShError (..),
+    FromShError (..),
+    ) where
+
+import HaskellCI.Prelude
+
+#ifdef MIN_VERSION_ShellCheck
+import           ShellCheck.Checker    (checkScript)
+import qualified ShellCheck.Interface  as SC
+#endif
+
+import qualified Control.Monad.Fail as Fail
+
+import HaskellCI.MonadErr
+
+-------------------------------------------------------------------------------
+-- shell command
+-------------------------------------------------------------------------------
+
+data Sh
+    = Sh String       -- ^ command
+    | Comment String  -- ^ comment
+  deriving Show
+
+isComment :: Sh -> Bool
+isComment (Comment _) = True
+isComment (Sh _)      = False
+
+-------------------------------------------------------------------------------
+-- class
+-------------------------------------------------------------------------------
+
+class Monad m => MonadSh m where
+    -- | Write shell command
+    sh' :: [Integer] -> String -> m ()
+
+    -- | Write comment
+    comment :: String -> m ()
+
+    -- | Commented block.
+    --
+    -- If the block is empty (or comments only), nothing might be written.
+    commentedBlock :: String -> m () -> m ()
+
+sh :: MonadSh m => String -> m ()
+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.
+    -- TODO: because HEREDOC doesn't work
+    , 2129 -- SC2129: Consider using { cmd1; cmd2; } >> file instead of individual redirects
+    , 2154 -- SC2154: PKGDIR_splitmix is referenced but not assigned.
+    ]
+
+-------------------------------------------------------------------------------
+-- Errors
+-------------------------------------------------------------------------------
+
+data ShError
+    = ShellCheckError String  -- ^ @ShellCheck@ disagrees.
+    | ShError String          -- ^ made by 'fail'.
+  deriving (Show)
+
+instance Exception ShError where
+    displayException (ShellCheckError s) = s
+    displayException (ShError s)         = "PANIC " ++ s
+
+class FromShError e where
+    fromShError :: ShError -> e
+
+instance FromShError ShError where
+    fromShError = id
+
+-------------------------------------------------------------------------------
+-- implementation
+-------------------------------------------------------------------------------
+
+newtype ShM a = ShM { unShM :: ([Sh] -> [Sh]) -> Either ShError ([Sh] -> [Sh], a) }
+  deriving (Functor)
+
+runSh :: (MonadErr e m, FromShError e) => ShM () -> m [Sh]
+runSh (ShM f) = case f id of
+    Left err      -> throwErr (fromShError err)
+    Right (g, ()) -> return (g [])
+
+instance Applicative ShM where
+    pure x = ShM $ \shs -> Right (shs, x)
+    (<*>) = ap
+
+instance Monad ShM where
+    return = pure
+
+    m >>= k = ShM $ \shs0 -> do
+        (shs1, x) <- unShM m     shs0
+        (shs2, y) <- unShM (k x) shs1
+        return (shs2, y)
+
+    fail = Fail.fail
+
+instance Fail.MonadFail ShM where
+    fail = throwErr . ShError
+
+instance MonadErr ShError ShM where
+    throwErr err = ShM $ \_ -> Left err
+
+unsafeSh :: String -> ShM ()
+unsafeSh x = ShM $ \shs -> Right (shs . (Sh x :),      ())
+
+instance MonadSh ShM where
+#ifndef MIN_VERSION_ShellCheck
+    sh' _ = unsafeSh
+#else
+    sh' excl cmd
+        | null (SC.crComments res) = unsafeSh cmd
+        | otherwise                = throwErr $ ShellCheckError $ 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.mockedSystemInterface []
+        spec  = SC.emptyCheckSpec
+            { SC.csFilename          = "stdin"
+            , SC.csScript            = cmd
+            , SC.csExcludedWarnings  = excl
+            , SC.csShellTypeOverride = Just SC.Sh
+            }
+#endif
+    comment x = ShM $ \shs -> Right (shs . (Comment x :), ())
+
+    commentedBlock c m = case runSh m of
+        Left err  -> throwErr err
+        Right shs
+            | all isComment shs -> pure ()
+            | otherwise         -> ShM $ \shs1 -> Right
+                (shs1 . (\shs2 -> Comment c : shs ++ shs2), ())
diff --git a/src/HaskellCI/ShVersionRange.hs b/src/HaskellCI/ShVersionRange.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/ShVersionRange.hs
@@ -0,0 +1,235 @@
+module HaskellCI.ShVersionRange (
+    compilerVersionPredicate,
+    ) where
+
+import HaskellCI.Prelude
+
+import Algebra.Lattice (bottom, top, joins)
+import Algebra.Heyting.Free (Free (..))
+import Algebra.Lattice.Wide (Wide (..))
+
+import qualified Algebra.Heyting.Free as F
+import qualified Algebra.Lattice.Wide as W
+import qualified Data.Set             as S
+import qualified Distribution.Version as C
+
+import HaskellCI.Compiler
+
+-- $setup
+-- >>> import Distribution.Pretty (prettyShow)
+
+compilerVersionPredicate :: Set CompilerVersion -> CompilerRange -> String
+compilerVersionPredicate cvs cr
+    | S.null ghcjsS = wideToString $ freeToWide ghcFree
+    | otherwise     = wideToString $ freeToWide $
+        (Var "$GHCJS" /\ ghcjsFree) \/ (Var "! $GHCJS" /\ ghcFree)
+  where
+    R hdS ghcS ghcjsS = partitionCompilerVersions cvs
+    R hdR ghcR ghcjsR = simplifyCompilerRange cr
+
+    -- GHCJS
+
+    ghcjsS' = S.filter (`C.withinRange` ghcjsR) ghcjsS
+
+    ghcjsFree :: Free String
+    ghcjsFree = ghcVersionPredicate ghcjsRange
+
+    ghcjsRange = case S.toList ghcjsS' of
+        []  -> C.noVersion
+        [_] -> C.anyVersion
+        _   -> error "multiple GHCJS versions unsupported"
+
+    -- GHC + GHC HEAD
+
+    ghcFree :: Free String
+    ghcFree = ghcVersionPredicate (ghcHeadRange \/ ghcRange)
+
+    -- GHC
+
+    ghcD = roundDown ghcS
+    ghcS' = S.filter (`C.withinRange` ghcR) ghcS
+
+    isMinGHC u = Just u == fmap fst (S.minView ghcD)
+
+    -- if we build with GHC HEAD, than none of known versions is maxGHC.
+    isMaxGHC u | hdS       = False
+               | otherwise = Just u == fmap fst (S.maxView ghcD)
+
+    findGhc :: Version -> VersionRange
+    findGhc v = case (S.lookupLE v ghcD, S.lookupGT v ghcD) of
+        (Nothing, _)      -> C.noVersion
+        (Just u, Nothing) -> orLater u
+        (Just u, Just w)  -> orLater u /\ earlier w
+      where
+        orLater u | isMinGHC u = C.anyVersion
+                  | otherwise  = C.orLaterVersion u
+
+        earlier u | isMaxGHC u = C.anyVersion
+                  | otherwise  = C.earlierVersion u
+      
+
+    ghcRange :: VersionRange
+    ghcRange = foldr (\/) C.noVersion $ map findGhc $ S.toList ghcS'
+
+    -- GHC HEAD
+
+    ghcHeadRange :: VersionRange
+    ghcHeadRange
+        | hdR && hdS = C.laterVersion (S.findMax ghcS)
+        | otherwise  = C.noVersion
+
+data R a = R Bool a a
+  deriving (Show)
+
+partitionCompilerVersions :: Set CompilerVersion -> R (Set Version)
+partitionCompilerVersions = foldr f (R False S.empty S.empty) where
+    f (GHC v)   (R hd ghc ghcjs) = R hd (S.insert v ghc) ghcjs
+    f (GHCJS v) (R hd ghc ghcjs) = R hd ghc (S.insert v ghcjs)
+    f GHCHead   (R _ ghc ghcjs)  = R True ghc ghcjs
+
+simplifyCompilerRange :: CompilerRange -> R VersionRange
+simplifyCompilerRange RangeGHC   = R True C.anyVersion C.noVersion
+simplifyCompilerRange RangeGHCJS = R False C.noVersion C.anyVersion
+simplifyCompilerRange (Range vr) = R (not $ C.hasUpperBound vr) vr vr
+simplifyCompilerRange (RangeUnion a b) =
+    case (simplifyCompilerRange a, simplifyCompilerRange b) of
+        (R x y z, R u v w) -> R (x \/ u) (y \/ v) (z \/ w)
+simplifyCompilerRange (RangeInter a b) =
+    case (simplifyCompilerRange a, simplifyCompilerRange b) of
+        (R x y z, R u v w) -> R (x /\ u) (y /\ v) (z /\ w)
+simplifyCompilerRange (RangePoints vs) = foldr f (R False C.noVersion C.noVersion) vs where
+    f (GHC v)   (R hd ghc ghcjs) = R hd (C.thisVersion v \/ ghc) ghcjs
+    f (GHCJS v) (R hd ghc ghcjs) = R hd ghc (C.thisVersion v \/ ghcjs)
+    f GHCHead   (R _  ghc ghcjs) = R True ghc ghcjs
+
+ghcVersionPredicate :: C.VersionRange -> Free String
+ghcVersionPredicate vr
+    | equivVersionRanges C.noVersion vr  = bottom
+    | equivVersionRanges C.anyVersion vr = top
+    | otherwise                          = ghcVersionPredicate' vr
+
+ghcVersionPredicate' :: C.VersionRange -> Free String
+ghcVersionPredicate' = conj . C.asVersionIntervals
+  where
+    conj = joins . map disj
+
+    disj :: C.VersionInterval -> Free String
+    disj (C.LowerBound v C.InclusiveBound, C.UpperBound u C.InclusiveBound)
+        | v == u                = Var ("[ $HCNUMVER -eq " ++ f v ++ " ]")
+    disj (lb, C.NoUpperBound)
+        | isInclZero lb         = top
+        | otherwise             = Var (lower lb)
+    disj (lb, C.UpperBound v b)
+        | isInclZero lb         = Var (upper v b)
+        | otherwise             = Var (lower lb) /\ Var (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
+
+-------------------------------------------------------------------------------
+-- Utilities
+-------------------------------------------------------------------------------
+
+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)
+
+-- | Round down a first version in major series.
+--
+-- >>> let rd = map prettyShow . S.toList . roundDown . S.fromList . map C.mkVersion
+--
+-- >>> rd []
+-- []
+--
+-- >>> rd [ [8,0,2] ]
+-- ["8.0","8.0.3"]
+--
+-- >>> rd [ [8,0,2], [8,2,2], [8,4,4], [8,6,5], [8,8,1] ]
+-- ["8.0","8.2","8.4","8.6","8.8","8.8.2"]
+--
+-- >>> rd [ [8,6,1], [8,6,2], [8,6,3], [8,6,4], [8,6,5] ]
+-- ["8.6","8.6.2","8.6.3","8.6.4","8.6.5","8.6.6"]
+--
+roundDown :: Set Version -> Set Version
+roundDown = go S.empty . S.toList where
+    go !acc []      = acc
+    go !acc [v]
+        | S.member m acc = S.insert v $ S.insert (up v) acc
+        | otherwise      = S.insert m $ S.insert (up v) acc
+      where
+        m = let (x,y) = ghcMajVer v in C.mkVersion [x,y]
+    go !acc (v:vs)
+        | S.member m acc = go (S.insert v acc) vs
+        | otherwise      = go (S.insert m acc) vs
+      where
+        m = let (x,y) = ghcMajVer v in C.mkVersion [x,y]
+
+    up v = C.mkVersion $ case C.versionNumbers v of
+        []     -> [1]
+        (x:xs) -> up' x xs
+
+    up' x []     = [x + 1]
+    up' x (y:ys) = x : up' y ys
+
+wideToString :: Wide String -> String
+wideToString W.Bottom     = "false"
+wideToString W.Top        = "top"
+wideToString (W.Middle x) = x
+
+
+freeToWide :: Free String -> Wide String
+freeToWide z
+    | z == top    = top
+    | z == bottom = bottom
+    | otherwise   = Middle (go 0 z)
+  where
+    go :: Int -> Free String -> String
+    go _ (Var x)  = x
+    go _ F.Bottom = "false"
+    go _ F.Top    = "true"
+
+    go d (x :/\: y) = parens (d > 3)
+        $ go 4 x ++ " && " ++ go 3 y
+    go d (x :\/: y) = parens (d > 2)
+        $ go 3 x ++ " || " ++ go 2 y
+
+    go d (x :=>: y) = parens (d > 2)
+        $ "! { " ++ go 0 x ++ " ; } || " ++ go 2 y
+
+    parens :: Bool -> String -> String
+    parens True  s = "{ " ++ s ++ "; }"
+    parens False s = s
+
+-------------------------------------------------------------------------------
+-- PosNeg
+-------------------------------------------------------------------------------
+
+{-
+data PosNeg a = Pos a | Neg a
+  deriving (Eq, Ord, Show, Functor)
+
+neg :: PosNeg a -> PosNeg a
+neg (Pos x) = Neg x
+neg (Neg x) = Pos x
+
+instance Applicative PosNeg where
+    pure  = Pos
+    (<*>) = ap
+
+instance Monad PosNeg where
+    return = pure
+
+    Pos x >>= f = f x
+    Neg x >>= f = neg (f x)
+-}
diff --git a/src/HaskellCI/TestedWith.hs b/src/HaskellCI/TestedWith.hs
--- a/src/HaskellCI/TestedWith.hs
+++ b/src/HaskellCI/TestedWith.hs
@@ -3,24 +3,24 @@
     checkVersions,
     ) where
 
-import           Prelude                         ()
-import           Prelude.Compat
+import Prelude ()
+import Prelude.Compat
 
-import           Control.Applicative             ((<|>))
-import           Data.Generics.Labels            ()
-import           Data.List                       (intercalate)
+import Control.Applicative  ((<|>))
+import Data.Generics.Labels ()
+import Data.List            (intercalate)
+import Data.Void            (Void)
 
 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.Parsec             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
+import HaskellCI.Compiler
+import HaskellCI.Package
+import HaskellCI.Project
 
 data TestedWithJobs
     = TestedWithUniform
@@ -41,33 +41,35 @@
 
 checkVersions
     :: TestedWithJobs
-    -> Project (Package, S.Set C.Version)
-    -> Either [String] (S.Set C.Version, Project Package)
+    -> Project Void Package
+    -> Either [String] (S.Set CompilerVersion, Project Void Package)
 checkVersions TestedWithUniform = checkVersionsUniform
 checkVersions TestedWithAny     = checkVersionsAny
 
 checkVersionsUniform
-    :: Project (Package, S.Set C.Version)
-    -> Either [String] (S.Set C.Version, Project Package)
+    :: Project Void Package
+    -> Either [String] (S.Set CompilerVersion, Project Void 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 })
+    else Right (allVersions, prj { prjPackages = names, prjOptPackages = [] })
   where
-    allVersions = F.foldMap snd prj
+    allVersions :: S.Set CompilerVersion
+    allVersions = F.foldMap pkgJobs prj
 
     collectConfig
         :: ([String], [Package])
-        -> (Package, S.Set C.Version)
+        -> Package
         -> ([String], [Package])
-    collectConfig aggregate (pkg, testWith) =
+    collectConfig aggregate pkg =
         aggregate <> (errors, [pkg])
       where
+        testWith = pkgJobs pkg
         symDiff a b = S.union a b `S.difference` S.intersection a b
         diff = symDiff testWith allVersions
-        missingVersions = map C.prettyShow $ S.toList diff
+        missingVersions = map dispGhcVersion $ S.toList diff
         errors | S.null diff = []
                | otherwise = pure $ mconcat
                     [ pkgName pkg
@@ -75,17 +77,11 @@
                     ] ++ intercalate "," missingVersions
 
 checkVersionsAny
-    :: Project (Package, S.Set C.Version)
-    -> Either [String] (S.Set C.Version, Project Package)
+    :: Project Void Package
+    -> Either [String] (S.Set CompilerVersion, Project Void Package)
 checkVersionsAny prj | null (prjPackages prj) = Left ["Error reading cabal file(s)!"]
 checkVersionsAny prj =
-    Right (allVersions, uncurry f <$> prj)
+    Right (allVersions, 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
+    allVersions :: S.Set CompilerVersion
+    allVersions = F.foldMap pkgJobs prj
diff --git a/src/HaskellCI/Tools.hs b/src/HaskellCI/Tools.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/Tools.hs
@@ -0,0 +1,112 @@
+-- Helpers to generate steps using tools: hlint and doctest
+module HaskellCI.Tools (
+    -- * Doctest
+    doctestJobVersionRange,
+    doctestArgs,
+    -- * HLint
+    hlintJobVersionRange,
+    hlintArgs,
+    ) where
+
+import HaskellCI.Prelude
+
+import qualified Data.Set                                      as S
+import qualified Distribution.PackageDescription               as C
+import qualified Distribution.PackageDescription.Configuration as C
+import qualified Distribution.Pretty                           as C
+import qualified Distribution.Types.VersionRange               as C
+import qualified Distribution.Version                          as C
+
+import qualified Distribution.Types.BuildInfo.Lens          as L
+import qualified Distribution.Types.PackageDescription.Lens as L
+
+import HaskellCI.Compiler
+import HaskellCI.Config.HLint
+
+-------------------------------------------------------------------------------
+-- Doctest
+-------------------------------------------------------------------------------
+
+doctestJobVersionRange :: CompilerRange
+doctestJobVersionRange = RangeGHC /\ Range (C.orLaterVersion $ C.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 :: C.GenericPackageDescription -> [[String]]
+doctestArgs gpd = nub $
+    [ libraryModuleArgs c
+    | c <- C.flattenPackageDescription gpd ^.. L.library . traverse
+    ] ++
+    [ libraryModuleArgs c
+    | c <- C.flattenPackageDescription gpd ^.. L.subLibraries . traverse
+    ]
+
+libraryModuleArgs :: C.Library -> [String]
+libraryModuleArgs l
+    | null dirsOrMods = []
+    | otherwise       = exts ++ dirsOrMods
+  where
+    bi = l ^. L.buildInfo
+
+    dirsOrMods
+        | null (C.hsSourceDirs bi) = map C.prettyShow (C.exposedModules l)
+        | otherwise                 = C.hsSourceDirs bi
+
+    exts = map (("-X" ++) . C.prettyShow) (C.defaultExtensions bi)
+
+executableModuleArgs :: C.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 (C.hsSourceDirs bi) = map C.prettyShow (C.otherModules bi)
+        | otherwise                 = C.hsSourceDirs bi
+
+    exts = map (("-X" ++) . C.prettyShow) (C.defaultExtensions bi)
+
+-------------------------------------------------------------------------------
+-- HLint
+-------------------------------------------------------------------------------
+
+hlintJobVersionRange
+    :: Set CompilerVersion  -- ^ all compilers
+    -> VersionRange         -- ^ head.hackage
+    -> HLintJob             -- ^ hlint-jobs
+    -> CompilerRange
+hlintJobVersionRange vs headHackage HLintJobLatest = case S.maxView vs' of
+    Just (v, _) -> RangePoints (S.singleton (GHC v))
+    _           -> RangePoints S.empty
+  where
+    -- remove non GHC versions, and head.hackage versions
+    vs' = S.fromList
+        $ filter (\v -> not $ C.withinRange v headHackage)
+        $ mapMaybe (maybeGHC Nothing Just)
+        $ S.toList vs
+
+hlintJobVersionRange _ _ (HLintJob v)   = RangePoints (S.singleton (GHC v))
+
+hlintArgs :: C.GenericPackageDescription -> [[String]]
+hlintArgs gpd = nub $
+    [ libraryModuleArgs c
+    | c <- C.flattenPackageDescription gpd ^.. L.library . traverse
+    ] ++
+    [ libraryModuleArgs c
+    | c <- C.flattenPackageDescription gpd ^.. L.subLibraries . traverse
+    ] ++
+    [ executableModuleArgs c
+    | c <- C.flattenPackageDescription gpd ^.. L.executables . traverse
+    ]
diff --git a/src/HaskellCI/Travis.hs b/src/HaskellCI/Travis.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/Travis.hs
@@ -0,0 +1,699 @@
+-- | Take configuration, produce 'Travis'.
+{-# LANGUAGE NamedFieldPuns    #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards   #-}
+{-# OPTIONS_GHC -Wno-unused-imports -Wno-unused-matches #-}
+module HaskellCI.Travis (
+    makeTravis,
+    travisHeader,
+    ) where
+
+import HaskellCI.Prelude
+import Prelude           (head)
+
+import qualified Data.Map.Strict                               as M
+import qualified Data.Set                                      as S
+import qualified Distribution.CabalSpecVersion                 as C
+import qualified Distribution.FieldGrammar                     as C
+import qualified Distribution.FieldGrammar.Pretty              as C
+import qualified Distribution.Fields.Pretty                    as C
+import qualified Distribution.Package                          as C
+import qualified Distribution.PackageDescription               as C
+import qualified Distribution.PackageDescription.Configuration as C
+import qualified Distribution.PackageDescription.FieldGrammar  as C
+import qualified Distribution.Pretty                           as C
+import qualified Distribution.Types.GenericPackageDescription  as C
+import qualified Distribution.Types.SourceRepo                 as C
+import qualified Distribution.Types.VersionRange               as C
+import qualified Distribution.Version                          as C
+import qualified Text.PrettyPrint                              as PP
+
+import qualified Distribution.Types.BuildInfo.Lens          as L
+import qualified Distribution.Types.PackageDescription.Lens as L
+
+import HaskellCI.Cli
+import HaskellCI.Compiler
+import HaskellCI.Config
+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.Config.Ubuntu
+import HaskellCI.Jobs
+import HaskellCI.List
+import HaskellCI.MonadErr
+import HaskellCI.Optimization
+import HaskellCI.Package
+import HaskellCI.Project
+import HaskellCI.Sh
+import HaskellCI.ShVersionRange
+import HaskellCI.Tools
+import HaskellCI.Travis.Yaml
+
+-------------------------------------------------------------------------------
+-- Travis header
+-------------------------------------------------------------------------------
+
+travisHeader :: Bool -> [String] -> [String]
+travisHeader insertVersion argv =
+    [ "This Travis job script has been generated by a script via"
+    , ""
+    , "  haskell-ci " ++ unwords [ "'" ++ a ++ "'" | a <- argv ]
+    , ""
+    , "For more information, see https://github.com/haskell-CI/haskell-ci"
+    , ""
+    ] ++
+    if insertVersion then
+    [ "version: " ++ haskellCIVerStr
+    , ""
+    ] else []
+
+-------------------------------------------------------------------------------
+-- Generate travis configuration
+-------------------------------------------------------------------------------
+
+makeTravis
+    :: [String]
+    -> Config
+    -> Project Void Package
+    -> JobVersions
+    -> Either ShError Travis -- TODO: writer
+makeTravis argv Config {..} prj JobVersions {..} = do
+    -- before caching: clear some redundant stuff
+    beforeCache <- runSh $ when cfgCache $ do
+        sh "rm -fv $CABALHOME/packages/hackage.haskell.org/build-reports.log"
+        comment "remove files that are regenerated by 'cabal update'"
+        sh "rm -fv $CABALHOME/packages/hackage.haskell.org/00-index.*" -- legacy
+        sh "rm -fv $CABALHOME/packages/hackage.haskell.org/*.json" -- TUF meta-data
+        sh "rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.cache"
+        sh "rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar"
+        sh "rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar.idx"
+        sh "rm -rfv $CABALHOME/packages/head.hackage" -- if we cache, it will break builds.
+
+    -- before install: we set up the environment, install GHC/cabal on OSX
+    beforeInstall <- runSh $ do
+        -- This have to be first
+        when anyGHCJS $ sh $ unlines
+            [ "if echo $CC | grep -q ghcjs; then"
+            , "    GHCJS=true;"
+            , "else"
+            , "    GHCJS=false;"
+            , "fi"
+            ]
+
+        when (anyGHCJS || isBionic) $ sh $ unlines $ buildList $ do
+            let item' x = item $ "  " ++ x ++ ";"
+
+            item "if [ \"$TRAVIS_OS_NAME\" = \"linux\" ]; then"
+            when isBionic $ item' "sudo add-apt-repository -y ppa:hvr/ghc"
+            traverse_ item' $ forJob RangeGHCJS "sudo add-apt-repository -y ppa:hvr/ghcjs"
+            traverse_ item' $ forJob RangeGHCJS "curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | sudo apt-key add -"
+            traverse_ item' $ forJob RangeGHCJS $ "sudo apt-add-repository 'https://deb.nodesource.com/node_8.x " ++ C.prettyShow cfgUbuntu  ++ " main'"
+            traverse_ item' $ forJob (boolToBoundedLattice isBionic \/ RangeGHCJS) "sudo apt-get update"
+            item' $ "sudo apt-get install $CC" ++
+                (if S.null cfgApt
+                then ""
+                else " " ++ unwords (S.toList cfgApt))
+            traverse_ item' $ forJob RangeGHCJS "sudo apt-get install -y nodejs cabal-install-3.0" -- TODO: select right `cabal-install` version.
+            item "fi"
+
+        -- Adjust $HC
+        sh "HC=$(echo \"/opt/$CC/bin/ghc\" | sed 's/-/\\//')"
+        sh "WITHCOMPILER=\"-w $HC\""
+        shForJob RangeGHCJS "HC=${HC}js"
+        shForJob RangeGHCJS "WITHCOMPILER=\"--ghcjs ${WITHCOMPILER}js\""
+
+        -- Hack: happy needs ghc. Let's install version matching GHCJS.
+        -- At the moment, there is only GHCJS-8.4, so we install GHC-8.4.4
+        when anyGHCJS $ do
+            shForJob RangeGHCJS $ "sudo apt-get install -y ghc-8.4.4"
+            shForJob RangeGHCJS $ "PATH=\"/opt/ghc/8.4.4/bin:$PATH\""
+
+        sh "HCPKG=\"$HC-pkg\""
+        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 "TOP=$(pwd)"
+        -- macOS installing
+        let haskellOnMacos = "https://haskell.futurice.com/haskell-on-macos.py"
+        unless (null cfgOsx) $ do
+            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; WITHCOMPILER=\"-w $HC\"; 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
+        sh "HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\\d+)\\.(\\d+)\\.(\\d+)(\\.(\\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))')"
+        sh "echo $HCNUMVER"
+        -- verbose in .cabal/config is not respected
+        -- https://github.com/haskell/cabal/issues/5956
+        sh "CABAL=\"$CABAL -vnormal+nowrap+markoutput\""
+
+        -- Color cabal output
+        sh' [2039] "set -o pipefail" -- SC2039: In POSIX sh, set option pipefail is undefined. Travis is bash, so it's fine :)
+        when cfgColor $ do
+            cat' ".colorful.awk"
+                [ "function blue(s) { printf \"\\033[0;34m\" s \"\\033[0m \" }"
+                , "BEGIN { state = \"output\"; }"
+                , "/^-----BEGIN CABAL OUTPUT-----$/ { state = \"cabal\" }"
+                , "/^-----END CABAL OUTPUT-----$/ { state = \"output\" }"
+                , "!/^(-----BEGIN CABAL OUTPUT-----|-----END CABAL OUTPUT-----)/ {"
+                , "  if (state == \"cabal\") {"
+                , "    print blue($0)"
+                , "  } else {"
+                , "    print $0"
+                , "  }"
+                , "}"
+                ]
+            sh "cat .colorful.awk"
+            sh $ unlines
+                [ "color_cabal_output () {"
+                , "  awk -f $TOP/.colorful.awk"
+                , "}"
+                ]
+            sh "echo text | color_cabal_output"
+
+    -- in install step we install tools and dependencies
+    install <- runSh $ do
+        sh "${CABAL} --version"
+        sh "echo \"$(${HC} --version) [$(${HC} --print-project-git-commit-id 2> /dev/null || echo '?')]\""
+        when anyGHCJS $ do
+            sh "node --version"
+            sh "echo $GHCJS"
+
+        sh "TEST=--enable-tests"
+        shForJob (invertCompilerRange $ Range cfgTests) "TEST=--disable-tests"
+        sh "BENCH=--enable-benchmarks"
+        shForJob (invertCompilerRange $ Range cfgBenchmarks) "BENCH=--disable-benchmarks"
+        sh "HEADHACKAGE=false"
+        shForJob (Range cfgHeadHackage \/ RangePoints (S.singleton GHCHead)) "HEADHACKAGE=true"
+
+        -- create ~/.cabal/config
+        sh "rm -f $CABALHOME/config"
+        cat "$CABALHOME/config"
+            [ "verbose: normal +nowrap +markoutput" -- https://github.com/haskell/cabal/issues/5956
+            , "remote-build-reporting: anonymous"
+            , "write-ghc-environment-files: always"
+            , "remote-repo-cache: $CABALHOME/packages"
+            , "logs-dir:          $CABALHOME/logs"
+            , "world-file:        $CABALHOME/world"
+            , "extra-prog-path:   $CABALHOME/bin"
+            , "symlink-bindir:    $CABALHOME/bin"
+            , "installdir:        $CABALHOME/bin"
+            , "build-summary:     $CABALHOME/logs/build.log"
+            , "store-dir:         $CABALHOME/store"
+            , "install-dirs user"
+            , "  prefix: $CABALHOME"
+            , "repository hackage.haskell.org"
+            , "  url: http://hackage.haskell.org/"
+            ]
+
+        -- Add head.hackage repository to ~/.cabal/config
+        -- (locally you want to add it to cabal.project)
+        unless (S.null headGhcVers) $ sh $ unlines $
+            [ "if $HEADHACKAGE; then"
+            , "echo \"allow-newer: $($HCPKG list --simple-output | sed -E 's/([a-zA-Z-]+)-[0-9.]+/*:\\1/g')\" >> $CABALHOME/config"
+            ] ++
+            lines (catCmd Double "$CABALHOME/config"
+            [ "repository head.hackage.ghc.haskell.org"
+            , "   url: https://ghc.gitlab.haskell.org/head.hackage/"
+            , "   secure: True"
+            , "   root-keys: 7541f32a4ccca4f97aea3b22f5e593ba2c0267546016b992dfadcd2fe944e55d"
+            , "              26021a13b401500c8eb2761ca95c61f2d625bfef951b939a8124ed12ecf07329"
+            , "              f76d08be13e9a61a377a85e2fb63f4c5435d40f8feb3e12eb05905edb8cdea89"
+            , "   key-threshold: 3"
+            ]) ++
+            [ "fi"
+            ]
+
+        -- Cabal jobs
+        for_ (cfgJobs >>= cabalJobs) $ \n ->
+            sh $ "echo 'jobs: " ++ show n ++ "' >> $CABALHOME/config"
+
+        -- GHC jobs
+        for_ (cfgJobs >>= ghcJobs) $ \m -> do
+            catForJob (Range $ C.orLaterVersion (C.mkVersion [7,8])) "$CABALHOME/config"
+                [ "program-default-options"
+                , "  ghc-options: -j" ++ show m
+                ]
+
+        sh "cat $CABALHOME/config"
+
+        -- remove project own cabal.project files
+        sh "rm -fv cabal.project cabal.project.local cabal.project.freeze"
+
+        -- Update hackage index.
+        sh "travis_retry ${CABAL} v2-update -v"
+
+        -- Install doctest
+        let doctestVersionConstraint
+                | C.isAnyVersion (cfgDoctestVersion cfgDoctest) = ""
+                | otherwise = " --constraint='doctest " ++ C.prettyShow (cfgDoctestVersion cfgDoctest) ++ "'"
+        when doctestEnabled $
+            shForJob (Range (cfgDoctestEnabled cfgDoctest) /\ doctestJobVersionRange) $
+                cabalInTmp $ "v2-install $WITHCOMPILER -j2 doctest" ++ doctestVersionConstraint
+
+        -- Install hlint
+        let hlintVersionConstraint
+                | C.isAnyVersion (cfgHLintVersion cfgHLint) = ""
+                | otherwise = " --constraint='hlint " ++ C.prettyShow (cfgHLintVersion cfgHLint) ++ "'"
+        when (cfgHLintEnabled cfgHLint) $ shForJob (hlintJobVersionRange versions cfgHeadHackage (cfgHLintJob cfgHLint)) $
+            cabalInTmp $ "v2-install $WITHCOMPILER -j2 hlint" ++ hlintVersionConstraint
+
+        when (anyGHCJS && cfgGhcjsTests) $ shForJob RangeGHCJS $ cabalInTmp "v2-install -w ghc-8.4.4 cabal-plan"
+
+        -- Install happy
+        when anyGHCJS $ shForJob RangeGHCJS $ cabalInTmp "v2-install -w ghc-8.4.4 happy"
+
+        -- create cabal.project file
+        generateCabalProject False
+
+        -- autoreconf
+        for_ pkgs $ \Pkg{pkgDir} ->
+            sh $ "if [ -f \"" ++ pkgDir ++ "/configure.ac\" ]; then (cd \"" ++ pkgDir ++ "\" && autoreconf -i); fi"
+
+        -- dump install plan
+        sh $ cabal "v2-freeze $WITHCOMPILER ${TEST} ${BENCH}"
+        sh "cat cabal.project.freeze | sed -E 's/^(constraints: *| *)//' | sed 's/any.//'"
+        sh "rm  cabal.project.freeze"
+
+        -- Install dependencies
+        when cfgInstallDeps $ do
+            -- install dependencies
+            sh $ cabal "v2-build $WITHCOMPILER ${TEST} ${BENCH} --dep -j2 all"
+
+            -- install dependencies for no-test-no-bench
+            shForJob (Range cfgNoTestsNoBench) $ cabal "v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --dep -j2 all"
+
+    -- 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 <- runSh $ do
+        sh "DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)"
+
+        -- sdist
+        foldedSh FoldSDist "Packaging..." cfgFolds $ do
+            sh $ cabal "v2-sdist all"
+
+        -- unpack
+        foldedSh FoldUnpack "Unpacking..." cfgFolds $ do
+            sh "mv dist-newstyle/sdist/*.tar.gz ${DISTDIR}/"
+            sh "cd ${DISTDIR} || false" -- fail explicitly, makes SC happier
+            sh "find . -maxdepth 1 -type f -name '*.tar.gz' -exec tar -xvf '{}' \\;"
+            sh "find . -maxdepth 1 -type f -name '*.tar.gz' -exec rm       '{}' \\;"
+
+            for_ pkgs $ \Pkg{pkgName} -> do
+                sh $ pkgNameDirVariable' pkgName ++ "=\"$(find . -maxdepth 1 -type d -regex '.*/" ++ pkgName ++ "-[0-9.]*')\""
+
+            generateCabalProject True
+
+        -- build no-tests no-benchmarks
+        unless (equivVersionRanges C.noVersion cfgNoTestsNoBench) $ foldedSh FoldBuild "Building..." cfgFolds $ do
+            comment "this builds all libraries and executables (without tests/benchmarks)"
+            shForJob (Range cfgNoTestsNoBench) $ cabal "v2-build $WITHCOMPILER --disable-tests --disable-benchmarks all"
+
+        -- build everything
+        foldedSh FoldBuildEverything "Building with tests and benchmarks..." cfgFolds $ do
+            comment "build & run tests, build benchmarks"
+            sh $ cabal "v2-build $WITHCOMPILER ${TEST} ${BENCH} all"
+
+        -- cabal v2-test fails if there are no test-suites.
+        foldedSh FoldTest "Testing..." cfgFolds $ do
+            shForJob (RangeGHC /\ Range (cfgTests /\ cfgRunTests) /\ hasTests) $
+                cabal "v2-test $WITHCOMPILER ${TEST} ${BENCH} all"
+
+            when cfgGhcjsTests $ shForJob (RangeGHCJS /\ hasTests)
+                "for testexe in $(cabal-plan list-bins '*:test:*' | awk '{ print $2 }'); do echo $testexe; nodejs ${testexe}.jsexe/all.js; done"
+
+        -- doctest
+        when doctestEnabled $ foldedSh FoldDoctest "Doctest..." cfgFolds $ do
+            let doctestOptions = unwords $ cfgDoctestOptions cfgDoctest
+            unless (null $ cfgDoctestFilterPkgs cfgDoctest) $ do
+                sh $ unlines $ concat
+                    [ [ "for ghcenv in .ghc.environment.*; do"
+                      , "mv $ghcenv ghcenv;"
+                      ]
+                    , cfgDoctestFilterPkgs cfgDoctest <&> \pn ->
+                        "grep -vE '^package-id " ++ C.unPackageName pn ++ "-([0-9]+(\\.[0-9]+)*)-' ghcenv > ghcenv.tmp; mv ghcenv.tmp ghcenv;"
+                    , [ "mv ghcenv $ghcenv;"
+                      , "cat $ghcenv;"
+                      , "done"
+                      ]
+                    ]
+            for_ pkgs $ \Pkg{pkgName,pkgGpd,pkgJobs} -> do
+                for_ (doctestArgs pkgGpd) $ \args -> do
+                    let args' = unwords args
+                    let vr = Range (cfgDoctestEnabled cfgDoctest)
+                          /\ doctestJobVersionRange
+                          /\ RangePoints pkgJobs
+                    unless (null args) $ shForJob  vr $
+                        "(cd " ++ pkgNameDirVariable pkgName ++ " && doctest " ++ doctestOptions ++ " " ++ args' ++ ")"
+
+        -- hlint
+        when (cfgHLintEnabled cfgHLint) $ foldedSh FoldHLint "HLint.." cfgFolds $ do
+            let "" <+> ys = ys
+                xs <+> "" = xs
+                xs <+> ys = xs ++ " " ++ ys
+
+                prependSpace "" = ""
+                prependSpace xs = " " ++ xs
+
+            let hlintOptions = prependSpace $ maybe "" ("-h ${TOP}/" ++) (cfgHLintYaml cfgHLint) <+> unwords (cfgHLintOptions cfgHLint)
+
+            for_ pkgs $ \Pkg{pkgName,pkgGpd,pkgJobs} -> do
+                for_ (hlintArgs pkgGpd) $ \args -> do
+                    let args' = unwords args
+                    unless (null args) $
+                        shForJob (hlintJobVersionRange versions cfgHeadHackage (cfgHLintJob cfgHLint) /\ RangePoints pkgJobs) $
+                        "(cd " ++ pkgNameDirVariable pkgName ++ " && hlint" ++ hlintOptions ++ " " ++ args' ++ ")"
+
+        -- cabal check
+        when cfgCheck $ foldedSh FoldCheck "cabal check..." cfgFolds $ do
+            for_ pkgs $ \Pkg{pkgName,pkgJobs} -> shForJob (RangePoints pkgJobs) $
+                "(cd " ++ pkgNameDirVariable pkgName ++ " && ${CABAL} -vnormal check)"
+
+        -- haddock
+        when (hasLibrary && not (equivVersionRanges C.noVersion cfgHaddock)) $
+            foldedSh FoldHaddock "haddock..." cfgFolds $
+                shForJob (RangeGHC /\ Range cfgHaddock) $ cabal "v2-haddock $WITHCOMPILER ${TEST} ${BENCH} all"
+
+        -- unconstained build
+        -- Have to build last, as we remove cabal.project.local
+        unless (equivVersionRanges C.noVersion cfgUnconstrainted) $
+            foldedSh FoldBuildInstalled "Building without installed constraints for packages in global-db..." cfgFolds $ do
+                shForJob (Range cfgUnconstrainted) "rm -f cabal.project.local"
+                shForJob (Range cfgUnconstrainted) $ cabal "v2-build $WITHCOMPILER --disable-tests --disable-benchmarks all"
+
+        -- and now, as we don't have cabal.project.local;
+        -- we can test with other constraint sets
+        unless (null cfgConstraintSets) $ do
+            comment "Constraint sets"
+            sh "rm -rf cabal.project.local"
+
+            for_ cfgConstraintSets $ \cs -> do
+                let name            = csName cs
+                let shForCs         = shForJob (Range (csGhcVersions cs))
+                let testFlag        = if csTests cs then "--enable-tests" else "--disable-tests"
+                let benchFlag       = if csBenchmarks cs then "--enable-benchmarks" else "--disable-benchmarks"
+                let constraintFlags = map (\x ->  "--constraint='" ++ x ++ "'") (csConstraints cs)
+                let allFlags        = unwords (testFlag : benchFlag : constraintFlags)
+
+                foldedSh' FoldConstraintSets name ("Constraint set " ++ name) cfgFolds $ do
+                    shForCs $ cabal $ "v2-build $WITHCOMPILER " ++ allFlags ++ " all"
+                    when (csRunTests cs) $
+                        shForCs $ cabal $ "v2-test $WITHCOMPILER " ++ allFlags ++ " all"
+                    when (csHaddock cs) $
+                        shForCs $ cabal $ "v2-haddock $WITHCOMPILER " ++ allFlags ++ " all"
+
+    -- assemble travis configuration
+    return Travis
+        { travisLanguage      = "c"
+        , travisUbuntu        = cfgUbuntu
+        , travisGit           = TravisGit
+            { tgSubmodules = cfgSubmodules
+            }
+        , travisCache         = TravisCache
+            { tcDirectories = buildList $ when cfgCache $ do
+                item "$HOME/.cabal/packages"
+                item "$HOME/.cabal/store"
+                -- on OSX ghc is installed in $HOME so we can cache it
+                -- independently of linux
+                when (cfgCache && not (null cfgOsx)) $ do
+                    item "$HOME/.ghc-install"
+            }
+        , travisBranches      = TravisBranches
+            { tbOnly = cfgOnlyBranches
+            }
+        , travisNotifications = TravisNotifications
+            { tnIRC = justIf (not $ null cfgIrcChannels) $ TravisIRC
+                { tiChannels = cfgIrcChannels
+                , tiSkipJoin = True
+                , tiTemplate =
+                    [ "\"\\x0313" ++ projectName ++ "\\x03/\\x0306%{branch}\\x03 \\x0314%{commit}\\x03 %{build_url} %{message}\""
+                    ]
+                }
+            }
+        , travisServices      = buildList $ do
+            when cfgPostgres $ item "postgresql"
+        , travisAddons        = TravisAddons
+            { taApt      = TravisApt [] []
+            , taPostgres = if cfgPostgres then Just "10" else Nothing
+            }
+        , travisMatrix        = TravisMatrix
+            { tmInclude = buildList $ do
+                let tellJob :: Bool -> CompilerVersion -> ListBuilder TravisJob ()
+                    tellJob osx gv = do
+                        let cvs = dispCabalVersion $ correspondingCabalVersion cfgCabalInstallVersion gv
+                        let gvs = dispGhcVersion gv
+
+                        -- GHCJS cannot be installed via apt plugin
+                        let addGvs | isGHCJS gv = id
+                                   | otherwise  = (gvs :)
+
+                        item TravisJob
+                            { tjCompiler = gvs
+                            , tjOS       = if osx then "osx" else "linux"
+                            , tjEnv      = case gv of
+                                GHC v -> M.lookup v cfgEnv
+                                _     -> Nothing
+                            , tjAddons   = TravisAddons
+                                { taApt = TravisApt
+                                    { taPackages = addGvs $ ("cabal-install-" ++ cvs) : S.toList cfgApt
+                                    , taSources  = ["hvr-ghc"]
+                                    }
+                                , taPostgres = Nothing
+                                }
+                            }
+
+                for_ (reverse $ S.toList versions) $ tellJob False
+                for_ (reverse $ S.toList osxVersions) $ tellJob True . GHC
+
+            , tmAllowFailures =
+                [ TravisAllowFailure $ dispGhcVersion compiler
+                | compiler <- toList versions
+                , previewGHC cfgHeadHackage compiler || maybeGHC False (`C.withinRange` cfgAllowFailures) compiler
+                ]
+            }
+        , travisBeforeCache   = beforeCache
+        , travisBeforeInstall = beforeInstall
+        , travisInstall       = install
+        , travisScript        = script
+        }
+  where
+    pkgs = prjPackages prj
+    projectName = fromMaybe (pkgName $ Prelude.head pkgs) cfgProjectName
+
+    justIf True x  = Just x
+    justIf False _ = Nothing
+
+    -- TODO: should this be part of MonadSh ?
+    foldedSh label = foldedSh' label ""
+
+    isBionic = cfgUbuntu == Bionic
+    anyGHCJS = any isGHCJS versions
+
+    -- https://github.com/travis-ci/docs-travis-ci-com/issues/949#issuecomment-276755003
+    -- https://github.com/travis-ci/travis-rubies/blob/9f7962a881c55d32da7c76baefc58b89e3941d91/build.sh#L38-L44
+    -- https://github.com/travis-ci/travis-build/blob/91bf066/lib/travis/build/shell/dsl.rb#L58-L63
+    foldedSh' :: Fold -> String -> String -> Set Fold -> ShM () -> ShM ()
+    foldedSh' label sfx plabel labels block
+        | label `S.notMember` labels = commentedBlock plabel block
+        | otherwise = case runSh block of
+            Left err  -> throwErr err
+            Right shs
+                | all isComment shs -> pure ()
+                | otherwise         -> ShM $ \shs1 -> Right $
+                    ( shs1
+                    . (Comment plabel :)
+                    . (Sh ("echo '" ++ plabel ++ "' && echo -en 'travis_fold:start:" ++ label' ++ "\\\\r'") :)
+                    . (shs ++)
+                    . (Sh ("echo -en 'travis_fold:end:" ++ label' ++ "\\\\r'") :)
+                    -- return ()
+                    , ()
+                    )
+      where
+        label' | null sfx  = showFold label
+               | otherwise = showFold label ++ "-" ++ sfx
+
+    doctestEnabled = any (maybeGHC False (`C.withinRange` cfgDoctestEnabled cfgDoctest)) versions
+
+    -- version range which has tests
+    hasTests :: CompilerRange
+    hasTests = RangePoints $ S.unions
+        [ pkgJobs
+        | Pkg{pkgGpd,pkgJobs} <- pkgs
+        , not $ null $ C.condTestSuites pkgGpd
+        ]
+
+    hasLibrary = any (\Pkg{pkgGpd} -> isJust $ C.condLibrary pkgGpd) pkgs
+
+    -- GHC versions which need head.hackage
+    headGhcVers :: Set CompilerVersion
+    headGhcVers = S.filter (previewGHC cfgHeadHackage) versions
+
+    cabal :: String -> String
+    cabal cmd | cfgColor  = cabalCmd ++ " | color_cabal_output"
+              | otherwise = cabalCmd
+      where
+        cabalCmd = "${CABAL} " ++ cmd
+
+    cabalInTmp :: String -> String
+    cabalInTmp cmd = "(cd /tmp && " ++ cabal cmd ++ ")"
+
+    forJob :: CompilerRange -> String -> Maybe String
+    forJob vr cmd
+        | all (`compilerWithinRange` vr) versions       = Just cmd
+        | not $ any (`compilerWithinRange` vr) versions = Nothing
+        | otherwise                                     = Just $ unwords
+            [ "if"
+            , compilerVersionPredicate versions vr
+            , "; then"
+            , cmd
+            , "; fi"
+            ]
+
+    shForJob :: CompilerRange -> String -> ShM ()
+    shForJob vr cmd = maybe (pure ()) sh (forJob vr cmd)
+
+    catForJob vr fp contents = shForJob vr (catCmd Double fp contents)
+
+    generateCabalProjectFields :: Bool -> [C.PrettyField ()]
+    generateCabalProjectFields dist = buildList $ do
+        -- copy files from original cabal.project
+        case cfgCopyFields of
+            CopyFieldsNone -> pure ()
+            CopyFieldsAll  -> traverse_ item (prjOrigFields prj)
+            CopyFieldsSome -> do
+                for_ (prjConstraints prj) $ \xs -> do
+                    let s = concat (lines xs)
+                    item $ C.PrettyField () "constraints" $ PP.text s
+
+                for_ (prjAllowNewer prj) $ \xs -> do
+                    let s = concat (lines xs)
+                    item $ C.PrettyField () "allow-newer" $ PP.text s
+
+                when (prjReorderGoals prj) $
+                    item $ C.PrettyField () "reorder-goals" $ PP.text "True"
+
+                for_ (prjMaxBackjumps prj) $ \bj ->
+                    item $ C.PrettyField () "max-backjumps" $ PP.text $ show bj
+
+                case prjOptimization prj of
+                    OptimizationOn      -> return ()
+                    OptimizationOff     -> item $ C.PrettyField () "optimization" $ PP.text "False"
+                    OptimizationLevel l -> item $ C.PrettyField () "optimization" $ PP.text $ show l
+
+                for_ (prjSourceRepos prj) $ \repo ->
+                    item $ C.PrettySection () "source-repository-package" [] $
+                        C.prettyFieldGrammar C.cabalSpecLatest (C.sourceRepoFieldGrammar $ C.RepoKindUnknown "unused") repo
+
+        -- local ghc-options
+        unless (null cfgLocalGhcOptions) $ for_ pkgs $ \Pkg{pkgName} -> do
+            let s = unwords $ map (show . C.showToken) cfgLocalGhcOptions
+            item $ C.PrettySection () "package" [PP.text pkgName] $ buildList $
+                item $ C.PrettyField () "ghc-options" $ PP.text s
+
+        -- raw-project is after local-ghc-options so we can override per package.
+        traverse_ item cfgRawProject
+
+    generateCabalProject :: Bool -> ShM ()
+    generateCabalProject dist = do
+        comment "Generate cabal.project"
+        sh "rm -rf cabal.project cabal.project.local cabal.project.freeze"
+        sh "touch cabal.project"
+
+        sh $ unlines
+            [ cmd
+            | pkg <- pkgs
+            , let p | dist      = pkgNameDirVariable (pkgName pkg)
+                    | otherwise = pkgDir pkg
+            , cmd <- toList $ forJob (RangePoints $ pkgJobs pkg) $
+                "echo \"packages: " ++ p ++ "\" >> cabal.project"
+            ]
+
+        cat "cabal.project" $ lines $ C.showFields' (const []) 2 $ generateCabalProjectFields dist
+
+        -- also write cabal.project.local file with
+        -- @
+        -- constraints: base installed
+        -- constraints: array installed
+        -- ...
+        --
+        -- omitting any local package names
+        case normaliseInstalled cfgInstalled of
+            InstalledDiff pns -> sh $ unwords
+                [ "for pkg in $($HCPKG list --simple-output); do"
+                , "echo $pkg"
+                , "| sed 's/-[^-]*$//'"
+                , "| (grep -vE -- " ++ re ++ " || true)"
+                , "| sed 's/^/constraints: /'"
+                , "| sed 's/$/ installed/'"
+                , ">> cabal.project.local; done"
+                ]
+              where
+                pns' = S.map C.unPackageName pns `S.union` foldMap (S.singleton . pkgName) pkgs
+                re = "'^(" ++ intercalate "|" (S.toList pns') ++ ")$'"
+
+            InstalledOnly pns | not (null pns') -> sh' [2043] $ unwords
+                [ "for pkg in " ++ unwords (S.toList pns') ++ "; do"
+                , "echo \"constraints: $pkg installed\""
+                , ">> cabal.project.local; done"
+                ]
+              where
+                pns' = S.map C.unPackageName pns `S.difference` foldMap (S.singleton . pkgName) pkgs
+
+            -- otherwise: nothing
+            _ -> pure ()
+
+        sh "cat cabal.project || true"
+        sh "cat cabal.project.local || true"
+
+pkgNameDirVariable' :: String -> String
+pkgNameDirVariable' n = "PKGDIR_" ++ map f n where
+    f '-' = '_'
+    f c   = c
+
+pkgNameDirVariable :: String -> String
+pkgNameDirVariable n = "${PKGDIR_" ++ map f n ++ "}" where
+    f '-' = '_'
+    f c   = c
+
+data Quotes = Single | Double
+
+escape :: Quotes -> String -> String
+escape Single xs = "'" ++ concatMap f xs ++ "'" where
+    f '\0' = ""
+    f '\'' = "'\"'\"'"
+    f x    = [x]
+escape Double xs = show xs
+
+catCmd :: Quotes -> FilePath -> [String] -> String
+catCmd q fp contents = unlines
+    [ "echo " ++ escape q l ++ replicate (maxLength - length l) ' ' ++ " >> " ++ fp
+    | l <- contents
+    ]
+  where
+    maxLength = foldl' (\a l -> max a (length l)) 0 contents
+{-
+-- https://travis-ci.community/t/multiline-commands-have-two-spaces-in-front-breaks-heredocs/2756
+catCmd fp contents = unlines $
+    [ "cat >> " ++ fp ++ " << HEREDOC" ] ++
+    contents ++
+    [ "HEREDOC" ]
+-}
+
+cat :: FilePath -> [String] -> ShM ()
+cat fp contents = sh $ catCmd Double fp contents
+
+cat' :: FilePath -> [String] -> ShM ()
+cat' fp contents = sh' [2016, 2129] $ catCmd Single fp contents
+-- SC2129: Consider using { cmd1; cmd2; } >> file instead of individual redirects
+-- SC2016: Expressions don't expand in single quotes
+-- that's the point!
+
+boolToBoundedLattice :: BoundedLattice a => Bool -> a
+boolToBoundedLattice True = top
+boolToBoundedLattice False = bottom
diff --git a/src/HaskellCI/Travis/Yaml.hs b/src/HaskellCI/Travis/Yaml.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/Travis/Yaml.hs
@@ -0,0 +1,232 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards   #-}
+{-# LANGUAGE StrictData        #-}
+-- | @travis.yaml@ structure.
+module HaskellCI.Travis.Yaml where
+
+import           HaskellCI.Prelude
+
+import qualified Data.Aeson              as Aeson
+import qualified Data.List.NonEmpty      as NE
+
+import           HaskellCI.Config.Ubuntu
+import           HaskellCI.List
+import           HaskellCI.Sh
+import           HaskellCI.YamlSyntax
+
+-------------------------------------------------------------------------------
+-- Data
+-------------------------------------------------------------------------------
+
+data Travis = Travis
+    { travisUbuntu        :: !Ubuntu
+    , travisLanguage      :: !String
+    , travisGit           :: !TravisGit
+    , travisCache         :: !TravisCache
+    , travisBranches      :: !TravisBranches
+    , travisNotifications :: !TravisNotifications
+    , travisServices      :: ![String]
+    , travisAddons        :: !TravisAddons
+    , travisMatrix        :: !TravisMatrix
+    , travisBeforeCache   :: ![Sh]
+    , travisBeforeInstall :: ![Sh]
+    , travisInstall       :: ![Sh]
+    , travisScript        :: ![Sh]
+    }
+  deriving Show
+
+newtype TravisGit = TravisGit
+    { tgSubmodules :: Bool
+    }
+  deriving Show
+
+newtype TravisCache = TravisCache
+    { tcDirectories :: [FilePath]
+    }
+  deriving Show
+
+newtype TravisBranches = TravisBranches
+    { tbOnly :: [String]
+    }
+  deriving Show
+
+newtype TravisNotifications = TravisNotifications
+    { tnIRC :: Maybe TravisIRC
+    }
+  deriving Show
+
+data TravisIRC = TravisIRC
+    { tiChannels :: [String]
+    , tiSkipJoin :: Bool
+    , tiTemplate :: [String]
+    }
+  deriving Show
+
+data TravisMatrix = TravisMatrix
+    { tmInclude       :: [TravisJob]
+    , tmAllowFailures :: [TravisAllowFailure]
+    }
+  deriving Show
+
+data TravisJob = TravisJob
+    { tjCompiler :: String
+    , tjEnv      :: Maybe String
+    , tjAddons   :: TravisAddons
+    , tjOS       :: String
+    }
+  deriving Show
+
+data TravisAddons = TravisAddons
+    { taApt      :: TravisApt
+    , taPostgres :: Maybe String
+    }
+  deriving Show
+
+data TravisApt = TravisApt
+    { taPackages :: [String]
+    , taSources  :: [String]
+    }
+  deriving Show
+
+newtype TravisAllowFailure = TravisAllowFailure
+    { tafCompiler :: String
+    }
+  deriving Show
+
+-------------------------------------------------------------------------------
+-- Serialisation helpers (move to Travis.Yaml?)
+-------------------------------------------------------------------------------
+
+(~>) :: String -> Yaml [String] -> ([String], String, Yaml [String])
+k ~> v = ([],k,v)
+
+(^^^) :: ([String], String, Yaml [String]) -> String -> ([String], String, Yaml [String])
+(a,b,c) ^^^ d = (d : a, b, c)
+
+shListToYaml :: [Sh] -> Yaml [String]
+shListToYaml shs = YList [] $ concat
+    [ YString cs x : map fromString xs
+    | (cs, x :| xs) <- gr shs
+    ]
+  where
+    gr :: [Sh] -> [([String], NonEmpty String)]
+    gr [] = []
+    gr (Sh x : rest) = case gr rest of
+        ([], xs) : xss -> ([], NE.cons x xs) : xss
+        xss            -> ([], pure x) : xss
+
+    gr (Comment c : rest) = case gr rest of
+        (cs, xs) : xss -> (c : cs, xs) : xss
+        []             -> [] -- end of comments are lost
+
+ykeyValuesFilt :: ann -> [(ann, String, Yaml ann)] -> Yaml ann
+ykeyValuesFilt ann xs = YKeyValues ann
+    [ x
+    | x@(_,_,y)  <- xs
+    , not (isEmpty y)
+    ]
+
+ylistFilt :: ann -> [Yaml ann] -> Yaml ann
+ylistFilt ann xs = YList ann
+    [ x
+    | x <- xs
+    , not (isEmpty x)
+    ]
+
+isEmpty :: Yaml ann -> Bool
+isEmpty (YList _ [])      = True
+isEmpty (YKeyValues _ []) = True
+isEmpty _                 = False
+
+-------------------------------------------------------------------------------
+-- ToYaml
+-------------------------------------------------------------------------------
+
+instance ToYaml Travis where
+    toYaml Travis {..} = ykeyValuesFilt []
+        [ "language"       ~> fromString travisLanguage
+        , "dist"           ~> fromString (showUbuntu travisUbuntu)
+        , "git"            ~> toYaml travisGit
+        , "branches"       ~> toYaml travisBranches
+        , "notifications"  ~> toYaml travisNotifications
+        , "services"       ~> YList [] (map fromString travisServices)
+        , "addons"         ~> toYaml travisAddons
+        , "cache"          ~> toYaml travisCache
+        , "before_cache"   ~> shListToYaml travisBeforeCache
+        , "matrix"         ~> toYaml travisMatrix
+        , "before_install" ~> shListToYaml travisBeforeInstall
+        , "install"        ~> shListToYaml travisInstall
+        , "script"         ~> shListToYaml travisScript
+        ]
+
+instance ToYaml TravisGit where
+    toYaml TravisGit {..} = ykeyValuesFilt []
+        [ "submodules" ~> toYaml tgSubmodules
+          ^^^ "whether to recursively clone submodules"
+        ]
+
+instance ToYaml TravisBranches where
+    toYaml TravisBranches {..} = ykeyValuesFilt []
+        [ "only" ~> ylistFilt [] (map fromString tbOnly)
+        ]
+
+instance ToYaml TravisNotifications where
+    toYaml TravisNotifications {..} = ykeyValuesFilt [] $ buildList $
+        for_ tnIRC $ \y -> item $ "irc" ~> toYaml y
+
+instance ToYaml TravisIRC where
+    toYaml TravisIRC {..} = ykeyValuesFilt []
+        [ "channels"  ~> YList [] (map fromString tiChannels)
+        , "skip_join" ~> toYaml tiSkipJoin
+        , "template"  ~> YList [] (map fromString tiTemplate)
+        ]
+
+instance ToYaml TravisCache where
+    toYaml TravisCache {..} = ykeyValuesFilt []
+        [ "directories" ~> ylistFilt []
+            [ fromString d
+            | d <- tcDirectories
+            ]
+        ]
+
+instance ToYaml TravisMatrix where
+    toYaml TravisMatrix {..} = ykeyValuesFilt []
+        [ "include"        ~> ylistFilt [] (map toYaml tmInclude)
+        , "allow_failures" ~> ylistFilt [] (map toYaml tmAllowFailures)
+        ]
+
+instance ToYaml TravisJob where
+    toYaml TravisJob {..} = ykeyValuesFilt [] $ buildList $ do
+        item $ "compiler" ~> fromString tjCompiler
+        item $ "addons" ~> toYaml (Aeson.toJSON tjAddons)
+        for_ tjEnv $ \e ->
+            item $ "env" ~> fromString e
+        unless (tjOS == "linux") $
+            item $ "os" ~> fromString tjOS
+
+instance ToYaml TravisAllowFailure where
+    toYaml TravisAllowFailure {..} = ykeyValuesFilt []
+        [ "compiler" ~> fromString tafCompiler
+        ]
+
+instance ToYaml TravisAddons where
+    toYaml TravisAddons {..} = ykeyValuesFilt [] $ buildList $ do
+        -- no apt on purpose
+        for_ taPostgres $ \p ->
+            item $ "postgresql" ~> fromString p
+
+-------------------------------------------------------------------------------
+-- ToJSON
+-------------------------------------------------------------------------------
+
+instance Aeson.ToJSON TravisAddons where
+    -- no postgresql on purpose
+    toJSON TravisAddons {..} = Aeson.object
+        [ "apt" Aeson..= taApt
+        ]
+
+instance Aeson.ToJSON TravisApt where
+    toJSON TravisApt {..} = Aeson.object
+        [ "packages" Aeson..= taPackages
+        , "sources"  Aeson..= taSources
+        ]
diff --git a/src/HaskellCI/Version.hs b/src/HaskellCI/Version.hs
deleted file mode 100644
--- a/src/HaskellCI/Version.hs
+++ /dev/null
@@ -1,42 +0,0 @@
-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/HaskellCI/YamlSyntax.hs b/src/HaskellCI/YamlSyntax.hs
new file mode 100644
--- /dev/null
+++ b/src/HaskellCI/YamlSyntax.hs
@@ -0,0 +1,317 @@
+{-# LANGUAGE DeriveFoldable      #-}
+{-# LANGUAGE DeriveFunctor       #-}
+{-# LANGUAGE DeriveTraversable   #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE GADTs #-}
+module HaskellCI.YamlSyntax (
+    Yaml (..),
+    reann,
+    ToYaml (..),
+    prettyYaml,
+    ) where
+
+import Prelude ()
+import Prelude.Compat
+
+import Data.Bifunctor     (first)
+import Data.List.NonEmpty (NonEmpty (..))
+import Data.Monoid        (Endo (..))
+import Data.String        (IsString (..))
+
+import qualified Data.Aeson              as Aeson
+import qualified Data.List.NonEmpty      as NE
+import qualified Data.Text               as T
+import qualified Data.Text.Encoding      as TE
+import qualified Data.Text.Lazy          as TL
+import qualified Data.Text.Lazy.Encoding as TLE
+import qualified Data.YAML               as YAML
+
+
+-- $setup
+-- >>> :set -XOverloadedStrings
+
+-------------------------------------------------------------------------------
+-- Yaml syntx
+-------------------------------------------------------------------------------
+
+-- | This is not complete YAML document tree;
+-- only as much as we need in @haskell-ci@.
+data Yaml ann
+    = YString ann String
+    | YBool ann Bool
+    | YList ann [Yaml ann]
+    | YKeyValues ann [(ann, String, Yaml ann)]
+    | YValue ann Aeson.Value  -- ^ inline JSON (for compactness)
+  deriving (Eq, Show, Functor, Foldable, Traversable)
+
+instance Monoid ann => IsString (Yaml ann) where
+    fromString = YString mempty
+
+-- | Re-annotate top-level term
+reann :: (ann -> ann) -> Yaml ann -> Yaml ann
+reann f (YString ann s)     = YString (f ann) s
+reann f (YBool ann b)       = YBool (f ann) b
+reann f (YList ann xs)      = YList (f ann) xs
+reann f (YKeyValues ann xs) = YKeyValues (f ann) xs
+reann f (YValue ann v)      = YValue (f ann) v
+
+-------------------------------------------------------------------------------
+-- Class
+-------------------------------------------------------------------------------
+
+class ToYaml a where
+    toYaml :: a -> Yaml [String]
+
+instance ann ~ [String] => ToYaml (Yaml ann) where
+    toYaml = id
+
+instance ToYaml Bool where
+    toYaml = YBool []
+
+instance ToYaml a => ToYaml [a] where
+    toYaml = YList [] . map toYaml
+
+instance ToYaml Aeson.Value where
+    toYaml = YValue []
+
+-------------------------------------------------------------------------------
+-- Converting to string
+-------------------------------------------------------------------------------
+
+-- | Convert 'Yaml' to 'String'. @ann@ can be converted to comments.
+--
+-- == Examples
+--
+-- >>> let demo = putStr . prettyYaml lines
+--
+-- >>> demo "foo"
+-- foo
+--
+-- >>> demo "foo: bar"
+-- "foo: bar"
+--
+-- >>> demo $ YString "a comment" "foo"
+-- # a comment
+-- foo
+--
+-- >>> demo $ YBool "a comment" True
+-- # a comment
+-- true
+--
+-- >>> demo $ YList "" []
+-- []
+--
+-- >>> demo $ YList "" ["foo", "foo: bar"]
+-- - foo
+-- - "foo: bar"
+--
+-- >>> demo $ YList "comment1" [YString "comment2" "foo", YString "comment3" "foo: bar"]
+-- # comment1
+-- #
+-- # comment2
+-- - foo
+-- # comment3
+-- - "foo: bar"
+--
+-- >>> demo $ YKeyValues "" []
+-- {}
+--
+-- >>> demo $ YKeyValues "" [("", "foo", "bar"), ("", "help", "welcome")]
+-- foo: bar
+-- help: welcome
+--
+-- >>> let nested = YKeyValues "comment1" [("comment2", "foo", YString "comment3" "bar"), ("comment4", "help", YString "comment5" "welcome")]
+-- >>> demo nested
+-- # comment1
+-- #
+-- # comment2
+-- #
+-- # comment3
+-- foo: bar
+-- # comment4
+-- #
+-- # comment5
+-- help: welcome
+--
+-- >>> demo $ YKeyValues "top" [("", "nested", nested)]
+-- # top
+-- nested:
+--   # comment1
+--   # 
+--   # comment2
+--   # 
+--   # comment3
+--   foo: bar
+--   # comment4
+--   # 
+--   # comment5
+--   help: welcome
+--
+-- >>> demo $ YValue "inline json" $ Aeson.toJSON [True, False, True]
+-- # inline json
+-- [true,false,true]
+--
+-- >>> demo $ YKeyValues "" [ ("", "addons", YValue "" $ Aeson.toJSON $ [Just "foo", Just "bar", Nothing]) ]
+-- addons: ["foo","bar",null]
+--
+-- >>> demo $ YString "" $ unlines ["foo","bar","baz"]
+-- "foo\nbar\nbaz\n"
+--
+-- >>> let multiline = YString "" $ unlines ["foo", "bar", "baz"]
+-- >>> demo $ YList "" [multiline, multiline]
+-- - |
+--   foo
+--   bar
+--   baz
+-- - |
+--   foo
+--   bar
+--   baz
+--
+-- >>> demo $ YKeyValues "" [("", "keyA", multiline), ("", "keyB", multiline)]
+-- keyA: |
+--   foo
+--   bar
+--   baz
+-- keyB: |
+--   foo
+--   bar
+--   baz
+--
+prettyYaml :: forall ann. (ann -> [String]) -> Yaml ann -> String
+prettyYaml comment' = flatten . go where
+    comment :: ann -> [String]
+    comment = concatMap lines' . comment' where
+        lines' "" = [""]
+        lines' s  = lines s 
+
+    go :: Yaml ann -> NonEmpty (Int, Line)
+    go (YString ann s) = case literal s of
+        Just ss -> pure (0, Line (comment ann) ss)
+        Nothing -> pure (0, Line (comment ann) (shows s))
+
+    go (YBool ann b) =
+        pure (0, Line (comment ann) (showString $ if b then "true" else "false"))
+
+    go (YValue ann v) =
+        pure (0, Line (comment ann) (showString $ TL.unpack $ TLE.decodeUtf8 $ Aeson.encode v))
+
+    go (YList ann [])     = pure (0, Line (comment ann) (showString "[]"))
+    go (YList ann (x:xs)) = y :| (ys ++ yss)
+      where
+        y :: (Int, Line)
+        ys :: [(Int, Line)]
+        ~(y :| ys) = case goSub x of
+            Right ((_, Line cs z) :| zs) ->
+                (0, Line (comment ann +++ cs) $ showString "- " . z) :|
+                fmap (first succ) zs
+
+            Left (cs, ls) -> 
+                (0, Line (comment ann +++ cs) $ showString "- |") :|
+                [ (1, Line [] (showString l))
+                | l <- ls
+                ]         
+
+        yss :: [(Int, Line)]
+        yss = do
+            e <- goSub <$> xs
+            case e of 
+                Right ((_, Line cs z) :| zs) ->
+                    (0, Line cs (showString "- " . z)) :
+                    fmap (first succ) zs
+                Left (cs, ls) -> 
+                    (0, Line cs $ showString "- |") :
+                    [ (1, Line [] (showString l))
+                    | l <- ls
+                    ]  
+
+    go (YKeyValues ann [])     = pure (0, Line (comment ann) (showString "{}"))
+    go (YKeyValues ann (x:xs)) = kv (comment ann) x <+> (xs >>= NE.toList . kv [])
+      where
+        kv :: [String] -> (ann, String, Yaml ann) -> NonEmpty (Int, Line)
+        kv cs (ann', k, v) = case goSub v of
+            -- single line
+            Right ((_, Line cs' s) :| []) | isScalar v ->
+                (0, Line (cs +++ comment ann' +++ cs') $
+                    showString k . showString ": " . s) :|
+                    []
+            -- multiline non escaped
+            Left (cs', ls) -> 
+                (0, Line (cs +++ comment ann' +++ cs') $
+                    showString k . showString ": |") :|
+                    [ (1, Line [] (showString l))
+                    | l <- ls
+                    ]
+            -- multiline
+            Right vs -> 
+                (0, Line (cs +++ comment ann') $ showString k . showChar ':') :|
+                NE.toList (fmap (first succ) vs)
+
+    -- which values can be on the same line with `:`
+    isScalar YBool {}   = True
+    isScalar YString {} = True
+    isScalar YValue {}  = True
+    isScalar _          = False
+
+    goSub :: Yaml ann -> Either ([String], [String]) (NonEmpty (Int, Line))
+    goSub (YString ann s) = case literal s of
+        Just ss -> Right (pure (0, Line (comment ann) ss))
+        Nothing -> case multiline s of
+            Just ll -> Left (comment ann, ll)
+            Nothing -> Right (pure (0, Line (comment ann) (shows s)))
+    goSub y = Right (go y)
+        
+    -- given "foo" can it be encode without quotes:
+    -- 
+    --    foo
+    --
+    literal :: String -> Maybe ShowS
+    literal s = case YAML.decodeStrict bs of
+        Right [t'] | t == t' -> Just (showString s)
+        _                    -> Nothing
+      where
+        t  = T.pack s
+        bs = TE.encodeUtf8 t
+        
+    -- when not top level, we can encode "foo\nbar\n" as
+    --
+    --     - |
+    --       foo
+    --       bar
+    --
+    -- Note: the input have to end with @\n@ for this to be triggered.
+    --
+    multiline :: String -> Maybe [String]
+    multiline s = case YAML.decodeStrict bs of
+        Right [[t']] | t == t' -> Just ls
+        _                      -> Nothing
+      where
+        ls = lines s
+        t  = T.pack s
+
+        ys = "- |\n" ++ concatMap (\l -> "  " ++ l ++ "\n") ls
+        yt  = T.pack ys
+        bs = TE.encodeUtf8 yt
+
+    -- when concatenating comment blocks, we add an empty line in between
+    (+++) :: [String] -> [String] -> [String]
+    [] +++ xs = xs
+    xs +++ [] = xs
+    xs +++ ys = xs ++ [""] ++ ys
+
+    -- We can concatenate a list to a 'NonEmpty' list, the result is 'NonEmpty'.
+    (<+>) :: NonEmpty a -> [a] -> NonEmpty a
+    (x :| xs) <+> ys = x :| (xs ++ ys)
+
+    flatten :: NonEmpty (Int, Line) -> String
+    flatten xs = appEndo (foldMap f xs) "" where
+        f (lvl, Line cs s) =
+            foldMap showComment cs <> g s
+          where
+            showComment "" = g (showString "#")
+            showComment c  = g (showString "# " . showString c)
+            g x = Endo (showString lvl' . x . showChar '\n')
+            lvl' = replicate (lvl * 2) ' '
+
+-- a 'Line' is comments before in and actual text after!
+data Line = Line [String] ShowS
diff --git a/test/Tests.hs b/test/Tests.hs
--- a/test/Tests.hs
+++ b/test/Tests.hs
@@ -1,28 +1,24 @@
 {-# LANGUAGE ViewPatterns #-}
 module Main (main) where
 
-import HaskellCI hiding (main)
+import HaskellCI             hiding (main)
+import HaskellCI.Diagnostics (runDiagnosticsT)
 
-import Control.Applicative ((<$>), (<*>))
-import Control.Exception (ErrorCall(..), throwIO, try)
-import Control.Monad.Trans.Maybe
-import Control.Monad.Trans.Writer
-import Data.Algorithm.Diff (Diff (..), getGroupedDiff)
-import Data.IORef
-import Data.List (isPrefixOf, stripPrefix)
-import Data.Maybe (mapMaybe)
-import Data.Monoid (mconcat)
-import System.Directory (doesFileExist, removeFile, setCurrentDirectory)
-import System.Exit (ExitCode(..), exitFailure)
-import System.FilePath (addExtension, (</>))
-import System.IO (hPutStrLn, stderr)
-import Test.Tasty (TestName, TestTree, defaultMain, testGroup)
+import Control.Applicative        ((<$>), (<*>))
+import Control.Exception          (ErrorCall (..), throwIO)
+import Data.Algorithm.Diff        (Diff (..), getGroupedDiff)
+import Data.List                  (stripPrefix)
+import Data.Maybe                 (mapMaybe)
+import Data.Monoid                (mconcat)
+import System.Directory           (doesFileExist, setCurrentDirectory)
+import System.FilePath            (addExtension)
+import Test.Tasty                 (TestName, TestTree, defaultMain, testGroup)
 import Test.Tasty.Golden.Advanced (goldenTest)
-import Text.Read (readMaybe)
+import Text.Read                  (readMaybe)
 
-import qualified Data.ByteString as BS
+import qualified Data.ByteString       as BS
 import qualified Data.ByteString.Char8 as BS8
-import qualified System.Console.ANSI as ANSI
+import qualified System.Console.ANSI   as ANSI
 
 main :: IO ()
 main = do
@@ -32,6 +28,7 @@
         , fixtureGoldenTest "cabal.project.empty-line"
         , fixtureGoldenTest "cabal.project.fail-versions"
         , fixtureGoldenTest "cabal.project.messy"
+        , fixtureGoldenTest "cabal.project.travis-patch"
         , testGroup "copy-fields"
             [ fixtureGoldenTest "cabal.project.copy-fields.all"
             , fixtureGoldenTest "cabal.project.copy-fields.some"
@@ -57,7 +54,7 @@
 fixtureGoldenTest fp = cabalGoldenTest fp outputRef errorRef $ do
     (argv, opts) <- makeTravisFlags
     let genConfig = travisFromConfigFile argv opts fp
-    execWriterT (runMaybeT genConfig)
+    runDiagnosticsT genConfig
   where
     outputRef = addExtension fp "travis.yml"
     errorRef = addExtension fp "stderr"
@@ -81,11 +78,16 @@
     (path, opts) <- parseTravis argv
     return (opts, path)
 
+data Result
+    = Success [String] [String]
+    | Failure [String]
+  deriving Eq
+
 cabalGoldenTest
     :: TestName
     -> FilePath
     -> FilePath
-    -> IO MakeTravisOutput
+    -> IO (Maybe [String], [String])
     -> TestTree
 cabalGoldenTest name outRef errRef act = goldenTest name readGolden act' cmp upd
   where
@@ -93,8 +95,8 @@
     readData fp = lines . BS8.unpack <$> BS.readFile fp
 
     act' = flip fmap act $ \r -> case r of
-        Success ws x -> Success (map formatDiagnostic ws) x
-        Failure errs -> Failure (map formatDiagnostic errs)
+        (Nothing, diags) -> Failure diags
+        (Just x, diags)  -> Success diags x
 
     readGolden = do
         refExists <- doesFileExist outRef
