dhall-to-cabal

Compile Dhall expressions to Cabal files

https://github.com/ocharles/dhall-to-cabal

Version on this page:1.3.0.1
Stackage Nightly 2018-09-28:1.3.0.1
Latest on Hackage:1.3.4.0

See all snapshots dhall-to-cabal appears in

MIT licensed by Ollie Charles
Maintained by [email protected]
This version can be pinned in stack with:dhall-to-cabal-1.3.0.1@sha256:e0dca5c7a372ea1193451190b9eb63e87800480ef1d28db9c5d24b8adf08e44b,8332

Module documentation for 1.3.0.1

dhall-to-cabal - generate Cabal files from Dhall expression

dhall-to-cabal takes Dhall expressions and compiles them into Cabal files. All of the features of Dhall are supported, such as let bindings and imports, and all features of Cabal are supported (including conditional stanzas).

Getting Started

To get started with dhall-to-cabal, first install the dhall-to-cabal executable. This can be done using cabal:

cabal install dhall-to-cabal

(You may need to run cabal update first).

Next, write the Dhall expression that you would like to compile to a Cabal file. For some example Dhall files, see

  • dhall-to-cabal.dhall - the Dhall expression building the Cabal file of this very project.
  • lens.dhall - a subset of the lens library’s Cabal file, translated to Dhall.
  • str-sig.dhall - an example of using Backpack.

To see the full schema supported by dhall-to-cabal, you can run dhall-to-cabal --print-type. (In the future, we will have more human-ready documentation - we’d love your help here!)

Once you have your Dhall file ready, run it through dhall-to-cabal and redirect the output to a Cabal file:

dhall-to-cabal input.dhall > output.cabal

(Replace the names input.dhall and output.dhall).

That’s it!

cabal-to-dhall

Good news! dhall-to-cabal is currently running a two-for-one deal! By installing dhall-to-cabal we’ll throw in the cabal-to-dhall executable absolutely free! cabal-to-dhall does the reverse of dhall-to-cabal - taking .cabal files and transforming them into appropriate Dhall expressions. This can be a great way to get started with dhall-to-cabal.

The Details

As the name suggests, dhall-to-cabal takes Dhall expressions and compiles them into Cabal files. There are two moving pieces here, so let’s break it down.

Dhall?

Dhall is a relatively new language started by Gabriel Gonzales in late 2016. The language bills itself as “a configuration language guaranteed to terminate”. In terms of features, we have a language with:

  • Let bindings and lambdas for abstraction
  • Built-in support for primitive types - naturals, doubles, integers, text, booleans
  • Composite types - optional values (like Maybe in Haskell), lists and records
  • An import mechanism to import expressions from foreign sources (local files or over HTTP)
  • A type system to type check all of the above
  • Standards to promote adoption in multiple languages.

To give you an example of a Dhall expression, let’s jump straight in and see an example of a Dhall expression that can be used with dhall-to-cabal:

    let GitHub-project =
          https://raw.githubusercontent.com/ocharles/dhall-to-cabal/1.0.0/dhall/GitHub-project.dhall

in  let prelude =
          https://raw.githubusercontent.com/ocharles/dhall-to-cabal/1.0.0/dhall/prelude.dhall

in    GitHub-project
      { owner = "ocharles", repo = "example" }
    ⫽ { version =
          prelude.v "1.0.0"
      , library =
          prelude.unconditional.library
          (   prelude.defaults.Library
            ⫽ { build-depends =
                  [ { package =
                        "base"
                    , bounds =
                        prelude.majorBoundVersion (prelude.v "4")
                    }
                  ]
              , exposed-modules = 
                  [ "Hello.World" ]
              }
          )
      } 

We can see quite a few features in use here. Ignoring what this file actually does (for now), let’s focus on the language features.

At the start of we can see a few let bindings whose values themselves are imports over HTTP. Finally, we get into the body of the expression which is formed from two parts - function application of GitHub-project which is given a record as argument (with owner and repo Text fields), and another larger record. The result of GitHub-project and the library-containing record are then “merged” together using the operator. In this case, you can think of this as overriding or extending the result of GitHub-project with an extra library field.

It’s OK if you don’t follow all of the above - my goal is to simply get you familiar with what a Dhall expression would look like. The Dhall language itself has a much more detailed tutorial and other documentation.

Cabal?

Cabal is something that you might already be familiar with, but if you’re not, Cabal is:

