Browse Source

Officially rename this project to Phoebe

next
Peter J. Jones 1 year ago
commit
320db663be
9 changed files with 637 additions and 0 deletions
  1. 26
    0
      LICENSE
  2. 40
    0
      README.md
  3. 18
    0
      default.nix
  4. 38
    0
      nix/call-package.nix
  5. 109
    0
      nix/interactive.nix
  6. 47
    0
      nix/package.nix
  7. 19
    0
      nix/stack.nix
  8. 320
    0
      src/nix-hs.sh
  9. 20
    0
      templates/default.nix

+ 26
- 0
LICENSE View File

@@ -0,0 +1,26 @@
Copyright (c) 2017 Peter J. Jones <pjones@devalot.com>
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.

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
OWNER 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.

+ 40
- 0
README.md View File

@@ -0,0 +1,40 @@
# Haskell + nixpkgs = nix-hs

Are you a [Haskell][] programmer? Do you use [nixpkgs][]? Want to
make using those two together really simple? You're in luck.

This project provides a set of Nix files and a tool called `nix-hs`
that makes working with Haskell projects very simple. For starters,
Nix files are automatically generated and updated as needed. Other
features include:

* Works with both `cabal` and `stack`
* Build with profiling using a command line option
* Easily use any version of GHC in `nixpkgs`
* Interactive development and package generation

## Installing nix-hs

Coming soon...

Hint: Install it as an [overlay] [].

## Interactive Development

Coming soon...

Hint: `$ nix-hs -h`

## Making a Private Package for nixpkgs

Coming soon...

## Other Things You Should Know

* In order to be idempotent, `nix-hs` runs `cabal` without a
configuration file (usually `~/.cabal/config`). This also keeps
`cabal` from downloading packages from hackage.

[haskell]: https://www.haskell.org/
[nixpkgs]: https://nixos.org/nix/
[overlay]: https://nixos.org/nixpkgs/manual/#chap-overlays

+ 18
- 0
default.nix View File

@@ -0,0 +1,18 @@
################################################################################
# This file is a nixpkgs overlay.

################################################################################
#
# This file is part of the package nix-hs. It is subject to the license
# terms in the LICENSE file found in the top-level directory of this
# distribution and at:
#
# git://git.devalot.com/nix-hs.git
#
# No part of this package, including this file, may be copied, modified,
# propagated, or distributed except according to the terms contained in
# the LICENSE file.

self: super: {
nix-hs = with self; callPackage ./nix/package.nix { };
}

+ 38
- 0
nix/call-package.nix View File

@@ -0,0 +1,38 @@
################################################################################
#
# This file is part of the package nix-hs. It is subject to the license
# terms in the LICENSE file found in the top-level directory of this
# distribution and at:
#
# git://git.devalot.com/nix-hs.git
#
# No part of this package, including this file, may be copied, modified,
# propagated, or distributed except according to the terms contained in
# the LICENSE file.

################################################################################
{ stdenv, pkgs, lib, ...}:

################################################################################
fn: # This is the function/file generated by cabal2nix.

{ src # Where to get the package from.
, haskell ? pkgs.haskellPackages # Pass this in from your Haskell overrides.
, buildInputs ? [] # Extra packages needed during building.
, postInstall ? "" # Extra install steps
, builderOptions ? { } # Extra options to pass to the Haskell mkDerivation
}:

################################################################################
let callPackage = haskell.callPackage fn {
mkDerivation = { buildTools ? []
, ...
}@args:
haskell.mkDerivation (args // builderOptions // {
buildTools = buildInputs ++ buildTools;
postInstall = postInstall;
src = src;
});
};

in callPackage

+ 109
- 0
nix/interactive.nix View File

