Skip to content

Commit

Permalink
Bring back cat_hs example
Browse files Browse the repository at this point in the history
This reverts commit ba7af70.

We migrated the cat_hs example from using Hazel to using
`haskell_cabal_library` and `stack_snapshot`. This is an example of
a non-trivial application, with system dependencies and
Hackage-downloaded depnedencies.

Closes #732
  • Loading branch information
mboes committed Aug 13, 2019
1 parent d7e7ee5 commit 8ae2e0b
Show file tree
Hide file tree
Showing 13 changed files with 476 additions and 0 deletions.
6 changes: 6 additions & 0 deletions examples/README.md
Expand Up @@ -6,6 +6,12 @@ for building Haskell code.
* [**vector:**](./vector/) shows how to build the `vector` package
that is found on Hackage (as well as transitive dependencies
`primitive` and `transformers`) without using Cabal.
* [**cat_hs:**](./cat_hs/) is an example of a non-trivial application
with multiple third-party dependencies downloaded from Hackage,
C library dependencies and split up into multiple libraries and
a binary. We use a rule wrapping Cabal to build the Hackage
dependencies. This example requires Nix installed. It is used to
build (or download from a binary cache) the C library dependencies.
* [**rts:**](./rts/) demonstrates foreign exports and shows how to
link against GHC's RTS library, i.e. `libHSrts.so`.

Expand Down
40 changes: 40 additions & 0 deletions examples/WORKSPACE
Expand Up @@ -54,3 +54,43 @@ load(
)

rules_haskell_toolchains(version = "8.6.5")

# Everything below for the cat_hs example.

nixpkgs_package(
name = "nixpkgs_zlib",
attribute_path = "zlib",
repository = "@rules_haskell//nixpkgs:default.nix",
)

nixpkgs_package(
name = "zlib.dev",
build_file_content = """
cc_library(
name = "zlib",
srcs = ["@nixpkgs_zlib//:lib"],
hdrs = glob(["include/*.h"]),
strip_include_prefix = "include",
visibility = ["//visibility:public"],
)
""",
repository = "@rules_haskell//nixpkgs:default.nix",
)

load("@rules_haskell//haskell:cabal.bzl", "stack_snapshot")

stack_snapshot(
name = "stackage",
packages = [
"base",
"bytestring",
"conduit",
"conduit-extra",
"hspec",
"optparse-applicative",
"text",
"text-show",
],
snapshot = "lts-14.0",
deps = ["@zlib.dev//:zlib"],
)
12 changes: 12 additions & 0 deletions examples/cat_hs/BUILD.bazel
@@ -0,0 +1,12 @@
load(
"@rules_haskell//haskell:defs.bzl",
"haskell_doc",
)

haskell_doc(
name = "api-doc",
deps = [
"//cat_hs/lib/args",
"//cat_hs/lib/cat",
],
)
41 changes: 41 additions & 0 deletions examples/cat_hs/README.md
@@ -0,0 +1,41 @@
# cat_hs - A rules_haskell Example Project

This project re-implements a subset of the `cat` command-line tool in
Haskell. It serves as an example of a project built using
the [Bazel][bazel] build system, using [rules_haskell][rules_haskell]
to define the Haskell build, including rules that wrap Cabal to build
third-party dependencies downloadable from Hackage, and
using [rules_nixpkgs][rules_nixpkgs] and [Nix][nix] to manage system
dependencies.

[bazel]: https://bazel.build/
[rules_haskell]: https://haskell.build/
[rules_nixpkgs]: https://github.com/tweag/rules_nixpkgs
[nix]: https://nixos.org/nix/

## Prerequisites

You need to install the [Nix package manager][nix]. All further dependencies
will be managed using Nix.

## Instructions

To build the package execute the following command in the checked out source
repository.

```
$ nix-shell --pure --run "bazel build //..."
```

To run the tests execute the following command.

```
$ nix-shell --pure --run "bazel test //..."
```

To run the executable enter the following commands.

```
$ nix-shell --pure --run "bazel run //exec/cat_hs -- -h"
$ nix-shell --pure --run "bazel run //exec/cat_hs -- $PWD/README.md"
```
18 changes: 18 additions & 0 deletions examples/cat_hs/exec/cat_hs/BUILD.bazel
@@ -0,0 +1,18 @@
load(
"@rules_haskell//haskell:defs.bzl",
"haskell_binary",
)
load(
"@rules_haskell//haskell:cabal.bzl",
"haskell_cabal_library",
)

haskell_binary(
name = "cat_hs",
srcs = glob(["src/**/*.hs"]),
deps = [
"//cat_hs/lib/args",
"//cat_hs/lib/cat",
"@stackage//:base",
],
)
10 changes: 10 additions & 0 deletions examples/cat_hs/exec/cat_hs/src/Main.hs
@@ -0,0 +1,10 @@
module Main
( main
) where

import qualified Args
import Cat (runCat)


main :: IO ()
main = Args.parse >>= runCat
30 changes: 30 additions & 0 deletions examples/cat_hs/lib/args/BUILD.bazel
@@ -0,0 +1,30 @@
load(
"@rules_haskell//haskell:defs.bzl",
"haskell_library",
"haskell_test",
)
load(
"@rules_haskell//haskell:cabal.bzl",
"haskell_cabal_library",
)