a system for building and packaging Haskell libraries and programs. It defines a common interface for package authors and distributors to easily build their applications in a portable way. Cabal is part of a larger infrastructure for distributing, organizing, and cataloging Haskell libraries and programs.

(taken from the Cabal homepage).

Almost all Haskell libraries are built using some parts of the Cabal system.

For the purposes of this project, we’re interested in .cabal files themselves. Cabal is a build-system, but it’s also a domain specific language for driving this build system.

A small example Cabal file is

name: example
cabal-version: 2.0
build-type: Simple
license: UnspecifiedLicense
homepage: https://github.com/ocharles/example
bug-reports: https://github.com/ocharles/example/issues

source-repository head
    type: git
    location: https://github.com/ocharles/example

library
    exposed-modules:
        Hello.World
    build-depends:
        base ^>=4

In this, we see some leading metadata about the Haskell package itself - the name of the package, its homepage and its license, and so on. Towards the end of the file, we add a library component to the package. To build this library, we depend on the base library (which contains the standard Haskell prelude) and will expose the Hello.World module as our API.

dhall-to-cabal?

dhall-to-cabal tries to bridge the gap between these two separate worlds by allowing users to drive the Cabal build system via Dhall expressions. If you hadn’t noticed by now, you’ve already seen an input Dhall-to-Cabal expression, and you’ve also already seen the corresponding output!

Getting Started

You can get dhall-to-cabal from Hackage. For usage, run dhall-to-cabal --help. For some example Dhall expressions, see dhall-to-cabal.dhall, lens.dhall or str-sig.dhall. These are all fairly big expressions to test the project itself… if you have a simpler example that you think would act as good reference, that would make a great starting pull request!

Why?

I wrote dhall-to-cabal for a few reasons. Firstly, let’s start with the self-centered arguments:

  • I wanted to learn more about Dhall. Specifically, I wanted to get comfortable with what I could do with the language, and how I could use it to solve problems in a specific domain. I’m beginning to view it in the same light as I would view Lua and would like to explore that more in the future.

  • I wanted to help drive Dhall development, as before even getting stuck in I believed it was a worth-while project. I think even the 1.0 release of dhall-to-cabal has been of value - I’ve reported 18 issues (14 of which have been closed, usually fixed!), and I think we’re starting to have discussions in Dhall that are helping drive the overall project forward.

  • It was requested and I agree that it sounds like a project that should exist. The existence of hpack-dhall strengthens this argument.

These are enough for me to justify the work, but it doesn’t necessarily imply that the project is of value. However, I think there is some value in this project.

  • Cabal (the language) is limited in expressivity. We only just got common blocks that allow us to abstract some details out and share them between stanzas, but they still don’t even come close to the amount of expressivity given with let, function abstraction and remote imports.

  • What a Cabal file “is” has historically been somewhat vague. Thankfully, there has been a recent effort to start moving stuff into an excellent Read the Docs user’s guide, but this is still incomplete. A search for “backpack” and “mixins” returns no results, though these are valid keywords in the library stanza.

    Dhall-to-Cabal does not suffer from the mismatch between documentation and internal implementation because as Dhall is type-checked we always have a canonical type of Cabal packages.

  • Cabal is one-size fits all, but is that actually true? Some languages require special treatment, and the best we can do right now is to document that in the README. Take the network library:

    In network-2.6 the Network.URI module was split off into its own package, network-uri-2.6. If you’re using the Network.URI module you can automatically get it from the right package by adding this to your .cabal file:

    That’s right - people have to copy and paste some code. I couldn’t imagine requiring people do that in actual Haskell code, so why do we treat Cabal files differently?

    In Dhall-to-Cabal, we can hide away the fiddly details in a separate Dhall expression, and provide it as something that users can import.

  • We can go beyond Cabal files. If Cabal is a domain specific language for building Haskell projects, what does a domain specific language for building Haskell web applications look like? Does the separate of library, executable, and test-suite make sense here? Maybe we’d rather:

    servant-project {
      api-route = "My.API.Route"
      server = "My.API.Server"
      models = [ "My.API.Pancake", "My.API.Waffle" ]
    }
    

    and have this take care of some other details.

    I don’t know what this bit of the future looks like, but I think there is certainly something there.

    We can also see the desire to have this import/reuse functionality in core libraries. The network library opens with a stanza about how you have to add the library to your cabal file which is totally non-standard. Just give me an expression to import, and then I don’t have to worry about the details!