@@ -0,0 +1,109 @@
################################################################################
#
# This file is part of the package nix-hs. It is subject to the license
# terms in the LICENSE file found in the top-level directory of this
# distribution and at:
#
# git://git.devalot.com/nix-hs.git
#
# No part of this package, including this file, may be copied, modified,
# propagated, or distributed except according to the terms contained in
# the LICENSE file.
#
################################################################################
{ pkgs ? (import <nixpkgs> {}).pkgs
, compiler ? "default" # Which version of GHC to use, or "default".
, profiling ? false # Enable profiling or not.
, optimization ? true # Enable optimization or not.
, doHaddock ? true # Create documentation for dependencies.
, file # The package file (default.nix) to load.
}:

with pkgs.lib;

let

# Some handy bindings:
cabal = "${haskell.cabal-install}/bin/cabal";

cabalConfigureFlags = concatStringsSep " "
[ (optionalString optimization "--enable-optimization")
"--enable-tests" # Safe to always keep on.
];

# These are the shell functions that `nix-hs' will call into.
shellFunctions = "\n" + ''
# NOTE: This is here because the generic Haskell builder doesn't
# end it's shellHook with a newline and so we get syntax errors
# without the blank line above.
alias cabal='${cabal} --config-file=/dev/null'

do_cabal_configure() {
set -e
cabal configure ${cabalConfigureFlags}
}

do_cabal_build() {
set -e
cabal build
}

do_cabal_test() {
set -e
cabal test
}

do_cabal_clean() {
set -e
cabal clean
}

do_cabal_repl() {
set -e
cabal repl "$@"
}
'';

# Select a compiler:
basePackages =
if compiler == "default"
then pkgs.haskellPackages
else pkgs.haskell.packages."ghc${compiler}";

# Overrides to control properties such as profiling. This is a bit
# of a mess because Haskell overrides in Nix don't seem to compose
# well: https://github.com/NixOS/nixpkgs/issues/26561
haskell = basePackages.override (orig: {
overrides = composeExtensions (orig.overrides or (_: _: {}))
(self: super: {
mkDerivation = { shellHook ? ""
, ...
}@args:
super.mkDerivation (args // {
inherit doHaddock;
shellHook = shellHook + shellFunctions;
enableLibraryProfiling = profiling;
enableExecutableProfiling = profiling;
});
});
});

# haskell = basePackages.extend (self: super: {
# mkDerivation = { shellHook ? ""
# , ...
# }@args:
# super.mkDerivation (args // {
# inherit doHaddock;
# shellHook = shellHook + shellFunctions;
# enableLibraryProfiling = profiling;
# enableExecutableProfiling = profiling;
# });
# });

# Override the Haskell package set with the one from above:
alteredPackages = pkgs // { haskellPackages = haskell; };

# Load the local file:
drv = import file { pkgs = alteredPackages; };

in drv.env

+ 47
- 0
nix/package.nix View File

@@ -0,0 +1,47 @@
################################################################################
#
# This file is part of the package nix-hs. It is subject to the license
# terms in the LICENSE file found in the top-level directory of this
# distribution and at:
#
# git://git.devalot.com/nix-hs.git
#
# No part of this package, including this file, may be copied, modified,
# propagated, or distributed except according to the terms contained in
# the LICENSE file.

################################################################################
{ stdenvNoCC, pkgs, lib
, bash, cabal2nix, haskellPackages
}:

with lib;

let
drv = stdenvNoCC.mkDerivation rec {
name = "nix-hs-${version}";
version = "0.2.0";

phases = [ "installPhase" ];

installPhase = ''
mkdir -p $out/bin $out/templates $out/lib

export bash=${bash}
export cabal2nix=${cabal2nix}
export stack=${haskellPackages.stack}
export templates=$out/templates
export interactive=$out/lib/interactive.nix
export stacknix=$out/lib/stack.nix

substituteAll ${../src/nix-hs.sh} $out/bin/nix-hs
chmod 0555 $out/bin/nix-hs

install -m0444 ${../templates/default.nix} $out/templates/default.nix
install -m0444 ${../nix/interactive.nix} $out/lib/interactive.nix
install -m0400 ${../nix/stack.nix} $out/lib/stack.nix
'';
};
in {
callPackage = pkgs.callPackage ./call-package.nix { };
} // drv

