Introduce ormolu CI.

This commit is contained in:
Matthias Fischmann 2021-06-04 15:08:14 +02:00
parent 644a63d9f9
commit ca413cb6dd
No known key found for this signature in database
GPG Key ID: 0DE4AA9C5446EBF4
4 changed files with 269 additions and 0 deletions

51
.github/workflows/ormolu.yaml vendored Normal file
View File

@ -0,0 +1,51 @@
# FUTUREWORK: add this to `ci.dhall`?
name: Ormolu
on:
- pull_request
jobs:
ormolu:
runs-on: ubuntu-18.04
steps:
- uses: "actions/checkout@v1"
- uses: "actions/setup-haskell@v1.1.4"
id: setup-haskell-cabal
with:
cabal-version: "${{ matrix.cabal }}"
enable-stack: false
ghc-version: "${{ matrix.ghc }}"
- uses: "actions/cache@v2"
name: Cache
with:
key: "${{ runner.os }}"
path: |
"${{ steps.setup-haskell-cabal.outputs.cabal-store }}"
~/.cabal/packages
~/.cabal/store
~/.cabal/bin
dist-newstyle
~/.local/bin
- name: Install dependencies
run: |
export PATH=$PATH:$HOME/.cabal/bin:$HOME/.local/bin
export ORMOLU_VERSION=$(cat ./layout/ormolu.version)
(ormolu -v 2>/dev/null | grep -q $ORMOLU_VERSION) || (cabal update && cabal install ormolu --constraint="ormolu ==$ORMOLU_VERSION")
test -e $HOME/.local/bin/yq || pip3 install yq
shell: bash
- name: Ormolu
run: |
export PATH=$PATH:$HOME/.cabal/bin:$HOME/.local/bin
./layout/ormolu.sh -c
shell: bash
strategy:
matrix:
cabal:
- '3.2'
ghc:
- '8.10.1'

92
layout/ormolu.sh Executable file
View File

@ -0,0 +1,92 @@
#!/usr/bin/env bash
set -e
cd "$( dirname "${BASH_SOURCE[0]}" )"
command -v grep >/dev/null 2>&1 || { echo >&2 "grep is not installed, aborting."; exit 1; }
command -v awk >/dev/null 2>&1 || { echo >&2 "awk is not installed, aborting."; exit 1; }
command -v sed >/dev/null 2>&1 || { echo >&2 "sed is not installed, aborting."; exit 1; }
command -v yq >/dev/null 2>&1 || { echo >&2 "yq is not installed, aborting. See https://github.com/mikefarah/yq"; exit 1; }
ORMOLU_VERSION=$(cat ormolu.version)
( ormolu -v 2>/dev/null | grep -q $ORMOLU_VERSION ) || ( echo "please install ormolu $ORMOLU_VERSION (eg., run 'cabal install ormolu' and ensure ormolu is on your PATH.)"; exit 1 )
echo "ormolu version: $ORMOLU_VERSION"
ARG_ALLOW_DIRTY_WC="0"
ARG_ORMOLU_MODE="inplace"
USAGE="
This bash script can either (a) apply ormolu formatting in-place to
all haskell modules in your working copy, or (b) check all modules for
formatting and fail if ormolu needs to be applied.
(a) is mostly for migrating from manually-formatted projects to
ormolu-formatted ones; (b) can be run in by a continuous integration
service to make sure no branches with non-ormolu formatting make get
merged.
For every-day dev work, consider using one of the ormolu editor
integrations (see https://github.com/tweag/ormolu#editor-integration).
USAGE: $0
-h: show this help.
-f: run even if working copy is dirty. default: ${ARG_ALLOW_DIRTY_WC}
-c: set ormolu mode to 'check'. default: 'inplace'
"
# Option parsing:
# https://sookocheff.com/post/bash/parsing-bash-script-arguments-with-shopts/
while getopts ":fch" opt; do
case ${opt} in
f ) ARG_ALLOW_DIRTY_WC="1"
;;
c ) ARG_ORMOLU_MODE="check"
;;
h ) echo "$USAGE" 1>&2
exit 0
;;
esac
done
shift $((OPTIND -1))
if [ "$#" -ne 0 ]; then
echo "$USAGE" 1>&2
exit 1
fi
cd ".."
if [ "$(git status -s | grep -v \?\?)" != "" ]; then
echo "working copy not clean."
if [ "$ARG_ALLOW_DIRTY_WC" == "1" ]; then
echo "running with -f. this will mix ormolu and other changes."
else
echo "run with -f if you want to force mixing ormolu and other changes."
exit 1
fi
fi
echo "ormolu mode: $ARG_ORMOLU_MODE"
FAILURES=0
for hsfile in $(git ls-files | grep '\.hsc\?$'); do
FAILED=0
ormolu --mode $ARG_ORMOLU_MODE --check-idempotence $LANGUAGE_EXTS "$hsfile" || FAILED=1
if [ "$FAILED" == "1" ]; then
((++FAILURES))
echo "$hsfile... *** FAILED"
else
echo "$hsfile... ok"
fi
done
if [ "$FAILURES" != 0 ]; then
echo "ormolu failed on $FAILURES files."
if [ "$ARG_ORMOLU_MODE" == "check" ]; then
echo -en "\n\nyou can fix this by running 'make format' from the git repo root.\n\n"
fi
exit 1
fi