Changes

dhall-to-cabal change log

1.3.0.1 – 2018-08-10

Distribution Changes

  • Include all files necessary to run tests.

1.3.0.0 – 2018-07-28

Breaking API Changes

  • DhallToCabal.dhallToCabal now takes an InputSettings from dhall as its first argument.

Functional Changes

  • dhall-to-cabal has a new --print-default TYPE flag.

  • When reading from a file, dhall-to-cabal now interprets imports as being relative to that file, rather than the current working directory. (#114)

1.2.0.0 – 2018-07-05

Breaking API Changes

  • Remove orphan Dhall.Core.Inject instances for [Char] and CompilerFlavor.

  • CabalToDhall.cabalToDhall is now a pure function that accepts a GenericPackageDescription. A new convenience function has been added to CabalToDhall, parseGenericPackageDescriptionThrows.

Functional Changes

  • os conditions where the operating system’s name was not recognised (e.g., os(multics)) were crashing cabal-to-dhall. They now work as expected.

  • dhall-to-cabal and cabal-to-dhall now respond to --version.

  • The dhall subdirectory has been reorganised so that things that are not types are not in the types subdirectory. Specifically, dhall/types/Version/v.dhall, all of dhall/types/VersionRange/*.dhall and the operations (i.e., the enumerations LicenseId.dhall and LicenseExceptionId.dhall) from dhall/types/SPDX/*.dhall have been moved to, respectively, dhall/Version/v.dhall, dhall/VersionRange/*.dhall, and dhall/SPDX/*.dhall. In addition, the files have been renamed as appropriate to reflect the name that they are exported from the prelude as; in practice, this means that they have gone from TitleCase to camelCase.

    Code that only imports prelude.dhall and types.dhall is unaffected by this change.

  • prelude.defaults.Package.license is now AllRightsReserved.

  • dhall-to-cabal now maps AllRightsReserved to SPDX.NONE when cabal-version is at least 2.2.

  • cabal-to-dhall will now generate more compact .dhall files by using defaults.

  • The default build-type is now omission, to use Cabal 2.2’s inference, and the default cabal-version has been bumped to 2.2.

  • Export prelude.types.Scopes.

Other Changes

  • Bump upper-bounds for base, containers and contravariant. This project can build on GHC 8.6 (though will need --allow-newer for Cabal until this is official released).

1.1.0.0 – 2018-06-03

Breaking Changes

  • The type of DhallToCabal.license has changed to Dhall.Type (Either SPDX.License Cabal.License) to accomodate Cabal 2.2.

Other Changes

  • Increase upper-bound of base to allow 4.11.

  • Increase upper-bound of tasty to allow 1.1.

  • Switch to Dhall 1.14.0.

  • dhall-to-cabal: Fix tracking which branches are already true or false in conditionals. Dhall expressions with lots of conditions previously produced Cabal files that did not correctly match the requested conditions. See https://github.com/dhall-lang/dhall-to-cabal/pull/56, https://github.com/dhall-lang/dhall-to-cabal/issues/53 and https://github.com/dhall-lang/dhall-to-cabal/issues/55 for more information.

    Thank you to @jneira and @quasicomputational for helping identify and fix this bug.

  • cabal-to-dhall: Rewrite conditional handling to avoid hangs with complicated ones. See https://github.com/dhall-lang/dhall-to-cabal/pull/54 and linked issues.

  • Added a warning to generated .cabal files against hand-editing.

  • cabal-to-dhall now pretty prints the resulting Dhall.

  • The signature of CabalToDhall.cabalToDhall has changed: it now takes the location of the prelude.dhall and types.dhall to import as a parameter.

  • Upgrade to Cabal 2.2. This introduces SPDX license identifiers and Dhall functionality to manipulate them; see <golden-tests/dhall-to-cabal/SPDX.dhall> for a (convoluted) demonstration.

  • prelude.defaults.Executable has lost its main-is field, as it makes little sense to have an executable without it.

  • --print-type now omits the lengthy definition of Extension, instead importing it from the prelude. --self-contained is a new switch to disable this behaviour.

1.0.0.1 – 2018-03-25

Small packaging only tweaks:

  • Missing README.md
  • Missing author

The irony of this change is not lost on me.

1.0.0 – 2018-03-25

First release!