+ 19
- 0
nix/stack.nix View File

@@ -0,0 +1,19 @@
{ pkgs ? (import <nixpkgs> {}).pkgs
, ghc ? pkgs.ghc
, file
}:

let
# Load the local package:
package = import file { pkgs = pkgs; };

# Pull build inputs from the local package: (not sure why, but there
# are some nulls in the buildInputs that we need to remove).
buildInputs = builtins.filter (p: p != null)
package.buildInputs;

in pkgs.haskell.lib.buildStackProject {
name = package.name;
buildInputs = buildInputs;
inherit ghc;
}

+ 320
- 0
src/nix-hs.sh View File

@@ -0,0 +1,320 @@
#! @bash@/bin/bash

################################################################################
#
# This file is part of the package nix-hs. It is subject to the license
# terms in the LICENSE file found in the top-level directory of this
# distribution and at:
#
# git://git.devalot.com/nix-hs.git
#
# No part of this package, including this file, may be copied, modified,
# propagated, or distributed except according to the terms contained in
# the LICENSE file.

################################################################################
set -e
set -u

################################################################################
export HASKELL_PROJECT_NAME
export HASKELL_PROJECT_DIR

################################################################################
option_compiler=default
option_profiling=false
option_debug=0
option_tool=""
option_nixshell_args=()

################################################################################
usage () {
cat <<EOF
Usage: nix-hs [options] (build|test|clean|repl|shell)

-c VER Use GHC version VER
-d Enable debugging info for nix-hs
-h This message
-I PATH Add PATH to NIX_PATH
-p Enable profiling [default: off]
-n PATH Shortcut for '-I nixpkgs=PATH'
-t TYPE Force using build type TYPE (cabal|stack|make)
EOF
}

################################################################################
die() {
echo "ERROR:" "$@" > /dev/stderr
exit 1
}

################################################################################
get_project_name() {
local name
local matches

name=$(ls ./?*.cabal 2> /dev/null)
matches=$(echo "$name" | wc -l)

if [ "$matches" != 1 ]; then
return 1
fi

basename "$name" .cabal
}

################################################################################
# Search for a project Cabal file, changing to the directory that
# contains it or dies.
find_project() {
local dir
local name

dir=$(pwd)

while [ "$dir" != / ]; do
if name=$(get_project_name); then
break
fi

dir=$(dirname "$dir")
cd "$dir"
done

if [ "$dir" = / ]; then
die "cannot find the .cabal file for this project"
else
HASKELL_PROJECT_NAME="$name"
HASKELL_PROJECT_DIR="$dir"
fi
}

################################################################################
# Check to see if the project root is *not* the same directory as the
# project directory. This is a common directory layout with stack
# where one stack.yaml file is used to point to several projects.
find_stack_root() {
# See if the parent directory has a stack file:
export STACK_YAML=${STACK_YAML:-stack.yaml}

if [ -r "$STACK_YAML" ]; then
return 0
elif [ ! -r "$STACK_YAML" ] && [ -r ../"$STACK_YAML" ]; then
export STACK_YAML="../$STACK_YAML"
return 0
fi

return 1
}

################################################################################
nix_shell() {
local extra_options=()

if [ "$option_debug" -eq 1 ]; then
extra_options+=("--show-trace")
fi

# FIXME: support all interactive.nix options.

nix-shell --pure "$@" \
--argstr file "$(pwd)/default.nix" \
--argstr compiler "$option_compiler" \
--arg profiling "$option_profiling" \
"${option_nixshell_args[@]}" "${extra_options[@]}" \
@interactive@
}

################################################################################
# Create/update the nix file from the cabal file.
prepare_nix_files() {
local cabal_file=${HASKELL_PROJECT_NAME}.cabal
local nix_file=${HASKELL_PROJECT_NAME}.nix

if [ ! -r "$nix_file" ] || [ "$cabal_file" -nt "$nix_file" ]; then
@cabal2nix@/bin/cabal2nix . > "$nix_file"
fi

if [ ! -r "default.nix" ]; then
sed -e "s/@PROJECT@/${HASKELL_PROJECT_NAME}/g" \
< @templates@/default.nix > default.nix
fi
}

