diff --git a/ALTERNATIVES.md b/ALTERNATIVES.md
new file mode 100644
--- /dev/null
+++ b/ALTERNATIVES.md
@@ -0,0 +1,76 @@
+# Alternatives to Updo
+
+Updo uses `.dhall` configuration but you may want to configure your project in
+stack and mirror it to cabal or vica-versa with a project-to-project conversion.
+
+Updo can be of help with mirroring if you chose stack-first or cabal-first
+project maintenance but then you're own your own as far as managing GHC upgrades
+goes.
+
+## Project to Project Conversion
+
+We provide makefiles for converting a stack project to a cabal project with
+[`stack2cabal`](https://github.com/hasufell/stack2cabal) and for going the other
+way with [`cabal2stack`](https://github.com/iconnect/cabal2stack).
+
+### Configure with Stack
+
+A stack project is YAML, `stack.yaml` by default. Without conditionals or
+imports everything has to be in the same file except for stack's own [snapshot
+indirection](https://docs.haskellstack.org/en/stable/custom_snapshot/) to
+another file.
+
+There are a bunch of YAML formatting tools such as
+[vscode-yaml](https://www.npmjs.com/package/vscode-yaml).
+
+### Configure with Cabal
+
+A cabal project, `cabal.project` by default, is its own format. It has
+conditionals and imports.
+
+There is not a tool for formatting it. The `cabal format` command and the
+[cabal-fmt](https://github.com/phadej/cabal-fmt) tool both format packages, not
+projects.
+
+### Mirroring with `stack2cabal`
+
+This tool inspects the stack project and downloads source repositories to check
+for version constraints and generates a `cabal.project` and
+`cabal.project.freeze`.
+
+This tool is slow as it clones whole and sometimes large source repositories
+locally. This can be skipped with `--no-inspect-remotes` but the inspection
+ensures correct versions in the freeze file.
+
+```
+> stack2cabal --help
+Usage: stack2cabal [-f|--file STACK_YAML] [-o|--output-file CABAL_PROJECT] 
+                   [--no-inspect-remotes] [--no-pin-ghc] [--no-sort-repos] 
+                   [--no-run-hpack] [-p|--pin-hackage-index FUZZY_DATE]
+
+Available options:
+  --no-inspect-remotes     Don't check package names from remote git sources
+                           (this is faster, but may leave incorrect versions in
+                           cabal.project.freeze if remote packages overwrite
+                           stack resolver versions)
+```
+
+It generates a freeze file but these locks are problematic as they don't
+function as people expect and there are platform-specific [freeze
+problems](https://github.com/haskell/cabal/issues/8059).
+
+### Mirroring with `cabal2stack`
+
+This tool inspects cabal's plan so is more tied to the machine architecture
+where the plan was created by cabal. The generated stack project will differ if
+created on mac or linux but by adding a `--resolver` flag the generated project
+is buildable on both mac and linux.
+
+Like `stack2cabal`, this tool also needs to download remote source repositories
+and is about as slow because the download takes most of the time.
+
+The generated stack project will [vary by platform][c2s1] and it will [repeat
+dependencies][c2s4] already in the resolver.
+
+[c2s1]: https://github.com/iconnect/cabal2stack/issues/1
+[c2s4]: https://github.com/iconnect/cabal2stack/issues/4
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,2 @@
+### 1.0.0
+* First release
diff --git a/HACKING.md b/HACKING.md
new file mode 100644
--- /dev/null
+++ b/HACKING.md
@@ -0,0 +1,70 @@
+# Updo Hacking
+
+## Package Groups
+
+During the cabal solving step, cabal would fail to solve for dependencies for
+packages in a particular order. The grouping of packages preserves this.
+
+To mirror package groups from `project-dhall/pkgs/*.dhall` to
+`project-cabal/pkgs/*.config` use make, either for `pkgs.config` that imports
+all `pkgs/*.config` or for a single package group.
+
+```
+$ make project-cabal/pkgs.config
+$ cat project-cabal/pkgs.config
+import: project-cabal/pkgs/shims.config
+...
+import: project-cabal/pkgs/tools.config
+```
+
+When `project-dhall/pkgs-upgrade-todo.dhall` is:
+
+```dhall
+[ "./jobs/runner/" ]
+```
+
+Then the single `jobs.config` package group as generated is:
+
+```
+$ make project-cabal/pkgs/jobs.config
+$ cat project-cabal/pkgs/jobs.config
+if impl(ghc <= 8.10.7)
+  packages:
+      ./jobs/core/
+    , ./jobs/runner/
+else
+  packages:
+      ./jobs/core/
+
+-- TODO for impl(ghc >= 9.2.5)
+-- ./jobs/runner/
+```
+
+To split a group of packages into a dhall record of `{pkgs, done, todo}`:
+
+```
+$ ./updo/project-dhall/pkgs-upgrade-partition.hs ./project-dhall/pkgs/jobs.dhall
+{ pkgs = [ "./jobs/core/", "./jobs/runner/" ]
+, done = [ "./jobs/core/" ]
+, todo = [ "./jobs/runner/" ]
+}
+```
+
+## GHC Upgrade Hacking
+
+During a GHC version uprade, start by putting all packages into
+`pkgs-upgrade-todo.dhall` and progressively take one or a few packages out at at
+time and rerun the make targets to build the project you want to use.
+
+To sort all the packages.
+
+```
+$ cd project-dhall
+$ ./pkgs-sorted.hs
+[ "./admin-client/"
+, "./aeson-shim/"
+...
+, "./docs/"
+, "./testing/"
+]
+```
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+    means each individual or legal entity that creates, contributes to
+    the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+    means the combination of the Contributions of others (if any) used
+    by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+    means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+    means Source Code Form to which the initial Contributor has attached
+    the notice in Exhibit A, the Executable Form of such Source Code
+    Form, and Modifications of such Source Code Form, in each case
+    including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+    means
+
+    (a) that the initial Contributor has attached the notice described
+        in Exhibit B to the Covered Software; or
+
+    (b) that the Covered Software was made available under the terms of
+        version 1.1 or earlier of the License, but not also under the
+        terms of a Secondary License.
+
+1.6. "Executable Form"
+    means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+    means a work that combines Covered Software with other material, in
+    a separate file or files, that is not Covered Software.
+
+1.8. "License"
+    means this document.
+
+1.9. "Licensable"
+    means having the right to grant, to the maximum extent possible,
+    whether at the time of the initial grant or subsequently, any and
+    all of the rights conveyed by this License.
+
+1.10. "Modifications"
+    means any of the following:
+
+    (a) any file in Source Code Form that results from an addition to,
+        deletion from, or modification of the contents of Covered
+        Software; or
+
+    (b) any new file in Source Code Form that contains any Covered
+        Software.
+
+1.11. "Patent Claims" of a Contributor
+    means any patent claim(s), including without limitation, method,
+    process, and apparatus claims, in any patent Licensable by such
+    Contributor that would be infringed, but for the grant of the
+    License, by the making, using, selling, offering for sale, having
+    made, import, or transfer of either its Contributions or its
+    Contributor Version.
+
+1.12. "Secondary License"
+    means either the GNU General Public License, Version 2.0, the GNU
+    Lesser General Public License, Version 2.1, the GNU Affero General
+    Public License, Version 3.0, or any later versions of those
+    licenses.
+
+1.13. "Source Code Form"
+    means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+    means an individual or a legal entity exercising rights under this
+    License. For legal entities, "You" includes any entity that
+    controls, is controlled by, or is under common control with You. For
+    purposes of this definition, "control" means (a) the power, direct
+    or indirect, to cause the direction or management of such entity,
+    whether by contract or otherwise, or (b) ownership of more than
+    fifty percent (50%) of the outstanding shares or beneficial
+    ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+    Licensable by such Contributor to use, reproduce, make available,
+    modify, display, perform, distribute, and otherwise exploit its
+    Contributions, either on an unmodified basis, with Modifications, or
+    as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+    for sale, have made, import, and otherwise transfer either its
+    Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+    or
+
+(b) for infringements caused by: (i) Your and any other third party's
+    modifications of Covered Software, or (ii) the combination of its
+    Contributions with other software (except as part of its Contributor
+    Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+    its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+    Form, as described in Section 3.1, and You must inform recipients of
+    the Executable Form how they can obtain a copy of such Source Code
+    Form by reasonable means in a timely manner, at a charge no more
+    than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+    License, or sublicense it under different terms, provided that the
+    license for the Executable Form does not attempt to limit or alter
+    the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+*                                                                      *
+*  6. Disclaimer of Warranty                                           *
+*  -------------------------                                           *
+*                                                                      *
+*  Covered Software is provided under this License on an "as is"       *
+*  basis, without warranty of any kind, either expressed, implied, or  *
+*  statutory, including, without limitation, warranties that the       *
+*  Covered Software is free of defects, merchantable, fit for a        *
+*  particular purpose or non-infringing. The entire risk as to the     *
+*  quality and performance of the Covered Software is with You.        *
+*  Should any Covered Software prove defective in any respect, You     *
+*  (not any Contributor) assume the cost of any necessary servicing,   *
+*  repair, or correction. This disclaimer of warranty constitutes an   *
+*  essential part of this License. No use of any Covered Software is   *
+*  authorized under this License except under this disclaimer.         *
+*                                                                      *
+************************************************************************
+
+************************************************************************
+*                                                                      *
+*  7. Limitation of Liability                                          *
+*  --------------------------                                          *
+*                                                                      *
+*  Under no circumstances and under no legal theory, whether tort      *
+*  (including negligence), contract, or otherwise, shall any           *
+*  Contributor, or anyone who distributes Covered Software as          *
+*  permitted above, be liable to You for any direct, indirect,         *
+*  special, incidental, or consequential damages of any character      *
+*  including, without limitation, damages for lost profits, loss of    *
+*  goodwill, work stoppage, computer failure or malfunction, or any    *
+*  and all other commercial damages or losses, even if such party      *
+*  shall have been informed of the possibility of such damages. This   *
+*  limitation of liability shall not apply to liability for death or   *
+*  personal injury resulting from such party's negligence to the       *
+*  extent applicable law prohibits such limitation. Some               *
+*  jurisdictions do not allow the exclusion or limitation of           *
+*  incidental or consequential damages, so this exclusion and          *
+*  limitation may not apply to You.                                    *
+*                                                                      *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+  This Source Code Form is subject to the terms of the Mozilla Public
+  License, v. 2.0. If a copy of the MPL was not distributed with this
+  file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+  This Source Code Form is "Incompatible With Secondary Licenses", as
+  defined by the Mozilla Public License, v. 2.0.
diff --git a/Makefile b/Makefile
new file mode 100644
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,103 @@
+# How are we going to generate stack.yaml and stack.yaml.lock files?
+#
+# Choices are:
+#  - dhall2stack = via a temporary ghc-x.y.z.dhall2stack.yaml
+#  - dhall2yaml2stack = via a temporary ghc-x.y.z.dhall2yaml2stack.yaml
+STACK_VIA := dhall2stack
+
+# How are we going to generate the cabal.project file?
+#
+# Choices are:
+#  - dhall2config = via a temporary ghc-x.y.z.dhall2config.project
+#  - dhall2cabal = via a temporary ghc-x.y.z.dhall2cabal.project
+#
+# dhall2config is good when upgrading to a new GHC version. The
+# <pkg-group>.config files will show how many in a group are yet to be upgraded.
+# With the cabal imports it is possible to try things out quickly by editing the
+# config files themselves if you want to momentarily sidestep Updo's make cabal
+# project generation.
+#
+# dhall2cabal is good if you want the cabal project to closely match the layout
+# of the stack project when that is generated using dhall2stack.
+
+# dhall2cabal is also good if you need to use cabal < 3.8 where everything needs
+# to be in one file. To achieve this, the
+# project-dhall/project-dhall2cabal.dhall template will need to bring in the
+# contents of the stackage import like this:
+#
+# ${../../project-stackage/$(STACKAGE_VERSION).config as Text}
+CABAL_VIA := dhall2config
+
+UPDO_TMP := .updo
+
+# Updo Dhall gives us these targets:
+#  - dhall2stack-projects
+#  - dhall2cabal-projects
+#  - dhall2config-projects
+#  - project-sha256maps
+include updo/project-dhall/Makefile
+include updo/project-dhall2config/Makefile
+include updo/project-nix/Makefile
+
+# Project (and related sha256map) files used in production, not in GHC upgrade.
+.PHONY: projects
+projects: \
+  ghc-$(GHC_VERSION).sha256map.nix \
+  stack.yaml \
+  stack.yaml.lock \
+  cabal.project
+
+.PHONY: upgrade-projects
+upgrade-projects: \
+  project-nix/ghc-$(GHC_UPGRADE)/sha256map.nix \
+  stack.upgrade.yaml \
+  stack.upgrade.yaml.lock \
+  cabal.upgrade.project \
+  ghc-$(GHC_UPGRADE).dhall2stack.yaml \
+  ghc-$(GHC_UPGRADE).dhall2stack.yaml.lock \
+  ghc-$(GHC_UPGRADE).dhall2cabal.project
+
+# All the kinds of project files we might want to generate.
+#
+# These are alternative methods we could include but don't.
+#  - dhall2yaml2stack-projects
+.PHONY: all-possible-projects
+all-possible-projects: \
+  projects \
+  dhall2config-projects \
+  dhall2cabal-projects \
+  dhall2stack-projects
+
+.PHONY: clean
+clean:
+	rm -f ghc-*.stack.* ghc-*.dhall2config.* ghc-*.dhall2cabal.* ghc-*.dhall2stack.* ghc-*.stack2cabal.* ghc-*.cabal2stack.* ghc-*.dhall2yaml2stack.* ghc-*.sha256map.nix
+
+.PHONY: cabal
+cabal: \
+  ghc-$(GHC_VERSION).$(CABAL_VIA).project \
+  ghc-$(GHC_UPGRADE).$(CABAL_VIA).project
+
+.PHONY: stack
+stack: \
+  ghc-$(GHC_VERSION).$(STACK_VIA).yaml \
+  ghc-$(GHC_VERSION).$(STACK_VIA).yaml.lock \
+  ghc-$(GHC_UPGRADE).$(STACK_VIA).yaml \
+  ghc-$(GHC_UPGRADE).$(STACK_VIA).yaml.lock
+
+cabal.project: ghc-$(GHC_VERSION).$(CABAL_VIA).project
+	cp $^ $@
+
+cabal.upgrade.project: ghc-$(GHC_UPGRADE).$(CABAL_VIA).project
+	cp $^ $@
+
+stack.yaml: ghc-$(GHC_VERSION).$(STACK_VIA).yaml
+	cp $< $@
+
+stack.upgrade.yaml: ghc-$(GHC_UPGRADE).$(STACK_VIA).yaml
+	cp $< $@
+
+stack.yaml.lock: stack.yaml
+	stack build --test --no-run-tests --bench --no-run-benchmarks --dry-run --stack-yaml $<
+
+stack.upgrade.yaml.lock: stack.upgrade.yaml
+	stack build --test --no-run-tests --bench --no-run-benchmarks --dry-run --stack-yaml $<
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,567 @@
+# 🫸 Updo 🫷
+
+> A hairstyle and a way of maintaining and upgrading Haskell projects.
+
+### What is a Project?
+
+Updo is good for project packages, constraints, source repository packages and
+stackage resolver[^download-stackage-config]. More than this can be added into a
+project template.
+
+[^download-stackage-config]: For cabal, the cabal.config will have to be
+  downloaded to `project-stackage/resolver.config` and then edited to comment
+  out constraints that would otherwise lead to impossible to solve conflicting
+  constraints because cabal constraints are additive and cannot be overridden.
+
+### Goals
+
+1. Map a single set of packages, constraints and source repository packages to
+   projects for cabal and stack.
+2. Minimize the effort of upgrading constraints, source repository packages and
+   GHC versions.
+
+We do this with configuration in `.dhall` files, makefiles and `dhall text`
+templates. We also have some simple Haskell scripts for operations not possible
+in Dhall like sorting a `List Text`[^dhall-sort-list-nat].
+
+[^dhall-sort-list-nat]: Dhall can only sort `List Natural`.
+
+# Template Outputs
+
+We generate projects from the configured inputs using templates.  With the
+`dhall text` command we're able to mirror to both stack and cabal projects using
+text templates. See [templates](./TEMPLATES.md) for more.
+
+We provide one stack template `dhall2stack`. The `dhall2cabal` template for
+cabal is so similar that the generated outputs can be compared using file diff
+tooling. However, we prefer the `dhall2config` template because it retains
+package groups that can bring order to an [upgrade](#upgrading-a-project) if the
+cabal solving order is roughly captured by package group ordering.
+
+We provide one level of nesting for grouping packages together as inputs. Groups
+are preserved or flattened depending on the template.
+
+* `dhall2stack` - Stack projects cannot import so necessarily must lose the
+  grouping and contain the list of packages.
+* `dhall2cabal` - This template lists packages within the cabal project.
+* `dhall2config` - When generating the cabal project we keep [package
+  groups](#package-groups), generating the same tree structure but replacing the
+  leaves. From each `group-name.dhall` we generate a `group-name.config` file.
+
+It is nice to be able to look at packages both grouped and as one sorted list.
+Find the flat sorted lists of packages used by some templates in the `.updo`
+folder.
+
+```
+.updo
+├── pkgs-sorted.dhall        ▨ List Text
+└── pkgs-upgrade-done.dhall  ▨ List Text
+```
+
+If you don't want groups then please put all packages in the one group.
+
+# Maintaining a Project
+
+Maintaining a project involves configuring or reconfiguring inputs and then
+using make to generate the projects. What do we want to configure? We don't need
+much:
+
+1. A stackage resolver[^download-stackage-config].
+2. A list of constraints (package dependencies with their versions).
+3. A list of source repository packages, for unpublished[^unpublished-packages]
+  packages or for unpublished versions of packages.
+4. A list of packages.
+
+[^unpublished-packages]: A package not published to hackage or stackage.
+
+
+All configuration goes into `./project-dhall` (where `.` is the root folder for
+your Haskell project) except for the `cabal.config` that we'll need to download
+from stackage. Save this file as `project-stackage/lts-m.n.config` using the
+exact resolver name.
+
+```
+project-stackage
+└── lts-m.n.config
+```
+
+For each `ghc-x.y.z` compiler version, create this set of inputs and templates:
+
+```
+project-dhall
+└── ghc-x.y.z
+    ├── constraints.dhall      ▨ List { dep : Text, ver : Text }
+    ├── deps-external.dhall    ▨ List { loc : Text, tag : Text, sub : List Text }
+    ├── deps-internal.dhall    ▨ List { loc : Text, tag : Text, sub : List Text }
+    ├── forks-external.dhall   ▨ List { loc : Text, tag : Text, sub : List Text }
+    ├── forks-internal.dhall   ▨ List { loc : Text, tag : Text, sub : List Text }
+    └── text-templates
+        ├── dhall2cabal.dhall  ▨ template for ghc-x.y.z.dhall2cabal.project
+        ├── dhall2config.dhall ▨ template for ghc-x.y.z.dhall2config.project
+        ├── dhall2stack.dhall  ▨ template for ghc-x.y.z.dhall2stack.yaml
+        └── stacksnippet.dhall ▨ anything for ghc-x.y.z.dhall2stack.yaml
+```
+
+Anything in `stacksnippet.dhall` gets added to the top of the generated stack
+project[^base.yaml].  The rest of the files are inputs.
+
+[^base.yaml]: Anything in `stacksnippet.dhall` will be used by
+  [dhall2yaml2stack](alternatives/yaml2stack#readme) too and is put into a
+  `base.yaml`, the topmost fragment when stitching together fragments.
+
+### 1. Stackage Resolver and Constraints
+
+The stackage resolver is able to provide us with a list of packages that work
+together for both stack and cabal. Any other dependencies as constraints or
+source packages are either not found on stackage or are different versions.
+
+By specifying a resolver with stack we get a list of exact `==` versions for
+packages published to stackage. Stackage also publishes a `cabal.config` file
+that has these package versions as cabal constraints. As cabal constraints are
+additive, in practice importing directly from stackage often leads to conflicts
+between stackage's constraints and the project's own constraints and for that
+reason we'll usually have to download the `cabal.config` from
+stackage[^stackage-config-url-path] and comment out lines that are conflicted.
+
+[^stackage-config-url-path]: The URL path to download this file from is
+  `/resolver/cabal.config`. The latest nightly is always at
+  https://www.stackage.org/nightly/cabal.config.
+
+We don't expect any packages to impose constraints on their dependencies at the
+package level, in their `.cabal` files, but if they do then these constraints
+must fit with constraints at the project level.
+
+### 2. Constraints
+
+In constraints[^constraints] put published packages that you want to use that are
+not on stackage or if they are on stackage where you want to use a different
+version.
+
+[^constraints]: To stack, constraints and source repository packages are
+    both `extra-deps`. We use cabal-like nomenclature but, be warned, cabal
+    constraints cover more than just `package ==version`, such as flags:
+    ```cabal
+    constraints:
+        bson -_old-network
+      , HaXml +splitbase
+    ```
+
+```dhall
+[ { dep = "diagrams-postscript", ver = "1.5" }
+, { dep = "diagrams-svg", ver = "1.4.3" }
+]
+```
+
+The type of constraints is a list of records with dependency and version fields:
+
+```dhall
+List { dep : Text, ver : Text }
+```
+
+### 3. Source Repository Packages
+
+There are various reasons to depend on source packages and forks of source
+packages; to keep a mirror within your organization, to add fixes that haven't been
+upstreamed yet, or to pick up a fix someone else has made but hasn't yet been merged
+upstream, released and published.
+
+```
+project-dhall
+└── ghc-x.y.z
+    ├── deps-external.dhall    ▨ List { loc : Text, tag : Text, sub : List Text }
+    ├── deps-internal.dhall    ▨ List { loc : Text, tag : Text, sub : List Text }
+    ├── forks-external.dhall   ▨ List { loc : Text, tag : Text, sub : List Text }
+    └── forks-internal.dhall   ▨ List { loc : Text, tag : Text, sub : List Text }
+```
+
+The type of these files are all the same. They contain a list of records with
+fields for location, tag (commit SHA) and subdirectories:
+
+```dhall
+List { loc : Text, tag : Text, sub : List Text }
+```
+
+Any empty `sub` field must be type annotated like this `[] : List Text`. Each
+source repository package record will bring in one dependency or as many as
+there are `sub` fields.
+
+These source repository `*.dhall` files are for original work from your
+organisation (internal) and from third parties (external) and for internal and
+external forks. If using the empty list in any one of these files then use an
+explicit type annotation:
+
+```dhall
+[] : List { loc : Text, tag : Text, sub : List Text }
+```
+
+### 4. Package Groups
+In the `pkgs` folder, create one or more groups for related packages.
+
+```
+project-dhall
+├── ghc-x.y.z
+├── pkgs
+│   ├── db.dhall      ▨ List Text
+│   ├── server.dhall  ▨ List Text
+│   └── tools.dhall   ▨ List Text
+└── pkg-groups.dhall  ▨ List Text
+```
+
+The contents of each group is a `List Text` of relative paths to folders
+containing package `.cabal` files.
+
+```dhall
+-- ./project-dhall/pkgs/tools.dhall
+[ "tool/linter/"
+, "tool/formatter/"
+]
+```
+
+List the package groups in `pkg-groups.dhall`[^pkg-groups]. A good order to use
+is the order of cabal constraint solving that you may witness when upgrading a
+project and seeing solving failures.
+
+[^pkg-groups]: Dhall can't do arbitrary IO like reading files from a folder.
+
+```dhall
+-- ./project-dhall/pkg-groups.dhall
+[ "tools"
+, "db"
+, "server"
+]
+```
+
+This gives you control of the imports in the generated
+`./project-cabal/pkgs.config`, itself imported into
+`ghc-x.y.z.dhall2config.project`:
+
+* Use `CABAL_RELATIVITY=ImportRelative` to be import-compatible with `cabal-3.10`.
+  ```cabal
+  -- ./project-cabal/pkgs.config
+  import: ./pkgs/tools.config
+  import: ./pkgs/db.config
+  import: ./pkgs/server.config
+  ```
+* The default of `CABAL_RELATIVITY=ProjectRelative` is import-compatible with `cabal-3.8`.
+  ```cabal
+  -- ./project-cabal/pkgs.config
+  import: ./project-cabal/pkgs/tools.config
+  import: ./project-cabal/pkgs/db.config
+  import: ./project-cabal/pkgs/server.config
+  ```
+
+
+```cabal
+-- ./ghc-x.y.z.dhall2config.project
+import: ./project-stackage/lts-m.n.config
+
+import: ./project-cabal/pkgs.config
+
+import: ./project-cabal/ghc-x.y.z/constraints.config
+import: ./project-cabal/ghc-x.y.z/deps-external.config
+import: ./project-cabal/ghc-x.y.z/deps-internal.config
+import: ./project-cabal/ghc-x.y.z/forks-external.config
+import: ./project-cabal/ghc-x.y.z/forks-internal.config
+```
+
+# Upgrading a Project
+
+### GHC Upgrade
+For a GHC compiler upgrade, add another folder to `project-dhall` for the
+upgrade from `ghc-u.v.w` to `ghc-x.y.z`:
+
+```
+project-dhall
+├── ghc-u.v.w
+├── ghc-x.y.z
+├── pkgs
+├── pkg-groups.dhall        ▨ List Text
+└── pkgs-upgrade-todo.dhall ▨ List Text
+```
+
+At the start of a GHC upgrade put all packages into
+`project-dhall/pkgs-upgrade-todo.dhall`. As the upgrade progresses remove
+packages from this list as you work on them.
+
+### Constraint Upgrade
+For a dependency version upgrade, add or change an entry in `constraints.dhall`
+and if that dependency is on stackage for the resolver we're using and if the
+version there differs then comment out that entry in the downloaded
+`project-stackage/resolver-name.config` file to avoid impossible constraint
+solving conflicts with cabal.
+
+### Source Repository Upgrade
+For a source repository upgrade, bump the `tag` field if picking up a newer
+version.
+
+If the `tag` you were on has been superseded by a published version then delete
+the entry and add a constraint unless the published version is already a match
+in the downloaded `cabal.config` file from stackage.
+
+### Adding a Fork
+If you are forking then you'll want to add a record to `fork-internal.dhall` and
+then:
+- If forking a stackage package: comment out the entry in the downloaded `cabal.config`
+- If forking a hackage package: delete the matching entry in `constraints.dhall`
+
+Using someone else's fork is the same except add the record to
+`fork-external.dhall`.
+
+Unforking goes the other way, remove the `fork-*.dhall` entry and either
+fallback to the stackage version or another version for which you'll add an
+entry in `constraints.dhall`.
+
+# Getting Started
+
+Dhall is a prerequisite.
+
+As well as other make targets, updo can bootstrap itself by adding one of two
+recipes to `project-files.mk`:
+
+```make
+UPDO_VERSION ?= 1.0.0
+HACKAGE := http://hackage.haskell.org/package
+UPDO_URL := ${HACKAGE}/updo-${UPDO_VERSION}/updo-${UPDO_VERSION}.tar.gz
+```
+
+1. When opting for the scripts:
+
+```make
+updo/Makefile:
+	rm -rf updo
+	curl -sSL ${UPDO_URL} | tar -xz
+	mv updo-* updo
+	chmod +x $$(grep -RIl '^#!' updo)
+```
+
+2. When opting to install the included executables:
+
+```make
+updo/Makefile:
+	rm -rf updo
+	curl -sSL ${UPDO_URL} | tar -xz
+	cd updo-${UPDO_VERSION}
+	stack install
+	cd -
+	mv updo-* updo
+```
+
+Instead of curl and tar, `cabal get` or `stack unpack` can unpack package sources.
+
+# Make Targets
+
+In the root of your project, add two files.
+
+```
+.
+├── project-files.mk
+└── project-versions.mk
+```
+
+In `project-versions.mk` set variables for GHC and STACKAGE.
+
+```make
+GHC_VERSION := u.v.w
+GHC_UPGRADE := x.y.z
+STACKAGE_VERSION := lts-j.k
+STACKAGE_UPGRADE := lts-m.n
+```
+
+Here is a basic set up for `project-files.mk`:
+
+```make
+include project-versions.mk
+include updo/Makefile
+```
+
+### Canonical Targets
+
+We consider the following as canonical project names and no target is required
+to build them (they're built by the default `all` target):
+
+```
+$ make -f project-files.mk
+```
+
+- `stack.yaml`
+- `cabal.project`
+- `stack.upgrade.yaml`
+- `cabal.upgrade.project`
+
+We use CABAL_VIA and STACK_VIA makefile variables to decide which `ghc-x.y.z`
+prefixed projects will be copied to the default project names (`cabal.project`
+and `stack.yaml`). This way, Updo will create a single pair of projects for one
+version of GHC.
+
+### GHC-Prefixed Targets
+
+We can build `ghc-x.y.z` projects explicitly:
+
+```
+$ make -f project-files.mk ghc-x.y.z.dhall2config.project
+$ make -f project-files.mk ghc-x.y.z.dhall2cabal.project
+$ make -f project-files.mk ghc-x.y.z.dhall2stack.yaml
+```
+
+### Modified Time Travel
+
+Be careful with modified time that make relies on. If you hand-modify a target
+so that it is newer than a prerequisite then make will not use its recipe to
+build the target. This is easy to do inadvertently with a git GUI tool when
+discarding changes or by doing a `git restore <project-name>`. As git advances
+the modified time, this is effectively the same as `touch <project-name>`.  To
+recover from this situation, either;
+- Remove the target with `rm <project-name>`.
+- Run make with its `--always-make` flag but be careful with this if
+  bootstrapping updo itself within the makefile.
+
+### Lock Files
+
+For stack, the project and its lock are separate targets (`.yaml` and
+`.yaml.lock`).
+
+```
+$ make -f project-files.mk ghc-x.y.z.dhall2stack.yaml
+dhall text --file project-dhall/ghc-x.y.z/project-stack.dhall > ghc-x.y.z.dhall2stack.yaml
+```
+
+```
+$ make -f project-files.mk ghc-x.y.z.dhall2stack.yaml.lock
+stack build --dry-run --stack-yaml ghc-x.y.z.dhall2stack.yaml
+```
+
+We don't provide a make target for cabal `.project.freeze` files, relying
+instead on a combination of constraints, those in the `cabal.config` downloaded
+from stackage and those we add ourselves to `constraints.dhall`. If you do want
+to freeze anyway there's a cabal command to generate a freeze file.
+
+```
+$ make -f project-files.mk ghc-x.y.z.dhall2cabal.project
+dhall text --file project-dhall/ghc-x.y.z/project-cabal.dhall > ghc-x.y.z.dhall2cabal.project
+```
+
+# Clean GHC-Prefixed Projects
+
+There's a make target to remove all `ghc-x.y.z` prefixed projects:
+
+```
+$ make -f project-files.mk clean
+rm -f ghc-*.stack.* ghc-*.dhall2config.* ghc-*.dhall2cabal.* ghc-*.dhall2stack.*
+ghc-*.stack2cabal.* ghc-*.cabal2stack.* ghc-*.dhall2yaml2stack.* ghc-*.sha256map.nix
+```
+
+# GHC-Prefixed Projects as Temporary
+
+To treat `ghc-x.y.z` prefixed files as temporary, add lines like these to
+`project-files.mk`:
+
+```make
+.INTERMEDIATE: ghc-$(GHC_VERSION).dhall2config.project
+.INTERMEDIATE: ghc-$(GHC_VERSION).dhall2cabal.project
+.INTERMEDIATE: ghc-$(GHC_VERSION).dhall2stack.yaml
+.INTERMEDIATE: ghc-$(GHC_VERSION).sha256map.nix
+```
+
+# SHA256 Map Generation Method
+
+With this snippet from `project-files.mk`, we can switch between two methods of
+generating `ghc-x.y.z.sha256map.nix`.
+
+```make
+# How to generate project-nix/ghc-$(GHC_VERSION)/sha256map.nix?
+# This is copied from ghc-$(GHC_VERSION).sha256map.nix.
+#  - false to generate from *.dhall inputs via sha256map.hs.
+#  - true to generate from stack.yaml via sha256map.py.
+SHA256MAP_VIA_PYTHON ?= false
+
+# If true, generate the sha256map from the stack.yaml with python,
+# overriding the recipe for this target.
+ifeq ($(SHA256MAP_VIA_PYTHON), true)
+ghc-$(GHC_VERSION).sha256map.nix: stack.yaml
+	updo/project-nix/sha256map.py <$^ >$@
+endif
+```
+
+The default is to use the `sha256map.hs` script to generate
+it[^replacing_versions]:
+
+[^replacing_versions]: Using ghc-x.y.z and lts-m.n in the example output, not
+  the actual GHC version or resolver.
+
+```
+$ make -f project-files.mk ghc-x.y.z.sha256map.nix
+echo \
+  '[./project-dhall/ghc-x.y.z/deps-external.dhall
+  , ./project-dhall/ghc-x.y.z/deps-internal.dhall
+  , ./project-dhall/ghc-x.y.z/forks-external.dhall
+  , ./project-dhall/ghc-x.y.z/forks-internal.dhall
+  , ([] : List {loc : Text, tag : Text, sub : List Text})
+  ]' \
+  | ./updo/project-nix/sha256map.hs > ghc-x.y.z.sha256map.nix
+```
+
+To use the `sha256map.py` script instead that is much slower:
+
+```
+$ SHA256MAP_VIA_PYTHON=true \
+  make -f project-files.mk project-nix/ghc-x.y.z/sha256map.nix
+updo/project-nix/sha256map.py <stack.yaml >ghc-x.y.z.sha256map.nix
+mkdir -p project-nix/ghc-x.y.z && cp ghc-x.y.z.sha256map.nix project-nix/ghc-x.y.z/sha256map.nix
+rm ghc-x.y.z.sha256map.nix
+
+```
+
+You can [read more about Updo Nix](project-nix#readme) and its use with
+haskell.nix.
+
+# Installed Exes or Scripts
+
+To use the executables of Updo's own package, by default, instead of its
+scripts, add these lines to `project-files.mk`:
+
+```make
+SHA256MAP_HS_EXE ?= true
+PKG_GROUPS_HS_EXE ?= true
+PKGS_SORTED_HS_EXE ?= true
+PKGS_UPGRADE_DONE_HS_EXE ?= true
+```
+
+### Why Dhall for Configuration?
+
+Dhall has excellent imports and is an intentionally limited typed programming
+language. We use dhall's `text` command to write cabal and stack projects using
+[dhall text templating][dhall-text-templating].
+
+The `dhall` executable comes with a `format` command[^format-lsp]. This helps in
+the maintenance of the various `.dhall` files.
+
+An example of the limits of the language we encountered in Updo is not being
+able to compare anything but `Natural` values. Sorting something like `List
+Text` is not possible but there is great interop with Haskell so this can be
+done over there as we do with scripts.
+
+## Acknowledgements
+
+I already knew Dhall well from maintaining hpack-dhall. Thanks Simon Hengel for
+suggesting I take over its maintenance.
+
+FP Complete is the corporate sponsor of this tooling. Thanks to my collegues;
+Daniel Herrera Rendón for first seeing this could work, Neil Mayhew for makefile
+tips and tricks, Abel Sen for careful reviews and thoughtful suggestions and
+Eduardo Lemos Rocha for advocacy and enthusiasm.
+
+Please contact me for any questions, problems, suggestions or the like.
+Maintenance of Updo will be coming from me and not FP Complete.
+
+Updo grew to fill a need after every other way I could find to maintain projects
+was unsatisfactory for the specific problem I had, herding hundreds of packages
+and their dependencies into buildable projects over two compiler versions for
+both cabal and stack.  The more I use it, the more I love it as a fast, simple
+and appropriate solution for maintaining Haskell projects.
+
+Thanks FP Complete.
+
+[^format-lsp]: Formatting is also available with the [Dhall LSP Server][LSP].
+
+[dhall-text-templating]: https://www.haskellforall.com/2017/06/dhall-is-now-template-engine.html
+[LSP]: https://github.com/PanAeon/vscode-dhall-lsp-server
diff --git a/TEMPLATES.md b/TEMPLATES.md
new file mode 100644
--- /dev/null
+++ b/TEMPLATES.md
@@ -0,0 +1,21 @@
+# Dhall Text Templates
+
+We use Dhall text templates in `project-dhall/ghc-x.y.z/text-templates`:
+
+- From template `dhall2cabal.dhall`, we generate `ghc-x.y.z.dhall2cabal.project`
+- From template `dhall2stack.dhall`, we generate `ghc-x.y.z.dhall2stack.yaml`
+
+Dhall text templating was
+[announced](https://www.haskellforall.com/2017/06/dhall-is-now-template-engine.html)
+in June 2017 and this is now a command of the dhall executable:
+
+```
+$ dhall text --help`
+Usage: dhall text [--file FILE] [--output FILE]
+  Render a Dhall expression that evaluates to a Text literal
+
+Available options:
+  --file FILE              Read expression from a file instead of standard input
+  --output FILE            Write result to a file instead of standard output
+  -h,--help                Show this help text
+```
diff --git a/TESTING.md b/TESTING.md
new file mode 100644
--- /dev/null
+++ b/TESTING.md
@@ -0,0 +1,185 @@
+# Testing Updo
+
+Assuming this layout of make files in updo and at the root of your project.
+
+```
+├── updo
+│   ├── Makefile
+│   ├── project-dhall
+│   │   └── Makefile
+│   ├── project-dhall2config
+│   │   ├── Makefile
+│   └── project-nix
+│       └── Makefile
+├── project-files.mk
+└── project-versions.mk
+```
+
+We'll need some versions, something like:
+
+```
+$ cat project-versions.mk 
+# Versions of GHC and stackage resolver, the ones we're on and the next ones
+# we're upgrading to.
+GHC_VERSION := 8.10.7
+GHC_UPGRADE := 9.2.5
+STACKAGE_VERSION := lts-18.27
+STACKAGE_UPGRADE := lts-20.5
+```
+
+We'll need to set up our `project-files.mk`:
+
+```make
+# How to generate project-nix/ghc-$(GHC_VERSION)/sha256map.nix?
+# This is copied from ghc-$(GHC_VERSION).sha256map.nix.
+#  - false to generate from *.dhall inputs via sha256map.hs.
+#  - true to generate from stack.yaml via sha256map.py.
+SHA256MAP_VIA_PYTHON := false
+
+include project-versions.mk
+include updo/Makefile
+
+project-nix/ghc-%/sha256map.nix: ghc-%.sha256map.nix
+	mkdir -p $(@D) && cp $^ $@
+
+.PHONY: all
+all: \
+  projects \
+  project-nix/ghc-$(GHC_VERSION)/sha256map.nix
+
+# To make stack.yaml or cabal.project and no other, mark the file we copy from
+# as intermediate. This is all we want when not doing a GHC upgrade.
+#
+# Comment out these .INTERMEDIATE targets to allow these files to be kept.
+.INTERMEDIATE: ghc-$(GHC_VERSION).$(CABAL_VIA).project
+.INTERMEDIATE: ghc-$(GHC_UPGRADE).$(CABAL_VIA).project
+.INTERMEDIATE: ghc-$(GHC_VERSION).$(STACK_VIA).yaml
+.INTERMEDIATE: ghc-$(GHC_UPGRADE).$(STACK_VIA).yaml
+.INTERMEDIATE: ghc-$(GHC_VERSION).sha256map.nix
+.INTERMEDIATE: ghc-$(GHC_UPGRADE).sha256map.nix
+
+# Alternative targets for generating project files (not recommended):
+#  - ghc-x.y.z.stack2cabal.project
+#  - ghc-x.y.z.cabal2stack.yaml
+#  - ghc-x.y.z.dhall2yaml2stack.yaml
+include updo/alternatives/cabal2stack/Makefile
+include updo/alternatives/stack2cabal/Makefile
+include updo/alternatives/yaml2stack/Makefile
+
+# If true, generate the sha256map from the stack.yaml with python,
+# overriding the recipe for this target.
+ifeq ($(SHA256MAP_VIA_PYTHON), true)
+ghc-$(GHC_VERSION).sha256map.nix: stack.yaml
+	updo/project-nix/sha256map.py <$^ >$@
+ghc-$(GHC_UPGRADE).sha256map.nix: stack.yaml
+	updo/project-nix/sha256map.py <$^ >$@
+endif
+
+.DEFAULT_GOAL := all
+```
+
+## Default Targets
+
+The default targets are `stack.yaml` and `cabal.project`.
+
+* [ ] Can we build just those without leaving behind[^check-temp-files] any
+      temporary files?
+
+[^check-temp-files]: You could check for unwanted files with `git status`.
+
+```
+$ make -f project-files.mk all --always-make
+$ make -f project-files.mk all
+```
+
+## All Possible Targets
+
+Comment out the `.INTERMEDIATE` targets in `project-files.mk` and then:
+
+```
+$ make -f project-files.mk all-possible-projects --always-make --jobs
+```
+
+* [ ] Does this make two sets of each of the following?
+
+- `ghc-x.y.z.dhall2cabal.project`
+- `ghc-x.y.z.dhall2config.project`
+- `ghc-x.y.z.dhall2stack.yaml`
+- `ghc-x.y.z.dhall2stack.yaml.lock`
+- `ghc-x.y.z.sha256map.nix`
+
+## Clean
+
+* [ ] After making `all-possible-projects`, can we clean all of these?
+
+```
+$ make -f project-files.mk clean
+```
+
+## SHA Map Targets
+
+* [ ] Can we build `ghc-x.y.z.sha256map.nix`?
+
+```
+$ make -f project-files.mk project-sha256maps
+```
+
+Try again flipping the sense of the variable SHA256MAP_VIA_PYTHON or by setting
+it on the command line:
+
+```
+$ make -f project-files.mk ghc-x.y.z.sha256map.nix --always-make SHA256MAP_VIA_PYTHON=true
+```
+
+* [ ] Is it generating maps using another script, the Python one instead of the Haskell one?
+* [ ] Are the entries in the generated map sorted in both cases?
+
+This should make two of `ghc-x.y.z.sha256map.nix`[^version_only]. You might like to use those
+file names as targets explicitly.
+
+[^version_only]: For now we only provide the option to pick which script to use
+  to generate `ghc-$(GHC_VERSION).sha256map.nix`. The
+  `ghc-$(GHC_UPGRADE).sha256map.nix` is only ever generated using the Haskell
+  script.
+
+## Sorting Packages Works
+
+We need to run a Haskell script to generate `.updo/pkgs-sorted.dhall`.  After a
+cabal clean, this script will need to download dependencies.
+
+* [ ] Can it do that without polluting the generated file with status updates from cabal saying
+      that it has downloaded this or that dependency[^silent-script]?
+      
+[^silent-script]: The Haskell script must run silently for this to work.
+
+```
+$ cabal clean
+$ make -f project-files.mk pkgs-sorted --always-make
+updo/project-dhall/pkgs-sorted.hs > .updo/pkgs-sorted.dhall
+```
+
+## Upgrade Packages Done Works
+
+Generated files, not for source control are written to  `.updo`.
+
+* [ ] Can we generate `.updo/pkgs-upgrade-done.dhall`?
+* [ ] Does adding or removing packages from
+      `project-dhall/pkgs-upgrade-todo.dhall` change the list of packages in
+      `.updo/pkgs-upgrade-done.dhall`?
+
+```
+$ make -f project-files.mk pkgs-upgrade-done --always-make
+updo/project-dhall/pkgs-sorted.hs > .updo/pkgs-sorted.dhall
+./updo/project-dhall/pkgs-upgrade-done.hs \
+  ./.updo/pkgs-sorted.dhall \
+  ./project-dhall/pkgs-upgrade-todo.dhall \
+  > .updo/pkgs-upgrade-done.dhall
+```
+
+## Alternative Targets
+
+* [ ] Can we build these targets?
+
+- `ghc-x.y.z.dhall2yaml2stack.yaml`
+- `ghc-x.y.z.stack2cabal.project`
+- `ghc-x.y.z.cabal2stack.yaml`
diff --git a/alternatives/cabal2stack/Makefile b/alternatives/cabal2stack/Makefile
new file mode 100644
--- /dev/null
+++ b/alternatives/cabal2stack/Makefile
@@ -0,0 +1,19 @@
+.PHONY: cabal2stack-projects
+cabal2stack-projects: \
+  ghc-$(GHC_VERSION).cabal2stack.yaml \
+  ghc-$(GHC_VERSION).cabal2stack.yaml.lock \
+  ghc-$(GHC_UPGRADE).cabal2stack.yaml \
+  ghc-$(GHC_UPGRADE).cabal2stack.yaml.lock
+
+ghc-$(GHC_VERSION).cabal2stack.yaml: ghc-$(GHC_VERSION).$(CABAL_VIA).project
+	rm -rf dist-newstyle/cache
+	cabal build all --enable-tests --project-file=$< --dry-run
+	cabal2stack --resolver=$(STACKAGE_VERSION) --allow-newer --output $@ --plan-json dist-newstyle/cache/plan.json
+
+ghc-$(GHC_UPGRADE).cabal2stack.yaml: ghc-$(GHC_UPGRADE).$(CABAL_VIA).project
+	rm -rf dist-newstyle/cache
+	cabal build all --enable-tests --project-file=$< --dry-run
+	cabal2stack --resolver=$(STACKAGE_UPGRADE) --allow-newer --output $@ --plan-json dist-newstyle/cache/plan.json
+
+ghc-%.cabal2stack.yaml.lock: ghc-%.cabal2stack.yaml
+	stack build --test --no-run-tests --bench --no-run-benchmarks --dry-run --stack-yaml $<
diff --git a/alternatives/cabal2stack/README.md b/alternatives/cabal2stack/README.md
new file mode 100644
--- /dev/null
+++ b/alternatives/cabal2stack/README.md
@@ -0,0 +1,4 @@
+# Updo Cabal2Stack
+
+Using [cabal2stack](https://github.com/iconnect/cabal2stack), the `Makefile` has
+targets for `ghc-x.y.z.cabal2stack.yaml` and its `.lock` file.
diff --git a/alternatives/stack2cabal/Makefile b/alternatives/stack2cabal/Makefile
new file mode 100644
--- /dev/null
+++ b/alternatives/stack2cabal/Makefile
@@ -0,0 +1,12 @@
+.PHONY: stack2cabal-projects
+stack2cabal-projects: \
+  ghc-$(GHC_VERSION).stack2cabal.project \
+  ghc-$(GHC_UPGRADE).stack2cabal.project
+
+ghc-$(GHC_VERSION).stack2cabal.project: ghc-$(GHC_VERSION).$(STACK_VIA).yaml
+	stack2cabal --file $< --output-file $@
+
+ghc-$(GHC_UPGRADE).stack2cabal.project: ghc-$(GHC_UPGRADE).$(STACK_VIA).yaml
+	stack2cabal --file $< --output-file $@
+
+ghc-%.stack2cabal.project.freeze: ghc-%.stack2cabal.project
diff --git a/alternatives/stack2cabal/README.md b/alternatives/stack2cabal/README.md
new file mode 100644
--- /dev/null
+++ b/alternatives/stack2cabal/README.md
@@ -0,0 +1,4 @@
+# Updo Stack2Cabal
+
+Using [stack2cabal](https://github.com/hasufell/stack2cabal), the `Makefile` has
+targets for `ghc-x.y.z.stack2cabal.project`.
diff --git a/alternatives/yaml2stack/Makefile b/alternatives/yaml2stack/Makefile
new file mode 100644
--- /dev/null
+++ b/alternatives/yaml2stack/Makefile
@@ -0,0 +1,74 @@
+# When the prerequisites are a function and a list, we echo a dhall expression
+# that calls this function with the list. Both function and list are in dhall
+# files that must be ./ prefixed to be imported into the expression.
+#
+# $(^:%=./%) does the ./ prefixing that dhall needs to recognize the imports.
+#
+# "The import resolution phase replaces all imports with the expression located
+# at that import, transitively resolving imports within the imported expression
+# if necessary."
+# SOURCE:https://github.com/dhall-lang/dhall-lang/blob/master/standard/imports.md#import-resolution-judgment
+
+UPDO_TMP := .updo
+Y2S_TMP := $(UPDO_TMP)/dhall2yaml2stack
+
+.PHONY: dhall2yaml2stack-projects
+dhall2yaml2stack-projects: \
+  ghc-$(GHC_VERSION).dhall2yaml2stack.yaml \
+  ghc-$(GHC_VERSION).dhall2yaml2stack.yaml.lock \
+  ghc-$(GHC_UPGRADE).dhall2yaml2stack.yaml \
+  ghc-$(GHC_UPGRADE).dhall2yaml2stack.yaml.lock
+
+.INTERMEDIATE: $(Y2S_TMP)/ghc-$(GHC_VERSION)/base.yaml
+$(Y2S_TMP)/ghc-$(GHC_VERSION)/base.yaml: \
+  project-dhall/ghc-$(GHC_VERSION)/text-templates/stacksnippet.dhall
+	mkdir -p $(@D) && echo '$(<:%=./%) (Some "$(STACKAGE_VERSION)" : Optional Text)' | dhall text --output $@
+
+.INTERMEDIATE: $(Y2S_TMP)/ghc-$(GHC_UPGRADE)/base.yaml
+$(Y2S_TMP)/ghc-$(GHC_UPGRADE)/base.yaml: \
+  project-dhall/ghc-$(GHC_UPGRADE)/text-templates/stacksnippet.dhall
+	mkdir -p $(@D) && echo '$(<:%=./%) (Some "$(STACKAGE_UPGRADE)" : Optional Text)' | dhall text --output $@
+
+# Mirror pkgs from dhall to yaml.
+.INTERMEDIATE: $(Y2S_TMP)/ghc-$(GHC_VERSION)/pkgs.yaml
+$(Y2S_TMP)/ghc-$(GHC_VERSION)/pkgs.yaml: \
+  updo/text-templates/yaml2stack/pkgs.dhall \
+  $(UPDO_TMP)/pkgs-sorted.dhall
+	echo "$(^:%=./%)" | dhall text --output $@
+
+.INTERMEDIATE: $(Y2S_TMP)/ghc-$(GHC_UPGRADE)/pkgs.yaml
+$(Y2S_TMP)/ghc-$(GHC_UPGRADE)/pkgs.yaml: \
+  updo/text-templates/yaml2stack/pkgs.dhall \
+  $(UPDO_TMP)/pkgs-upgrade-done.dhall
+	echo "$(^:%=./%)" | dhall text --output $@
+
+# Mirror constraints from dhall to yaml.
+$(Y2S_TMP)/ghc-%/constraints.yaml: \
+  updo/text-templates/yaml2stack/constraints.dhall \
+  project-dhall/ghc-%/constraints.dhall
+	echo "$(^:%=./%)" | dhall text --output $@
+
+# Mirror source repositories from dhall to yaml.
+$(Y2S_TMP)/ghc-%.yaml: \
+  updo/text-templates/yaml2stack/repo-items.dhall \
+  project-dhall/ghc-%.dhall
+	echo "$(^:%=./%)" | dhall text --output $@
+
+# Join the YAML fragments into a single file, the stack project.
+ghc-%.dhall2yaml2stack.yaml: \
+  $(Y2S_TMP)/ghc-%/base.yaml \
+  $(Y2S_TMP)/ghc-%/pkgs.yaml \
+  $(Y2S_TMP)/ghc-%/constraints.yaml \
+  $(Y2S_TMP)/ghc-%/deps-external.yaml \
+  $(Y2S_TMP)/ghc-%/deps-internal.yaml \
+  $(Y2S_TMP)/ghc-%/forks-external.yaml \
+  $(Y2S_TMP)/ghc-%/forks-internal.yaml \
+  updo/alternatives/yaml2stack/join-sort-fragments.sh
+	./updo/alternatives/yaml2stack/join-sort-fragments.sh $(<D) >$@
+
+ghc-%.dhall2yaml2stack.yaml.lock: ghc-%.dhall2yaml2stack.yaml
+	stack build --test --no-run-tests --bench --no-run-benchmarks --dry-run --stack-yaml $<
+
+# To keep dhall to yaml intermediate files, uncomment the following (no need to
+# comment out the .INTERMEDIATE lines above):
+# .SECONDARY: $(wildcard $(Y2S_TMP)/*.yaml)
diff --git a/alternatives/yaml2stack/README.md b/alternatives/yaml2stack/README.md
new file mode 100644
--- /dev/null
+++ b/alternatives/yaml2stack/README.md
@@ -0,0 +1,13 @@
+# Updo Dhall2Yaml2Stack
+
+The `Makefile` has targets for `ghc-x.y.z.dhall2yaml2stack.yaml` and its `.lock`
+file.
+
+The `.dhall` files are mirrored to `.yaml` files[^working-dir] followed by a very lightweight
+stitching together of these fragments with
+[yq](https://mikefarah.gitbook.io/yq/), removing comments and sorting
+dependencies.
+
+[^working-dir]: The `.yaml` fragments are written to `.updo/dhall2yaml2stack`,
+copied to a bash `$(mktemp -d)` temporary directory and then stitched together
+there. There's a note in the make file explaining how to keep these fragments.
diff --git a/alternatives/yaml2stack/join-sort-fragments.sh b/alternatives/yaml2stack/join-sort-fragments.sh
new file mode 100644
--- /dev/null
+++ b/alternatives/yaml2stack/join-sort-fragments.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+set -eu
+
+WORKDIR=$(mktemp -d)
+trap 'rm -rf "$WORKDIR"' EXIT
+
+cd "$1"
+
+for f in *.yaml
+do
+    cp "$PWD/${f}" "$WORKDIR/${f}"
+done
+
+cd "$WORKDIR"
+for f in *.yaml
+do
+    yq '... comments=""' --inplace --prettyPrint "${f}"
+done
+
+yq '.extra-deps |= sort' --inplace constraints.yaml
+yq '.packages |= sort_by(sub("\/", ""))' --inplace pkgs.yaml
+yq '.extra-deps |= sort_by(.git | sub("(.*\.com:|.*\.com\/)(.+$)", "$2"))' --inplace deps-external.yaml
+yq '.extra-deps |= sort_by(.git | sub("(.*\.com:|.*\.com\/)(.+$)", "$2"))' --inplace deps-internal.yaml
+yq '.extra-deps |= sort_by(.git | sub("(.*\.com:|.*\.com\/)(.+$)", "$2"))' --inplace forks-external.yaml
+yq '.extra-deps |= sort_by(.git | sub("(.*\.com:|.*\.com\/)(.+$)", "$2"))' --inplace forks-internal.yaml
+yq eval-all '. as $item ireduce ({}; . *+ $item )' base.yaml pkgs.yaml deps-external.yaml deps-internal.yaml forks-external.yaml forks-internal.yaml constraints.yaml
diff --git a/package.dhall b/package.dhall
new file mode 100644
--- /dev/null
+++ b/package.dhall
@@ -0,0 +1,34 @@
+{ name = "updo"
+, version = "1.0.0"
+, synopsis = "A style of maintaining and upgrading Haskell projects"
+, description =
+    ''
+    From configuration in .dhall, generate projects (stack.yaml and
+    cabal.project) and progressively upgrade projects.
+    ''
+, category = "Development"
+, github = "cabalism/updo"
+, author = "Phil de Joux"
+, maintainer = "phil.dejoux@blockscope.com"
+, copyright = "© 2023 Phil de Joux, © 2023 Block Scope Limited"
+, tested-with =
+  [ "GHC == 8.6.5", "GHC == 8.10.7", "GHC == 9.2.8", "GHC == 9.4.5" ]
+, extra-source-files =
+  [ "**/*.md", "**/*.dhall", "**/Makefile", "**/*hs", "**/*.py", "**/*.sh" ]
+, dependencies =
+  [ "aeson >= 2.1.2 && <2.2"
+  , "base >=4.12 && <5"
+  , "dhall >= 1.41.1 && <1.42"
+  , "filepath >= 1.4.2 && <1.5"
+  , "text >= 1.2.3 && <2.1"
+  , "turtle >= 1.6.1 && <1.7"
+  , "utf8-string >= 1.0.2 && <1.1"
+  ]
+, executables =
+  { updo-pkgs-sorted.main = "project-dhall/pkgs-sorted.hs"
+  , updo-pkgs-upgrade-done.main = "project-dhall/pkgs-upgrade-done.hs"
+  , updo-pkgs-upgrade-partition.main = "project-dhall/pkgs-upgrade-partition.hs"
+  , updo-pkg-groups.main = "project-dhall2config/pkg-groups.hs"
+  , updo-sha256map.main = "project-nix/sha256map.hs"
+  }
+}
diff --git a/project-dhall/Makefile b/project-dhall/Makefile
new file mode 100644
--- /dev/null
+++ b/project-dhall/Makefile
@@ -0,0 +1,76 @@
+UPDO_TMP := ./.updo
+
+ifeq ($(PKGS_SORTED_HS_EXE), true)
+PKGS_SORTED_HS := updo-pkgs-sorted
+else
+PKGS_SORTED_HS := ./updo/project-dhall/pkgs-sorted.hs
+endif
+
+ifeq ($(PKGS_UPGRADE_DONE_HS_EXE), true)
+PKGS_UPGRADE_DONE_HS := updo-pkgs-upgrade-done
+else
+PKGS_UPGRADE_DONE_HS := ./updo/project-dhall/pkgs-upgrade-done.hs
+endif
+
+.PHONY: dhall2stack-projects
+dhall2stack-projects: \
+  ghc-$(GHC_VERSION).dhall2stack.yaml \
+  ghc-$(GHC_VERSION).dhall2stack.yaml.lock \
+  ghc-$(GHC_UPGRADE).dhall2stack.yaml \
+  ghc-$(GHC_UPGRADE).dhall2stack.yaml.lock \
+
+.PHONY: dhall2cabal-projects
+dhall2cabal-projects: \
+  ghc-$(GHC_VERSION).dhall2cabal.project \
+  ghc-$(GHC_UPGRADE).dhall2cabal.project
+
+.PHONY: pkgs-sorted
+pkgs-sorted: $(UPDO_TMP)/pkgs-sorted.dhall
+
+.PHONY: pkgs-upgrade-done
+pkgs-upgrade-done: $(UPDO_TMP)/pkgs-upgrade-done.dhall
+
+$(UPDO_TMP)/pkgs-sorted.dhall: \
+  project-dhall/pkg-groups.dhall \
+  project-dhall/pkgs/*.dhall
+	mkdir -p $(@D) && $(PKGS_SORTED_HS) > $@
+
+$(UPDO_TMP)/pkgs-upgrade-done.dhall: \
+  project-dhall/pkgs/*.dhall \
+  $(UPDO_TMP)/pkgs-sorted.dhall \
+  project-dhall/pkgs-upgrade-todo.dhall
+	./updo/project-dhall/pkgs-upgrade-done.hs \
+	  ./$(UPDO_TMP)/pkgs-sorted.dhall \
+	  ./project-dhall/pkgs-upgrade-todo.dhall \
+	  > $@
+
+ghc-$(GHC_VERSION).dhall2stack.yaml: \
+  project-dhall/ghc-$(GHC_VERSION)/text-templates/dhall2stack.dhall \
+  $(UPDO_TMP)/pkgs-sorted.dhall \
+  project-dhall/ghc-$(GHC_VERSION)/*.dhall \
+  updo/text-templates/stack/*.dhall
+	echo './$< ./$(UPDO_TMP)/pkgs-sorted.dhall "$(STACKAGE_VERSION)"' | dhall text --output $@
+
+ghc-$(GHC_UPGRADE).dhall2stack.yaml: \
+  project-dhall/ghc-$(GHC_UPGRADE)/text-templates/dhall2stack.dhall \
+  $(UPDO_TMP)/pkgs-upgrade-done.dhall \
+  project-dhall/ghc-$(GHC_UPGRADE)/*.dhall \
+  updo/text-templates/stack/*.dhall
+	echo './$< ./$(UPDO_TMP)/pkgs-upgrade-done.dhall "$(STACKAGE_UPGRADE)"' | dhall text --output $@
+
+ghc-$(GHC_VERSION).dhall2cabal.project: \
+  project-dhall/ghc-$(GHC_VERSION)/text-templates/dhall2cabal.dhall \
+  $(UPDO_TMP)/pkgs-sorted.dhall \
+  project-dhall/ghc-$(GHC_VERSION)/*.dhall \
+  updo/text-templates/cabal/*.dhall
+	echo './$< ./$(UPDO_TMP)/pkgs-sorted.dhall "$(STACKAGE_VERSION)"' | dhall text --output $@
+
+ghc-$(GHC_UPGRADE).dhall2cabal.project: \
+  project-dhall/ghc-$(GHC_UPGRADE)/text-templates/dhall2cabal.dhall \
+  $(UPDO_TMP)/pkgs-upgrade-done.dhall \
+  project-dhall/ghc-$(GHC_UPGRADE)/*.dhall \
+  updo/text-templates/cabal/*.dhall
+	echo './$< ./$(UPDO_TMP)/pkgs-upgrade-done.dhall "$(STACKAGE_UPGRADE)"' | dhall text --output $@
+
+ghc-%.dhall2stack.yaml.lock: ghc-%.dhall2stack.yaml
+	stack build  --test --no-run-tests --bench --no-run-benchmarks --dry-run --stack-yaml $<
diff --git a/project-dhall/README.md b/project-dhall/README.md
new file mode 100644
--- /dev/null
+++ b/project-dhall/README.md
@@ -0,0 +1,7 @@
+# Updo Dhall
+
+The `Makefile` has targets for stack and cabal projects generated using Dhall
+text templating:
+
+* `ghc-x.y.z.dhall2stack.yaml`
+* `ghc-x.y.z.dhall2cabal.project`
diff --git a/project-dhall/pkgs-sorted.hs b/project-dhall/pkgs-sorted.hs
new file mode 100644
--- /dev/null
+++ b/project-dhall/pkgs-sorted.hs
@@ -0,0 +1,27 @@
+#!/usr/bin/env stack
+-- stack script --resolver=lts-18.27 --package=base --package=dhall --package=filepath --package=text
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+-- | Description: Sorts packages as a flat list.
+module Main where
+
+import Data.List (concat, sortOn)
+import qualified Data.Text as T (filter, pack)
+import qualified Data.Text.IO as T (putStrLn)
+import Dhall (Text, auto, embed, inject, input)
+import Dhall.Core (pretty)
+import System.FilePath ((<.>), (</>))
+
+grab :: String -> IO [Text]
+grab name = input auto (T.pack $ "./project-dhall/pkgs" </> name <.> "dhall")
+
+main :: IO ()
+main = do
+    pkgGroups :: [String] <- input auto "./project-dhall/pkg-groups.dhall"
+    xs <- traverse grab pkgGroups
+    -- Package items are (usually in this project) paths to folders containing a
+    -- .cabal file.  These aren't canonical so may or may not have a trailing
+    -- slash.  To sort them consistently, we filter out the slashes.
+    let sortedPkgPaths = sortOn (T.filter (/= '/')) $ concat xs
+    T.putStrLn . pretty $ embed inject sortedPkgPaths
diff --git a/project-dhall/pkgs-upgrade-done.hs b/project-dhall/pkgs-upgrade-done.hs
new file mode 100644
--- /dev/null
+++ b/project-dhall/pkgs-upgrade-done.hs
@@ -0,0 +1,20 @@
+#!/usr/bin/env stack
+-- stack script --resolver=lts-18.27 --package=base --package=dhall --package=text
+{-# LANGUAGE ScopedTypeVariables #-}
+
+-- | Description: The difference between all packages and those in the todo
+-- list, the packgaes that have been upgraded.
+module Main where
+
+import Data.List ((\\))
+import qualified Data.Text as T (pack)
+import qualified Data.Text.IO as T (putStrLn)
+import Dhall (Text, auto, embed, inject, input)
+import Dhall.Core (pretty)
+import System.Environment (getArgs)
+
+main = do
+    sortedFile : todoFile : _ <- getArgs
+    sorted :: [Text] <- input auto (T.pack sortedFile)
+    todo :: [Text] <- input auto (T.pack todoFile)
+    T.putStrLn . pretty $ embed inject (sorted \\ todo)
diff --git a/project-dhall/pkgs-upgrade-partition.hs b/project-dhall/pkgs-upgrade-partition.hs
new file mode 100644
--- /dev/null
+++ b/project-dhall/pkgs-upgrade-partition.hs
@@ -0,0 +1,34 @@
+#!/usr/bin/env stack
+-- stack script --resolver=lts-18.27 --package=base --package=dhall --package=text
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+-- | Description: Partition the packages into those that have been upgraded and
+-- those that have not.
+module Main where
+
+import Data.List (intersect, (\\))
+import qualified Data.Text as T (pack)
+import qualified Data.Text.IO as T (putStrLn)
+import Dhall (FromDhall, Generic, Text, ToDhall, auto, embed, inject, input)
+import Dhall.Core (pretty)
+import System.Environment (getArgs)
+
+data PkgUpgrade = PkgUpgrade
+    { pkgs :: [Text]
+    , done :: [Text]
+    , todo :: [Text]
+    }
+    deriving (Show, Generic, ToDhall, FromDhall)
+
+main :: IO ()
+main = do
+    file : _ <- getArgs
+    pkgs :: [Text] <- input auto (T.pack file)
+    remaining :: [Text] <- input auto "./project-dhall/pkgs-upgrade-todo.dhall"
+    let todo = pkgs `intersect` remaining
+    let done = pkgs \\ todo
+    T.putStrLn . pretty $ embed inject PkgUpgrade{..}
diff --git a/project-dhall/pkgs-upgrade.dhall b/project-dhall/pkgs-upgrade.dhall
new file mode 100644
--- /dev/null
+++ b/project-dhall/pkgs-upgrade.dhall
@@ -0,0 +1,65 @@
+-- NOTE: With cabal-3.10, imports are relative to each other but imported
+-- packages are not, they are still relative to the project root. With
+-- cabal-3.8, the first version with imports, imports are relative to the
+-- project root too.
+let TYPES = ../types.dhall
+
+in  \(pkg-import : TYPES.CabalRelativity) ->
+    \(ghc-version : Text) ->
+    \(ghc-upgrade : Text) ->
+    \(x : { pkgs : List Text, done : List Text, todo : List Text }) ->
+      let N = https://prelude.dhall-lang.org/Natural/package.dhall
+
+      let L = https://prelude.dhall-lang.org/List/package.dhall
+
+      let concatMapSep = https://prelude.dhall-lang.org/Text/concatMapSep
+
+      let relativePkg =
+            merge
+              { CabalProjectRelative = \(s : Text) -> "./${s}"
+              , CabalImportRelative = \(s : Text) -> "./${s}"
+              }
+              pkg-import
+
+      let pkgList =
+            \(indent : Text) ->
+            \(xs : List Text) ->
+              concatMapSep
+                ''
+
+                ${indent}, ''
+                Text
+                relativePkg
+                xs
+
+      let pkgListComment =
+            \(xs : List Text) ->
+              concatMapSep "\n" Text (\(s : Text) -> "-- ${relativePkg s}") xs
+
+      in  if        L.null Text x.todo
+                ||  N.equal (L.length Text x.pkgs) (L.length Text x.done)
+          then  ''
+                packages:
+                    ${pkgList "  " x.pkgs}
+                ''
+          else      ''
+                    if impl(ghc <= ${ghc-version})
+                      packages:
+                          ${pkgList "    " x.pkgs}
+                    ''
+                ++  ( if    L.null Text x.done
+                      then  ""
+                      else  ''
+                            else
+                              packages:
+                                  ${pkgList "    " x.done}
+                            ''
+                    )
+                ++  ( if    L.null Text x.done
+                      then  ""
+                      else  ''
+
+                            -- TODO for impl(ghc >= ${ghc-upgrade})
+                            ${pkgListComment x.todo}
+                            ''
+                    )
diff --git a/project-dhall2config/Makefile b/project-dhall2config/Makefile
new file mode 100644
--- /dev/null
+++ b/project-dhall2config/Makefile
@@ -0,0 +1,75 @@
+ifeq ($(PKG_GROUPS_HS_EXE), true)
+PKG_GROUPS_HS := updo-pkg-groups
+else
+PKG_GROUPS_HS := ./updo/project-dhall2config/pkg-groups.hs
+endif
+
+.PHONY: dhall2config-projects
+dhall2config-projects: \
+  ghc-$(GHC_VERSION).dhall2config.project \
+  ghc-$(GHC_UPGRADE).dhall2config.project
+
+project-cabal/pkgs.config: \
+  updo/text-templates/cabal/pkg-groups.dhall \
+  project-dhall/pkg-groups.dhall \
+  $(patsubst project-dhall/pkgs/%.dhall, project-cabal/pkgs/%.config, $(wildcard project-dhall/pkgs/*.dhall))
+  ifeq ($(CABAL_RELATIVITY), ImportRelative)
+	echo 'let f = ./updo/types/CabalRelativity.dhall in ./$< f.CabalImportRelative ./project-dhall/pkg-groups.dhall' | dhall text --output $@
+  else
+	echo 'let f = ./updo/types/CabalRelativity.dhall in ./$< f.CabalProjectRelative ./project-dhall/pkg-groups.dhall' | dhall text --output $@
+  endif
+
+generate-pkg-configs: \
+  project-dhall/pkg-groups.dhall \
+  project-dhall/pkgs-upgrade-todo.dhall
+  ifeq ($(CABAL_RELATIVITY), ImportRelative)
+	mkdir -p project-cabal/pkgs && $(PKG_GROUPS_HS) "let f = ./updo/types/CabalRelativity.dhall in f.CabalImportRelative" "$(GHC_VERSION)" "$(GHC_UPGRADE)"
+  else
+	mkdir -p project-cabal/pkgs && $(PKG_GROUPS_HS) "let f = ./updo/types/CabalRelativity.dhall in f.CabalProjectRelative" "$(GHC_VERSION)" "$(GHC_UPGRADE)"
+  endif
+
+# Mirror packages (package groups) from dhall to config with
+# generate-pkg-configs doing the work once for all package groups.
+#
+# The "target: prerequisites ;" explicitly defines an empty recipe.
+# SEE: https://www.gnu.org/software/make/manual/html_node/Empty-Recipes.html
+project-cabal/pkgs/%.config: project-dhall/pkgs/%.dhall generate-pkg-configs ;
+
+# Mirror constraints from dhall to config. Any revisions in the constraints are
+# stripped, like this for example:
+# - hedgehog-quickcheck ==0.1.1@rev:4
+# + hedgehog-quickcheck ==0.1.1
+project-cabal/ghc-%/constraints.config : \
+  updo/text-templates/cabal/package.dhall \
+  project-dhall/ghc-%/constraints.dhall
+	mkdir -p $(@D)
+	echo "let f = ./$< in f.constraints ./project-dhall/ghc-$*/constraints.dhall" \
+	| dhall text | sed "s/@rev:.*//" >$@
+
+# Mirror source repositories from dhall to config.
+project-cabal/ghc-%.config: \
+  updo/text-templates/cabal/package.dhall \
+  project-dhall/ghc-%.dhall
+	mkdir -p $(@D) && echo "let f = ./$< in f.repo-items ./project-dhall/ghc-$*.dhall" | dhall text --output $@
+
+ghc-$(GHC_VERSION).dhall2config.project: \
+  project-dhall/ghc-$(GHC_VERSION)/text-templates/dhall2config.dhall \
+  updo/text-templates/dhall2config.dhall \
+  project-cabal/ghc-$(GHC_VERSION)/constraints.config \
+  project-cabal/ghc-$(GHC_VERSION)/deps-external.config \
+  project-cabal/ghc-$(GHC_VERSION)/deps-internal.config \
+  project-cabal/ghc-$(GHC_VERSION)/forks-external.config \
+  project-cabal/ghc-$(GHC_VERSION)/forks-internal.config \
+  project-cabal/pkgs.config
+	echo './$< "$(STACKAGE_VERSION)" "$(GHC_VERSION)"' | dhall text --output $@
+
+ghc-$(GHC_UPGRADE).dhall2config.project: \
+  project-dhall/ghc-$(GHC_UPGRADE)/text-templates/dhall2config.dhall \
+  updo/text-templates/dhall2config.dhall \
+  project-cabal/ghc-$(GHC_UPGRADE)/constraints.config \
+  project-cabal/ghc-$(GHC_UPGRADE)/deps-external.config \
+  project-cabal/ghc-$(GHC_UPGRADE)/deps-internal.config \
+  project-cabal/ghc-$(GHC_UPGRADE)/forks-external.config \
+  project-cabal/ghc-$(GHC_UPGRADE)/forks-internal.config \
+  project-cabal/pkgs.config
+	echo './$< "$(STACKAGE_UPGRADE)" "$(GHC_UPGRADE)"' | dhall text --output $@
diff --git a/project-dhall2config/README.md b/project-dhall2config/README.md
new file mode 100644
--- /dev/null
+++ b/project-dhall2config/README.md
@@ -0,0 +1,27 @@
+# Updo Cabal
+
+We offer two ways of generating cabal projects, `dhall2cabal` and
+`dhall2config`. This folder deals with the later.
+
+The `Makefile` has targets for `ghc-x.y.z.dhall2config.project` that is mostly
+imports of `.config` files generated from the `.dhall` configuration, something
+like:
+
+```
+import: project-stackage/stackage-resolver.config
+
+import: project-cabal/pkgs.config
+
+import: project-cabal/ghc-x.y.z/constraints.config
+import: project-cabal/ghc-x.y.z/deps-external.config
+import: project-cabal/ghc-x.y.z/deps-internal.config
+import: project-cabal/ghc-x.y.z/forks-external.config
+import: project-cabal/ghc-x.y.z/forks-internal.config
+
+build-info: True
+```
+
+For this rule using `make --jobs`[^parallel-make] will speed up the recipe a
+lot.
+
+[^parallel-make]: https://www.gnu.org/software/make/manual/html_node/Parallel.html
diff --git a/project-dhall2config/pkg-groups.hs b/project-dhall2config/pkg-groups.hs
new file mode 100644
--- /dev/null
+++ b/project-dhall2config/pkg-groups.hs
@@ -0,0 +1,52 @@
+#!/usr/bin/env stack
+-- stack script --resolver=lts-18.27 --package=base --package=dhall --package=filepath --package=text
+
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+-- | Description: Convert each package group from .dhall to .config with upgrade
+-- conditionals if needed.
+module Main where
+
+import Control.Monad (forM_)
+import Data.List (concat, filter, intersect, sortOn, (\\))
+import qualified Data.Text as T (pack, unpack)
+import qualified Data.Text.IO as T (putStrLn)
+import Dhall (FromDhall, Generic, Text, ToDhall, auto, embed, inject, input)
+import Dhall.Core (pretty)
+import System.Environment (getArgs)
+import System.FilePath ((<.>), (</>))
+
+type UpgradeConfig = CabalRelativity -> Text -> Text -> PkgUpgrade -> Text
+
+data PkgUpgrade = PkgUpgrade
+    { pkgs :: [Text]
+    , done :: [Text]
+    , todo :: [Text]
+    }
+    deriving (Show, Generic, ToDhall, FromDhall)
+
+data CabalRelativity = CabalProjectRelative | CabalImportRelative
+    deriving (Show, Generic, ToDhall, FromDhall)
+
+groupUpgrade :: [Text] -> String -> IO PkgUpgrade
+groupUpgrade remaining name = do
+    pkgs :: [Text] <- input auto (T.pack $ "./project-dhall/pkgs" </> name <.> "dhall")
+    let todo = pkgs `intersect` remaining
+    let done = pkgs \\ todo
+    return PkgUpgrade{..}
+
+main :: IO ()
+main = do
+    cabalRelativity : ghcVersion : ghcUpgrade : _ <- getArgs
+    remaining :: [Text] <- input auto "./project-dhall/pkgs-upgrade-todo.dhall"
+    relativity :: CabalRelativity <- input auto $ T.pack cabalRelativity
+    pkgUpgrade :: UpgradeConfig <- input auto "./updo/project-dhall/pkgs-upgrade.dhall"
+    pkgGroups :: [String] <- input auto "./project-dhall/pkg-groups.dhall"
+    forM_ pkgGroups $ \p -> do
+        up <- groupUpgrade remaining p
+        let config = pkgUpgrade relativity (T.pack ghcVersion) (T.pack ghcUpgrade) up
+        writeFile ("./project-cabal/pkgs" </> p <.> "config") (T.unpack config)
diff --git a/project-nix/Makefile b/project-nix/Makefile
new file mode 100644
--- /dev/null
+++ b/project-nix/Makefile
@@ -0,0 +1,19 @@
+ifeq ($(SHA256MAP_HS_EXE), true)
+SHA256MAP_HS := updo-sha256map
+else
+SHA256MAP_HS := ./updo/project-nix/sha256map.hs
+endif
+
+project-sha256maps: \
+  ghc-$(GHC_VERSION).sha256map.nix \
+  ghc-$(GHC_UPGRADE).sha256map.nix
+
+ghc-%.sha256map.nix: \
+  project-dhall/ghc-%/deps-external.dhall \
+  project-dhall/ghc-%/deps-internal.dhall \
+  project-dhall/ghc-%/forks-external.dhall \
+  project-dhall/ghc-%/forks-internal.dhall
+	echo '[$(^:%=./%,) ([] : List {loc : Text, tag : Text, sub : List Text})]' | $(SHA256MAP_HS) > $@
+
+project-versions.nix:
+	echo './updo/project-nix/project-versions.dhall "$(GHC_VERSION)" "$(GHC_UPGRADE)"' | dhall text > $@
diff --git a/project-nix/README.md b/project-nix/README.md
new file mode 100644
--- /dev/null
+++ b/project-nix/README.md
@@ -0,0 +1,67 @@
+# Updo Nix
+
+The `Makefile` has targets for `ghc-x.y.z.sha256map.nix` which has the form:
+
+```nix
+{
+  "repo-url"."commit-rev" = "sha256";
+}
+```
+
+This can be used with [haskell.nix][haskell-nix].  We grab the sha256 hash with
+`nix-prefetch-git` from the shell so you'll need that installed.
+
+[haskell-nix]: https://input-output-hk.github.io/haskell.nix/tutorials/source-repository-hashes.html#avoiding-modifying-cabalproject-and-stackyaml
+
+```
+$ nix-prefetch-git --help
+syntax: nix-prefetch-git [options] [URL [REVISION [EXPECTED-HASH]]]
+
+Options:
+      --out path      Path where the output would be stored.
+      --url url       Any url understood by 'git clone'.
+      --rev ref       Any sha1 or references (such as refs/heads/master)
+      --hash h        Expected hash.
+      --branch-name   Branch name to check out into
+      --sparse-checkout Only fetch and checkout part of the repository.
+      --deepClone     Clone the entire repository.
+      --no-deepClone  Make a shallow clone of just the required ref.
+      --leave-dotGit  Keep the .git directories.
+      --fetch-lfs     Fetch git Large File Storage (LFS) files.
+      --fetch-submodules Fetch submodules.
+      --builder       Clone as fetchgit does, but url, rev, and out option are mandatory.
+      --quiet         Only print the final json summary.
+```
+
+```
+$ nix-prefetch-git https://github.com/input-output-hk/haskell.nix.git bc01ebc05a8105035c9449943046b46c8364b932
+...
+{
+  "url": "https://github.com/input-output-hk/haskell.nix.git",
+  "rev": "bc01ebc05a8105035c9449943046b46c8364b932",
+  "date": "2019-05-30T13:13:18+08:00",
+  "sha256": "003lm3pm024vhbfmii7xcdd9v2rczpflxf7gdl2pyxia7p014i8z",
+  "fetchSubmodules": false
+}
+```
+
+We also include a `sha256map.py` script from haskell.nix[^sorted] but this is 4
+times slower.
+
+The `Makefile` also has `project-versions.nix` as a target.
+
+```nix
+{ current-version =
+    { ghc-xyz-project-stack = "stack.yaml";
+      ghc-xyz-project-cabal = "cabal.project";
+      ghc-xyz-sha256map = import ./project-nix/ghc-u.v.w/sha256map.nix;
+    };
+  upgrade-version =
+    { ghc-xyz-project-stack = "stack.upgrade.yaml";
+      ghc-xyz-project-cabal = "cabal.upgrade.project";
+      ghc-xyz-sha256map = import ./project-nix/ghc-x.y.z/sha256map.nix;
+    };
+}
+```
+
+[^sorted]: Renamed from `sha256map-regenerate.py` and modified to sort the map by keys.
diff --git a/project-nix/project-versions.dhall b/project-nix/project-versions.dhall
new file mode 100644
--- /dev/null
+++ b/project-nix/project-versions.dhall
@@ -0,0 +1,15 @@
+\(ghc-version : Text) ->
+\(ghc-upgrade : Text) ->
+  ''
+  { current-version =
+      { ghc-xyz-project-stack = "stack.yaml";
+        ghc-xyz-project-cabal = "cabal.project";
+        ghc-xyz-sha256map = import ./project-nix/ghc-${ghc-version}/sha256map.nix;
+      };
+    upgrade-version =
+      { ghc-xyz-project-stack = "stack.upgrade.yaml";
+        ghc-xyz-project-cabal = "cabal.upgrade.project";
+        ghc-xyz-sha256map = import ./project-nix/ghc-${ghc-upgrade}/sha256map.nix;
+      };
+  }
+  ''
diff --git a/project-nix/sha256map.hs b/project-nix/sha256map.hs
new file mode 100644
--- /dev/null
+++ b/project-nix/sha256map.hs
@@ -0,0 +1,76 @@
+#!/usr/bin/env stack
+-- stack script --resolver=lts-18.27 --package=aeson --package=base --package=dhall --package=text --package=turtle --package=utf8-string
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+-- | Description: Generates a sha256map.
+module Main where
+
+import Control.Monad (unless)
+import Data.Aeson (FromJSON, decode)
+import Data.ByteString.Lazy.UTF8 (fromString)
+import Data.Either (partitionEithers)
+import Data.Text (pack, unpack)
+import qualified Data.Text.IO as T (getContents)
+import Dhall (FromDhall, Generic, Text, ToDhall, auto, input)
+import GHC.Generics
+import System.Exit (ExitCode (..), die)
+import Turtle (empty, parallel, shellStrictWithErr, sort)
+import Turtle.Format (printf, (%), s)
+
+data SourceRepoPkg = SourceRepoPkg
+    { loc :: Text
+    , tag :: Text
+    , sub :: [Text]
+    }
+    deriving (Eq, Ord, Show, Generic, ToDhall, FromDhall)
+
+data Warning
+    = NixPrefetchGitFailed Int Text
+    | InvalidPrefetchGitOutput Text
+    deriving (Eq, Ord, Show)
+
+data NixPrefetchGitOutput = NixPrefetchGitOutput
+    { url :: Text
+    , rev :: Text
+    , sha256 :: Text
+    , date :: Text
+    }
+    deriving (Eq, Ord, Show, Generic, FromJSON)
+
+prefetchSourceRepoPkg :: SourceRepoPkg -> IO (Either Warning NixPrefetchGitOutput)
+prefetchSourceRepoPkg SourceRepoPkg{..} = nixPrefetchGit (loc, tag)
+{-# INLINE prefetchSourceRepoPkg #-}
+
+nixPrefetchGit :: (Text, Text) -> IO (Either Warning NixPrefetchGitOutput)
+nixPrefetchGit (repo, commit) = do
+    (exitCode, stdout, stderr) <-
+        shellStrictWithErr
+            ("nix-prefetch-git " <> repo <> " " <> commit)
+            empty
+    case exitCode of
+        ExitFailure e -> return (Left $ NixPrefetchGitFailed e stderr)
+        ExitSuccess ->
+            return $
+                maybe
+                    (Left $ InvalidPrefetchGitOutput stdout)
+                    Right
+                    (decode (fromString $ unpack stdout))
+
+printUrlTagSha :: NixPrefetchGitOutput -> IO ()
+printUrlTagSha NixPrefetchGitOutput{..} =
+    printf ("  \""%s%"\".\""%s%"\" = \""%s%"\";\n") url rev sha256
+
+main :: IO ()
+main = do
+    repos :: [[SourceRepoPkg]] <- input auto =<< T.getContents
+    fetches <- sort . parallel $ prefetchSourceRepoPkg <$> concat repos
+    let (errs, xs) = partitionEithers fetches
+    unless (null errs) $
+        die . unlines $ map show errs
+    printf "{\n"
+    mapM_ printUrlTagSha xs
+    printf "}\n"
diff --git a/project-nix/sha256map.py b/project-nix/sha256map.py
new file mode 100644
--- /dev/null
+++ b/project-nix/sha256map.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python3
+#
+# NOTE: Adapted from input-output-hk/iohk-nix, a repository with no license.
+# SOURCE: https://github.com/input-output-hk/iohk-nix/blob/master/ci/sha256map-regenerate/sha256map-regenerate.py
+#
+# This script is intended to be run as
+#
+#  python3 sha256map.py
+#
+# And it will iterate over all git references in the given stack project (stack.yaml file)
+# and generate a set of sha256 value for them to stdout.
+
+# These can then be imported as:
+# haskell-nix.stackProject' { sha256map = import ./sha256map.nix; }
+#
+# This allows building a project with haskell.nix in a restricted nix setup
+# where network access is only permitted if the sha256 of the download is known
+# beforehand.
+
+# TODO: accept a cabal.project as input as well.
+
+import re
+import subprocess
+import json
+import sys
+
+# Read stack.yaml from stin
+project = sys.stdin.read()
+
+# match looks like this:
+
+#- git: https://github.com/input-output-hk/cardano-base
+#  commit: e8a48cf0500b03c744c7fc6f2fedb86e8bdbe055
+
+pattern = r'\- git: (?P<loc>[^ \n]+).*\n' \
+        + r'(?P<pad> .*)commit: (?P<tag>[^ \n]+).*\n'
+
+def sha256entry(match):
+  dict = match.groupdict()
+  prefetchJSON = subprocess.run(
+    ["nix-prefetch-git", "--fetch-submodules", "--quiet", dict['loc'], dict['tag']],
+    capture_output=True, check=True).stdout
+  sha256 = json.loads(prefetchJSON)["sha256"]
+  return '"{loc}"."{tag}" = "{sha256}";'.format(**{**dict, **{"sha256": sha256}})
+
+# Write sha256map to stdout
+print("{")
+for x in sorted([sha256entry(match) for match in re.finditer(pattern, project)]):
+  print(" ", x)
+print("}")
diff --git a/text-templates/cabal/package.dhall b/text-templates/cabal/package.dhall
new file mode 100644
--- /dev/null
+++ b/text-templates/cabal/package.dhall
@@ -0,0 +1,11 @@
+{ constraints =
+    ../internal/constraint-section.dhall
+      ""
+      ''
+      constraints:
+          ''
+      "  , "
+      (\(c : { dep : Text, ver : Text }) -> "${c.dep} ==${c.ver}")
+, packages = ../internal/pkg-section.dhall "" "    " "  , "
+, repo-items = ./repo-items.dhall (\(sub : Text) -> "    ${sub}")
+}
diff --git a/text-templates/cabal/pkg-groups.dhall b/text-templates/cabal/pkg-groups.dhall
new file mode 100644
--- /dev/null
+++ b/text-templates/cabal/pkg-groups.dhall
@@ -0,0 +1,20 @@
+let TYPES = ./../../types.dhall
+
+in  \(pkg-import : TYPES.CabalRelativity) ->
+    \(pkg-groups : List Text) ->
+      let null = https://prelude.dhall-lang.org/List/null
+
+      let concatMapSep = https://prelude.dhall-lang.org/Text/concatMapSep
+
+      let import-pkg =
+            merge
+              { CabalProjectRelative =
+                  \(pkg : Text) -> "import: ./project-cabal/pkgs/${pkg}.config"
+              , CabalImportRelative =
+                  \(pkg : Text) -> "import: ./pkgs/${pkg}.config"
+              }
+              pkg-import
+
+      in  if    null Text pkg-groups
+          then  ""
+          else  concatMapSep "\n" Text import-pkg pkg-groups
diff --git a/text-templates/cabal/repo-items.dhall b/text-templates/cabal/repo-items.dhall
new file mode 100644
--- /dev/null
+++ b/text-templates/cabal/repo-items.dhall
@@ -0,0 +1,20 @@
+let TYPES = ../../types.dhall
+
+in  \(sub-template : Text -> Text) ->
+    \(deps-git : List TYPES.SourceRepoPkg) ->
+      let T = https://prelude.dhall-lang.org/Text/package.dhall
+
+      in  T.concatMapSep
+            "\n"
+            TYPES.SourceRepoPkg
+            ( \(s : TYPES.SourceRepoPkg) ->
+                ''
+                source-repository-package
+                  type: git
+                  location: ${s.loc}
+                  tag: ${s.tag}${../internal/sub-items.dhall
+                                   "  subdir:"
+                                   sub-template
+                                   s.sub}''
+            )
+            deps-git
diff --git a/text-templates/dhall2cabal.dhall b/text-templates/dhall2cabal.dhall
new file mode 100644
--- /dev/null
+++ b/text-templates/dhall2cabal.dhall
@@ -0,0 +1,86 @@
+let TYPES = ../types.dhall
+
+let length = https://prelude.dhall-lang.org/List/length
+
+let show = https://prelude.dhall-lang.org/Natural/show
+
+in  \(stackage-resolver : Text) ->
+    \(pkg-set : TYPES.PkgSet) ->
+    \ ( pkg-config
+      : { constraints : List TYPES.PkgVer
+        , source-pkgs :
+            { deps-external : List TYPES.SourceRepoPkg
+            , deps-internal : List TYPES.SourceRepoPkg
+            , forks-external : List TYPES.SourceRepoPkg
+            , forks-internal : List TYPES.SourceRepoPkg
+            }
+        }
+      ) ->
+      let deps-external = pkg-config.source-pkgs.deps-external
+
+      let deps-internal = pkg-config.source-pkgs.deps-internal
+
+      let forks-external = pkg-config.source-pkgs.forks-external
+
+      let forks-internal = pkg-config.source-pkgs.forks-internal
+
+      let source-deps =
+            deps-external # deps-internal # forks-external # forks-internal
+
+      let count =
+            \(xs : List TYPES.SourceRepoPkg) ->
+              show (length TYPES.SourceRepoPkg xs)
+
+      let countPkgs = \(xs : List Text) -> show (length Text xs)
+
+      let pkgs =
+            merge
+              { AllPkgs = \(pkgs : List Text) -> pkgs
+              , PkgUpgrade = \(pkgs : TYPES.PkgTodoList) -> pkgs.done
+              }
+              pkg-set
+
+      let pkgs-comment =
+            merge
+              { AllPkgs =
+                  \(pkgs : List Text) ->
+                    "-- We have ${countPkgs pkgs} packages."
+              , PkgUpgrade =
+                  \(pkgs : TYPES.PkgTodoList) ->
+                    "-- We have upgraded ${countPkgs
+                                             pkgs.done} packages and have ${countPkgs
+                                                                              pkgs.todo} yet to do."
+              }
+              pkg-set
+
+      let cabal = ./cabal/package.dhall
+
+      in  ''
+          import: ./project-stackage/${stackage-resolver}.config
+
+          ${pkgs-comment}
+          ${cabal.packages pkgs}
+
+          -- We have ${count source-deps} source packages listed in this order:
+          --   * external ${count deps-external}
+          --   * internal ${count deps-internal}
+          --   * external forks ${count forks-external}
+          --   * internal forks ${count forks-internal}
+
+          -- Source Packages, external (3rd party).
+          ${cabal.repo-items deps-external}
+
+          -- Source Packages, internal to this organisation (private and public).
+          ${cabal.repo-items deps-internal}
+
+          -- Source Packages, external (3rd party) forks of other repositories.
+          -- Can we help upstream?
+          ${cabal.repo-items forks-external}
+
+          -- Source Packages, internal forks of other repositories.
+          -- Can we upstream and unfork?
+          ${cabal.repo-items forks-internal}
+
+          -- Constraints are equivalent to stack package-version extra dependencies.
+          ${cabal.constraints pkg-config.constraints}
+          ''
diff --git a/text-templates/dhall2config.dhall b/text-templates/dhall2config.dhall
new file mode 100644
--- /dev/null
+++ b/text-templates/dhall2config.dhall
@@ -0,0 +1,15 @@
+\(stackage-resolver : Text) ->
+\(ghc-version : Text) ->
+  ''
+  import: ./project-stackage/${stackage-resolver}.config
+
+  import: ./project-cabal/pkgs.config
+
+  import: ./project-cabal/ghc-${ghc-version}/constraints.config
+  import: ./project-cabal/ghc-${ghc-version}/deps-external.config
+  import: ./project-cabal/ghc-${ghc-version}/deps-internal.config
+  import: ./project-cabal/ghc-${ghc-version}/forks-external.config
+  import: ./project-cabal/ghc-${ghc-version}/forks-internal.config
+
+  build-info: True
+  ''
diff --git a/text-templates/dhall2stack.dhall b/text-templates/dhall2stack.dhall
new file mode 100644
--- /dev/null
+++ b/text-templates/dhall2stack.dhall
@@ -0,0 +1,89 @@
+let TYPES = ../types.dhall
+
+let L = https://prelude.dhall-lang.org/List/package.dhall
+
+let show = https://prelude.dhall-lang.org/Natural/show
+
+in  \(stackage-resolver : Text) ->
+    \(pkg-set : TYPES.PkgSet) ->
+    \ ( pkg-config
+      : { constraints : List TYPES.PkgVer
+        , source-pkgs :
+            { deps-external : List TYPES.SourceRepoPkg
+            , deps-internal : List TYPES.SourceRepoPkg
+            , forks-external : List TYPES.SourceRepoPkg
+            , forks-internal : List TYPES.SourceRepoPkg
+            }
+        }
+      ) ->
+      let deps-external = pkg-config.source-pkgs.deps-external
+
+      let deps-internal = pkg-config.source-pkgs.deps-internal
+
+      let forks-external = pkg-config.source-pkgs.forks-external
+
+      let forks-internal = pkg-config.source-pkgs.forks-internal
+
+      let source-deps =
+            deps-external # deps-internal # forks-external # forks-internal
+
+      let count =
+            \(xs : List TYPES.SourceRepoPkg) ->
+              show (L.length TYPES.SourceRepoPkg xs)
+
+      let countPkgs = \(xs : List Text) -> show (L.length Text xs)
+
+      let pkgs =
+            merge
+              { AllPkgs = \(pkgs : List Text) -> pkgs
+              , PkgUpgrade = \(pkgs : TYPES.PkgTodoList) -> pkgs.done
+              }
+              pkg-set
+
+      let pkgs-comment =
+            merge
+              { AllPkgs =
+                  \(pkgs : List Text) -> "# We have ${countPkgs pkgs} packages."
+              , PkgUpgrade =
+                  \(pkgs : TYPES.PkgTodoList) ->
+                    "# We have upgraded ${countPkgs
+                                            pkgs.done} packages and have ${countPkgs
+                                                                             pkgs.todo} yet to do."
+              }
+              pkg-set
+
+      let stack = ./stack/package.dhall
+
+      in      ''
+              resolver: ${stackage-resolver}
+
+              ${pkgs-comment}
+              ${stack.packages pkgs}
+              # We have ${count
+                            source-deps} source packages listed in this order:
+              #   * external ${count deps-external}
+              #   * internal ${count deps-internal}
+              #   * external forks ${count forks-external}
+              #   * internal forks ${count forks-internal}
+              ''
+          ++  ( if        L.null TYPES.SourceRepoPkg source-deps
+                      &&  L.null TYPES.PkgVer pkg-config.constraints
+                then  "extra-deps: []"
+                else  ''
+                      extra-deps:
+
+                        # Source Packages, external (3rd party).
+                      ${stack.repo-items deps-external}
+                        # Source Packages, internal to this organisation (private and public).
+                      ${stack.repo-items deps-internal}
+                        # Source Packages, external (3rd party) forks of other repositories.
+                        # Can we help upstream?
+                      ${stack.repo-items forks-external}
+                        # Source Packages, internal forks of other repositories.
+                        # Can we upstream and unfork?
+                      ${stack.repo-items forks-internal}
+                        # Package versions for published packages either not on Stackage or
+                        # not matching the version on Stackage for the resolver we use.
+                        # These package-version extra dependencies are equivalent to cabal constraints.
+                      ${stack.constraints pkg-config.constraints}''
+              )
diff --git a/text-templates/internal/constraint-items.dhall b/text-templates/internal/constraint-items.dhall
new file mode 100644
--- /dev/null
+++ b/text-templates/internal/constraint-items.dhall
@@ -0,0 +1,18 @@
+let TYPES = ../../types.dhall
+
+in  \(separator : Text) ->
+    \(dep-ver-template : TYPES.PkgVer -> Text) ->
+    \(xs : List TYPES.PkgVer) ->
+      let null = https://prelude.dhall-lang.org/List/null
+
+      let concatMapSep = https://prelude.dhall-lang.org/Text/concatMapSep
+
+      in  if    null TYPES.PkgVer xs
+          then  ""
+          else  concatMapSep
+                  ''
+
+                  ${separator}''
+                  TYPES.PkgVer
+                  dep-ver-template
+                  xs
diff --git a/text-templates/internal/constraint-section.dhall b/text-templates/internal/constraint-section.dhall
new file mode 100644
--- /dev/null
+++ b/text-templates/internal/constraint-section.dhall
@@ -0,0 +1,15 @@
+let TYPES = ../../types.dhall
+
+in  \(null-content : Text) ->
+    \(leader : Text) ->
+    \(separator : Text) ->
+    \(dep-ver-template : TYPES.PkgVer -> Text) ->
+    \(constraints : List TYPES.PkgVer) ->
+      let null = https://prelude.dhall-lang.org/List/null
+
+      in  if    null TYPES.PkgVer constraints
+          then  null-content
+          else  "${leader}${./constraint-items.dhall
+                              separator
+                              dep-ver-template
+                              constraints}"
diff --git a/text-templates/internal/pkg-items.dhall b/text-templates/internal/pkg-items.dhall
new file mode 100644
--- /dev/null
+++ b/text-templates/internal/pkg-items.dhall
@@ -0,0 +1,13 @@
+\(separator : Text) ->
+\(xs : List Text) ->
+  let null = https://prelude.dhall-lang.org/List/null
+
+  let concatSep = https://prelude.dhall-lang.org/Text/concatSep
+
+  in  if    null Text xs
+      then  ""
+      else  concatSep
+              ''
+
+              ${separator}''
+              xs
diff --git a/text-templates/internal/pkg-section.dhall b/text-templates/internal/pkg-section.dhall
new file mode 100644
--- /dev/null
+++ b/text-templates/internal/pkg-section.dhall
@@ -0,0 +1,12 @@
+\(null-content : Text) ->
+\(first-separator : Text) ->
+\(separator : Text) ->
+\(pkgs : List Text) ->
+  let null = https://prelude.dhall-lang.org/List/null
+
+  in  if    null Text pkgs
+      then  null-content
+      else  ''
+            packages:
+            ${first-separator}${./pkg-items.dhall separator pkgs}
+            ''
diff --git a/text-templates/internal/sub-items.dhall b/text-templates/internal/sub-items.dhall
new file mode 100644
--- /dev/null
+++ b/text-templates/internal/sub-items.dhall
@@ -0,0 +1,14 @@
+\(sub-field : Text) ->
+\(sub-template : Text -> Text) ->
+\(xs : List Text) ->
+  let L = https://prelude.dhall-lang.org/List/package.dhall
+
+  let T = https://prelude.dhall-lang.org/Text/package.dhall
+
+  in  if    L.null Text xs
+      then  ""
+      else      ''
+
+                ${sub-field}
+                ''
+            ++  T.concatMapSep "\n" Text sub-template xs
diff --git a/text-templates/stack/package.dhall b/text-templates/stack/package.dhall
new file mode 100644
--- /dev/null
+++ b/text-templates/stack/package.dhall
@@ -0,0 +1,9 @@
+{ constraints =
+    ../internal/constraint-section.dhall
+      ""
+      "  - "
+      "  - "
+      (\(c : { dep : Text, ver : Text }) -> "${c.dep}-${c.ver}")
+, packages = ../internal/pkg-section.dhall "packages: []" "  - ./" "  - ./"
+, repo-items = ./repo-items.dhall (\(sub : Text) -> "      - ${sub}")
+}
diff --git a/text-templates/stack/repo-items.dhall b/text-templates/stack/repo-items.dhall
new file mode 100644
--- /dev/null
+++ b/text-templates/stack/repo-items.dhall
@@ -0,0 +1,22 @@
+let TYPES = ../../types.dhall
+
+in  \(sub-template : Text -> Text) ->
+    \(deps-git : List TYPES.SourceRepoPkg) ->
+      let L = https://prelude.dhall-lang.org/List/package.dhall
+
+      let T = https://prelude.dhall-lang.org/Text/package.dhall
+
+      in  if    L.null TYPES.SourceRepoPkg deps-git
+          then  ""
+          else  T.concatMap
+                  TYPES.SourceRepoPkg
+                  ( \(s : TYPES.SourceRepoPkg) ->
+                      ''
+                        - git: ${s.loc}
+                          commit: ${s.tag}${../internal/sub-items.dhall
+                                              "    subdirs:"
+                                              sub-template
+                                              s.sub}
+                      ''
+                  )
+                  deps-git
diff --git a/text-templates/yaml2stack/constraints.dhall b/text-templates/yaml2stack/constraints.dhall
new file mode 100644
--- /dev/null
+++ b/text-templates/yaml2stack/constraints.dhall
@@ -0,0 +1,13 @@
+let TYPES = ../../types.dhall
+
+in  \(xs : List TYPES.PkgVer) ->
+      let L = https://prelude.dhall-lang.org/List/package.dhall
+
+      let x = ../stack/package.dhall
+
+      in  if    L.null TYPES.PkgVer xs
+          then  "extra-deps: []"
+          else  ''
+                extra-deps:
+                ${x.constraints xs}
+                ''
diff --git a/text-templates/yaml2stack/pkgs.dhall b/text-templates/yaml2stack/pkgs.dhall
new file mode 100644
--- /dev/null
+++ b/text-templates/yaml2stack/pkgs.dhall
@@ -0,0 +1,1 @@
+let x = ../stack/package.dhall in x.packages
diff --git a/text-templates/yaml2stack/repo-items.dhall b/text-templates/yaml2stack/repo-items.dhall
new file mode 100644
--- /dev/null
+++ b/text-templates/yaml2stack/repo-items.dhall
@@ -0,0 +1,13 @@
+let TYPES = ../../types.dhall
+
+in  \(xs : List TYPES.SourceRepoPkg) ->
+      let L = https://prelude.dhall-lang.org/List/package.dhall
+
+      let x = ../stack/package.dhall
+
+      in  if    L.null TYPES.SourceRepoPkg xs
+          then  "extra-deps: []"
+          else  ''
+                extra-deps:
+                ${x.repo-items xs}
+                ''
diff --git a/types.dhall b/types.dhall
new file mode 100644
--- /dev/null
+++ b/types.dhall
@@ -0,0 +1,6 @@
+{ PkgTodoList = ./types/PkgTodoList.dhall
+, PkgSet = ./types/PkgSet.dhall
+, PkgVer = ./types/PkgVer.dhall
+, SourceRepoPkg = ./types/SourceRepoPkg.dhall
+, CabalRelativity = ./types/CabalRelativity.dhall
+}
diff --git a/types/CabalRelativity.dhall b/types/CabalRelativity.dhall
new file mode 100644
--- /dev/null
+++ b/types/CabalRelativity.dhall
@@ -0,0 +1,1 @@
+< CabalProjectRelative | CabalImportRelative >
diff --git a/types/PkgSet.dhall b/types/PkgSet.dhall
new file mode 100644
--- /dev/null
+++ b/types/PkgSet.dhall
@@ -0,0 +1,3 @@
+let PkgTodoList = ./PkgTodoList.dhall
+
+in  < AllPkgs : List Text | PkgUpgrade : PkgTodoList >
diff --git a/types/PkgTodoList.dhall b/types/PkgTodoList.dhall
new file mode 100644
--- /dev/null
+++ b/types/PkgTodoList.dhall
@@ -0,0 +1,1 @@
+{ done : List Text, todo : List Text }
diff --git a/types/PkgVer.dhall b/types/PkgVer.dhall
new file mode 100644
--- /dev/null
+++ b/types/PkgVer.dhall
@@ -0,0 +1,1 @@
+{ dep : Text, ver : Text }
diff --git a/types/SourceRepoPkg.dhall b/types/SourceRepoPkg.dhall
new file mode 100644
--- /dev/null
+++ b/types/SourceRepoPkg.dhall
@@ -0,0 +1,1 @@
+{ loc : Text, tag : Text, sub : List Text }
diff --git a/updo.cabal b/updo.cabal
new file mode 100644
--- /dev/null
+++ b/updo.cabal
@@ -0,0 +1,151 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.dhall by hpack version 0.35.2.
+--
+-- see: https://github.com/sol/hpack
+
+name:           updo
+version:        1.0.0
+synopsis:       A style of maintaining and upgrading Haskell projects
+description:    From configuration in .dhall, generate projects (stack.yaml and
+                cabal.project) and progressively upgrade projects.
+category:       Development
+homepage:       https://github.com/cabalism/updo#readme
+bug-reports:    https://github.com/cabalism/updo/issues
+author:         Phil de Joux
+maintainer:     phil.dejoux@blockscope.com
+copyright:      © 2023 Phil de Joux, © 2023 Block Scope Limited
+license:        MPL-2.0
+license-file:   LICENSE
+build-type:     Simple
+tested-with:
+    GHC == 8.6.5
+  , GHC == 8.10.7
+  , GHC == 9.2.8
+  , GHC == 9.4.5
+extra-source-files:
+    ALTERNATIVES.md
+    alternatives/cabal2stack/README.md
+    alternatives/stack2cabal/README.md
+    alternatives/yaml2stack/README.md
+    CHANGELOG.md
+    HACKING.md
+    project-dhall/README.md
+    project-dhall2config/README.md
+    project-nix/README.md
+    README.md
+    TEMPLATES.md
+    TESTING.md
+    package.dhall
+    project-dhall/pkgs-upgrade.dhall
+    project-nix/project-versions.dhall
+    text-templates/cabal/package.dhall
+    text-templates/cabal/pkg-groups.dhall
+    text-templates/cabal/repo-items.dhall
+    text-templates/dhall2cabal.dhall
+    text-templates/dhall2config.dhall
+    text-templates/dhall2stack.dhall
+    text-templates/internal/constraint-items.dhall
+    text-templates/internal/constraint-section.dhall
+    text-templates/internal/pkg-items.dhall
+    text-templates/internal/pkg-section.dhall
+    text-templates/internal/sub-items.dhall
+    text-templates/stack/package.dhall
+    text-templates/stack/repo-items.dhall
+    text-templates/yaml2stack/constraints.dhall
+    text-templates/yaml2stack/pkgs.dhall
+    text-templates/yaml2stack/repo-items.dhall
+    types.dhall
+    types/CabalRelativity.dhall
+    types/PkgSet.dhall
+    types/PkgTodoList.dhall
+    types/PkgVer.dhall
+    types/SourceRepoPkg.dhall
+    alternatives/cabal2stack/Makefile
+    alternatives/stack2cabal/Makefile
+    alternatives/yaml2stack/Makefile
+    Makefile
+    project-dhall/Makefile
+    project-dhall2config/Makefile
+    project-nix/Makefile
+    project-dhall/pkgs-sorted.hs
+    project-dhall/pkgs-upgrade-done.hs
+    project-dhall/pkgs-upgrade-partition.hs
+    project-dhall2config/pkg-groups.hs
+    project-nix/sha256map.hs
+    project-nix/sha256map.py
+    alternatives/yaml2stack/join-sort-fragments.sh
+
+source-repository head
+  type: git
+  location: https://github.com/cabalism/updo
+
+executable updo-pkg-groups
+  main-is: project-dhall2config/pkg-groups.hs
+  other-modules:
+      Paths_updo
+  build-depends:
+      aeson >=2.1.2 && <2.2
+    , base >=4.12 && <5
+    , dhall >=1.41.1 && <1.42
+    , filepath >=1.4.2 && <1.5
+    , text >=1.2.3 && <2.1
+    , turtle >=1.6.1 && <1.7
+    , utf8-string >=1.0.2 && <1.1
+  default-language: Haskell2010
+
+executable updo-pkgs-sorted
+  main-is: project-dhall/pkgs-sorted.hs
+  other-modules:
+      Paths_updo
+  build-depends:
+      aeson >=2.1.2 && <2.2
+    , base >=4.12 && <5
+    , dhall >=1.41.1 && <1.42
+    , filepath >=1.4.2 && <1.5
+    , text >=1.2.3 && <2.1
+    , turtle >=1.6.1 && <1.7
+    , utf8-string >=1.0.2 && <1.1
+  default-language: Haskell2010
+
+executable updo-pkgs-upgrade-done
+  main-is: project-dhall/pkgs-upgrade-done.hs
+  other-modules:
+      Paths_updo
+  build-depends:
+      aeson >=2.1.2 && <2.2
+    , base >=4.12 && <5
+    , dhall >=1.41.1 && <1.42
+    , filepath >=1.4.2 && <1.5
+    , text >=1.2.3 && <2.1
+    , turtle >=1.6.1 && <1.7
+    , utf8-string >=1.0.2 && <1.1
+  default-language: Haskell2010
+
+executable updo-pkgs-upgrade-partition
+  main-is: project-dhall/pkgs-upgrade-partition.hs
+  other-modules:
+      Paths_updo
+  build-depends:
+      aeson >=2.1.2 && <2.2
+    , base >=4.12 && <5
+    , dhall >=1.41.1 && <1.42
+    , filepath >=1.4.2 && <1.5
+    , text >=1.2.3 && <2.1
+    , turtle >=1.6.1 && <1.7
+    , utf8-string >=1.0.2 && <1.1
+  default-language: Haskell2010
+
+executable updo-sha256map
+  main-is: project-nix/sha256map.hs
+  other-modules:
+      Paths_updo
+  build-depends:
+      aeson >=2.1.2 && <2.2
+    , base >=4.12 && <5
+    , dhall >=1.41.1 && <1.42
+    , filepath >=1.4.2 && <1.5
+    , text >=1.2.3 && <2.1
+    , turtle >=1.6.1 && <1.7
+    , utf8-string >=1.0.2 && <1.1
+  default-language: Haskell2010
