Building Cabal Package

To build a cabal package with Finkel, make a cabal configuration file as in the Haskell cabal package, but with custom setup script and some build tool and package dependencies.

Note

This documentation assumes the readers are using the stack build tool for building cabal packages. Those who prefer other tools such as cabal-install may translate the invoked commands and modify the file contents as necessary.

Building My First Package

Make a directory named my-first-package, and create a file named package.yaml under the directory with following contents:

# File: my-first-package/package.yaml

name: my-first-package
version: 0.1.0.0

extra-source-files:
  - src/**.fnk

custom-setup:
  dependencies:
    - base
    - Cabal
    - finkel-setup

library:
  source-dirs: src
  exposed-modules: MyFirstPackage
  build-tools:
    - finkel:finkel
  dependencies:
    - base

And a custom Setup.hs script:

-- File: my-first-package/Setup.hs

import Distribution.Simple.Finkel (finkelMakeMain)

main :: IO ()
main = finkelMakeMain

And a Finkel source code src/MyFirstPackage.fnk for exposed module:

;;;; File: my-first-package/src/MyFirstPackage.fnk

(defmodule MyFirstPackage
  (export factorial))

(defn (:: factorial (-> Integer Integer))
  "Compute factorial of the given number.

This function does not support negative numbers. If the argument was
negative, constantly returns @-1@.

==== __Example__

>>> (factorial 10)
3628800
>>> (factorial -42)
1
"
  [n]
  (if (<= n 1)
      1
      (* n (factorial (- n 1)))))

And a stack.yaml:

resolver: lts-16.4
packages:
  - .

At this point the files under the my-first-project directory should look like below:

my-first-package
├── package.yaml
├── Setup.hs
├── src
│   └── MyFirstPackage.fnk
└── stack.yaml

Now one can build the my-first-package package with stack:

$ stack build my-first-package
... Output messages omitted ...
[1 of 2] Compiling Main
[2 of 2] Compiling StacksetupShim
... More output messages ...
[1 of 1] Compiling MyFirstPackage

Note

While building packages with stack, one may see a warning message saying “Unable to find a known candidate for the Cabal entry”. Although the message says it cannot find the candidate, the compilation process does work. This is a known issue, and hoping to fix in not so far future.

Tip

To build a package containing Finkel source codes with the latest finkel built from source, one can specify the packages from finkel git repository as extra dependencies.

For example, the following stack.yaml is set to build a package in the current directory with finkel from the git repository:

resolver: lts-16.4
packages:
  - .
extra-deps:
  - git: https://github.com/finkel-lang/finkel
    commit: c0ce56cc220d533dc2e46e0bcf6387fed238abc7
    subdirs:
      - finkel-kernel
      - fkc
      - finkel-setup
      - finkel-core
      - finkel-tool
      - finkel

See the stack documentation and the Cabal User Guide for more information about using remote git repository for extra dependencies.

Mixing Finkel And Haskell Source Codes

One can mix Finkel source codes and Haskell source codes in a package. This time, making a package my-second-package from Finkel specific template:

$ stack new my-second-package https://raw.githubusercontent.com/finkel-lang/finkel/master/finkel-tool/finkel.hsfiles

Warning

At the time of writing, one may encounter messages similar to the following when running stack new with the above template:

Selecting the best among 17 snapshots...

* Partially matches lts-15.7
    finkel-setup not found
        - my-second-pkg requires -any

* Partially matches nightly-2020-03-04
    finkel-setup not found
        - my-second-pkg requires -any

...

Selected resolver: lts-15.7
Resolver 'lts-15.7' does not have all the packages to match your requirements.
    finkel-setup not found
        - my-second-pkg requires -any

This may be resolved by:
    - Using '--omit-packages' to exclude mismatching package(s).
    - Using '--resolver' to specify a matching snapshot/resolver

This is because the packages for finkel is not yet uploaded to stackage.

As the message indicates, one can pass --omit-packages option or --resolver option to stack new until the finkel dependency packages are uploaded to the upstream, and add the git repository to stack.yaml.

The above command will make a directory named my-second-package with a cabal configuration file, Setup.hs script, and a stub Finkel source code file. Directory contents of my-second-package should look like below:

my-second-package
├── app
│  └── Main.hs
├── LICENSE
├── my-second-package.cabal
├── README.md
├── Setup.hs
├── src
│  └── Lib.fnk
└── test
   └── Spec.hs

Note

As of cabal version 3.0.0, the file extension of an executable in a cabal package needs to end with .hs or .c file extension. From this restriction, one needs to make a wrapper file to run an executable written in Finkel. This is why the executable and test stanzas in cabal configuration file generated from template contains dummy Main.hs and Spec.hs files instead of .fnk files.

Add a new file named my-second-package/src/FnkCodes.fnk, with Finkel source codes:

;;;; File: my-second-package/src/FnkCodes.fnk

(defmodule FnkCodes
  (export fnkfactorial))

(defn (:: fnkfactorial (-> Int Int))
  [n]
  (if (<= n 1)
      n
      (* n (fnkfactorial (- n 1)))))

And another new file named my-second-package/src/HsCodes.hs, with Haskell source codes:

-- File: my-second-package/src/HsCodes.hs

module HsCodes
  ( hsfactorial
  , fnkfactorial
  ) where

import FnkCodes

hsfactorial :: Int -> Int
hsfactorial = fnkfactorial

Modify the library stanza of the file my-second-package.cabal and add HsCodes and FnkCodes modules as shown below:

library
  hs-source-dirs:      src
  exposed-modules:     Lib
                       HsCodes
                       FnkCodes
  build-depends:       base >= 4.7 && < 5
  build-tool-depends:  finkel:finkel >= 0.0 && < 1
  default-language:    Haskell2010

The functions exported from HsCodes module could be used from Lib module, as in compilation of cabal package without Finkel codes. Modify the file my-second-package/src/Lib.fnk to import hsfactorial and fnkfactorial functions from HsCodes:

;;;; File: my-second-package/src/Lib.fnk

(defmodule Lib
  (export someFunc)
  (import (HsCodes [hsfactorial fnkfactorial])))

(defn (:: someFunc (IO ()))
  (putStrLn
   (++ "From `Lib.someFunc':\n"
       "  hsfactorial 10  : " (show (hsfactorial 10)) "\n"
       "  fnkfactorial 10 : " (show (fnkfactorial 10)))))

One can build the my-second-package with stack build command, as before:

$ stack build my-second-package

Note

It is also possible to use a library package containing Finkel code from other Haskell packages as a build dependency since the resulting object codes are compiled by compatible ghc version.

Executable, Test, Coverage, And Haddock

The my-second-package cabal package contains an executable named my-second-package. The executable simply invokes the Lib.someFunc function. To compile and run the executable:

$ stack run my-second-package:my-second-package
From `Lib.someFunc':
  hsfactorial 10  : 3628800
  fnkfactorial 10 : 3628800

To run tests, invoke stack test or stack build --test:

$ stack build --test my-second-package

To generate code coverage report, add --coverage option when running test:

$ stack build --test --coverage my-second-package

And, to generate haddock documentation of the package, add --haddock option to stack build command:

$ stack build --haddock my-second-package