haskell_library(
name = "args",
srcs = glob(["src/**/*.hs"]),
visibility = ["//visibility:public"],
deps = [
"@stackage//:base",
"@stackage//:optparse-applicative",
],
)

haskell_test(
name = "unit",
srcs = glob(["test/**/*.hs"]),
deps = [
":args",
"@stackage//:base",
"@stackage//:hspec",
"@stackage//:optparse-applicative",
],
)
71 changes: 71 additions & 0 deletions examples/cat_hs/lib/args/src/Args.hs
@@ -0,0 +1,71 @@
{-# LANGUAGE LambdaCase #-}

module Args
( -- * Configuration Types
Args (..)
, FileArg (..)
-- * I/O Interface
, parse
-- * Command-Line Parser
, parser
) where

import Options.Applicative


-- | A file argument.
data FileArg
= StdIn
-- ^ Read from standard input.
| File FilePath
-- ^ Read from the given file.
deriving (Eq, Show)


-- | Parsed command-line arguments.
data Args = Args
{ number :: Bool
-- ^ Number output lines flag.
, files :: [FileArg]
-- ^ The list of input files.
} deriving (Eq, Show)


-- | Parse the command-line arguments.
parse :: IO Args
parse = execParser parser


-- | Command-line parser.
parser :: ParserInfo Args
parser =
info (argsParser <**> helper)
( fullDesc
<> progDesc "Concatenate files to standard output."
<> header "cat_hs - A Haskell implementation of cat." )


argsParser :: Parser Args
argsParser = Args
<$> switch
( long "number"
<> short 'n'
<> help "Number all output lines." )
<*> many fileArgParser


fileArgParser :: Parser FileArg
fileArgParser =
argument readFileArg
( metavar "FILES..."
<> help
"Read from the given file, or from standard input if '-'.\
\ Use './-' to read from a file named '-'." )


-- | Read a 'FileArg' from a 'String'.
readFileArg :: ReadM FileArg
readFileArg =
maybeReader $ \case
"-" -> Just StdIn
fname -> Just $ File fname
67 changes: 67 additions & 0 deletions examples/cat_hs/lib/args/test/Main.hs
@@ -0,0 +1,67 @@
module Main
( main
) where

import Args (Args (Args))
import qualified Args
import Options.Applicative
import Test.Hspec


main :: IO ()
main = hspec $ do
describe "Args.parser" $ do
it "parses no arguments" $ do
parse []
`shouldBe`
Just Args { Args.files = [], Args.number = False }
it "parses one stdin" $ do
parse ["-"]
`shouldBe`
Just Args { Args.files = [Args.StdIn], Args.number = False }
it "parses two stdin" $ do
parse ["-", "-"]
`shouldBe`
Just Args
{ Args.files = [Args.StdIn, Args.StdIn], Args.number = False }
it "parses numbered stdin" $ do
parse ["-n", "-"]
`shouldBe`
Just Args { Args.files = [Args.StdIn], Args.number = True }
it "parses numbered stdin reversed" $ do
parse ["-", "-n"]
`shouldBe`
Just Args { Args.files = [Args.StdIn], Args.number = True }
it "parses file -n" $ do
parse ["--", "-n"]
`shouldBe`
Just Args { Args.files = [Args.File "-n"], Args.number = False }
it "parses file -" $ do
parse ["./-"]
`shouldBe`
Just Args { Args.files = [Args.File "./-"], Args.number = False }
it "parses stdin and file" $ do
parse ["-", "file"]
`shouldBe`
Just Args
{ Args.files = [Args.StdIn, Args.File "file"], Args.number = False }
it "recognizes -h" $ do
parse ["-h"]
`shouldBe`
Nothing
it "recognizes --help" $ do
parse ["-h"]
`shouldBe`
Nothing
it "parses file -h" $ do
parse ["--", "-h"]
`shouldBe`
Just Args { Args.files = [Args.File "-h"], Args.number = False }


-- | Execute the command-line parser on the given arguments.
-- Returns 'Nothing' if the parser failed, or @--help@ was passed.
parse :: [String] -> Maybe Args
parse args =
getParseResult $
execParserPure defaultPrefs Args.parser args
37 changes: 37 additions & 0 deletions examples/cat_hs/lib/cat/BUILD.bazel
@@ -0,0 +1,37 @@
load(
"@rules_haskell//haskell:defs.bzl",
"haskell_library",
"haskell_test",
)
load(
"@rules_haskell//haskell:cabal.bzl",
"haskell_cabal_library",
)

haskell_library(
name = "cat",
srcs = glob(["src/**/*.hs"]),
visibility = ["//visibility:public"],
deps = [
"//cat_hs/lib/args",
"@stackage//:base",
"@stackage//:bytestring",
"@stackage//:conduit",
"@stackage//:conduit-extra",
"@stackage//:text",
"@stackage//:text-show",
],
)

haskell_test(
name = "unit",
srcs = glob(["test/**/*.hs"]),
deps = [
":cat",
"//cat_hs/lib/args",
"@stackage//:base",
"@stackage//:conduit",
"@stackage//:hspec",
"@stackage//:text",
],
)

0 comments on commit 8ae2e0b

Please sign in to comment.