ghc-settings-edit: init at 0.1.0

Changed files
+144
pkgs
development
haskell-modules
tools
haskell
+2
pkgs/development/haskell-modules/non-hackage-packages.nix
···
# from the latest master instead of the current version on Hackage.
cabal2nix-unstable = self.callPackage ./cabal2nix-unstable.nix { };
+
ghc-settings-edit = self.callPackage ../tools/haskell/ghc-settings-edit { };
+
# https://github.com/channable/vaultenv/issues/1
vaultenv = self.callPackage ../tools/haskell/vaultenv { };
+33
pkgs/development/tools/haskell/ghc-settings-edit/default.nix
···
+
{
+
stdenv,
+
ghc,
+
lib,
+
}:
+
+
stdenv.mkDerivation {
+
pname = "ghc-settings-edit";
+
version = "0.1.0";
+
# See the source code for an explanation
+
src = ./ghc-settings-edit.lhs;
+
dontUnpack = true;
+
dontBuild = true;
+
+
nativeBuildInputs = [ ghc ];
+
installPhase = ''
+
runHook preInstall
+
+
mkdir -p "$out/bin"
+
${ghc.targetPrefix}ghc --make "$src" -o "$out/bin/ghc-settings-edit"
+
+
runHook postInstall
+
'';
+
+
meta = {
+
license = [
+
lib.licenses.mit
+
lib.licenses.bsd3
+
];
+
platforms = lib.platforms.all;
+
description = "Tool for editing GHC's settings file";
+
};
+
}
+109
pkgs/development/tools/haskell/ghc-settings-edit/ghc-settings-edit.lhs
···
+
ghc-settings-edit is a small tool for changing certain fields in the settings
+
file that is part of every GHC installation (usually located at
+
lib/ghc-$version/lib/settings or lib/ghc-$version/settings). This is sometimes
+
necessary because GHC's build process leaks the tools used at build time into
+
the final settings file. This is fine, as long as the build and host platform
+
of the GHC build is the same since it will be possible to execute the tools
+
used at build time at run time. In case we are cross compiling GHC itself,
+
the settings file needs to be changed so that the correct tools are used in the
+
final installation. The GHC build system itself doesn't allow for this due to
+
its somewhat peculiar bootstrapping mechanism.
+
+
This tool was originally written by sternenseemann and is licensed under the MIT
+
license (as is nixpkgs) as well as the BSD 3 Clause license since it incorporates
+
some code from GHC. It is primarily intended for use in nixpkgs, so it should be
+
considered unstable: No guarantees about the stability of its command line
+
interface are made at this time.
+
+
> -- SPDX-License-Identifier: MIT AND BSD-3-Clause
+
> {-# LANGUAGE LambdaCase #-}
+
> module Main where
+
+
ghc-settings-edit requires no additional dependencies to the ones already
+
required to bootstrap GHC. This means that it only depends on GHC and core
+
libraries shipped with the compiler (base and containers). This property should
+
be preserved going forward as to not needlessly complicate bootstrapping GHC
+
in nixpkgs. Additionally, a wide range of library versions and thus GHC versions
+
should be supported (via CPP if necessary).
+
+
> import Control.Monad (foldM)
+
> import qualified Data.Map.Lazy as Map
+
> import System.Environment (getArgs, getProgName)
+
> import Text.Read (readEither)
+
+
Note that the containers dependency is needed to represent the contents of the
+
settings file. In theory, [(String, String)] (think lookup) would suffice, but
+
base doesn't provide any facilities for updating such lists. To avoid needlessly
+
reinventing the wheel here, we depend on an extra core library.
+
+
> type SettingsMap = Map.Map String String
+
+
ghc-settings-edit accepts the following arguments:
+
+
- The path to the settings file which is edited in place.
+
- For every field in the settings file to be updated, two arguments need to be
+
passed: the name of the field and its new value. Any number of these pairs
+
may be provided. If a field is missing from the given settings file,
+
it won't be added (see also below).
+
+
> usage :: String -> String
+
> usage name = "Usage: " ++ name ++ " FILE [KEY NEWVAL [KEY2 NEWVAL2 ...]]"
+
+
The arguments and the contents of the settings file are fed into the performEdits
+
function which implements the main logic of ghc-settings-edit (except IO).
+
+
> performEdits :: [String] -> String -> Either String String
+
> performEdits editArgs settingsString = do
+
+
First, the settings file is parsed and read into the SettingsMap structure. For
+
parsing, we can simply rely read, as GHC uses the familiar Read/Show format
+
(plus some formatting) for storing its settings. This is the main reason
+
ghc-settings-edit is written in Haskell: We don't need to roll our own parser.
+
+
> settingsMap <- Map.fromList <$> readEither settingsString
+
+
We also need to parse the remaining command line arguments (after the path)
+
which means splitting them into pairs of arguments describing the individual
+
edits. We use the chunkList utility function from GHC for this which is vendored
+
below. Since it doesn't guarantee that all sublists have the exact length given,
+
we'll have to check the length of the returned “pairs” later.
+
+
> let edits = chunkList 2 editArgs
+
+
Since each edit is a transformation of the SettingsMap, we use a fold to go
+
through the edits. The Either monad allows us to bail out if one is malformed.
+
The use of Map.adjust ensures that fields that aren't present in the original
+
settings file aren't added since the corresponding GHC installation wouldn't
+
understand them. Note that this is done silently which may be suboptimal:
+
It could be better to fail.
+
+
> show . Map.toList <$> foldM applyEdit settingsMap edits
+
> where
+
> applyEdit :: SettingsMap -> [String] -> Either String SettingsMap
+
> applyEdit m [key, newValue] = Right $ Map.adjust (const newValue) key m
+
> applyEdit _ _ = Left "Uneven number of edit arguments provided"
+
+
main just wraps performEdits and takes care of reading from and writing to the
+
given file.
+
+
> main :: IO ()
+
> main =
+
> getArgs >>= \case
+
> (settingsFile:edits) -> do
+
> orig <- readFile settingsFile
+
> case performEdits edits orig of
+
> Right edited -> writeFile settingsFile edited
+
> Left errorMsg -> error errorMsg
+
> _ -> do
+
> name <- getProgName
+
> error $ usage name
+
+
As mentioned, chunkList is taken from GHC, specifically GHC.Utils.Misc of GHC
+
verson 9.8.2. We don't depend on the ghc library directly (which would be
+
possible in theory) since there are no stability guarantees or deprecation
+
windows for the ghc's public library.
+
+
> -- | Split a list into chunks of /n/ elements
+
> chunkList :: Int -> [a] -> [[a]]
+
> chunkList _ [] = []
+
> chunkList n xs = as : chunkList n bs where (as,bs) = splitAt n xs