Skip to content

Commit 26f07f6

Browse files
committed
Use a code generator for singletons-base's test suite
This replaces `singletons-base`'s custom `Setup.hs` script with a `cabal` code generator. This finally allows `singletons-base` to have a `Simple` build type, but at the (relatively less extreme) cost of requiring `Cabal-3.8` or later in order to build. Remarkably, everything that the custom `Setup.hs` script did can be done in a much simpler way with a `cabal` code generator, as all of the information that the `singletons-base` test suite needs to invoke GHC can be inferred from the arguments passed to a code generator. One downside is that due to haskell/cabal#8421, the code generator must be implemented in a standalone executable package (`singletons-base-code-generator`). Until that `cabal` issue is fixed, we will need to upload `singletons-base-code-generator` to Hackage with each `singletons` release. Fixes #532.
1 parent 8babd09 commit 26f07f6

File tree

11 files changed

+214
-170
lines changed

11 files changed

+214
-170
lines changed

.github/workflows/haskell-ci.yml

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
#
99
# For more information, see https://github.com/haskell-CI/haskell-ci
1010
#
11-
# version: 0.19.20240708
11+
# version: 0.19.20241202
1212
#
13-
# REGENDATA ("0.19.20240708",["github","cabal.project"])
13+
# REGENDATA ("0.19.20241202",["github","cabal.project"])
1414
#
1515
name: Haskell-CI
1616
on:
@@ -90,15 +90,29 @@ jobs:
9090
allow-failure: false
9191
fail-fast: false
9292
steps:
93-
- name: apt
93+
- name: apt-get install
9494
run: |
9595
apt-get update
9696
apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common libtinfo5 libnuma-dev
97+
- name: Install GHCup
98+
run: |
9799
mkdir -p "$HOME/.ghcup/bin"
98100
curl -sL https://downloads.haskell.org/ghcup/0.1.30.0/x86_64-linux-ghcup-0.1.30.0 > "$HOME/.ghcup/bin/ghcup"
99101
chmod a+x "$HOME/.ghcup/bin/ghcup"
100-
"$HOME/.ghcup/bin/ghcup" install ghc "$HCVER" || (cat "$HOME"/.ghcup/logs/*.* && false)
102+
- name: Install cabal-install
103+
run: |
101104
"$HOME/.ghcup/bin/ghcup" install cabal 3.12.1.0 || (cat "$HOME"/.ghcup/logs/*.* && false)
105+
echo "CABAL=$HOME/.ghcup/bin/cabal-3.12.1.0 -vnormal+nowrap" >> "$GITHUB_ENV"
106+
- name: Install GHC (GHCup)
107+
if: matrix.setup-method == 'ghcup'
108+
run: |
109+
"$HOME/.ghcup/bin/ghcup" install ghc "$HCVER" || (cat "$HOME"/.ghcup/logs/*.* && false)
110+
HC=$("$HOME/.ghcup/bin/ghcup" whereis ghc "$HCVER")
111+
HCPKG=$(echo "$HC" | sed 's#ghc$#ghc-pkg#')
112+
HADDOCK=$(echo "$HC" | sed 's#ghc$#haddock#')
113+
echo "HC=$HC" >> "$GITHUB_ENV"
114+
echo "HCPKG=$HCPKG" >> "$GITHUB_ENV"
115+
echo "HADDOCK=$HADDOCK" >> "$GITHUB_ENV"
102116
env:
103117
HCKIND: ${{ matrix.compilerKind }}
104118
HCNAME: ${{ matrix.compiler }}
@@ -109,21 +123,12 @@ jobs:
109123
echo "LANG=C.UTF-8" >> "$GITHUB_ENV"
110124
echo "CABAL_DIR=$HOME/.cabal" >> "$GITHUB_ENV"
111125
echo "CABAL_CONFIG=$HOME/.cabal/config" >> "$GITHUB_ENV"
112-
HCDIR=/opt/$HCKIND/$HCVER
113-
HC=$("$HOME/.ghcup/bin/ghcup" whereis ghc "$HCVER")
114-
HCPKG=$(echo "$HC" | sed 's#ghc$#ghc-pkg#')
115-
HADDOCK=$(echo "$HC" | sed 's#ghc$#haddock#')
116-
echo "HC=$HC" >> "$GITHUB_ENV"
117-
echo "HCPKG=$HCPKG" >> "$GITHUB_ENV"
118-
echo "HADDOCK=$HADDOCK" >> "$GITHUB_ENV"
119-
echo "CABAL=$HOME/.ghcup/bin/cabal-3.12.1.0 -vnormal+nowrap" >> "$GITHUB_ENV"
120126
HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\d+)\.(\d+)\.(\d+)(\.(\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))')
121127
echo "HCNUMVER=$HCNUMVER" >> "$GITHUB_ENV"
122128
echo "ARG_TESTS=--enable-tests" >> "$GITHUB_ENV"
123129
echo "ARG_BENCH=--enable-benchmarks" >> "$GITHUB_ENV"
124130
echo "HEADHACKAGE=false" >> "$GITHUB_ENV"
125131
echo "ARG_COMPILER=--$HCKIND --with-compiler=$HC" >> "$GITHUB_ENV"
126-
echo "GHCJSARITH=0" >> "$GITHUB_ENV"
127132
env:
128133
HCKIND: ${{ matrix.compilerKind }}
129134
HCNAME: ${{ matrix.compiler }}
@@ -182,6 +187,7 @@ jobs:
182187
echo "packages: $GITHUB_WORKSPACE/source/./singletons" >> cabal.project
183188
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then echo "packages: $GITHUB_WORKSPACE/source/./singletons-th" >> cabal.project ; fi
184189
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then echo "packages: $GITHUB_WORKSPACE/source/./singletons-base" >> cabal.project ; fi
190+
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then echo "packages: $GITHUB_WORKSPACE/source/./singletons-base-code-generator" >> cabal.project ; fi
185191
cat cabal.project
186192
- name: sdist
187193
run: |
@@ -199,25 +205,30 @@ jobs:
199205
echo "PKGDIR_singletons_th=${PKGDIR_singletons_th}" >> "$GITHUB_ENV"
200206
PKGDIR_singletons_base="$(find "$GITHUB_WORKSPACE/unpacked" -maxdepth 1 -type d -regex '.*/singletons-base-[0-9.]*')"
201207
echo "PKGDIR_singletons_base=${PKGDIR_singletons_base}" >> "$GITHUB_ENV"
208+
PKGDIR_singletons_base_code_generator="$(find "$GITHUB_WORKSPACE/unpacked" -maxdepth 1 -type d -regex '.*/singletons-base-code-generator-[0-9.]*')"
209+
echo "PKGDIR_singletons_base_code_generator=${PKGDIR_singletons_base_code_generator}" >> "$GITHUB_ENV"
202210
rm -f cabal.project cabal.project.local
203211
touch cabal.project
204212
touch cabal.project.local
205213
echo "packages: ${PKGDIR_singletons}" >> cabal.project
206214
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then echo "packages: ${PKGDIR_singletons_th}" >> cabal.project ; fi
207215
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then echo "packages: ${PKGDIR_singletons_base}" >> cabal.project ; fi
216+
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then echo "packages: ${PKGDIR_singletons_base_code_generator}" >> cabal.project ; fi
208217
if [ $((HCNUMVER >= 80200)) -ne 0 ] ; then echo "package singletons" >> cabal.project ; fi
209218
if [ $((HCNUMVER >= 80200)) -ne 0 ] ; then echo " ghc-options: -Werror=missing-methods" >> cabal.project ; fi
210219
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then echo "package singletons-th" >> cabal.project ; fi
211220
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then echo " ghc-options: -Werror=missing-methods" >> cabal.project ; fi
212221
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then echo "package singletons-base" >> cabal.project ; fi
213222
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then echo " ghc-options: -Werror=missing-methods" >> cabal.project ; fi
223+
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then echo "package singletons-base-code-generator" >> cabal.project ; fi
224+
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then echo " ghc-options: -Werror=missing-methods" >> cabal.project ; fi
214225
cat >> cabal.project <<EOF
215226
source-repository-package
216227
type: git
217228
location: https://github.com/goldfirere/th-desugar
218229
tag: 730a0ed799c91324d42b70d3780d6b3215cafc3c
219230
EOF
220-
$HCPKG list --simple-output --names-only | perl -ne 'for (split /\s+/) { print "constraints: any.$_ installed\n" unless /^(Cabal|Cabal-syntax|singletons|singletons-base|singletons-th)$/; }' >> cabal.project.local
231+
$HCPKG list --simple-output --names-only | perl -ne 'for (split /\s+/) { print "constraints: any.$_ installed\n" unless /^(Cabal|Cabal-syntax|singletons|singletons-base|singletons-base-code-generator|singletons-th)$/; }' >> cabal.project.local
221232
cat cabal.project
222233
cat cabal.project.local
223234
- name: dump install plan
@@ -248,12 +259,14 @@ jobs:
248259
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then ${CABAL} -vnormal check ; fi
249260
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then cd ${PKGDIR_singletons_base} || false ; fi
250261
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then ${CABAL} -vnormal check ; fi
262+
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then cd ${PKGDIR_singletons_base_code_generator} || false ; fi
263+
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then ${CABAL} -vnormal check ; fi
251264
- name: haddock
252265
run: |
253266
$CABAL v2-haddock --disable-documentation $ARG_COMPILER --with-haddock $HADDOCK $ARG_TESTS $ARG_BENCH all
254267
- name: save cache
255-
uses: actions/cache/save@v4
256268
if: always()
269+
uses: actions/cache/save@v4
257270
with:
258271
key: ${{ runner.os }}-${{ matrix.compiler }}-${{ github.sha }}
259272
path: ~/.cabal/store

cabal.project

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
packages: ./singletons
22
./singletons-th
33
./singletons-base
4+
./singletons-base-code-generator
45

56
source-repository-package
67
type: git
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Changelog for the `singletons-base-code-generator` project
2+
==========================================================
3+
4+
0.1 [????.??.??]
5+
----------------
6+
* Initial release.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
Copyright (c) 2022-2024, Ryan Scott
2+
All rights reserved.
3+
4+
Redistribution and use in source and binary forms, with or without
5+
modification, are permitted provided that the following conditions are met:
6+
7+
1. Redistributions of source code must retain the above copyright notice, this
8+
list of conditions and the following disclaimer.
9+
10+
2. Redistributions in binary form must reproduce the above copyright notice,
11+
this list of conditions and the following disclaimer in the documentation
12+
and/or other materials provided with the distribution.
13+
14+
3. Neither the name of the author nor the names of its contributors may be
15+
used to endorse or promote products derived from this software without
16+
specific prior written permission.
17+
18+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
`singletons-base-code-generator`
2+
================================
3+
4+
[![Hackage](https://img.shields.io/hackage/v/singletons-base-code-generator.svg)](http://hackage.haskell.org/package/singletons-base-code-generator)
5+
6+
A [`cabal` code
7+
generator](https://cabal.readthedocs.io/en/stable/cabal-package-description-file.html#pkg-field-test-suite-code-generators)
8+
used in the test suite for the
9+
[`singletons-base`](https://hackage.haskell.org/package/singletons-base)
10+
library.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
cabal-version: 3.8
2+
name: singletons-base-code-generator
3+
version: 0.1
4+
synopsis: Code generator for the singletons-base test suite
5+
homepage: http://www.github.com/goldfirere/singletons
6+
category: Dependent Types
7+
author: Ryan Scott <[email protected]>
8+
maintainer: Ryan Scott <[email protected]>
9+
bug-reports: https://github.com/goldfirere/singletons/issues
10+
stability: experimental
11+
tested-with: GHC == 9.10.1
12+
extra-doc-files: CHANGES.md
13+
extra-source-files: README.md
14+
license: BSD-3-Clause
15+
license-file: LICENSE
16+
build-type: Simple
17+
description:
18+
A [@cabal@ code
19+
generator](https://cabal.readthedocs.io/en/stable/cabal-package-description-file.html#pkg-field-test-suite-code-generators)
20+
used in the test suite for the
21+
[@singletons-base@](https://hackage.haskell.org/package/singletons-base)
22+
library.
23+
24+
source-repository this
25+
type: git
26+
location: https://github.com/goldfirere/singletons.git
27+
subdir: singletons-base-code-generator
28+
tag: v0.1
29+
30+
source-repository head
31+
type: git
32+
location: https://github.com/goldfirere/singletons.git
33+
subdir: singletons-base-code-generator
34+
branch: master
35+
36+
executable singletons-base-code-generator
37+
hs-source-dirs: src
38+
ghc-options: -Wall -Wcompat -threaded
39+
default-language: GHC2021
40+
main-is: SingletonsBaseCodeGenerator.hs
41+
build-depends: base >= 4.20 && < 4.21,
42+
directory >= 1.2 && < 1.4,
43+
filepath >= 1.3 && < 1.6
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
-- | A @cabal@ code generator used in the test suite for the @singletons-base@
2+
-- library. This records all of the GHC flags used when building
3+
-- @singletons-base@ so that when the test suite invokes GHC, it can find the
4+
-- locally built version of @singletons-base@ and its dependencies.
5+
module Main (main) where
6+
7+
import Data.List (isPrefixOf)
8+
import System.Directory (createDirectoryIfMissing, getCurrentDirectory)
9+
import System.Environment (getArgs)
10+
import System.FilePath ((</>), (<.>))
11+
12+
13+
main :: IO ()
14+
main = do
15+
-- The directory in which singletons-base and its test suite are located. This
16+
-- only works under the assumption that cabal will navigate to that directory
17+
-- before invoking the code generator. This is always the case when I've
18+
-- tested it, but if this assumption does not hold in general, we may need to
19+
-- revisit this assumption.
20+
singletonsBaseDir <- getCurrentDirectory
21+
args <- getArgs
22+
(tgt, rest) <-
23+
case args of
24+
(tgt:allFlags) -> pure (tgt, allFlags)
25+
[] -> fail "Expected at least one argument for code generator"
26+
ghcFlags <- takeGhcArgs rest
27+
-- Filter out GHC language extensions and warnings, as the singletons-base
28+
-- test suite wants to have finer-grained control over these.
29+
let ghcFlags' = filter (\flag -> not (isLangExtension flag || isWarning flag)) ghcFlags
30+
createDirectoryIfMissing True tgt
31+
writeFile (tgt </> generatedFileName <.> "hs") $ unlines
32+
[ "module " ++ generatedFileName ++ " where"
33+
, ""
34+
, "ghcFlags :: [String]"
35+
, "ghcFlags = " ++ show ghcFlags'
36+
, ""
37+
, "rootDir :: FilePath"
38+
, "rootDir = " ++ show singletonsBaseDir
39+
]
40+
putStrLn generatedFileName
41+
42+
-- | @cabal@ code generators have a convention that GHC-specific arguments are
43+
-- separated from the rest of the @cabal@-specific arguments using @--@.
44+
-- Assuming this convention, this function looks up the GHC-specific arguments.
45+
takeGhcArgs :: [String] -> IO [String]
46+
takeGhcArgs ("--":xs) = pure xs
47+
takeGhcArgs (_:xs) = takeGhcArgs xs
48+
takeGhcArgs [] = fail "Expected -- to separate arguments"
49+
50+
-- | Returns 'True' if a GHC command-line argument corresponds to a language
51+
-- extension (e.g., @-XTypeFamilies@).
52+
isLangExtension :: String -> Bool
53+
isLangExtension = isPrefixOf "-X"
54+
55+
-- | Returns 'True' if a GHC command-line argument corresponds to a warning flag
56+
-- (e.g., @-Wtabs@).
57+
isWarning :: String -> Bool
58+
isWarning flag = any (`isPrefixOf` flag) ["-W", "-fwarn", "-fno-warn"]
59+
60+
-- | The name of the generated file containing the GHC flags.
61+
generatedFileName :: String
62+
generatedFileName = "SingletonsBaseGHCFlags"

singletons-base/CHANGES.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ Changelog for the `singletons-base` project
33

44
next [????.??.??]
55
-----------------
6+
* Remove the use of a custom `Setup.hs` script. This script has now been
7+
replaced with a [`cabal` code
8+
generator](https://cabal.readthedocs.io/en/stable/cabal-package-description-file.html#pkg-field-test-suite-code-generators)
9+
As such, `singletons-base` now requires the use of `Cabal-3.8` or later in
10+
order to build.
611
* The types of `sError`, `sErrorWithoutStackTrace`, and `sUndefined` are now
712
less polymorphic than they were before:
813

0 commit comments

Comments
 (0)