1
layout/ormolu.version Normal file
View File

@ -0,0 +1 @@
0.1.4.1

View File

@ -0,0 +1,125 @@
#!/usr/bin/env bash
# written by mheinzel
set -euo pipefail
command -v sed >/dev/null 2>&1 || { echo >&2 "sed is not installed, aborting."; exit 1; }
BASE_COMMIT=${1:-}
TARGET_COMMIT=${2:-}
FORMATTING_COMMAND='make formatf'
USAGE="
USAGE: $0 BASE_COMMIT TARGET_COMMIT
BASE_COMMIT:
A commit that contains the changes to formatting version and
config already from TARGET_COMMIT, but not the automatically
applied formatting changes. Must be the first commit on the
branch you are about to rebase (not the one returned by
git-merge-base). It will be removed from the resulting branch.
TARGET_COMMIT:
The commit introducing the formatting that you want to rebase onto.
Rebase a branch onto changes created by an automated formatter. The script
will keep the (linear) history of the branch intact and make the commits appear
as if the changes had been applied onto the newly-formatted version all along.
INSTRUCTIONS:
1. Make a copy of your branch (or be prepared to salvage it from reflog).
$ git branch mybranch-backup
2. Find out what the base commit is.
3. Rebase onto the base commit yourself.
$ git rebase \$BASE_COMMIT
4. Make sure the formatting tool is installed with the correct version and settings.
$ stack install ormolu
5. Run this script.
$ $0 \$BASE_COMMIT \$TARGET_COMMIT
"
if [ -z "$BASE_COMMIT" ] || [ -z "$TARGET_COMMIT" ] || [ -z "$FORMATTING_COMMAND" ]
then
echo "$USAGE" 1>&2
exit 1
fi
echo "Running the script now. This might take a while..."
# The general idea is the following:
#
# We have a branch consisting of commits C1, C2, ... on top of our BASE_COMMIT C0.
# Also, from C0 an automated formatting change f was made on some branch (e.g. develop).
#
# C0 ----> C1 ----> C2 ----> ... ----> Cn
# |
# f
# |
# v
# C0'
#
# Now, how do we obtain versions of our commits operating on the formatted code (let's call them Ci')?
#
# C0 ----> C1 ----> C2 ----> ... ----> Cn
# |
# f
# |
# v
# C0' ---> C1' ---> C2' ---> ... ----> Cn'
#
# One useful thing is that since f is defined by an automated tool,
# we know f applied at every commit Ci, resulting in a hypothetical Ci'.
#
# C0 ----> C1 ----> C2 ----> ... ----> Cn
# | | | |
# f f f f
# | | | |
# v v v v
# C0' C1' C2' Cn'
#
# And we can also get its inverse g (applied at Ci') by reverting the commit.
#
# C0 ----> C1 ----> C2 ----> ... ----> Cn
# |^ |^ |^ |^
# f| f| f| f|
# |g |g |g |g
# v| v| v| v|
# C0' C1' C2' Cn'
#
# Finally, we can get from C(i-1)' to Ci' by composing three arrows:
# - g at C(i-1)
# - Ci
# - f at C1
#
# C0 ----> C1 ----> C2 ----> ... ----> Cn
# |^ |^ |^ |^
# f| f| f| f|
# |g |g |g |g
# v| v| v| v|
# C0' ---> C1' ---> C2' ---> ... ----> Cn'
set -x
# edit every commit Ci, adding new commits representing f at Ci and it's inverse g
git rebase $BASE_COMMIT~1 --exec "$FORMATTING_COMMAND && git commit -am format && git revert HEAD --no-edit"
# drop last commit (do not revert formatting at the end of the branch)
git reset HEAD~1 --hard
# now for every Ci, squash with the previous and next commit (i.e. g at C(i-1) and f at Ci).
# However, we want to use Ci's commit message and author.
# To do this, we run the following command after each group of these 3 commits:
# Ci=$(git rev-parse HEAD~1); git reset --soft HEAD~3; git commit --reuse-message $Ci
# We do an interactive rebase, but instead of editing the commit sequence manually,
# we use sed for that, inserting an `exec` command after every 3 commits.
GIT_SEQUENCE_EDITOR='sed -i -e "4~3s/^\(pick \S* format\)$/\1\nexec Ci=\$(git rev-parse HEAD~1); git reset --soft HEAD~3; git commit --reuse-message \$Ci/"' \
git rebase --interactive $BASE_COMMIT
# rebase onto TARGET_COMMIT.
# Annoyingly, we still have this first "format" commit that should already be
# part of the TARGET_COMMIT. So we drop it.
GIT_SEQUENCE_EDITOR='sed -i "1s/pick/drop/"' \
git rebase --interactive $BASE_COMMIT --onto $TARGET_COMMIT
echo "Done."
echo "Please check that the history looks as it should and all expected commits are there."