################################################################################
# If needed, run `cabal configure'.
cabal_configure() {
local cabal_file=${HASKELL_PROJECT_NAME}.cabal
local datestamp=dist/.configure-run-date

if [ ! -r "$datestamp" ] || [ "$cabal_file" -nt "$datestamp" ]; then
nix_shell --command "do_cabal_configure"
date > dist/.configure-run-date
fi
}

################################################################################
run_cabal() {
prepare_nix_files
cabal_configure

case "${1:-build}" in
repl)
nix_shell --command "do_cabal_repl lib:$HASKELL_PROJECT_NAME"
;;

shell)
nix_shell
;;

*)
nix_shell --command "do_cabal_$1"
;;
esac
}

################################################################################
# TOOL: stack
run_stack() {
prepare_nix_files
local stack_flags=()

if [ "$option_profiling" = true ]; then
if [ "${1:-build}" = build ]; then
stack_flags+=("--library-profiling")
stack_flags+=("--executable-profiling")
fi
fi

case "${1:-build}" in
shell)
nix_shell
;;

*)
@stack@/bin/stack \
--nix --nix-shell-file=@stacknix@ \
--nix-shell-options "--argstr file $(pwd)/default.nix" \
"$@" "${stack_flags[@]}"
;;
esac
}

################################################################################
# TOOL: make
run_make() {
case "${1:-build}" in
build)
make
;;

*)
make "$@"
;;
esac
}

################################################################################
# Process the command line:
while getopts "c:dhI:pn:t:" o; do
case "${o}" in
c) option_compiler=$(echo "$OPTARG" | tr -d '.')
;;

d) option_debug=1
set -x
;;

h) usage
exit
;;

I) option_nixshell_args+=("-I" "$OPTARG")
;;

p) option_profiling=true
;;

n) option_nixshell_args+=("-I" "nixpkgs=$OPTARG")
;;

t) option_tool=$OPTARG
;;

*) exit 1
;;
esac
done

shift $((OPTIND-1))

################################################################################
find_project

################################################################################
# For tools that treat this script like `cabal':
if [ "${1:-build}" = cabal ] && [ "$#" -eq 2 ]; then shift; fi

################################################################################
# Figure out which tool we should be using:
if [ -n "$option_tool" ]; then
tool=$option_tool
else
if find_stack_root; then
tool=stack
elif [ -r Makefile ] || [ -r GNUmakefile ]; then
tool=make
else
tool=cabal
fi
fi

case "$tool" in
cabal)
: # No settings needed
;;

stack)
: # No settings needed
;;

make)
: # No settings needed
;;

*)
die "unknown build tool: $tool"
;;
esac

################################################################################
# Main dispatch code:
command="${1:-build}"
if [ $# -gt 0 ]; then shift; fi
if [ "${1:-}" = "--" ]; then shift; fi

case "$command" in
new-build|build)
"run_${tool}" build "$@"
;;

test)
"run_${tool}" test "$@"
;;

clean)
"run_${tool}" clean "$@"
;;

new-repl|repl)
"run_${tool}" repl "$@"
;;

shell)
"run_${tool}" shell "$@"
;;

*)
die "unknown command: $1"
;;
esac

+ 20
- 0
templates/default.nix View File

@@ -0,0 +1,20 @@
{ pkgs ? (import <nixpkgs> {}).pkgs }:

let
# List any extra packages you want available while your package is
# building or while in a nix shell:
extraPackages = with pkgs; [ ];

# Helpful if you want to override any Haskell packages:
haskell = pkgs.haskellPackages;
in

# Load the local nix file and use the overrides from above:
haskell.callPackage ./@PROJECT@.nix {
mkDerivation = { buildTools ? []
, ...
}@args:
haskell.mkDerivation (args // {
buildTools = buildTools ++ extraPackages;
});
}

Loading…
Cancel
Save