Skip to content

Commit

Permalink
Use a code generator for singletons-base's test suite
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
RyanGlScott committed Dec 10, 2024
1 parent 8babd09 commit 26f07f6
Show file tree
Hide file tree
Showing 11 changed files with 214 additions and 170 deletions.
43 changes: 28 additions & 15 deletions .github/workflows/haskell-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
#
# For more information, see https://github.com/haskell-CI/haskell-ci
#
# version: 0.19.20240708
# version: 0.19.20241202
#
# REGENDATA ("0.19.20240708",["github","cabal.project"])
# REGENDATA ("0.19.20241202",["github","cabal.project"])
#
name: Haskell-CI
on:
Expand Down Expand Up @@ -90,15 +90,29 @@ jobs:
allow-failure: false
fail-fast: false
steps:
- name: apt
- name: apt-get install
run: |
apt-get update
apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common libtinfo5 libnuma-dev
- name: Install GHCup
run: |
mkdir -p "$HOME/.ghcup/bin"
curl -sL https://downloads.haskell.org/ghcup/0.1.30.0/x86_64-linux-ghcup-0.1.30.0 > "$HOME/.ghcup/bin/ghcup"
chmod a+x "$HOME/.ghcup/bin/ghcup"
"$HOME/.ghcup/bin/ghcup" install ghc "$HCVER" || (cat "$HOME"/.ghcup/logs/*.* && false)
- name: Install cabal-install
run: |
"$HOME/.ghcup/bin/ghcup" install cabal 3.12.1.0 || (cat "$HOME"/.ghcup/logs/*.* && false)
echo "CABAL=$HOME/.ghcup/bin/cabal-3.12.1.0 -vnormal+nowrap" >> "$GITHUB_ENV"
- name: Install GHC (GHCup)
if: matrix.setup-method == 'ghcup'
run: |
"$HOME/.ghcup/bin/ghcup" install ghc "$HCVER" || (cat "$HOME"/.ghcup/logs/*.* && false)
HC=$("$HOME/.ghcup/bin/ghcup" whereis ghc "$HCVER")
HCPKG=$(echo "$HC" | sed 's#ghc$#ghc-pkg#')
HADDOCK=$(echo "$HC" | sed 's#ghc$#haddock#')
echo "HC=$HC" >> "$GITHUB_ENV"
echo "HCPKG=$HCPKG" >> "$GITHUB_ENV"
echo "HADDOCK=$HADDOCK" >> "$GITHUB_ENV"
env:
HCKIND: ${{ matrix.compilerKind }}
HCNAME: ${{ matrix.compiler }}
Expand All @@ -109,21 +123,12 @@ jobs:
echo "LANG=C.UTF-8" >> "$GITHUB_ENV"
echo "CABAL_DIR=$HOME/.cabal" >> "$GITHUB_ENV"
echo "CABAL_CONFIG=$HOME/.cabal/config" >> "$GITHUB_ENV"
HCDIR=/opt/$HCKIND/$HCVER
HC=$("$HOME/.ghcup/bin/ghcup" whereis ghc "$HCVER")
HCPKG=$(echo "$HC" | sed 's#ghc$#ghc-pkg#')
HADDOCK=$(echo "$HC" | sed 's#ghc$#haddock#')
echo "HC=$HC" >> "$GITHUB_ENV"
echo "HCPKG=$HCPKG" >> "$GITHUB_ENV"
echo "HADDOCK=$HADDOCK" >> "$GITHUB_ENV"
echo "CABAL=$HOME/.ghcup/bin/cabal-3.12.1.0 -vnormal+nowrap" >> "$GITHUB_ENV"
HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\d+)\.(\d+)\.(\d+)(\.(\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))')
echo "HCNUMVER=$HCNUMVER" >> "$GITHUB_ENV"
echo "ARG_TESTS=--enable-tests" >> "$GITHUB_ENV"
echo "ARG_BENCH=--enable-benchmarks" >> "$GITHUB_ENV"
echo "HEADHACKAGE=false" >> "$GITHUB_ENV"
echo "ARG_COMPILER=--$HCKIND --with-compiler=$HC" >> "$GITHUB_ENV"
echo "GHCJSARITH=0" >> "$GITHUB_ENV"
env:
HCKIND: ${{ matrix.compilerKind }}
HCNAME: ${{ matrix.compiler }}
Expand Down Expand Up @@ -182,6 +187,7 @@ jobs:
echo "packages: $GITHUB_WORKSPACE/source/./singletons" >> cabal.project
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then echo "packages: $GITHUB_WORKSPACE/source/./singletons-th" >> cabal.project ; fi
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then echo "packages: $GITHUB_WORKSPACE/source/./singletons-base" >> cabal.project ; fi
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then echo "packages: $GITHUB_WORKSPACE/source/./singletons-base-code-generator" >> cabal.project ; fi
cat cabal.project
- name: sdist
run: |
Expand All @@ -199,25 +205,30 @@ jobs:
echo "PKGDIR_singletons_th=${PKGDIR_singletons_th}" >> "$GITHUB_ENV"
PKGDIR_singletons_base="$(find "$GITHUB_WORKSPACE/unpacked" -maxdepth 1 -type d -regex '.*/singletons-base-[0-9.]*')"
echo "PKGDIR_singletons_base=${PKGDIR_singletons_base}" >> "$GITHUB_ENV"
PKGDIR_singletons_base_code_generator="$(find "$GITHUB_WORKSPACE/unpacked" -maxdepth 1 -type d -regex '.*/singletons-base-code-generator-[0-9.]*')"
echo "PKGDIR_singletons_base_code_generator=${PKGDIR_singletons_base_code_generator}" >> "$GITHUB_ENV"
rm -f cabal.project cabal.project.local
touch cabal.project
touch cabal.project.local
echo "packages: ${PKGDIR_singletons}" >> cabal.project
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then echo "packages: ${PKGDIR_singletons_th}" >> cabal.project ; fi
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then echo "packages: ${PKGDIR_singletons_base}" >> cabal.project ; fi
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then echo "packages: ${PKGDIR_singletons_base_code_generator}" >> cabal.project ; fi
if [ $((HCNUMVER >= 80200)) -ne 0 ] ; then echo "package singletons" >> cabal.project ; fi
if [ $((HCNUMVER >= 80200)) -ne 0 ] ; then echo " ghc-options: -Werror=missing-methods" >> cabal.project ; fi
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then echo "package singletons-th" >> cabal.project ; fi
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then echo " ghc-options: -Werror=missing-methods" >> cabal.project ; fi
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then echo "package singletons-base" >> cabal.project ; fi
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then echo " ghc-options: -Werror=missing-methods" >> cabal.project ; fi
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then echo "package singletons-base-code-generator" >> cabal.project ; fi
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then echo " ghc-options: -Werror=missing-methods" >> cabal.project ; fi
cat >> cabal.project <<EOF
source-repository-package
type: git
location: https://github.com/goldfirere/th-desugar
tag: 730a0ed799c91324d42b70d3780d6b3215cafc3c
EOF
$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
$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
cat cabal.project
cat cabal.project.local
- name: dump install plan
Expand Down Expand Up @@ -248,12 +259,14 @@ jobs:
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then ${CABAL} -vnormal check ; fi
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then cd ${PKGDIR_singletons_base} || false ; fi
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then ${CABAL} -vnormal check ; fi
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then cd ${PKGDIR_singletons_base_code_generator} || false ; fi
if [ $((HCNUMVER >= 91000)) -ne 0 ] ; then ${CABAL} -vnormal check ; fi
- name: haddock
run: |
$CABAL v2-haddock --disable-documentation $ARG_COMPILER --with-haddock $HADDOCK $ARG_TESTS $ARG_BENCH all
- name: save cache
uses: actions/cache/save@v4
if: always()
uses: actions/cache/save@v4
with:
key: ${{ runner.os }}-${{ matrix.compiler }}-${{ github.sha }}
path: ~/.cabal/store
1 change: 1 addition & 0 deletions cabal.project
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
packages: ./singletons
./singletons-th
./singletons-base
./singletons-base-code-generator

source-repository-package
type: git
Expand Down
6 changes: 6 additions & 0 deletions singletons-base-code-generator/CHANGES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Changelog for the `singletons-base-code-generator` project
==========================================================

0.1 [????.??.??]
----------------
* Initial release.
27 changes: 27 additions & 0 deletions singletons-base-code-generator/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Copyright (c) 2022-2024, Ryan Scott
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. Neither the name of the author nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
10 changes: 10 additions & 0 deletions singletons-base-code-generator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
`singletons-base-code-generator`
================================

[![Hackage](https://img.shields.io/hackage/v/singletons-base-code-generator.svg)](http://hackage.haskell.org/package/singletons-base-code-generator)

A [`cabal` code
generator](https://cabal.readthedocs.io/en/stable/cabal-package-description-file.html#pkg-field-test-suite-code-generators)
used in the test suite for the
[`singletons-base`](https://hackage.haskell.org/package/singletons-base)
library.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
cabal-version: 3.8
name: singletons-base-code-generator
version: 0.1
synopsis: Code generator for the singletons-base test suite
homepage: http://www.github.com/goldfirere/singletons
category: Dependent Types
author: Ryan Scott <[email protected]>
maintainer: Ryan Scott <[email protected]>
bug-reports: https://github.com/goldfirere/singletons/issues
stability: experimental
tested-with: GHC == 9.10.1
extra-doc-files: CHANGES.md
extra-source-files: README.md
license: BSD-3-Clause
license-file: LICENSE
build-type: Simple
description:
A [@cabal@ code
generator](https://cabal.readthedocs.io/en/stable/cabal-package-description-file.html#pkg-field-test-suite-code-generators)
used in the test suite for the
[@singletons-base@](https://hackage.haskell.org/package/singletons-base)
library.

source-repository this
type: git
location: https://github.com/goldfirere/singletons.git
subdir: singletons-base-code-generator
tag: v0.1

source-repository head
type: git
location: https://github.com/goldfirere/singletons.git
subdir: singletons-base-code-generator
branch: master

executable singletons-base-code-generator
hs-source-dirs: src
ghc-options: -Wall -Wcompat -threaded
default-language: GHC2021
main-is: SingletonsBaseCodeGenerator.hs
build-depends: base >= 4.20 && < 4.21,
directory >= 1.2 && < 1.4,
filepath >= 1.3 && < 1.6
62 changes: 62 additions & 0 deletions singletons-base-code-generator/src/SingletonsBaseCodeGenerator.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
-- | A @cabal@ code generator used in the test suite for the @singletons-base@
-- library. This records all of the GHC flags used when building
-- @singletons-base@ so that when the test suite invokes GHC, it can find the
-- locally built version of @singletons-base@ and its dependencies.
module Main (main) where

import Data.List (isPrefixOf)
import System.Directory (createDirectoryIfMissing, getCurrentDirectory)
import System.Environment (getArgs)
import System.FilePath ((</>), (<.>))


main :: IO ()
main = do
-- The directory in which singletons-base and its test suite are located. This
-- only works under the assumption that cabal will navigate to that directory
-- before invoking the code generator. This is always the case when I've
-- tested it, but if this assumption does not hold in general, we may need to
-- revisit this assumption.
singletonsBaseDir <- getCurrentDirectory
args <- getArgs
(tgt, rest) <-
case args of
(tgt:allFlags) -> pure (tgt, allFlags)
[] -> fail "Expected at least one argument for code generator"
ghcFlags <- takeGhcArgs rest
-- Filter out GHC language extensions and warnings, as the singletons-base
-- test suite wants to have finer-grained control over these.
let ghcFlags' = filter (\flag -> not (isLangExtension flag || isWarning flag)) ghcFlags
createDirectoryIfMissing True tgt
writeFile (tgt </> generatedFileName <.> "hs") $ unlines
[ "module " ++ generatedFileName ++ " where"
, ""
, "ghcFlags :: [String]"
, "ghcFlags = " ++ show ghcFlags'
, ""
, "rootDir :: FilePath"
, "rootDir = " ++ show singletonsBaseDir
]
putStrLn generatedFileName

-- | @cabal@ code generators have a convention that GHC-specific arguments are
-- separated from the rest of the @cabal@-specific arguments using @--@.
-- Assuming this convention, this function looks up the GHC-specific arguments.
takeGhcArgs :: [String] -> IO [String]
takeGhcArgs ("--":xs) = pure xs
takeGhcArgs (_:xs) = takeGhcArgs xs
takeGhcArgs [] = fail "Expected -- to separate arguments"

-- | Returns 'True' if a GHC command-line argument corresponds to a language
-- extension (e.g., @-XTypeFamilies@).
isLangExtension :: String -> Bool
isLangExtension = isPrefixOf "-X"

-- | Returns 'True' if a GHC command-line argument corresponds to a warning flag
-- (e.g., @-Wtabs@).
isWarning :: String -> Bool
isWarning flag = any (`isPrefixOf` flag) ["-W", "-fwarn", "-fno-warn"]

-- | The name of the generated file containing the GHC flags.
generatedFileName :: String
generatedFileName = "SingletonsBaseGHCFlags"
5 changes: 5 additions & 0 deletions singletons-base/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ Changelog for the `singletons-base` project

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

Expand Down
Loading

0 comments on commit 26f07f6

Please sign in to comment.